/*
 * @(#)XMLDecoder.java	1.18 01/12/03
 *
 * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
package java.beans;

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

import org.xml.sax.*;

import javax.xml.parsers.SAXParserFactory;  
import javax.xml.parsers.ParserConfigurationException;  
import javax.xml.parsers.SAXParser;  

/**
 * The <code>XMLDecoder</code> class is used to read XML documents 
 * created using the <code>XMLEncoder</code> and is used just like 
 * the <code>ObjectInputStream</code>. For example, one can use 
 * the following fragment to read the first object defined 
 * in an XML document written by the <code>XMLEncoder</code> 
 * class: 
 * <pre>
 *       XMLDecoder d = new XMLDecoder(
 *                          new BufferedInputStream(
 *                              new FileInputStream("Test.xml")));
 *       Object result = d.readObject();
 *       d.close();
 * </pre>
 * 
 * @see XMLEncoder
 * @see java.io.ObjectInputStream
 *
 * @since 1.4
 * 
 * @version 1.5 11/20/00
 * @author Philip Milne
 */
public class XMLDecoder { 
    private InputStream in; 
    private Object owner; 
    private ExceptionListener exceptionListener; 
    private ObjectHandler handler; 
    
    /** 
     * Creates a new input stream for reading archives  
     * created by the <code>XMLEncoder</code> class. 
     *
     * @param in The underlying stream. 
     *
     * @see XMLEncoder#XMLEncoder(OutputStream)
     */ 
    public XMLDecoder(InputStream in) { 
        this(in, null); 
    } 
    
    /** 
     * Creates a new input stream for reading archives  
     * created by the <code>XMLEncoder</code> class. 
     *
     * @param in The underlying stream. 
     * @param owner The owner of this stream. 
     *
     */ 
    public XMLDecoder(InputStream in, Object owner) { 
        this(in, owner, null); 
    } 
    
    /** 
     * Creates a new input stream for reading archives  
     * created by the <code>XMLEncoder</code> class. 
     *
     * @param in the underlying stream. 
     * @param owner the owner of this stream. 
     *
     */ 
    public XMLDecoder(InputStream in, Object owner, ExceptionListener exceptionListener) { 
        this.in = in;  
        setOwner(owner);  
        setExceptionListener(exceptionListener); 
        Statement.setCaching(true); 
        SAXParserFactory factory = SAXParserFactory.newInstance();
        try {
           SAXParser saxParser = factory.newSAXParser();
           handler = new ObjectHandler(this);
           saxParser.parse(in, handler); 
        } 
        catch (ParserConfigurationException e) {
            getExceptionListener().exceptionThrown(e);
        } 
        catch (SAXException se) { 
            Exception e = se.getException(); 
            getExceptionListener().exceptionThrown((e == null) ? se : e); 
        }
        catch (IOException ioe) { 
            getExceptionListener().exceptionThrown(ioe); 
        }
    } 
    
    /**
     * This method closes the input stream associated 
     * with this stream. 
     */
    public void close() { 
        Statement.setCaching(false); 
        try { 
            in.close(); 
        } 
        catch (IOException e) { 
            getExceptionListener().exceptionThrown(e); 
        }
    }
    
    /** 
     * Sets the exception handler for this stream to <code>exceptionListener</code>. 
     * The exception handler is notified when this stream catches recoverable 
     * exceptions.
     * 
     * @param exceptionListener The exception handler for this stream. 
     *
     * @see #getExceptionListener
     */ 
    public void setExceptionListener(ExceptionListener exceptionListener) { 
        this.exceptionListener = exceptionListener; 
    } 
    
    /**
     * Gets the exception handler for this stream. 
     * 
     * @return The exception handler for this stream. 
     *
     * @see #setExceptionListener
     */ 
    public ExceptionListener getExceptionListener() { 
        return (exceptionListener != null) ? exceptionListener : Statement.defaultExceptionListener;
    } 
    
    /** 
     * Reads the next object from the input. 
     *
     * @throws ArrayIndexOutOfBoundsException if the stream contains no objects (or no more objects)
     * @see XMLEncoder#writeObject
     */ 
    public Object readObject() { 
        return handler.dequeueResult(); 
    } 
	
