// Copyright 1996, Marimba Inc. All Rights Reserved.


// @(#)Props.java, 1.39, 01/04/97





package marimba.util;





import java.io.*;


import java.util.*;





import marimba.io.*;





/**


 * A wrapper class for Properties. Add adds a header, file name,


 * is case insensitive, and keeps track of changes.


 *


 * @author	Arthur van Hoff


 * @version 	1.39, 01/04/97


 */


public class Props extends Properties implements Cloneable {


    static byte hex[] = {'0', '1', '2', '3', '4', '5',


			 '6', '7', '8', '9', 'A',


			 'B', 'C', 'D', 'E', 'F'};


    String hdr;


    String file;


    boolean sensitive;


    public boolean changed;





    /**


     * Constructor.


     */


    public Props(String hdr, String file) {


	this(hdr, file, false);


    }





    /**


     * Constructor.


     */


    public Props(String hdr, File file, boolean sensitive) {


	this(hdr, file.getPath(), sensitive);


    }





    /**


     * Constructor.


     */


    public Props(String hdr, String file, boolean sensitive) {


	this.hdr = hdr;


	this.file = file;


	this.sensitive = sensitive;


    }





    /**


     * Construct from parent.


     */


    public Props(Properties parent, String hdr, String file) {


	super(parent);


	this.hdr = hdr;


	this.file = file;


    }





    public Object clone() {


	Props copy = (Props) super.clone();


	copy.defaults = (Properties) defaults.clone();


	return copy;


    }





    /**


     * Create a file. If the directory does not exist, create the parent directories.


     */


    private static FileOutputStream createFile(File file) throws IOException {


	SecurityManager security = System.getSecurityManager();


	if (security != null) {


	    security.checkWrite(file.getPath());


	}


	try {


	    return new FileOutputStream(file);


	} catch (IOException e) {


	    File parent = new File(file.getParent());


	    if ((!parent.exists()) && (!parent.mkdirs())) {


		throw e;


	    }


	    return new FileOutputStream(file);


	}


    }





    final String getBackupName() {


	return file + ".bak";


    }





    /**


     * Save to the file. Return false if the save fails for any reason.


     */


    public synchronized boolean save() {


	if (changed) {


	    File f = new File(file);


	    File b = new File(getBackupName());


	    boolean moved = f.exists();





	    if (b.exists()) {


		b.delete();


	    }


	    if (moved) {


		f.renameTo(b);


	    }


	    try {


		FileOutputStream fout = createFile(new File(file));


		FastOutputStream out = new FastOutputStream(fout);


		save(out, hdr);


		out.close();


		changed = false;


	    } catch (IOException e) {


		//e.printStackTrace();


		return false;


	    }


	    if (moved) {


		b.delete();


	    }


	}


	return true;


    }





    /**


     * Save properties to an OutputStream. Use the header as


     * a comment at the top of the file. This now uses a FastOutputStream


     * so that it is more efficient and so that it saves it in the


     * appropriate format.


     */


    public synchronized void save(FastOutputStream prnt, String header) {


	if (header != null) {


	    prnt.write('#');


	    prnt.println(header);


	}





	// Sort the keys before saving


	int siz = size(), n = 0;


	String keys[] = new String[siz];


	for (Enumeration e = keys() ; e.hasMoreElements() ; n++) {


	    keys[n] = (String)e.nextElement();


	}


	QuickSort.sort(keys, 0, siz);





	// Save the keys


	for (n = 0 ; n < siz ; n++) {


	    String key = (String)keys[n];


	    prnt.print(key);


	    prnt.write('=');





	    String val = (String)get(key);


	    int len = val.length();


	    boolean empty = false;





	    for (int i = 0 ; i < len ; i++) {


		int ch = val.charAt(i);





		switch (ch) {


		  case '\\': prnt.write('\\'); prnt.write('\\'); break;


		  case '\t': prnt.write('\\'); prnt.write('t'); break;


		  case '\n': prnt.write('\\'); prnt.write('n'); break;


		  case '\r': prnt.write('\\'); prnt.write('r'); break;





		  default:


		    if ((ch < ' ') || (ch >= 127) || (empty && (ch == ' '))) {


			prnt.write('\\');


			prnt.write('u');


			prnt.write(hex[(ch >> 12) & 0xF]);


			prnt.write(hex[(ch >> 8) & 0xF]);


			prnt.write(hex[(ch >> 4) & 0xF]);


			prnt.write(hex[(ch >> 0) & 0xF]);


		    } else {


			prnt.write(ch);


		    }


		}


		empty = false;


	    }


	    prnt.println();


	}


    }





