001: /* ====================================================================
002: * The JRefactory License, Version 1.0
003: *
004: * Copyright (c) 2001 JRefactory. All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions
008: * are met:
009: *
010: * 1. Redistributions of source code must retain the above copyright
011: * notice, this list of conditions and the following disclaimer.
012: *
013: * 2. Redistributions in binary form must reproduce the above copyright
014: * notice, this list of conditions and the following disclaimer in
015: * the documentation and/or other materials provided with the
016: * distribution.
017: *
018: * 3. The end-user documentation included with the redistribution,
019: * if any, must include the following acknowledgment:
020: * "This product includes software developed by the
021: * JRefactory (http://www.sourceforge.org/projects/jrefactory)."
022: * Alternately, this acknowledgment may appear in the software itself,
023: * if and wherever such third-party acknowledgments normally appear.
024: *
025: * 4. The names "JRefactory" must not be used to endorse or promote
026: * products derived from this software without prior written
027: * permission. For written permission, please contact seguin@acm.org.
028: *
029: * 5. Products derived from this software may not be called "JRefactory",
030: * nor may "JRefactory" appear in their name, without prior written
031: * permission of Chris Seguin.
032: *
033: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
034: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
035: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
036: * DISCLAIMED. IN NO EVENT SHALL THE CHRIS SEGUIN OR
037: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
038: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
039: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
040: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
041: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
042: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
043: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
044: * SUCH DAMAGE.
045: * ====================================================================
046: *
047: * This software consists of voluntary contributions made by many
048: * individuals on behalf of JRefactory. For more information on
049: * JRefactory, please see
050: * <http://www.sourceforge.org/projects/jrefactory>.
051: */
052: package org.acm.seguin.util;
053:
054: import org.acm.seguin.tools.RefactoryInstaller;
055: import java.io.BufferedReader;
056: import java.io.File;
057: import java.io.FileReader;
058: import java.io.FileWriter;
059: import java.io.PrintWriter;
060: import java.io.IOException;
061: import java.util.Hashtable;
062: import java.util.Properties;
063: import java.util.Enumeration;
064: import org.acm.seguin.awt.ExceptionPrinter;
065: import org.acm.seguin.project.Project;
066:
067: /**
068: * Settings loaded from a file
069: *
070: *@author Chris Seguin
071: *@author Mike Atkinson
072: *@created October 3, 1999
073: */
074: public class FileSettings implements Settings {
075: private String project;
076: private String app;
077: private String type;
078: private File file;
079: private long lastModified;
080: private Properties props;
081: private boolean continuallyReload;
082: private boolean reloadNow;
083: private FileSettings parent;
084:
085: private static Hashtable map = null;
086: private static File settingsRoot = null;
087: private static File refactorySettingsRoot = null;
088:
089: /**
090: * Constructor for the FileSettings object
091: *
092: *@param express The file to use for loading
093: *@exception MissingSettingsException The file is not found
094: */
095: public FileSettings(File express) throws MissingSettingsException {
096: file = express;
097: app = express.getParent();
098: type = express.getName();
099:
100: if (!file.exists()) {
101: throw new NoSettingsFileException(app, type);
102: }
103:
104: load();
105:
106: continuallyReload = false;
107: reloadNow = false;
108:
109: parent = null;
110: }
111:
112: /**
113: * Constructor for the FileSettings object
114: *
115: *@param project Description of the Parameter
116: *@param app The application name
117: *@param type The application type
118: *@exception MissingSettingsException The file is not found
119: */
120: protected FileSettings(String project, String app, String type)
121: throws MissingSettingsException {
122: //System.out.println("FileSettings(" + project + "," + app + "," + type + ")");
123: File directory = getSettingsRoot();
124: //System.out.println("(1) directory=" + directory);
125:
126: directory = new File(directory, "." + app);
127: //System.out.println("(2) directory=" + directory);
128: if (!directory.exists()) {
129: directory.mkdirs();
130: System.out.println("directory=" + directory);
131: throw new NoSettingsFileException(app, type);
132: }
133:
134: if (project != null) {
135: directory = new File(directory, "projects");
136: //System.out.println("(3) directory=" + directory);
137: if (!directory.exists()) {
138: directory.mkdirs();
139: //throw new NoSettingsFileException(app, type);
140: }
141: directory = new File(directory, project);
142: //System.out.println("(4) directory=" + directory);
143: if (!directory.exists()) {
144: directory.mkdirs();
145: //throw new NoSettingsFileException(app, type);
146: }
147: }
148:
149: file = new File(directory, type + ".settings");
150: if (!file.exists()) {
151: System.out.println("creating file=" + file);
152: try {
153: java.io.PrintWriter pw = new java.io.PrintWriter(
154: new java.io.FileWriter(file));
155: pw.println("# project " + type + " pretty.settings");
156: pw.close();
157: } catch (java.io.IOException e) {
158: }
159: //throw new NoSettingsFileException(app, type);
160: }
161:
162: load();
163:
164: this .app = app;
165: this .type = type;
166: this .project = project;
167:
168: continuallyReload = false;
169: reloadNow = false;
170:
171: parent = (project == null) ? null
172: : getSettings(null, app, type);
173: }
174:
175: /**
176: * Constructor for the FileSettings object
177: */
178: /*protected FileSettings(String app, String type) throws MissingSettingsException {
179: File directory = new File(getSettingsRoot(), "." + app);
180: if (!directory.exists()) {
181: directory.mkdirs();
182: throw new NoSettingsFileException(app, type);
183: }
184: file = new File(directory, type + ".settings");
185: if (!file.exists()) {
186: throw new NoSettingsFileException(app, type);
187: }
188: load();
189: this.app = app;
190: this.type = type;
191: this.project = null;
192: continuallyReload = false;
193: reloadNow = false;
194: parent = null;
195: }
196: */
197: /**
198: * Sets the ContinuallyReload attribute of the FileSettings object
199: *
200: *@param way The new ContinuallyReload value
201: */
202: public void setContinuallyReload(boolean way) {
203: continuallyReload = way;
204: }
205:
206: /**
207: * Sets the ReloadNow attribute of the FileSettings object
208: *
209: *@param way The new ReloadNow value
210: */
211: public void setReloadNow(boolean way) {
212: reloadNow = way;
213:
214: if (reloadNow) {
215: load();
216: }
217: }
218:
219: /**
220: * Gets the keys associated with this properties
221: *
222: *@return the iterator
223: */
224: public Enumeration getKeys() {
225: reloadIfNecessary();
226: return props.keys();
227: }
228:
229: /**
230: * Description of the Method
231: *
232: *@param key Description of the Parameter
233: */
234: public void removeKey(String key) {
235: props.remove(key);
236: }
237:
238: /**
239: * Gets a string
240: *
241: *@param key The code to look up
242: *@return The associated string
243: */
244: public boolean isLocalProperty(String key) {
245: reloadIfNecessary();
246:
247: if (key == null || props == null) {
248: return false;
249: }
250: String result = props.getProperty(key);
251: return result != null;
252: }
253:
254: /**
255: * Gets a string
256: *
257: *@param key The code to look up
258: *@return The associated string
259: */
260: public String getProperty(String key) {
261: reloadIfNecessary();
262:
263: String result = props.getProperty(key);
264: if ((result == null) && (parent != null)) {
265: result = parent.getString(key);
266: }
267: return result;
268: }
269:
270: /**
271: * Gets a string
272: *
273: *@param key The code to look up
274: *@return The associated string
275: */
276: public String getString(String key) {
277: String result = getProperty(key);
278: if (result == null) {
279: throw new SettingNotFoundException(app, type, key);
280: }
281:
282: return result;
283: }
284:
285: /**
286: * Gets a string
287: *
288: *@param key The code to look up
289: *@param def Use this if the code is not found
290: *@return The associated string
291: */
292: public String getProperty(String key, String def) {
293: String result = getProperty(key);
294: if (result == null) {
295: result = def;
296: }
297: return result;
298: }
299:
300: /**
301: * Sets a string
302: *
303: *@param key The code to look up
304: *@param value New value for the setting code.
305: */
306: public void setString(String key, String value) {
307: reloadIfNecessary();
308: props.setProperty(key, value);
309: }
310:
311: /**
312: * Gets a integer
313: *
314: *@param key The code to look up
315: *@return The associated integer
316: */
317: public int getInteger(String key) {
318: try {
319: return Integer.parseInt(getString(key));
320: } catch (NumberFormatException mfe) {
321: throw new SettingNotFoundException(app, type, key);
322: }
323: }
324:
325: /**
326: * Gets a double
327: *
328: *@param key The code to look up
329: *@return The associated double
330: */
331: public double getDouble(String key) {
332: try {
333: Double value = new Double(getString(key));
334: return value.doubleValue();
335: } catch (NumberFormatException mfe) {
336: throw new SettingNotFoundException(app, type, key);
337: }
338: }
339:
340: /**
341: * Gets a boolean
342: *
343: *@param key The code to look up
344: *@return The associated boolean
345: */
346: public boolean getBoolean(String key) {
347: try {
348: Boolean value = new Boolean(getString(key));
349: return value.booleanValue();
350: } catch (NumberFormatException mfe) {
351: throw new SettingNotFoundException(app, type, key);
352: }
353: }
354:
355: /**
356: * Sets the Parent attribute of the FileSettings object
357: *
358: *@param value The new Parent value
359: */
360: protected void setParent(FileSettings value) {
361: parent = value;
362: }
363:
364: /**
365: * Get the escaped character
366: *
367: *@param ch the character
368: *@return The character it should be replaced with
369: */
370: private char getSpecial(char ch) {
371: switch (ch) {
372: case 'b':
373: return (char) 8;
374: case 'r':
375: return (char) 13;
376: case 'n':
377: return (char) 10;
378: case 'f':
379: return (char) 12;
380: case 't':
381: return (char) 9;
382: default:
383: return ch;
384: }
385: }
386:
387: /**
388: * Returns true if the file is up to date. This method is used to determine
389: * if it is necessary to reload the file.
390: *
391: *@return true if it is up to date.
392: */
393: private boolean isUpToDate() {
394: if (continuallyReload || reloadNow) {
395: return (lastModified == file.lastModified());
396: }
397: return true;
398: // Assume that it is up to date
399: }
400:
401: /**
402: * Loads all the settings from the file
403: */
404: private synchronized void load() {
405: //System.out.println("Loading from: " + file.getPath() + " " + file.length());
406:
407: props = new Properties();
408:
409: // FIXME? use Properties.load()
410: try {
411: BufferedReader input = new BufferedReader(new FileReader(
412: file));
413: String line = input.readLine();
414: while (line != null) {
415: if ((line.length() == 0) || (line.charAt(0) == '#')) {
416: // Comment - skip the line
417: } else {
418: int equalsAt = line.indexOf('=');
419: if (equalsAt > 0) {
420: String key = line.substring(0, equalsAt);
421: String value = unescapeChars(line
422: .substring(equalsAt + 1));
423: props.put(key, value);
424: }
425: }
426: line = input.readLine();
427: }
428:
429: input.close();
430: //System.out.println(" - loaded OK");
431: } catch (IOException ioe) {
432: ioe.printStackTrace();
433: ExceptionPrinter.print(ioe, false);
434: }
435:
436: setReloadNow(false);
437: lastModified = file.lastModified();
438: }
439:
440: /**
441: * Stores all the settings from the file
442: */
443: public synchronized void save() {
444: System.out.println("Saving to: " + file.getPath());
445: props.list(new java.io.PrintWriter(System.out));
446:
447: try {
448: props.store(new java.io.FileOutputStream(file), app
449: + " property file");
450: //System.out.println(" - saved OK");
451: } catch (IOException ioe) {
452: ExceptionPrinter.print(ioe, false);
453: }
454:
455: setReloadNow(false);
456: lastModified = file.lastModified();
457: }
458:
459: /**
460: * A transformation on the characters in the string
461: *
462: *@param value the string we are updating
463: *@return the updated string
464: */
465: private String unescapeChars(String value) {
466: StringBuffer buffer = new StringBuffer();
467: int last = value.length();
468:
469: for (int ndx = 0; ndx < last; ndx++) {
470: char ch = value.charAt(ndx);
471: if (ch == '\\') {
472: char nextChar = value.charAt(ndx + 1);
473: char result = ' ';
474: if (nextChar == 'u') {
475: result = unicode(value, ndx);
476: ndx += 5;
477: } else if (Character.isDigit(nextChar)) {
478: result = octal(value, ndx);
479: ndx += 3;
480: } else if (ndx == last - 1) {
481: // Continuation...
482: } else {
483: result = getSpecial(nextChar);
484: ndx++;
485: }
486:
487: buffer.append(result);
488: } else {
489: buffer.append(ch);
490: }
491: }
492:
493: return buffer.toString();
494: }
495:
496: /**
497: * Determine the unicode character
498: *
499: *@param value Description of Parameter
500: *@param ndx Description of Parameter
501: *@return Description of the Returned Value
502: */
503: private char unicode(String value, int ndx) {
504: String hex = value.substring(ndx + 2, ndx + 6);
505: int result = Integer.parseInt(hex, 16);
506: return (char) result;
507: }
508:
509: /**
510: * Determine the octal character
511: *
512: *@param value Description of Parameter
513: *@param ndx Description of Parameter
514: *@return Description of the Returned Value
515: */
516: private char octal(String value, int ndx) {
517: String oct = value.substring(ndx + 1, ndx + 4);
518: int result = Integer.parseInt(oct, 8);
519: return (char) result;
520: }
521:
522: /**
523: * Sets the root directory for settings files
524: *
525: *@param dir The new SettingsRoot value
526: */
527: public static void setSettingsRoot(String dir) {
528: settingsRoot = new File(dir);
529: refactorySettingsRoot = null;
530: }
531:
532: /**
533: * Sets the root directory for settings files
534: *
535: *@param dir The new SettingsRoot value
536: */
537: public static void setSettingsRoot(File dir) {
538: settingsRoot = dir;
539: refactorySettingsRoot = null;
540: }
541:
542: /**
543: * Factory method to create FileSettings objects
544: *
545: *@param project The name of the project
546: *@param app The name of the application
547: *@param name The name of the specific settings
548: *@return A settings object
549: */
550: public static FileSettings getSettings(String project, String app,
551: String name) {
552: //System.out.println("getSettings(" + project + "," + app + "," + name + ")");
553: initIfNecessary();
554:
555: String key = (project == null) ? "default" : project;
556: key = key + "::" + app + "::" + name;
557: FileSettings result = null;
558: result = (FileSettings) map.get(key);
559: //System.out.println(" map.get(" + key + ") -> " + result);
560: if (result == null) {
561: result = new FileSettings(project, app, name);
562: map.put(key, result);
563: //System.out.println(" map.put(" + key + "," + result + ")");
564: }
565: return result;
566: }
567:
568: /**
569: * Factory method to create FileSettings objects
570: *
571: *@param app The name of the application
572: *@param name The name of the specific settings
573: *@return A settings object
574: */
575: public static FileSettings getSettings(String app, String name) {
576: return getSettings(Project.getCurrentProjectName(), app, name);
577: }
578:
579: /**
580: * Factory method to create FileSettings objects. Equivalent to
581: * getSettings("Refactory",name)
582: *
583: *@param name The name of the specific settings
584: *@return A settings object
585: *@since 2.7.04
586: */
587: public static FileSettings getRefactorySettings(String name) {
588: return getSettings("Refactory", name);
589: }
590:
591: /**
592: * Factory method to create FileSettings objects. Equivalent to
593: * getSettings("Refactory","pretty")
594: *
595: *@return A settings object
596: *@since 2.7.04
597: */
598: public static FileSettings getRefactoryPrettySettings() {
599: return getSettings("Refactory", "pretty");
600: }
601:
602: /**
603: * Gets the SettingsRoot for a Refactory application
604: *
605: *@return The SettingsRoot value
606: *@since 2.7.04
607: */
608: public static File getRefactorySettingsRoot() {
609: if (refactorySettingsRoot == null) {
610: refactorySettingsRoot = new File(settingsRoot, ".Refactory");
611: }
612:
613: return refactorySettingsRoot;
614: }
615:
616: /**
617: * Gets the SettingsRoot attribute of the FileSettings class
618: *
619: *@return The SettingsRoot value
620: */
621: public static File getSettingsRoot() {
622: if (settingsRoot == null) {
623: initRootDir();
624: }
625:
626: return settingsRoot;
627: }
628:
629: /**
630: * Main program to test the FileSettings object
631: *
632: *@param args the command line arguments
633: */
634: public static void main(String[] args) {
635: // Make sure everything is installed properly
636: (new RefactoryInstaller(false)).run();
637:
638: String key = "author";
639: if (args.length > 0) {
640: key = args[0];
641: }
642:
643: String type = "pretty";
644: if (args.length > 1) {
645: type = args[1];
646: }
647:
648: String app = "Refactory";
649: if (args.length > 2) {
650: app = args[2];
651: }
652:
653: String project = "JRefactory";
654: if (args.length > 3) {
655: project = args[3];
656: }
657:
658: System.out
659: .println("Found: "
660: + (new FileSettings(project, app, type))
661: .getString(key));
662: }
663:
664: /**
665: * Initializes static variables
666: */
667: private static synchronized void initIfNecessary() {
668: if (map == null) {
669: map = new Hashtable();
670: initRootDir();
671: }
672: }
673:
674: /**
675: * Initializes the root directory
676: */
677: private static void initRootDir() {
678: if (settingsRoot != null) {
679: return;
680: }
681:
682: String javaHome = System.getProperty("jrefactory.home");
683: if (javaHome != null) {
684: //System.out.println("Home: " + javaHome);
685: settingsRoot = new File(javaHome);
686: return;
687: }
688:
689: javaHome = System.getProperty("user.home");
690: if (javaHome != null) {
691: //System.out.println("Home: " + javaHome);
692: settingsRoot = new File(javaHome);
693: return;
694: }
695:
696: settingsRoot = new File("~/");
697: if (settingsRoot.exists()) {
698: //System.out.println("Home: ~/");
699: return;
700: }
701:
702: settingsRoot = new File("C:\\winnt\\profiles");
703: if (settingsRoot.exists()) {
704: File attempt = new File(settingsRoot, System
705: .getProperty("user.name"));
706: if (attempt.exists()) {
707: //System.out.println("Home: C:\\winnt\\profiles\\currentuser");
708: settingsRoot = attempt;
709: return;
710: }
711: }
712:
713: settingsRoot = new File("c:\\windows");
714: //System.out.println("Home: C:\\windows");
715: }
716:
717: /**
718: * Return the file we are monitoring
719: *
720: *@return The file value
721: */
722: public File getFile() {
723: return file;
724: }
725:
726: /**
727: * Description of the Method
728: */
729: private void reloadIfNecessary() {
730: if (!isUpToDate()) {
731: load();
732: }
733: reloadNow = false;
734: }
735:
736: /**
737: * Description of the Method
738: *
739: *@return Description of the Return Value
740: */
741: public String toString() {
742: return project + "::" + app + "::" + type + ", parent="
743: + parent;
744: }
745: }
|