// PropertyManager.java
// $Id: PropertyManager.java,v 1.13 1998/01/22 13:23:05 bmahe Exp $
// (c) COPYRIGHT MIT and INRIA, 1997.
// Please first read the full copyright statement in file COPYRIGHT.html

package org.w3c.jigadm;

import java.util.*;
import java.io.*;

import org.w3c.jigadm.*;
import org.w3c.jigadm.editors.*;
import org.w3c.jigsaw.admin.*;
import org.w3c.tools.resources.*;

class ResourceClassProperties {
    // The class we're handling the configuration for
    String clsname = null;
    // The directory this classes configuration is at
    File dir = null;
    // The class editor properties
    Properties editorProperties = null;
    // The set of registered helpers for that editor's class
    String helperClasses[] = new String[0];
    // The helpers properties (map help classes to properties)
    Hashtable helperProperties = null;
    // The attribute properties
    Hashtable attributeProperties = null;

    /**
     * Parse | separated property value into a String array.
     * @param p Properties to look into.
     * @param name The property to look for and parse.
     * @return A non-null but potentially zero-length string array.
     */

    static String[] getStringArray(Properties p, String name) {
	String v = (String) p.get(name);
	if ( v == null )
	    return new String[0];
	// Parse the property value:
	StringTokenizer st    = new StringTokenizer(v, "|");
	int             len   = st.countTokens();
	String          ret[] = new String[len];
	for (int i = 0 ; i < ret.length ; i++) {
	    ret[i] = st.nextToken();
	}
	return ret;
	
    }

    /**
     * Does this class config allows for helper aggregation.
     * If <strong>true</strong> this means that the GUI should instantiate
     * all inherited helpers to construct the resource editor.
     * @return A boolean.
     */

    boolean aggregateHelpers() {
	if ( editorProperties == null )
	    return true;
	String s = (String) editorProperties.get("aggregateHelpers");
	return (s != null) && s.equalsIgnoreCase("true");
    }

    /**
     * Does this class config allows for attribute aggregation.
     * If <strong>true</strong> this means that the GUI should instantiate
     * an editor for all inherited attributes.
     * @return A boolean.
     */
     
    boolean aggregateAttributes() {
	if ( editorProperties == null )
	    return true;
	String s = (String) editorProperties.get("aggregateAttributes");
	return (s != null) && s.equalsIgnoreCase("true");
    }

    /**
     * Should the config aggregate specific attribute editor properties.
     * @return A boolean.
     */

    boolean aggregateAttributeProperties() {
	return false;
    }

    /**
     * Utility - Load a directory of prop files into given hashtable.
     * @param into Hashtable to fill in with Properties instance.
     * @param dir Directory to load.
     */

    void loadPropertyDirectory(Hashtable into, File dir) {
	try {
	    String files[] = dir.list();
	    if ( files == null ) 
		return;
	    for (int i = 0 ; i < files.length ; i++) {
		// Skip files that aren't property holders
		if ( ! files[i].endsWith(".p") )
		    continue;
		// Create and init the properties:
		Properties  p  = new Properties();
		InputStream in = (new BufferedInputStream
				  (new FileInputStream
				   (new File(dir, files[i]))));
		p.load(in);
		in.close();
		// Register them into the hashtable:
		into.put(files[i].substring(0, files[i].length()-2), p);
	    }
	} catch (IOException ex) {
	    ex.printStackTrace();
	}
    }

    /**
     * Initialize that class config.
     * This method will load the resource editor properties, than the helper
     * specific properties, and finally the attribute properties.
     */

    void initialize() {
	// Load the editor properties:
	try {
	    InputStream in = (new BufferedInputStream
			      (new FileInputStream
			       (new File(dir, "properties"))));
	    editorProperties = new Properties();
	    editorProperties.load(in);
	    in.close();
	} catch (FileNotFoundException ex) {
	} catch (Exception ex) {
	    ex.printStackTrace();
	}
	// From the editor properties, get the set of helpers:
	if ( editorProperties != null )
	    helperClasses = getStringArray(editorProperties, "helpers");
	// Load the helpers specific properties:
	helperProperties = new Hashtable(11);
	loadPropertyDirectory(helperProperties, new File(dir, "helpers"));
	// Load the specific attribute editor properties:
	attributeProperties = new Hashtable(11);
	loadPropertyDirectory(attributeProperties, new File(dir, "attrs"));
    }

    String[] getHelperClasses() {
	return helperClasses;
    }

    
    Properties getHelperProperties(String clsname) {
	Properties p = (Properties) helperProperties.get(clsname);
	return p;
    }

    Properties getAttributeProperties(String name) {
	Properties p = (Properties) attributeProperties.get(name);
	return p;
    }

    Properties getEditorProperties() {
	return editorProperties;
    }

    ResourceClassProperties(String clsname, File dir) {
	this.clsname = clsname;
	this.dir     = dir;
	initialize();
    }
}

public class PropertyManager {
    /**
     * The root for the property files.
     */
    protected File root = null;
    /**
     * The set of resource classes we know about.
     * Maps class names to ResourceClassProperty
     */
    protected Hashtable classProperties = null;
    /**
    /**
     * the mapping of icon names to icon locations
     */
    protected Properties iconProperties = null;
    