    /**


     * Load from the file. Returns whether the load succeeded.


     */


    public synchronized boolean load() {


	boolean first = true;


	String path = file;





	while (true) {


	    try {


		InputStream in = new FastInputStream(path);


		try {


		    load(in);


		    changed = false;


		    if (!first) {


			// means we used a backup name - save it to the real name now


			new File(path).renameTo(new File(file));


		    }


		    return true;


		} finally {


		    in.close();


		}


	    } catch (IOException e) {


	    }


	    if (first) {


		path = getBackupName();


		first = false;


	    } else {


		break;


	    }


	}


	return false;


    }





    /**


     * Mark the properties as changed.


     */


    public synchronized void touch() {


	changed = true;


    }





    public synchronized void clear() {


	super.clear();


	changed = true;


    }





    public synchronized void clean() {


	changed = false;


    }





    /**


     * Remove a key.


     */


    public synchronized Object remove(Object key) {


	if (super.get(key) != null) {


	    touch();


	    return super.remove(key);


	}


	return null;


    }





    /**


     * Get a property, ignore case.


     */


    public Object get(Object key) {


	if ((!sensitive) && (key instanceof String)) {


	    return super.get(((String)key).toLowerCase());   


	}


	return super.get(key);


    }





    /**


     * Get a property, ignore case.


     */


    public String getProperty(String key) {


	return super.getProperty(sensitive ? key : key.toLowerCase());


    }





    /**


     * Get a property, ignore case.


     */


    public String getProperty(String key, String defaultValue) {


	return super.getProperty(sensitive ? key : key.toLowerCase(), defaultValue);


    }


    


    /**


     * Store a property. If the key is a string, then convert it


     * to lower case.


     */


    public synchronized Object put(Object key, Object val) {


	if ((!sensitive) && (key instanceof String)) {


	    key = ((String)key).toLowerCase();


	}


	Object oldval = super.get(key);


	if ((oldval != null) && oldval.equals(val)) {


	    return oldval;


	}


	touch();


	return super.put(key, val);


    }


    


    public synchronized void addPropsFrom(Props src) {


	Enumeration e = src.keys();


	while (e.hasMoreElements()) {


	    Object key = e.nextElement();


	    put(key, src.get(key));


	}


    }





    /**


     * Store a property only if the value is not null or


     * an empty string.


     */


    public synchronized Object setProp(Object key, Object val) {


	return ((val == null) || "".equals(val)) ?


	    remove(key) : put(key, val);


    }





    /**


     * Set a property, ignore case.


     */


    public synchronized void setProperty(String key, String val) {


	put(key, val);


    }


    


    public String getFile() {


	return file;


    }





    public long getLong(String name, long def) {


	String val = getProperty(name);


	if (val != null) {


	    try {


		return Long.parseLong(val);


	    } catch (NumberFormatException e) {


	    }


	}


	return def;


    }





    public int getInteger(String name, int def) {


	String val = getProperty(name);


	if (val != null) {


	    try {


		return Integer.parseInt(val);


	    } catch (NumberFormatException e) {


	    }


	}


	return def;


    }





    public boolean getBoolean(String name, boolean def) {


	String val = getProperty(name);


	return (val != null) ? val.toLowerCase().equals("true") : def;


    }





    /**


     * Debugging.


     */


    public String toString() {


	return getClass().getName() + "[hdr=" + hdr + ",file=" + file +


	    ", props=" + super.toString() + "]";


    }


}


