openedFiles) {
+ StringBuilder result = new StringBuilder("");
+ for (PreferredFile aPreferredFile : openedFiles) {
+ result.append(aPreferredFile.toString()).append(PreferencesConstant.FILE_SEPARATOR.toString());
+ }
+ this.dao.put(PreferencesConstant.OPENED_FILES_ON_WORKSPACE, result.toString());
+ }
+
+
+
+
+
+
+
+ /**
+ * Indicates which diagram is currently focused on workspace and saves it into user preferences
+ *
+ * @param path file path (could be relative or absolute)
+ */
+ public void setActiveDiagramFile(IFile aFile)
+ {
+ if (aFile != null)
+ {
+ PreferredFile preferredFile = new PreferredFile(aFile);
+ this.dao.put(PreferencesConstant.ACTIVE_FILE, preferredFile.toString());
+ }
+ }
+
+ /**
+ * Gets from user preferences which diagram was setted as focused
+ *
+ * @return file path (could be relative or absolute). Returns null by default.
+ */
+ public IFile getActiveDiagramFile()
+ {
+ String entry = this.dao.get(PreferencesConstant.ACTIVE_FILE, "");
+ IFile aFile = null;
+ try
+ {
+ aFile = new PreferredFile(entry);
+ }
+ catch (IOException e)
+ {
+ // TODO : logger needed
+ }
+ return aFile;
+ }
+
+ /**
+ * Clear user preferences
+ */
+ public void reset() {
+ this.dao.reset();
+ }
+
+ /**
+ * Allows to store and retrieve preferences
+ */
+ @InjectedBean
+ private IUserPreferencesDao dao;
+
+ /**
+ * Recent opened files list capacity
+ */
+ private static final int DEFAULT_MAX_RECENT_FILES = 5;
+}
diff --git a/VioletFramework/VioletFramework/src/main/java/com/horstmann/violet/framework/util/BrowserLauncher.java b/VioletFramework/VioletFramework/src/main/java/com/horstmann/violet/framework/util/BrowserLauncher.java
new file mode 100644
index 0000000..c6c2c9b
--- /dev/null
+++ b/VioletFramework/VioletFramework/src/main/java/com/horstmann/violet/framework/util/BrowserLauncher.java
@@ -0,0 +1,80 @@
+/*
+ Violet - A program for editing UML diagrams.
+
+ Copyright (C) 2007 Cay S. Horstmann (http://horstmann.com)
+ Alexandre de Pellegrin (http://alexdp.free.fr);
+
+ 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.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+package com.horstmann.violet.framework.util;
+
+import java.lang.reflect.Method;
+
+public class BrowserLauncher
+{
+
+ public static boolean openURL(String url)
+ {
+ String osName = System.getProperty("os.name");
+ try
+ {
+ if (osName.startsWith("Mac OS"))
+ {
+ Class> fileMgr = Class.forName("com.apple.eio.FileManager");
+ Method openURL = fileMgr.getDeclaredMethod("openURL", new Class>[]
+ {
+ String.class
+ });
+ openURL.invoke(null, new Object[]
+ {
+ url
+ });
+ }
+ else if (osName.startsWith("Windows")) Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler " + url);
+ else
+ { // assume Unix or Linux
+ String[] browsers =
+ {
+ "firefox",
+ "opera",
+ "konqueror",
+ "epiphany",
+ "mozilla",
+ "netscape"
+ };
+ String browser = null;
+ for (int count = 0; count < browsers.length && browser == null; count++)
+ if (Runtime.getRuntime().exec(new String[]
+ {
+ "which",
+ browsers[count]
+ }).waitFor() == 0) browser = browsers[count];
+ if (browser == null) throw new Exception("Could not find web browser");
+ else Runtime.getRuntime().exec(new String[]
+ {
+ browser,
+ url
+ });
+ }
+ return true;
+ }
+ catch (Exception e)
+ {
+ return false;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/VioletFramework/VioletFramework/src/main/java/com/horstmann/violet/framework/util/ClipboardPipe.java b/VioletFramework/VioletFramework/src/main/java/com/horstmann/violet/framework/util/ClipboardPipe.java
new file mode 100644
index 0000000..d3e2289
--- /dev/null
+++ b/VioletFramework/VioletFramework/src/main/java/com/horstmann/violet/framework/util/ClipboardPipe.java
@@ -0,0 +1,102 @@
+/*
+ Violet - A program for editing UML diagrams.
+
+ Copyright (C) 2007 Cay S. Horstmann (http://horstmann.com)
+ Alexandre de Pellegrin (http://alexdp.free.fr);
+
+ 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.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+package com.horstmann.violet.framework.util;
+
+import java.awt.Image;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.io.IOException;
+
+/**
+ * This class is used to hold an image or a text while on the clipboard.
+ *
+ * @author Alexandre de Pellegrin
+ */
+public class ClipboardPipe implements Transferable
+{
+ private Image image;
+ private String text;
+
+ public ClipboardPipe(Image image)
+ {
+ this.image = image;
+ }
+
+ public ClipboardPipe(String text)
+ {
+ this.text = text;
+ }
+
+ // Returns supported flavors
+ public DataFlavor[] getTransferDataFlavors()
+ {
+ if (this.image != null)
+ {
+ return new DataFlavor[]
+ {
+ DataFlavor.imageFlavor
+ };
+ }
+ if (this.text != null)
+ {
+ return new DataFlavor[]
+ {
+ DataFlavor.stringFlavor
+ };
+ }
+ return new DataFlavor[] {
+
+ };
+ }
+
+ // Returns true if flavor is supported
+ public boolean isDataFlavorSupported(DataFlavor flavor)
+ {
+ if (this.image != null)
+ {
+ return DataFlavor.imageFlavor.equals(flavor);
+ }
+ if (this.text != null)
+ {
+ return DataFlavor.stringFlavor.equals(flavor);
+ }
+ return false;
+ }
+
+ // Returns image or text
+ public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException
+ {
+ if (DataFlavor.imageFlavor.equals(flavor) && this.image != null)
+ {
+ return this.image;
+ }
+ else if (DataFlavor.stringFlavor.equals(flavor) && this.text != null)
+ {
+ return this.text;
+ }
+ else
+ {
+ throw new UnsupportedFlavorException(flavor);
+ }
+ }
+}
\ No newline at end of file
diff --git a/VioletFramework/VioletFramework/src/main/java/com/horstmann/violet/framework/util/GeometryUtils.java b/VioletFramework/VioletFramework/src/main/java/com/horstmann/violet/framework/util/GeometryUtils.java
new file mode 100644
index 0000000..7fd076d
--- /dev/null
+++ b/VioletFramework/VioletFramework/src/main/java/com/horstmann/violet/framework/util/GeometryUtils.java
@@ -0,0 +1,47 @@
+/*
+ Violet - A program for editing UML diagrams.
+
+ Copyright (C) 2007 Cay S. Horstmann (http://horstmann.com)
+ Alexandre de Pellegrin (http://alexdp.free.fr);
+
+ 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.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+package com.horstmann.violet.framework.util;
+
+import java.awt.geom.Point2D;
+import java.awt.geom.RectangularShape;
+
+/**
+ * Miscellaneous geometry utilities.
+ */
+public class GeometryUtils
+{
+ /**
+ * Moves this rectangle by a given x- and y-offset
+ * @param r the rectangle to move
+ * @param dx the x-offset
+ * @param dy the y-offset
+ */
+ public static void translate(RectangularShape r, double dx, double dy)
+ {
+ r.setFrame(r.getX() + dx, r.getY() + dy, r.getWidth(), r.getHeight());
+ }
+
+ public static Point2D getMin(RectangularShape r)
+ {
+ return new Point2D.Double(r.getX(), r.getY());
+ }
+}
diff --git a/VioletFramework/VioletFramework/src/main/java/com/horstmann/violet/framework/util/GrabberUtils.java b/VioletFramework/VioletFramework/src/main/java/com/horstmann/violet/framework/util/GrabberUtils.java
new file mode 100644
index 0000000..961ba18
--- /dev/null
+++ b/VioletFramework/VioletFramework/src/main/java/com/horstmann/violet/framework/util/GrabberUtils.java
@@ -0,0 +1,29 @@
+package com.horstmann.violet.framework.util;
+
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.geom.Rectangle2D;
+
+
+public class GrabberUtils
+{
+
+ /**
+ * Draws a single "grabber", a filled square
+ *
+ * @param g2 the graphics context
+ * @param x the x coordinate of the center of the grabber
+ * @param y the y coordinate of the center of the grabber
+ */
+ public static void drawGrabber(Graphics2D g2, double x, double y)
+ {
+ final int SIZE = 5;
+ Color oldColor = g2.getColor();
+ g2.setColor(GrabberUtils.PURPLE);
+ g2.fill(new Rectangle2D.Double(x - SIZE / 2, y - SIZE / 2, SIZE, SIZE));
+ g2.setColor(oldColor);
+ }
+
+ private static final Color PURPLE = new Color(0.7f, 0.4f, 0.7f);
+
+}
diff --git a/VioletFramework/VioletFramework/src/main/java/com/horstmann/violet/framework/util/NanoHTTPD.java b/VioletFramework/VioletFramework/src/main/java/com/horstmann/violet/framework/util/NanoHTTPD.java
new file mode 100644
index 0000000..ca2b76c
--- /dev/null
+++ b/VioletFramework/VioletFramework/src/main/java/com/horstmann/violet/framework/util/NanoHTTPD.java
@@ -0,0 +1,698 @@
+package com.horstmann.violet.framework.util;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.URLEncoder;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Locale;
+import java.util.Properties;
+import java.util.StringTokenizer;
+import java.util.TimeZone;
+
+/**
+ * A simple, tiny, nicely embeddable HTTP 1.0 server in Java
+ *
+ *
+ * NanoHTTPD version 1.1, Copyright © 2001,2005-2007 Jarno Elonen (elonen@iki.fi, http://iki.fi/elonen/)
+ *
+ *
+ * Features + limitations:
+ *
+ *
+ * - Only one Java file
+ * - Java 1.1 compatible
+ * - Released as open source, Modified BSD licence
+ * - No fixed config files, logging, authorization etc. (Implement yourself if you need them.)
+ * - Supports parameter parsing of GET and POST methods
+ * - Supports both dynamic content and file serving
+ * - Never caches anything
+ * - Doesn't limit bandwidth, request time or simultaneous connections
+ * - Default code serves files and shows all HTTP parameters and headers
+ * - File server supports directory listing, index.html and index.htm
+ * - File server does the 301 redirection trick for directories without '/'
+ * - File server supports simple skipping for files (continue download)
+ * - File server uses current directory as a web root
+ * - File server serves also very long files without memory overhead
+ * - Contains a built-in list of most common mime types
+ * - All header names are converted lowercase so they don't vary between browsers/clients
+ *
+ *
+ *
+ *
+ * Ways to use:
+ *
+ *
+ * - Run as a standalone app, serves files from current directory and shows requests
+ * - Subclass serve() and embed to your own program
+ * - Call serveFile() from serve() with your own base directory
+ *
+ *
+ *
+ * See the end of the source file for distribution license (Modified BSD licence)
+ */
+public class NanoHTTPD
+{
+ // ==================================================
+ // API parts
+ // ==================================================
+
+ /**
+ * Override this to customize the server.
+ *
+ *
+ * (By default, this delegates to serveFile() and allows directory listing.)
+ *
+ * @parm uri Percent-decoded URI without parameters, for example "/index.cgi"
+ * @parm method "GET", "POST" etc.
+ * @parm parms Parsed, percent decoded parameters from URI and, in case of POST, data.
+ * @parm header Header entries, percent decoded
+ * @return HTTP response, see class Response for details
+ */
+ public Response serve(String uri, String method, Properties header, Properties parms)
+ {
+ // System.out.println(method + " '" + uri + "' ");
+
+ // Enumeration e = header.propertyNames();
+ // while (e.hasMoreElements())
+ // {
+ // String value = (String) e.nextElement();
+ // System.out.println(" HDR: '" + value + "' = '" + header.getProperty(value) + "'");
+ // }
+ // e = parms.propertyNames();
+ // while (e.hasMoreElements())
+ // {
+ // String value = (String) e.nextElement();
+ // System.out.println(" PRM: '" + value + "' = '" + parms.getProperty(value) + "'");
+ // }
+
+ return serveFile(uri, header, new File("."), true);
+ }
+
+ /**
+ * HTTP response. Return one of these from serve().
+ */
+ public class Response
+ {
+ /**
+ * Default constructor: response = HTTP_OK, data = mime = 'null'
+ */
+ public Response()
+ {
+ this.status = HTTP_OK;
+ }
+
+ /**
+ * Basic constructor.
+ */
+ public Response(String status, String mimeType, InputStream data)
+ {
+ this.status = status;
+ this.mimeType = mimeType;
+ this.data = data;
+ }
+
+ /**
+ * Convenience method that makes an InputStream out of given text.
+ */
+ public Response(String status, String mimeType, String txt)
+ {
+ this.status = status;
+ this.mimeType = mimeType;
+ this.data = new ByteArrayInputStream(txt.getBytes());
+ }
+
+ /**
+ * Adds given line to the header.
+ */
+ public void addHeader(String name, String value)
+ {
+ header.put(name, value);
+ }
+
+ /**
+ * HTTP status code after processing, e.g. "200 OK", HTTP_OK
+ */
+ public String status;
+
+ /**
+ * MIME type of content, e.g. "text/html"
+ */
+ public String mimeType;
+
+ /**
+ * Data of the response, may be null.
+ */
+ public InputStream data;
+
+ /**
+ * Headers for the HTTP response. Use addHeader() to add lines.
+ */
+ public Properties header = new Properties();
+ }
+
+ /**
+ * Some HTTP response status codes
+ */
+ public static final String HTTP_OK = "200 OK", HTTP_REDIRECT = "301 Moved Permanently", HTTP_FORBIDDEN = "403 Forbidden",
+ HTTP_NOTFOUND = "404 Not Found", HTTP_BADREQUEST = "400 Bad Request", HTTP_INTERNALERROR = "500 Internal Server Error",
+ HTTP_NOTIMPLEMENTED = "501 Not Implemented";
+
+ /**
+ * Common mime types for dynamic content
+ */
+ public static final String MIME_PLAINTEXT = "text/plain", MIME_HTML = "text/html",
+ MIME_DEFAULT_BINARY = "application/octet-stream";
+
+ // ==================================================
+ // Socket & server code
+ // ==================================================
+
+ /**
+ * Starts a HTTP server to given port.
+ *
+ * Throws an IOException if the socket is already in use
+ */
+ public NanoHTTPD(int port) throws IOException
+ {
+ myTcpPort = port;
+
+ final ServerSocket ss = new ServerSocket(myTcpPort);
+ Thread t = new Thread(new Runnable()
+ {
+ public void run()
+ {
+ try
+ {
+ while (true)
+ new HTTPSession(ss.accept());
+ }
+ catch (IOException ioe)
+ {
+ }
+ }
+ });
+ t.setDaemon(true);
+ t.start();
+ }
+
+ /**
+ * Starts as a standalone file server and waits for Enter.
+ */
+ public static void main(String[] args)
+ {
+ System.out.println("NanoHTTPD 1.1 (C) 2001,2005-2007 Jarno Elonen\n" + "(Command line options: [port] [--licence])\n");
+
+ // Show licence if requested
+ int lopt = -1;
+ for (int i = 0; i < args.length; ++i)
+ if (args[i].toLowerCase().endsWith("licence"))
+ {
+ lopt = i;
+ System.out.println(LICENCE + "\n");
+ }
+
+ // Change port if requested
+ int port = 80;
+ if (args.length > 0 && lopt != 0) port = Integer.parseInt(args[0]);
+
+ if (args.length > 1 && args[1].toLowerCase().endsWith("licence")) System.out.println(LICENCE + "\n");
+
+ NanoHTTPD nh = null;
+ try
+ {
+ nh = new NanoHTTPD(port);
+ }
+ catch (IOException ioe)
+ {
+ System.err.println("Couldn't start server:\n" + ioe);
+ System.exit(-1);
+ }
+ nh.myFileDir = new File("");
+
+ System.out.println("Now serving files in port " + port + " from \"" + new File("").getAbsolutePath() + "\"");
+ System.out.println("Hit Enter to stop.\n");
+
+ try
+ {
+ System.in.read();
+ }
+ catch (Throwable t)
+ {
+ }
+ ;
+ }
+
+ /**
+ * Handles one session, i.e. parses the HTTP request and returns the response.
+ */
+ private class HTTPSession implements Runnable
+ {
+ public HTTPSession(Socket s)
+ {
+ mySocket = s;
+ Thread t = new Thread(this);
+ t.setDaemon(true);
+ t.start();
+ }
+
+ public void run()
+ {
+ try
+ {
+ InputStream is = mySocket.getInputStream();
+ if (is == null) return;
+ BufferedReader in = new BufferedReader(new InputStreamReader(is));
+
+ // Read the request line
+ StringTokenizer st = new StringTokenizer(in.readLine());
+ if (!st.hasMoreTokens()) sendError(HTTP_BADREQUEST, "BAD REQUEST: Syntax error. Usage: GET /example/file.html");
+
+ String method = st.nextToken();
+
+ if (!st.hasMoreTokens()) sendError(HTTP_BADREQUEST, "BAD REQUEST: Missing URI. Usage: GET /example/file.html");
+
+ String uri = decodePercent(st.nextToken());
+
+ // Decode parameters from the URI
+ Properties parms = new Properties();
+ int qmi = uri.indexOf('?');
+ if (qmi >= 0)
+ {
+ decodeParms(uri.substring(qmi + 1), parms);
+ uri = decodePercent(uri.substring(0, qmi));
+ }
+
+ // If there's another token, it's protocol version,
+ // followed by HTTP headers. Ignore version but parse headers.
+ // NOTE: this now forces header names uppercase since they are
+ // case insensitive and vary by client.
+ Properties header = new Properties();
+ if (st.hasMoreTokens())
+ {
+ String line = in.readLine();
+ while (line.trim().length() > 0)
+ {
+ int p = line.indexOf(':');
+ header.put(line.substring(0, p).trim().toLowerCase(), line.substring(p + 1).trim());
+ line = in.readLine();
+ }
+ }
+
+ // If the method is POST, there may be parameters
+ // in data section, too, read it:
+ if (method.equalsIgnoreCase("POST"))
+ {
+ long size = 0x7FFFFFFFFFFFFFFFl;
+ String contentLength = header.getProperty("content-length");
+ if (contentLength != null)
+ {
+ try
+ {
+ size = Integer.parseInt(contentLength);
+ }
+ catch (NumberFormatException ex)
+ {
+ }
+ }
+ String postLine = "";
+ char buf[] = new char[512];
+ int read = in.read(buf);
+ while (read >= 0 && size > 0 && !postLine.endsWith("\r\n"))
+ {
+ size -= read;
+ postLine += String.valueOf(buf, 0, read);
+ if (size > 0) read = in.read(buf);
+ }
+ postLine = postLine.trim();
+ decodeParms(postLine, parms);
+ }
+
+ // Ok, now do the serve()
+ Response r = serve(uri, method, header, parms);
+ if (r == null) sendError(HTTP_INTERNALERROR, "SERVER INTERNAL ERROR: Serve() returned a null response.");
+ else sendResponse(r.status, r.mimeType, r.header, r.data);
+
+ in.close();
+ }
+ catch (IOException ioe)
+ {
+ try
+ {
+ sendError(HTTP_INTERNALERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
+ }
+ catch (Throwable t)
+ {
+ }
+ }
+ catch (InterruptedException ie)
+ {
+ // Thrown by sendError, ignore and exit the thread.
+ }
+ }
+
+ /**
+ * Decodes the percent encoding scheme.
For example: "an+example%20string" -> "an example string"
+ */
+ private String decodePercent(String str) throws InterruptedException
+ {
+ try
+ {
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < str.length(); i++)
+ {
+ char c = str.charAt(i);
+ switch (c)
+ {
+ case '+':
+ sb.append(' ');
+ break;
+ case '%':
+ sb.append((char) Integer.parseInt(str.substring(i + 1, i + 3), 16));
+ i += 2;
+ break;
+ default:
+ sb.append(c);
+ break;
+ }
+ }
+ return new String(sb.toString().getBytes());
+ }
+ catch (Exception e)
+ {
+ sendError(HTTP_BADREQUEST, "BAD REQUEST: Bad percent-encoding.");
+ return null;
+ }
+ }
+
+ /**
+ * Decodes parameters in percent-encoded URI-format ( e.g. "name=Jack%20Daniels&pass=Single%20Malt" ) and adds them to given
+ * Properties.
+ */
+ private void decodeParms(String parms, Properties p) throws InterruptedException
+ {
+ if (parms == null) return;
+
+ StringTokenizer st = new StringTokenizer(parms, "&");
+ while (st.hasMoreTokens())
+ {
+ String e = st.nextToken();
+ int sep = e.indexOf('=');
+ if (sep >= 0) p.put(decodePercent(e.substring(0, sep)).trim(), decodePercent(e.substring(sep + 1)));
+ }
+ }
+
+ /**
+ * Returns an error message as a HTTP response and throws InterruptedException to stop furhter request processing.
+ */
+ private void sendError(String status, String msg) throws InterruptedException
+ {
+ sendResponse(status, MIME_PLAINTEXT, null, new ByteArrayInputStream(msg.getBytes()));
+ throw new InterruptedException();
+ }
+
+ /**
+ * Sends given response to the socket.
+ */
+ private void sendResponse(String status, String mime, Properties header, InputStream data)
+ {
+ try
+ {
+ if (status == null) throw new Error("sendResponse(): Status can't be null.");
+
+ OutputStream out = mySocket.getOutputStream();
+ PrintWriter pw = new PrintWriter(out);
+ pw.print("HTTP/1.0 " + status + " \r\n");
+
+ if (mime != null) pw.print("Content-Type: " + mime + "\r\n");
+
+ if (header == null || header.getProperty("Date") == null) pw.print("Date: " + gmtFrmt.format(new Date()) + "\r\n");
+
+ if (header != null)
+ {
+ Enumeration