    /** 
     * Sets the owner of this decoder to <code>owner</code>. 
     * 
     * @param owner The owner of this decoder. 
     *
     * @see #getOwner
     */ 
    public void setOwner(Object owner) { 
        this.owner = owner; 
	}
	    
    /**
     * Gets the owner of this decoder. 
     * 
     * @return The owner of this decoder. 
     *
     * @see #setOwner
     */ 
    public Object getOwner() {
	    return owner; 
    }
}

class MutableExpression extends Expression { 
    private Object property; 
    private Vector argV = new Vector();  

    private String capitalize(String propertyName) { 
        if (propertyName.length() == 0) { 
            return propertyName; 
        }
        return propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
    } 
    
    public MutableExpression() { 
        super(null, null, null); 
    } 
    
    public Object[] getArguments() { 
    	return argV.toArray(); 
    }
    
    public String getMethodName() { 
    	if (property == null) { 
            return super.getMethodName(); 
    	} 
        int setterArgs = (property instanceof String) ? 1 : 2; 
        String methodName = (argV.size() == setterArgs) ? "set" : "get"; 
        if (property instanceof String) { 
            return methodName + capitalize((String)property); 
        }
        else { 
            return methodName; 
        }
    }
    
    public void addArg(Object arg) { 
    	argV.add(arg); 
    } 
    
    public void setTarget(Object target) { 
    	this.target = target; 
    } 
    
    public void setMethodName(String methodName) { 
    	this.methodName = methodName; 
    }
    public void setProperty(Object property) { 
    	this.property = property; 
    }
}

class ObjectHandler extends HandlerBase {
    private Hashtable environment; 
    private Vector expStack; 
    private StringBuffer chars; 
    private XMLDecoder is; 
    private int itemsRead = 0; 

    public ObjectHandler(XMLDecoder is) { 
        environment = new Hashtable();
        expStack = new Vector(); 
        chars = new StringBuffer(); 
        this.is = is; 
    } 
    
    private Object getValue(Expression exp) { 
        try { 
            return exp.getValue(); 
        }
        catch (Exception e) { 
            is.getExceptionListener().exceptionThrown(e); 
            return null; 
        }
    }
    
    private void addArg(Object arg) { 
        // System.out.println("addArg: " + instanceName(arg)); 
        lastExp().addArg(arg); 
    } 
    
    private Object pop(Vector v) { 
        int last = v.size()-1; 
        Object result = v.get(last); 
        v.remove(last); 
        return result; 
    }
    
    private Object eval() { 
        return getValue((Expression)lastExp()); 
    }
    
    private MutableExpression lastExp() { 
        return (MutableExpression)expStack.lastElement(); 
    } 
    
    Object dequeueResult() { 
        // System.out.println("dequeueResult: " + expStack);
        Object[] results = lastExp().getArguments(); 
        return results[itemsRead++]; 
    }
    
    private boolean isPrimitive(String name) { 
        return name != "void" && Statement.typeNameToClass(name) != null;
    }
    
    private void simulateException(String message) { 
        Exception e = new Exception(message); 
    	e.fillInStackTrace(); 
        is.getExceptionListener().exceptionThrown(e); 
    } 
	
    private Class classForName(String name) { 
        try { 
            return Statement.classForName(name); 
        }
        catch (ClassNotFoundException e) { 
            is.getExceptionListener().exceptionThrown(e);
        } 
        return null; 
    } 
    
    private HashMap getAttributes(AttributeList attrs) { 
        HashMap attributes = new HashMap(); 
        if (attrs != null && attrs.getLength() > 0) {
            for(int i = 0; i < attrs.getLength(); i++) { 
                attributes.put(attrs.getName(i), attrs.getValue(i)); 
            }
        } 
        return attributes; 
    }
    