    /**
     * the hashtable of mime types
     */
    protected Hashtable mimeTypes = null;

    private boolean inited = false;

    protected static Properties merge(Properties into
				      , Properties source
				      , boolean overide) {
	Properties  p = (Properties) into.clone();
	Enumeration e = source.keys();
	while ( e.hasMoreElements() ) {
	    Object key = e.nextElement();
	    if ((! overide) && into.get(key) != null)
		continue;
	    Object val = source.get(key);
	    p.put(key, val);
	}
	return p;
    }

    protected static Properties merge(Properties into, Properties source) {
	return merge(into, source, true);
    }


    /**
     * Load the properties from the root directory.
     */

    protected void initialize() {
	classProperties = new Hashtable(11);
	// List all available directories, and load them:
	String dirs[] = root.list();
	if ( dirs == null )
	    return;
	for (int i = 0 ; i < dirs.length ; i++) {
	    if ( dirs[i].equals("icons")) // reserved directory
		continue;
	    // Skip non directory files:
	    File file = new File(root, dirs[i]);
	    if ( ! file.isDirectory() )
		continue;
	    // Register others:
	    ResourceClassProperties rcp = null;
	    rcp = new ResourceClassProperties(dirs[i], file);
	    classProperties.put(dirs[i], rcp);
	}
	// and now the icons mapping
	try {
	    InputStream in = (new BufferedInputStream
			      (new FileInputStream
			       (new File(root, "icons.p"))));
	    iconProperties = new Properties();
	    iconProperties.load(in);
	    in.close();
	} catch (Exception ex) {
	    ex.printStackTrace();
	}
	// and now the mime types
	mimeTypes = new Hashtable(8);
	try {
	    InputStream in = (new BufferedInputStream
			      (new FileInputStream
			       (new File(root, "mimetypes.p"))));
	    Properties p = new Properties();
	    p.load(in);
	    in.close();
	    String[] major = ResourceClassProperties.getStringArray(p,"Types");
	    String[] minor;
	    for(int i=0; i<major.length; i++) {
		minor = ResourceClassProperties.getStringArray(p, major[i]);
		mimeTypes.put(major[i], minor);
	    }
	} catch (Exception ex) {
	    ex.printStackTrace();
	}
	inited = true;
    }

    /**
     * Get the best matching resource class properties for given class.
     * @param classes The class names we're looking a macth for.
     * @return A ResourceClassProperties instance.
     */

    protected ResourceClassProperties 
    getResourceClassProperties(String names[], int from) {
	// Look along the hierarchy for a class editor:
	ResourceClassProperties rcp = null;
	for (int i = from ; i < names.length ; i++) {
	    rcp = (ResourceClassProperties) classProperties.get(names[i]);
	    if ( rcp != null)
		break;
	}
	if ( rcp == null )
	    throw new RuntimeException("configuration error, no editor for: "
				       + names[0]);	
	return rcp;
    }

    protected
    ResourceClassProperties getResourceClassProperties(String names[]) {
	return getResourceClassProperties(names, 0);
    }

    protected int
    findResourceClassProperties(String classes[], int offset) {
	// Look along the hierarchy for a class editor:
	ResourceClassProperties rcp = null;
	for (int i = offset ; i < classes.length ; i++) {
	    rcp = (ResourceClassProperties) classProperties.get(classes[i]);
	    if ( rcp != null )
		return i;
	}
	return -1;
    }

    protected int findResourceClassProperties(String classes[]) {
	return findResourceClassProperties(classes, 0);
    }

    protected String[] getClassHierarchy(RemoteResourceWrapper rrw) {
	try {
	    String classes[] = rrw.getResource().getClassHierarchy();
	    return classes;
	} catch (RemoteAccessException ex) {
	    ex.printStackTrace();
	}
	return new String[0];
    }

    protected Properties getDefaultHelperProperties(String clsname) {
	ResourceClassProperties rcp = null;
	rcp = (ResourceClassProperties) classProperties.get("defaults");
	if ( rcp != null )
	    return rcp.getHelperProperties(clsname);
	return null;
    }

    protected Properties getDefaultAttributeProperties(String clsname) {
	ResourceClassProperties rcp = null;
	rcp = (ResourceClassProperties) classProperties.get("defaults");
	if ( rcp != null )
	    return rcp.getAttributeProperties(clsname);
	return null;
    }

    /**
     * Get any properties for the editor of the given remote resource instance.
     * @return A Properties instance.
     */

    public Properties getEditorProperties(RemoteResourceWrapper rrw) {
	String classes[] = getClassHierarchy(rrw);
	return getResourceClassProperties(classes).getEditorProperties();
    }

    /**
     * Get the list of helpers to be created for the given remote resource.
     * @return A set of helper class names (as an array).
     */

