001: /*
002: ** $Id: ConfigModel.java,v 1.11 2000/11/26 13:11:21 mrw Exp $
003: **
004: ** Model for the ViennaSQL configuration. Parses the vienna.xml config file.
005: ** Only makes a half-hearted attempt at validating the config file.
006: ** Needs work...
007: **
008: ** Mike Wilson, July 2000, mrw@whisperingwind.co.uk
009: **
010: ** (C) Copyright 2000, Mike Wilson, Reading, Berkshire, UK
011: **
012: ** This program is free software; you can redistribute it and/or modify
013: ** it under the terms of the GNU General Public License as published by
014: ** the Free Software Foundation; either version 2 of the License, or
015: ** (at your option) any later version.
016: **
017: ** This program is distributed in the hope that it will be useful,
018: ** but WITHOUT ANY WARRANTY; without even the implied warranty of
019: ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
020: ** GNU General Public License for more details.
021: **
022: ** You should have received a copy of the GNU Library General
023: ** Public License along with this library; if not, write to the
024: ** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
025: ** Boston, MA 02111-1307 USA.
026: */
027:
028: package uk.co.whisperingwind.vienna;
029:
030: import java.awt.Font;
031: import java.io.File;
032: import java.io.FileInputStream;
033: import java.io.FileNotFoundException;
034: import java.io.FileOutputStream;
035: import java.io.InputStreamReader;
036: import java.io.IOException;
037: import java.io.PrintStream;
038: import java.util.Collection;
039: import java.util.Iterator;
040: import java.util.StringTokenizer;
041: import java.util.TreeMap;
042: import java.util.Vector;
043: import org.xml.sax.AttributeList;
044: import org.xml.sax.SAXException;
045: import org.xml.sax.SAXParseException;
046: import uk.co.whisperingwind.framework.Dialogs;
047: import uk.co.whisperingwind.framework.ExceptionDialog;
048: import uk.co.whisperingwind.framework.JarFile;
049: import uk.co.whisperingwind.framework.Model;
050: import uk.co.wilson.xml.MinML;
051:
052: public class ConfigModel extends Model {
053: private static final String CONFIG_FILE_NAME = "vienna.xml";
054: private static final String NEW_FILE_NAME = "vienna.new";
055: private static final String BACKUP_FILE_NAME = "vienna.bak";
056:
057: private boolean savePasswords = false;
058: private int maxRows = 1000;
059: private Font tableFont = null;
060: private Font textFont = null;
061: private ConnectionMap connections = null;
062: private ConnectionListModel connectionListModel = null;
063:
064: public ConfigModel() throws FileNotFoundException, ConfigException {
065: new ConfigLoader();
066: }
067:
068: public void save() throws FileNotFoundException, IOException {
069: new ConfigSaver();
070: }
071:
072: /*
073: ** Deep copy constructor. Not used at the moment -- I will need
074: ** this when I implement a Configure dialog so I can work on a
075: ** copy and leave the config as it was on "Cancel".
076: */
077:
078: public ConfigModel(ConfigModel copy) {
079: assign(copy);
080: }
081:
082: /*
083: ** Assorted accessors...
084: */
085:
086: public boolean getSavePasswords() {
087: return savePasswords;
088: }
089:
090: public void setSavePasswords(boolean aSave) {
091: if (aSave != savePasswords) {
092: savePasswords = aSave;
093: fireEvent("password", new Boolean(savePasswords));
094: }
095: }
096:
097: public int getMaxRows() {
098: return maxRows;
099: }
100:
101: public void setMaxRows(int max) {
102: if (max != maxRows) {
103: maxRows = max;
104: fireEvent("maxrows", new Integer(maxRows));
105: }
106: }
107:
108: public Font getTableFont() {
109: return tableFont;
110: }
111:
112: public void setTableFont(Font font) {
113: if (!font.equals(tableFont)) {
114: tableFont = new Font(font.getFamily(), font.getStyle(),
115: font.getSize());
116: fireEvent("tablefont", tableFont);
117: }
118: }
119:
120: public Font getTextFont() {
121: return textFont;
122: }
123:
124: public void setTextFont(Font font) {
125: if (!font.equals(textFont)) {
126: textFont = new Font(font.getFamily(), font.getStyle(), font
127: .getSize());
128: fireEvent("textfont", textFont);
129: }
130: }
131:
132: public ConnectionModel getConnectionModel(String name) {
133: return (ConnectionModel) connections.get(name);
134: }
135:
136: public String getConnectionUrl(String name) {
137: String url = null;
138: ConnectionModel connection = (ConnectionModel) connections
139: .get(name);
140:
141: if (connection != null)
142: url = connection.getUrl();
143:
144: return url;
145: }
146:
147: public String getConnectionDriverClass(String name) {
148: String driverClass = null;
149: ConnectionModel connection = (ConnectionModel) connections
150: .get(name);
151:
152: if (connection != null)
153: driverClass = connection.getDriverClass();
154:
155: return driverClass;
156: }
157:
158: public String getConnectionUserName(String name) {
159: String userName = null;
160: ConnectionModel connection = (ConnectionModel) connections
161: .get(name);
162:
163: if (connection != null)
164: userName = connection.getUserName();
165:
166: return userName;
167: }
168:
169: public String getConnectionPassword(String name) {
170: String password = null;
171: ConnectionModel connection = (ConnectionModel) connections
172: .get(name);
173:
174: if (connection != null)
175: password = connection.getPassword();
176:
177: return password;
178: }
179:
180: /**
181: ** Encode a string for XML. Replaces amphersand with & (others
182: ** can be added if needed).
183: */
184:
185: public String encodeXML(String uncoded) {
186: String result = uncoded;
187:
188: result = encodeChar(result, '&', "&");
189: result = encodeChar(result, '<', "<");
190: result = encodeChar(result, '>', ">");
191: result = encodeChar(result, '"', """);
192:
193: return result;
194: }
195:
196: /**
197: ** Replace all instances of a character in the string with the
198: ** encode-with string.
199: */
200:
201: public String encodeChar(String uncoded, char encodeChar,
202: String encodeWith) {
203: String result = uncoded;
204:
205: if (result != null) {
206: int i = result.indexOf(encodeChar);
207:
208: while (i >= 0) {
209: result = result.substring(0, i) + encodeWith
210: + result.substring(i + 1);
211:
212: i = result.indexOf(encodeChar, i + 1);
213: }
214: }
215:
216: return result;
217: }
218:
219: /*
220: ** Add a connection.
221: */
222:
223: public void addConnection(String name, String url,
224: String driverClass, String userName, String password)
225: throws ConfigException {
226: if (getConnectionModel(name) != null)
227: throw new ConfigException("Duplicate connection name");
228:
229: if (name.equals(""))
230: throw new ConfigException("Connection name cannot be null");
231:
232: if (url.equals(""))
233: throw new ConfigException("URL cannot be null");
234:
235: if (driverClass.equals(""))
236: throw new ConfigException("Driver class cannot be null");
237:
238: ConnectionModel connection = new ConnectionModel();
239: connection.setName(name);
240: connection.setUrl(url);
241: connection.setDriverClass(driverClass);
242: connection.setUserName(userName);
243: connection.setPassword(password);
244:
245: connections.put(name, connection);
246:
247: if (connectionListModel != null)
248: connectionListModel.addElement(name);
249:
250: fireEvent("add", name);
251: }
252:
253: /*
254: ** Amend an existing connection.
255: */
256:
257: public void amendConnection(String name, String newName,
258: String url, String driverClass, String userName,
259: String password) throws ConfigException {
260: if (name.equals(newName)) {
261: ConnectionModel connection = (ConnectionModel) connections
262: .get(name);
263: connection.setUrl(url);
264: connection.setDriverClass(driverClass);
265: connection.setUserName(userName);
266: connection.setPassword(password);
267: fireEvent("amend", name);
268: } else {
269: removeConnection(name);
270:
271: if (connectionListModel != null)
272: connectionListModel.removeElement(name);
273:
274: addConnection(newName, url, driverClass, userName, password);
275: }
276: }
277:
278: /*
279: ** Remove a connection.
280: */
281:
282: public void removeConnection(String name) {
283: connections.remove(name);
284:
285: if (connectionListModel != null)
286: connectionListModel.removeElement(name);
287:
288: fireEvent("remove", name);
289: }
290:
291: /*
292: ** Construct a ConnectionListModel with a list of all my
293: ** connection names. Adds a "Disconnected" option at the top of
294: ** the list.
295: */
296:
297: public ConnectionListModel getConnectionListModel() {
298: if (connectionListModel == null) {
299: Vector connectionList = new Vector();
300: connectionList.addElement("[Disconnected]");
301:
302: Collection c = connections.values();
303: Iterator i = c.iterator();
304:
305: while (i.hasNext()) {
306: ConnectionModel m = (ConnectionModel) i.next();
307: connectionList.addElement(m.getName());
308: connectionListModel = new ConnectionListModel(
309: connectionList);
310: }
311: }
312:
313: return connectionListModel;
314: }
315:
316: /*
317: ** Make a deep copy of a ConfigModel. Fires a "begin" event before
318: ** changing anything and an "end" event when it's done.
319: */
320:
321: public void assign(ConfigModel copy) {
322: fireEvent("update", "begin");
323: savePasswords = copy.savePasswords;
324: maxRows = copy.maxRows;
325:
326: setTableFont(copy.tableFont);
327: setTextFont(copy.textFont);
328:
329: connections = new ConnectionMap(copy.connections);
330:
331: getConnectionListModel();
332: connectionListModel.assign(copy.connectionListModel);
333: fireEvent("update", "end");
334: }
335:
336: /*
337: ** The XML parser.
338: */
339:
340: private class ConfigLoader extends MinML {
341: private String currentElement = null;
342: private Font currentFont = null;
343: private ConnectionModel currentConnection = null;
344: private String fontName = null;
345: private String fontStyle = null;
346: private String fontSize = null;
347: private String errorMessage = null;
348:
349: public ConfigLoader() throws FileNotFoundException,
350: ConfigException {
351: connections = new ConnectionMap();
352:
353: try {
354: String userHome = System.getProperty("user.home");
355: File configFile = new File(userHome, CONFIG_FILE_NAME);
356:
357: if (!configFile.exists())
358: createConfig(configFile);
359:
360: parse(new InputStreamReader(new FileInputStream(
361: configFile)));
362:
363: if (errorMessage != null)
364: throw (new ConfigException(errorMessage));
365: } catch (FileNotFoundException ex) {
366: throw (ex);
367: } catch (IOException ex) {
368: new ExceptionDialog(ex);
369: System.exit(1);
370: } catch (SAXException ex) {
371: new ExceptionDialog(ex);
372: System.exit(1);
373: }
374: }
375:
376: /*
377: ** MinML callback for start of element.
378: */
379:
380: public void startElement(String name, AttributeList attributes) {
381: currentElement = new String(name);
382:
383: if (name.equals("tablefont")) {
384: tableFont = new Font("Dialog", Font.PLAIN, 12);
385: currentFont = tableFont;
386: } else if (name.equals("textfont")) {
387: textFont = new Font("Dialog", Font.PLAIN, 12);
388: currentFont = textFont;
389: } else if (name.equals("connection")) {
390: currentConnection = new ConnectionModel();
391: }
392: }
393:
394: /*
395: ** MinML callback for end of element.
396: */
397:
398: public void endElement(String name) {
399: if (name.equals("textfont")) {
400: textFont = parseFont(fontName, fontStyle, fontSize);
401: } else if (name.equals("tablefont")) {
402: tableFont = parseFont(fontName, fontStyle, fontSize);
403: } else if (name.equals("connection")) {
404: connections.put(currentConnection.getName(),
405: currentConnection);
406: currentConnection = null;
407: }
408: }
409:
410: /*
411: ** MinML callback for element text.
412: */
413:
414: public void characters(char ch[], int start, int length) {
415: String text = new String(ch, start, length);
416:
417: if (currentElement.equals("savepassword")) {
418: Boolean aSave = new Boolean(text);
419: savePasswords = aSave.booleanValue();
420: } else if (currentElement.equals("maxrows")) {
421: Integer max = new Integer(text);
422: maxRows = max.intValue();
423: } else if (currentElement.equals("family")) {
424: fontName = new String(text);
425: } else if (currentElement.equals("style")) {
426: fontStyle = new String(text);
427: } else if (currentElement.equals("size")) {
428: fontSize = new String(text);
429: } else if (currentElement.equals("name")) {
430: if (currentConnection == null)
431: errorMessage = "Misplaced <name> tag.";
432: else
433: currentConnection.setName(text);
434: } else if (currentElement.equals("url")) {
435: if (currentConnection == null)
436: errorMessage = "Misplaced <url> tag.";
437: else
438: currentConnection.setUrl(text);
439: } else if (currentElement.equals("driver")) {
440: if (currentConnection == null)
441: errorMessage = "Misplaced <driver> tag.";
442: else
443: currentConnection.setDriverClass(text);
444: } else if (currentElement.equals("username")) {
445: if (currentConnection == null)
446: errorMessage = "Misplaced <username> tag.";
447: else
448: currentConnection.setUserName(text);
449: } else if (currentElement.equals("password")) {
450: if (currentConnection == null)
451: errorMessage = "Misplaced <password> tag.";
452: else
453: currentConnection.setPassword(text);
454: } else {
455: }
456: }
457:
458: /*
459: ** MinML callback for fatal error in XML.
460: */
461:
462: public void fatalError(SAXParseException ex)
463: throws SAXException {
464: errorMessage = ex.getMessage();
465: }
466:
467: /*
468: ** Parse the font options into a Font instance.
469: */
470:
471: private Font parseFont(String fontName, String fontStyle,
472: String fontSize) {
473: int styles = Font.PLAIN;
474: StringTokenizer st = new StringTokenizer(fontStyle, ",");
475:
476: while (st.hasMoreElements()) {
477: String style = (String) st.nextElement();
478:
479: if (style.equals("plain"))
480: styles |= Font.PLAIN;
481: else if (style.equals("bold"))
482: styles |= Font.BOLD;
483: else if (style.equals("italic"))
484: styles |= Font.ITALIC;
485: }
486:
487: Integer size = new Integer(fontSize);
488:
489: return new Font(fontName, styles, size.intValue());
490: }
491:
492: private void createConfig(File configFile) throws IOException {
493: JarFile.copyTextFromJar(CONFIG_FILE_NAME, configFile);
494: Dialogs
495: .showInformation(
496: "Created configuration",
497: "The file "
498: + configFile.getName()
499: + " has been created in your home directory.");
500: }
501: }
502:
503: /*
504: ** Write the config state back to vienna.xml.
505: */
506:
507: private class ConfigSaver {
508: public ConfigSaver() throws FileNotFoundException, IOException {
509: String userHome = System.getProperty("user.home");
510:
511: File backupFile = new File(userHome, BACKUP_FILE_NAME);
512: File configFile = new File(userHome, CONFIG_FILE_NAME);
513: File newFile = new File(userHome, NEW_FILE_NAME);
514:
515: saveFile(newFile);
516: backupOriginal(configFile, backupFile);
517: renameNew(newFile, configFile);
518: }
519:
520: private void saveFile(File newFile)
521: throws FileNotFoundException, IOException {
522: //
523: // Write the configuration to the new file. The file will
524: // be renamed later if the save is successful.
525: //
526:
527: FileOutputStream os = new FileOutputStream(newFile);
528: PrintStream out = new PrintStream(os);
529:
530: out.println("<vienna>");
531: out.println(" <options>");
532:
533: out.print(" <savepassword>");
534: out.print(savePasswords);
535: out.println("</savepassword>");
536:
537: out.print(" <maxrows>");
538: out.print(maxRows);
539: out.println("</maxrows>");
540:
541: saveFont(out, "tablefont", tableFont);
542: saveFont(out, "textfont", textFont);
543:
544: out.println(" </options>");
545:
546: Collection c = connections.values();
547: Iterator i = c.iterator();
548:
549: while (i.hasNext()) {
550: ConnectionModel connection = (ConnectionModel) i.next();
551:
552: out.println(" <connection>");
553:
554: out.print(" <name>");
555: out.print(encodeXML(connection.getName()));
556: out.println("</name>");
557:
558: out.print(" <url>");
559: out.print(encodeXML(connection.getUrl()));
560: out.println("</url>");
561:
562: out.print(" <driver>");
563: out.print(encodeXML(connection.getDriverClass()));
564: out.println("</driver>");
565:
566: out.print(" <username>");
567: out.print(encodeXML(connection.getUserName()));
568: out.println("</username>");
569:
570: if (savePasswords) {
571: out.print(" <password>");
572: out.print(encodeXML(connection.getPassword()));
573: out.println("</password>");
574: }
575:
576: out.println(" </connection>");
577: }
578:
579: out.println("</vienna>");
580: out.close();
581: }
582:
583: private void backupOriginal(File originalFile, File backupFile) {
584: //
585: // Remove the old backup if it exists.
586: //
587:
588: if (backupFile.exists())
589: backupFile.delete();
590:
591: //
592: // Rename the original to the backup.
593: //
594:
595: if (originalFile.exists())
596: originalFile.renameTo(backupFile);
597: }
598:
599: private void renameNew(File newFile, File configFile) {
600: newFile.renameTo(configFile);
601: }
602:
603: private void saveFont(PrintStream out, String title, Font font) {
604: out.println(" <" + title + ">");
605: out.print(" <family>");
606: out.print(font.getFamily());
607: out.println("</family>");
608: out.print(" <style>");
609: int style = font.getStyle();
610:
611: if (style == Font.BOLD + Font.ITALIC)
612: out.print("bold,italic");
613: else if (style == Font.BOLD)
614: out.print("bold");
615: else if (style == Font.ITALIC)
616: out.print("italic");
617: else
618: out.print("plain");
619:
620: out.println("</style>");
621: out.print(" <size>");
622: out.print(font.getSize());
623: out.println("</size>");
624: out.println(" </" + title + ">");
625: }
626: }
627:
628: /*
629: ** TreeMap with deep copy constructor.
630: */
631:
632: private class ConnectionMap extends TreeMap {
633: public ConnectionMap() {
634: super ();
635: }
636:
637: /*
638: ** Deep copy constructor.
639: */
640:
641: public ConnectionMap(ConnectionMap copy) {
642: Collection c = copy.values();
643: Iterator i = c.iterator();
644:
645: while (i.hasNext()) {
646: ConnectionModel original = (ConnectionModel) i.next();
647: ConnectionModel m = new ConnectionModel(original);
648: put(original.getName(), m);
649: }
650: }
651: }
652: }
|