    public void startElement(String name, AttributeList attrs) throws SAXException { 
        // System.out.println("startElement" + name);
        name = name.intern(); // Xerces parser does not supply unique tag names.
        chars.setLength(0); 
        if (name == "null" || 
            name == "string" || 
            name == "class" || 
            isPrimitive(name)) { 
            return; 
        }
        HashMap attributes = getAttributes(attrs);  
        
        MutableExpression e = new MutableExpression(); 
        
        // Target
        String className = (String)attributes.get("class"); 
        if (className != null) {
            e.setTarget(classForName(className));
        } 
        
        // Property
        Object property = attributes.get("property"); 
        String index = (String)attributes.get("index"); 
        if (index != null) { 
            property = new Integer(index); 
            e.addArg(property); 
        } 
        e.setProperty(property); 
        
        // Method
        String methodName = (String)attributes.get("method"); 
        if (methodName == null && property == null) {
            methodName = "new"; 
        } 
        e.setMethodName(methodName); 
        
        // Tags
        if (name == "void") { 
            if (e.getTarget() == null) { // this check is for "void class="foo" method= ..." 
	        e.setTarget(eval()); 
	    }
        }
    	else if (name == "array") { 
            // The class attribute means sub-type for arrays. 
            String subtypeName = (String)attributes.get("class"); 
            Class subtype = (subtypeName == null) ? Object.class : classForName(subtypeName); 
            String length = (String)attributes.get("length"); 
            if (length != null) { 
                e.setTarget(java.lang.reflect.Array.class); 
                e.addArg(subtype); 
                e.addArg(new Integer(length)); 
            }
            else { 
                Class arrayClass = java.lang.reflect.Array.newInstance(subtype, 0).getClass(); 
                e.setTarget(arrayClass); 
            }
        } 
        else if (name == "java") { 
            e.setValue(is); // The outermost scope is the stream itself. 
        }
	else if (name == "object") { 
	}
	else { 
            simulateException("Unrecognized opening tag: " + name + " " + attrsToString(attrs)); 
	    return; 
        } 
		
        // ids
        String idName = (String)attributes.get("id"); 
        if (idName != null) { 
            environment.put(idName, e); 
        }
		
        // idrefs
        String idrefName = (String)attributes.get("idref"); 
        if (idrefName != null) { 
            e.setValue(lookup(idrefName)); 
        } 
        
        // fields
        String fieldName = (String)attributes.get("field"); 
        if (fieldName != null) { 
            e.setValue(getFieldValue(e.getTarget(), fieldName)); 
        }
        expStack.add(e); 
    }
    
    private Object getFieldValue(Object target, String fieldName) { 
        try { 
            Class type = target.getClass(); 
            if (type == Class.class) { 
                type = (Class)target; 
            } 
            java.lang.reflect.Field f = type.getField(fieldName); 
            return f.get(target); 
        }
        catch (Exception e) { 
            is.getExceptionListener().exceptionThrown(e); 
            return null; 
        }
    }            
            
    private String attrsToString(AttributeList attrs) { 
        StringBuffer b = new StringBuffer(); 
        for (int i = 0; i < attrs.getLength (); i++) {
            b.append(attrs.getName(i)+"=\""+attrs.getValue(i)+"\" ");
        }
        return b.toString(); 
    }            
            
    public void characters(char buf [], int offset, int len) throws SAXException {
        chars.append(new String(buf, offset, len));
    }

    private Object lookup(String s) { 
        Expression e = (Expression)environment.get(s); 
        if (e == null) { 
            simulateException("Unbound variable: " + s); 
        }
        return getValue(e); 
    }

    public void endElement(String name) throws SAXException {
        // System.out.println("endElement: " + expStack);
        name = name.intern(); // Xerces parser does not supply unique tag names.
        if (name == "null") {
            addArg(null); 
            return; 
        }
        if (name == "java") { 
            return; 
        }
        if (name == "string") { 
            addArg(chars.toString()); 
            return; 
        }
        if (name == "class") { 
            addArg(classForName(chars.toString())); 
            return; 
        }
        if (isPrimitive(name)) { 
            Class wrapper = Expression.typeNameToClass(name); 
            Expression e = new Expression(wrapper, "new", new Object[]{chars.toString()}); 
            addArg(getValue(e)); 
            return; 
        }
        if (name == "object" || name == "array" || name == "void") { 
            Expression e = (Expression)pop(expStack); 
            Object value = getValue(e); 
            if (name == "object" || name == "array") { 
                addArg(value); 
            }
        }
        else { 
            simulateException("Unrecognized closing tag: " + name);
        }
    }
}