    public String[] getHelperClasses(RemoteResourceWrapper rrw) {
	String classes[] = getClassHierarchy(rrw);
	// Load in the most specific helpers:
	int slot = findResourceClassProperties(classes);
	if ( slot < 0 )
	    return new String[0];
	ResourceClassProperties rcp = getResourceClassProperties(classes);
	Vector vhelpers = new Vector();
	vhelpers.addElement(rcp.getHelperClasses());
	// Look up the hierarchy, while allowed:
	while ( rcp.aggregateHelpers() ) {
	    if ( ++slot >= classes.length )
		break;
	    if ((slot = findResourceClassProperties(classes, slot)) < 0)
		break;
	    rcp  = getResourceClassProperties(classes, slot);
	    vhelpers.addElement(rcp.getHelperClasses());
	}
	// Convert the vector of String arrays into a single String array:
	if ( vhelpers.size() == 1 ) 
	    return (String[]) vhelpers.elementAt(0);
	int sz = 0;
	int n  = 0;
	for (int i = 0 ; i < vhelpers.size() ; i++)
	    sz += ((String[]) vhelpers.elementAt(i)).length;
	String helpers[] = new String[sz];
	for (int i = 0 ; i < vhelpers.size() ; i++) {
	    String s[] = (String[]) vhelpers.elementAt(i);
	    for (int j = 0 ; j < s.length ; j++)
		helpers[n++] = s[j];
	}
	return helpers;
    }

    /**
     * Get the properties for the helper of a given remote resource.
     * @param rr The remote resource being edited.
     * @param helperClass Class of the helper about to be created.
     * @return An instance of Properties.
     */

    public Properties getHelperProperties(RemoteResourceWrapper rrw
					  , String helperClass) {
	String classes[] = getClassHierarchy(rrw);
	int slot = findResourceClassProperties(classes);
	if ( slot < 0 )
	    return new Properties();
	ResourceClassProperties rcp = getResourceClassProperties(classes);
	// Properties p = getDefaultProperties(helperClass);
	Properties p = rcp.getHelperProperties(helperClass);
	while ( rcp.aggregateHelpers() ) {
	    if ( ++slot >= classes.length )
		break;
	    if ((slot = findResourceClassProperties(classes, slot)) < 0)
		break;
	    rcp  = getResourceClassProperties(classes, slot);
	    Properties more = rcp.getHelperProperties(helperClass);
	    if ( more != null )
		p = (p != null) ? merge(p, more, false) : more;
	}
	return (p == null) ? new Properties() : p;
    }

    /**
     * Get the properties for the attribute editor of a given remote resource.
     * @param rr The remote resource being edited.
     * @param attr The attribute being edited.
     */

    public Properties getAttributeProperties(RemoteResourceWrapper rrw
					     , Attribute attr) {
	String classes[] = getClassHierarchy(rrw);
	String attrClass = attr.getClass().getName();
	int slot = findResourceClassProperties(classes);
	if ( slot < 0 )
	    return new Properties();
	ResourceClassProperties rcp = getResourceClassProperties(classes);
	Properties p = rcp.getAttributeProperties(attr.getName());
	if ( p == null )
	    p = new Properties();
	Properties defs = getDefaultAttributeProperties(attrClass);
	if ( defs != null ) 
	    p = merge(defs, p);
	while ( rcp.aggregateAttributeProperties() ) {
	    if ( ++slot >= classes.length )
		break;
	    if ((slot = findResourceClassProperties(classes, slot)) < 0 )
		break;
	    rcp  = getResourceClassProperties(classes, slot);	    
	    Properties more = rcp.getAttributeProperties(attr.getName());
	    if ( more != null )
		p = merge(more, p);
	}
	return p;
    }

    /**
     * get the icon name resolved from its description
     * @param name a String 
     */
    
    public String getIconLocation(String name) {
        String rname = iconProperties.getProperty(name);
        if (rname != null) {
            boolean rel; 
            rel = iconProperties.getProperty("relative").equals("true");
            return (rel) ? root.getAbsolutePath() +
                File.separator + "icons" + File.separator + rname : rname;
        }
        return null;
    }
    
    /**
     * get the hashtable of mimetypes
     */

    public Hashtable getMimeTypes() {
	return mimeTypes;
    }

    public static String ROOT_P = "jigadmRoot";

    public static PropertyManager propertyManager = null;

    public static PropertyManager getPropertyManager() {
	if ( propertyManager == null ) {
	    Properties p = System.getProperties();
	    String base = p.getProperty(ROOT_P);
	    if (base == null) {
		propertyManager = new PropertyManager(
		    new File(p.getProperty("user.dir") + File.separator +
			     "config" + File.separator + "jigadm"));
	    }
	    else
		propertyManager = new PropertyManager(
		    new File(base + File.separator +"config" +
			     File.separator + "jigadm"));
	}
	return propertyManager;
    }

    public PropertyManager(File root) {
	this.root = root;
	initialize();
	if ( ! this.inited ) {
	    // The one who wrote that code has already booked a special
	    // place in hell
	    System.out.println("PropertyManager: unable to initialize.");
	    System.out.println("\tCouldn't find mandatory properties in \""+
			       root+"\" use the \"-root\" option or run "+
			       "jigadmin from Jigsaw root directory.");
	    System.exit(1);
	}
    }
}

