// copyright=> http://www.jxml.com/mdsax/License.txt

package com.jxml.quick.util.qjml2java;

import java.io.*;
import java.util.ArrayList;
import java.util.Vector;

import com.jxml.quick.model.qjml.*;


/**
 * This class represents a Java class. It is called Element because it's
 * usually created from an XML element definition, but it can
 * also be created from an attribute definition.
 */
public class Element {
    static final String slash = System.getProperty("file.separator");
    
    // QJML2Java command-line options
    public static boolean printClone = true; // print the clone methods
    public static boolean printQAware = true;
    public static boolean setParents = false;
    public static boolean printPrint = false;
    public static boolean printXPath = false;
    public static boolean printFullConst = true;
    public static boolean printMoreMethods = true;
    public static String fileExt = "_Quick";
    static String genPackage = null;
    static boolean oneNewClass = false;

    public String targetClass;
    public String tagName;
    public ElList selseq;
    public ElList attributes;
    public boolean textConstructor;
    public boolean newClass; // true if class will be generated
    public boolean hasAClass; // False is type is int, float, double, boolean
    public boolean clonable;
    public ArrayList values = new ArrayList(); // of String
    public ArrayList comments = new ArrayList(); // of String
    public boolean wild;
    public boolean bimodal;
    public Element parentElement = null;
    public boolean abst = false; // abstract
    public ArrayList subclasses = new ArrayList(); // list of classes that extend this one
    public boolean intf = false; // true if class is an interface
    public ArrayList imp = new ArrayList(); // of Element
    public boolean generate = true; // generate attribute in QJML
    
    public ArrayList fields = new ArrayList(); // of ClassField
    boolean fields_inited = false;
    boolean inited = false;
    
    
    public Element() {
    }
    
    public void init() {
        if (inited)
            return;
        inited = true;
        if (attributes != null)
            attributes.init();
        if (selseq != null)
            selseq.init();
        if (subclasses != null)
            for (int i=0; i<subclasses.size(); i++)
                ((Element)subclasses.get(i)).init();
        if (parentElement != null)
            parentElement.init();
        if (imp != null)
            for (int i=0; i<imp.size(); i++)
                ((Element)imp.get(i)).init();
    }
    
    /* add a list of classes elements to allElements */
    public void getAllClasses(ArrayList allElements, ArrayList discarded) {
        if (allElements.indexOf(this) != -1)
            return; // cheap way to avoid a loop
        if (discarded.indexOf(this) != -1)
            return; // cheap way to avoid a loop
        
        if (targetClass != null) {
            int ind = targetClass.lastIndexOf('.');
            String packagepath;
            if (ind == -1)
                packagepath = null;
            else
                packagepath = targetClass.substring(0, ind);
            if (genPackage != null && !genPackage.equals(packagepath))
                newClass = false;
        } else
            newClass = false;

        if (newClass && generate)
            allElements.add(this);
        else
            discarded.add(this);
        if (attributes != null)
            attributes.getAllClasses(allElements, discarded);
        if (selseq != null)
            selseq.getAllClasses(allElements, discarded);
        if (subclasses != null)
            for (int i=0; i<subclasses.size(); i++)
                ((Element)subclasses.get(i)).getAllClasses(allElements, discarded);
        if (parentElement != null)
            parentElement.getAllClasses(allElements, discarded);
        if (imp != null)
            for (int i=0; i<imp.size(); i++)
                ((Element)imp.get(i)).getAllClasses(allElements, discarded);
    }
    
    /* create Java files for this element and all its children */
    public void makeAllFiles(String folderPath) {
        ArrayList allElements = new ArrayList();
        getAllClasses(allElements, new ArrayList());
        
        String packagepath=null;
        if (printPrint || printXPath || setParents || printClone || printQAware) {
            if (genPackage != null)
                packagepath = genPackage;
            else if (!abst && targetClass != null) {
                int ind = targetClass.lastIndexOf(".");
                if (ind == -1)
                    packagepath = null;
                else
                    packagepath = targetClass.substring(0, ind);
            } else {
                System.err.println("Root element has no associated classpath: path to generated package unknown.");
                System.err.println("Use option -g to specify the package");
                return;
            }
            makeQGBaseIntf(folderPath, packagepath);
            makeQGBase(folderPath, packagepath);
        }
        
        Element el;
        for (int i=0; i<allElements.size(); i++) {
            el = (Element)allElements.get(i);
            if (el.targetClass == null)
                System.err.println("Warning: no target class for " + el.tagName);
            else
                el.makeFile(folderPath, packagepath);
        }
    }
    
    public PrintWriter openFile(String filename) {
        try {
            File file = new File(filename);
            File folder = file.getParentFile();
            if (folder != null && !folder.exists()) {
                System.out.println("creating directories for " + filename);
                boolean done = folder.mkdirs();
                if (!done) {
                    System.err.println("could not create the directories for " + filename);
                    return(null);
                }
            }
        
            FileWriter fw = new FileWriter(file);
            PrintWriter pw = new PrintWriter(fw);
            System.out.println("new file: "+filename);
            return(pw);
        } catch (IOException ex) {
            System.err.println("IOException: "+ex.getMessage());
            //ex.printStackTrace();
            return(null);
        }
    }
    
    public void makeQGBaseIntf(String folderPath, String packagepath) {
        String filename, targetClass;
        if (packagepath == null)
            targetClass = "QGBaseIntf";
        else
            targetClass = packagepath + ".QGBaseIntf";
        filename = folderPath +slash+ targetClass.replace('.', slash.charAt(0)) + ".java";
        PrintWriter pw = openFile(filename);
        if (pw == null)
            return;
        
        if (packagepath != null)
            pw.println("package " + packagepath + ";");
        pw.println("");
        if (printPrint) {
            pw.println("    // with the print option:");
            pw.println("import java.io.PrintWriter;");
            pw.println("");
        }
        if (setParents) {
            pw.println("    // with the qParent option:");
            pw.println("import com.jxml.quick.QContext;");
            pw.println("import com.jxml.quick.QPE;");
            pw.println("");
        }
        if (printXPath || setParents) {
            pw.println("    // with the XPath option:");
            pw.println("import java.util.ArrayList;");
            pw.println("");
        }
        pw.print("public interface QGBaseIntf");
        if (printClone) {
            pw.println(" extends Cloneable {");
            pw.println("    // with the clone option:");
            pw.println("    public abstract Object clone();");
            pw.println("    ");
        } else
            pw.println(" {");
        if (printPrint) {
            pw.println("    // with the print option:");
            pw.println("    /** print a summary of this object */");
            pw.println("    public abstract void print(PrintWriter pw, String indent);");
            pw.println("    /** print a summary of this object */");
            pw.println("    public abstract void print(PrintWriter pw);");
            pw.println("    ");
        }
        if (setParents) {
            pw.println("    // with the qParent option:");
            pw.println("    /** set the XML parent for this object */");
            pw.println("    public abstract void setXParent(QGBaseIntf obj);");
            pw.println("    /** get the XML parent for this object */");
            pw.println("    public abstract QGBaseIntf getXParent();");
            pw.println("    /** set the XML parents of the children to this object */");
            pw.println("    public abstract void setXParentLinks();");
            pw.println("    /** Method called at the end of a Quick transformation. Can be used to check integrity. */");
            pw.println("    public abstract void endElement(String tagName, QContext outputContext) throws QPE;");
            pw.println("    ");
        }
        if (printXPath) {
            pw.println("    // with the XPath option:");
            pw.println("    /** test if this object matches an XPath test */");
            pw.println("    public abstract boolean xPathTest(String test);");
            pw.println("    /** return a list of objects matching an XPath expression. */");
            pw.println("    public abstract ArrayList xPath(String path);");
            pw.println("    /** get an XML attribute value */");
            pw.println("    public abstract String getXAttVal(String attname);");
            pw.println("    /** get a list of all the objects corresponding to an XML child with the given tag */");
            pw.println("    public abstract ArrayList getXChildren(String childTagName);");
        }
        if (printXPath || setParents) {
            pw.println("    /** get a list of all the objects corresponding to XML children */");
            pw.println("    public abstract ArrayList getAllXChildren();");
        }
        pw.println("}");
        
        pw.flush();
        pw.close();
    }
    
    public void makeQGBase(String folderPath, String packagepath) {
        String filename, targetClass;
        if (packagepath == null)
            targetClass = "QGBase";
        else
            targetClass = packagepath + ".QGBase";
        filename = folderPath +slash+ targetClass.replace('.', slash.charAt(0)) + ".java";
        PrintWriter pw = openFile(filename);
        if (pw == null)
            return;
        
        if (packagepath != null)
            pw.println("package " + packagepath + ";");
        pw.println("");
        if (setParents)
            pw.println("import com.jxml.quick.QAware;");
        if (printXPath)
            pw.println("import com.jxml.quick.QBiModal;");
        if (setParents) {
            pw.println("import com.jxml.quick.QContext;");
            pw.println("import com.jxml.quick.QPE;");
        }
        if (printPrint)
            pw.println("import java.io.PrintWriter;");
        pw.println("import java.util.ArrayList;");
        pw.println("");
        pw.print("public abstract class QGBase implements QGBaseIntf");
        //if (printClone)
        //    pw.print(", Cloneable");
        if (setParents)
            pw.print(", QAware");
        pw.println(" {");
        pw.println("");
        if (setParents) {
            pw.println("    protected QGBaseIntf qParent = null;");
            pw.println("    ");
        }
        pw.println("    public QGBase() {");
        pw.println("    }");
        pw.println("    ");
        pw.println("    // to override:");
        if (printXPath) {
            pw.println("        // for XPath:");
            pw.println("    /** get a list of all the objects corresponding to an XML child with the given tag */");
            pw.println("    public abstract ArrayList getXChildren(String childTagName);");
            pw.println("    public abstract Object getXAttribute(String attName);");
        }
        if (printXPath || setParents) {
            pw.println("        // for XPath or setParents:");
            pw.println("    /** get a list of all the objects corresponding to XML children */");
            pw.println("    public abstract ArrayList getAllXChildren();");
        }
        if (printClone) {
            pw.println("        // for clone:");
            pw.println("    public abstract Object clone();");
        }
        if (printPrint) {
            pw.println("        // for print:");
            pw.println("    public abstract void print(PrintWriter pw, String indent);");
        }
        pw.println("    ");
        pw.println("    ");
        if (setParents) {
            pw.println("    /** set the XML parent for this object */");
            pw.println("    public void setXParent(QGBaseIntf obj) {");
            pw.println("        qParent = obj;");
            pw.println("    }");
            pw.println("");
            pw.println("    /** get the XML parent for this object */");
            pw.println("    public QGBaseIntf getXParent() {");
            pw.println("        return(qParent);");
            pw.println("    }");
            pw.println("");
            pw.println("    /** set the XML parents of the children to this object */");
            pw.println("    public void setXParentLinks() {");
            pw.println("        ArrayList xchildren = getAllXChildren();");
            pw.println("        for (int i=0; i<xchildren.size(); i++)");
            pw.println("            if (xchildren.get(i) != null && xchildren.get(i) instanceof QGBaseIntf)");
            pw.println("                ((QGBaseIntf)xchildren.get(i)).setXParent(this);");
            pw.println("    }");
            pw.println("");
            pw.println("    // endElement can be overriden to add integrity checks");
            pw.println("    /** Method called at the end of a Quick transformation. Used here to set the XML parent links */");
            pw.println("    public void endElement(String tagName, QContext outputContext) throws QPE {");
            pw.println("        /* setup parent links */");
            pw.println("        setXParentLinks();");
            pw.println("");
            pw.println("        /* perform integrity checks */");
            pw.println("        ");
            pw.println("    }");
            pw.println("");
        }
        if (printPrint) {
            pw.println("    /** print a summary of this object */");
            pw.println("    public void print(PrintWriter pw) {");
            pw.println("        print(pw, \"\");");
            pw.println("        pw.flush();");
            pw.println("    }");
            pw.println("");
        }
        if (printXPath) {
            pw.println("    /** test if this object matches an XPath test of the form '@attname=\"attval\"' or 'childname=\"childval\"'*/");
            pw.println("    public boolean xPathTest(String test) {");
            pw.println("        boolean isAtt = test.startsWith(\"@\");");
            pw.println("        if (isAtt)");
            pw.println("            test = test.substring(1);");
            pw.println("        int ie = test.indexOf(\"=\");");
            pw.println("        if (ie == -1)");
            pw.println("            return(false);");
            pw.println("        String name = test.substring(0, ie);");
            pw.println("        test = test.substring(ie+1);");
            pw.println("        int iq1 = test.indexOf(\"\\\"\");");
            pw.println("        if (iq1 == -1)");
            pw.println("            return(false);");
            pw.println("        int iq2 = test.substring(iq1+1).indexOf(\"\\\"\") + iq1+1;");
            pw.println("        if (iq2 == -1)");
            pw.println("            return(false);");
            pw.println("        String val = test.substring(iq1+1, iq2);");
            pw.println("        ");
            pw.println("        if (isAtt)");
            pw.println("            return((val.equals(getXAttVal(name))));");
            pw.println("        else {");
            pw.println("            ArrayList children = getXChildren(name);");
            pw.println("            boolean found = false;");
            pw.println("            for (int i=0; i<children.size(); i++) {");
            pw.println("                Object child = children.get(i);");
            pw.println("                String childval = null;");
            pw.println("                if (child instanceof String)");
            pw.println("                    childval = (String)child;");
            pw.println("                else if (child instanceof QBiModal)");
            pw.println("                    childval = ((QBiModal)child).getQText();");
            pw.println("                else");
            pw.println("                    childval = String.valueOf(child);");
            pw.println("                if (val.equals(childval)) {");
            pw.println("                    found = true;");
            pw.println("                    break;");
            pw.println("                }");
            pw.println("            }");
            pw.println("            return(found);");
            pw.println("        }");
            pw.println("    }");
            pw.println("    ");
            
            pw.println("    /**");
            pw.println("     * return a list of objects matching an XPath expression.");
            pw.println("     * The supported expressions are in the form");
            pw.println("     *   A/B[@attname=\"attval\"]/C");
            pw.println("     *   ../A (element A in the parent)");
            pw.println("     *   //A (any element A in the descendants)");
            pw.println("     *   A/*");
            pw.println("     */");
            pw.println("    public ArrayList xPath(String path) {");
            pw.println("        if (path == null)");
            pw.println("            return(null);");
            pw.println("        ArrayList r = new ArrayList();");
            pw.println("        int is = path.indexOf(\"/\");");
            pw.println("        String s1,s2;");
            pw.println("        if (is == -1) {");
            pw.println("            s1 = path;");
            pw.println("            s2 = null;");
            pw.println("        } else {");
            pw.println("            s1 = path.substring(0, is);");
            pw.println("            s2 = path.substring(is + 1);");
            pw.println("        }");
            pw.println("        int ib1 = s1.indexOf(\"[\");");
            pw.println("        boolean withTest = (ib1 != -1);");
            pw.println("        String test = null;");
            pw.println("        if (withTest) {");
            pw.println("            int ib2 = s1.substring(ib1+1).indexOf(\"]\") + ib1+1;");
            pw.println("            if (ib2 == -1)");
            pw.println("                return(null);");
            pw.println("            test = s1.substring(ib1+1, ib2);");
            pw.println("            s1 = s1.substring(0, ib1);");
            pw.println("        }");
            pw.println("        if (!withTest) {");
            pw.println("            if (is != 0) {");
            pw.println("                if (s1.equals(\"*\"))");
            pw.println("                    r.addAll(getAllXChildren());");
            pw.println("                else if (s1.equals(\"..\")) {");
            pw.println("                    if (getXParent() != null)");
            pw.println("                        r.add(getXParent());");
            pw.println("                } else if (s1.startsWith(\"@\")) {");
            pw.println("                    Object o = getXAttribute(s1.substring(1));");
            pw.println("                    if (o != null)");
            pw.println("                        r.add(o);");
            pw.println("                } else");
            pw.println("                    r.addAll(getXChildren(s1));");
            pw.println("            } else if (s2.startsWith(\"/\"))");
            pw.println("                r.addAll(xPath(s2.substring(1)));");
            pw.println("            else {");
            pw.println("                if (getXParent() != null) {");
            pw.println("                    s2 = \"/\" + s2;");
            pw.println("                    r.add(getXParent());");
            pw.println("                } else");
            pw.println("                    r.add(this);");
            pw.println("            }");
            pw.println("        } else {");
            pw.println("            ArrayList xchildren;");
            pw.println("            if (s1.equals(\"*\"))");
            pw.println("                xchildren = getAllXChildren();");
            pw.println("            else if (s1.equals(\"..\")) {");
            pw.println("                xchildren = new ArrayList();");
            pw.println("                if (getXParent() != null)");
            pw.println("                    xchildren.add(getXParent());");
            pw.println("            } else");
            pw.println("                xchildren = getXChildren(s1);");
            pw.println("            for (int i=0; i<xchildren.size(); i++)");
            pw.println("                if (xchildren.get(i) instanceof QGBaseIntf)");
            pw.println("                    if (((QGBaseIntf)xchildren.get(i)).xPathTest(test))");
            pw.println("                        r.add(xchildren.get(i));");
            pw.println("        }");
            pw.println("        if (s2 == null)");
            pw.println("            return(r);");
            pw.println("        else if (is != 0 || !s2.startsWith(\"/\")) {");
            pw.println("            ArrayList r2 = new ArrayList();");
            pw.println("            ArrayList r3;");
            pw.println("            for (int i=0; i<r.size(); i++) {");
            pw.println("                r3 = ((QGBaseIntf)r.get(i)).xPath(s2);");
            pw.println("                if (r3 != null)");
            pw.println("                    r2.addAll(r3);");
            pw.println("            }");
            pw.println("            return(r2);");
            pw.println("        } else {");
            pw.println("            ArrayList r2 = r;");
            pw.println("            r = getAllXChildren();");
            pw.println("            ArrayList r3;");
            pw.println("            for (int i=0; i<r.size(); i++)");
            pw.println("                if (r.get(i) instanceof QGBaseIntf) {");
            pw.println("                    r3 = ((QGBaseIntf)r.get(i)).xPath(path);");
            pw.println("                    if (r3 != null)");
            pw.println("                        r2.addAll(r3);");
            pw.println("                }");
            pw.println("            return(r2);");
            pw.println("        }");
            pw.println("    }");
            
            pw.println("    ");
            pw.println("    /** get an XML attribute value */");
            pw.println("    public String getXAttVal(String attname) {");
            pw.println("        Object o = getXAttribute(attname);");
            pw.println("        if (o == null)");
            pw.println("            return(null);");
            pw.println("        else if (o instanceof String)");
            pw.println("            return((String)o);");
            pw.println("        else if (o instanceof QBiModal)");
            pw.println("            return(((QBiModal)o).getQText());");
            pw.println("        else");
            pw.println("            return(String.valueOf(o));");
            /* PropertyEditor.getAsText() should be used instead if a 
               targetEditor is used and there is no toString method */
            pw.println("    }");
        }
        pw.println("");
        pw.println("}");
        
        pw.flush();
        pw.close();
    }
    
    public void printClassStart(PrintWriter pw, String classname, String extClass, boolean quick_class) {
        if (comments != null && comments.size() > 0) {
            pw.println("/**");
            pw.println(" * " + classname + " -");
            pw.println(" * ");
            for (int i=0; i<comments.size(); i++)
                pw.println(" * " + (String)comments.get(i));
            pw.println(" */");
        }
        
        pw.print("public ");
        if (!intf && (classname.endsWith(fileExt) || abst))
        // truly a _Quick class if classname.endsWith(fileExt)
            pw.print("abstract ");
        if (intf)
            pw.print("interface ");
        else
            pw.print("class ");
        pw.print(classname + " ");
        String implement = null;
        if (!intf && extClass != null)
            pw.print("extends " + extClass + " ");
        if (printClone || setParents || printPrint || printXPath)
            implement = "QGBaseIntf";
        if (intf && extClass != null && !extClass.equals("QGBase"))
            if (implement == null)
                implement = extClass;
            else
                implement += ", " + extClass;
        if (quick_class) {
            /*if (printClone && clonable)
                if (implement == null)
                    implement = "Cloneable";
                else
                    implement += ", Cloneable";*/
            if (bimodal)
                if (implement == null)
                    implement = "QBiModal";
                else
                    implement += ", QBiModal";
            if (wild)
                if (implement == null)
                    implement = "QName";
                else
                    implement += ", QName";
            if (printQAware)
                if (implement == null)
                    implement = "QAware";
                else
                    implement += ", QAware";
            if (imp != null)
                for (int i=0; i<imp.size(); i++)
		{
		    if (implement==null)
			implement="";
		    else
                    	implement += ", ";
                    implement += getClassName(((Element)imp.get(i)).targetClass);
		}
        }
        if (implement != null) {
            if (intf)
	    {
                pw.println("extends " + implement + " {");
		pw.println();
	    }
            else
	    {
                pw.println("implements " + implement + " {");
		pw.println();
	    }
        } else
	    {
                pw.println("{");
		pw.println();
	    }
    }
    
    /* create 2 Java files (or one if oneNewClass) for this element */
    public void makeFile(String folderPath, String basePackagePath) {
        String classname1, filename1, packagepath;
        String classname2, filename2;
        int i = targetClass.lastIndexOf('.');
        if (i == -1) {
            classname1 = targetClass + fileExt;
            classname2 = targetClass;
            packagepath = null;
        } else {
            classname1 = targetClass.substring(i+1) + fileExt;
            classname2 = targetClass.substring(i+1);
            packagepath = targetClass.substring(0,i);
        }
        //if (genPackage != null && !genPackage.equals(packagepath))
        //    return;
        if (oneNewClass)
            classname1 = classname2;
        
        String fn = folderPath +slash+ targetClass.replace('.', slash.charAt(0));
        filename1 = fn + fileExt + ".java";
        filename2 = fn + ".java";
        if (oneNewClass)
            filename1 = filename2;
        PrintWriter pw = openFile(filename1);
        if (pw == null)
            return;
        
        if (!fields_inited)
            makeFields();
        
        if (packagepath != null)
	{
            pw.println("package " + packagepath + ";");
	    pw.println();
	}
        if (basePackagePath != null && !basePackagePath.equals(packagepath) &&
            (printClone || setParents || printPrint || printXPath))
            printImports(pw, basePackagePath + ".QGBaseIntf");
        else
            printImports(pw, null);
        
        if (parentElement != null && parentElement.targetClass != null &&
                !parentElement.targetClass.equals("java.lang.Object"))
            printClassStart(pw, classname1,
                getClassName(parentElement.targetClass), true);
        else if (printClone || setParents || printPrint || printXPath)
            printClassStart(pw, classname1, "QGBase", true);
        else
            printClassStart(pw, classname1, null, true);
        if (!intf) {
            if (wild)
	    {
                pw.println("    public String name;");
		pw.println();
	    }
            if ((textConstructor && (parentElement == null ||
                !parentElement.textConstructor)) || bimodal)
	    {
                pw.println("    public String text;");
		pw.println();
	    }
            printFieldDeclarations(pw);
            printConstructors(pw, classname1, true);
        }
        printClassMethods(pw);
        if (printClone && !intf) // even if not clonable, to implement the interface
            printCloneMethod(pw);
        pw.println("}");
        pw.flush();
        pw.close();
        
        if (!oneNewClass && !(new File(filename2)).exists()) {
            pw = openFile(filename2);
            if (pw == null)
                return;
            if (packagepath != null)
	    {
                pw.println("package " + packagepath + ";");
		pw.println();
	    }
            if (basePackagePath != null && !basePackagePath.equals(packagepath) &&
                (printClone || setParents || printPrint || printXPath))
                printImports(pw, basePackagePath + ".QGBaseIntf");
            else
                printImports(pw, null);
            printClassStart(pw, classname2, classname1, false);
            if (!intf)
                printConstructors(pw, classname2, false);
            pw.println("");
            pw.println("}");
            pw.flush();
            pw.close();
        }
    }
    
    public static boolean isKnownPackage(String name, Vector allPackages) {
        boolean known = false;
        String pack;
        String namepack;
        int ip = name.lastIndexOf('.');
        if (ip == -1)
            namepack = null;
        else
            namepack = name.substring(0, ip);
        
        for (int i=0; i<allPackages.size(); i++) {
            pack = (String)allPackages.get(i);
            if (pack == null)
                System.err.println("Warning: allPackages("+i+") is null");
            if (namepack != null && pack.endsWith("*")) {
                pack = pack.substring(0, pack.length()-2);
                if (namepack.equals(pack))
                    known = true;
            } else if (pack.equals(name))
                known = true;
        }
        return(known);
    }
    
    public void getPackages(Vector allPackages) {
        if (imp != null)
            for (int i=0; i<imp.size(); i++) {
                if (imp.get(i) == null)
                    System.err.println("Warning: imp("+i+") is null for "+tagName);
                if (!isKnownPackage(((Element)imp.get(i)).targetClass, allPackages))
                    allPackages.add(((Element)imp.get(i)).targetClass);
            }
        if (parentElement != null && parentElement.targetClass != null)
            if (!isKnownPackage(parentElement.targetClass, allPackages))
                allPackages.add(parentElement.targetClass);
        if (fields != null) {
            for (int i=0; i<fields.size(); i++) {
                String tc = getField(i).classpath;
                if (tc == null)
                    System.err.println("Warning: classpath is null for " + getField(i).name);
                if (!isKnownPackage(tc, allPackages))
                    allPackages.add(tc);
                if (getField(i).children != null) {
                    ArrayList fChildren = getField(i).children;
                    for (int j=0; j<fChildren.size(); j++) {
                        tc = ((Element)fChildren.get(j)).targetClass;
                        if (!isKnownPackage(tc, allPackages))
                            allPackages.add(tc);
                    }
                }
            }
        }
    }
    
    public void printImports(PrintWriter pw, String importToAdd) {
        //pw.println("import java.util.ArrayList;\n");
        Vector packages = new Vector();
        
        // known packages
        int nb_known = 1;
        packages.add("java.lang.*");
        if (targetClass.lastIndexOf('.') > 0) {
            packages.add(targetClass.substring(0,
                targetClass.lastIndexOf('.')+1) + "*");
            nb_known++;
        }
        
        if (bimodal)
            packages.add("com.jxml.quick.QBiModal");
        
        if (wild)
            packages.add("com.jxml.quick.QName");
            
        if (printQAware) {
            packages.add("com.jxml.quick.QAware");
            packages.add("com.jxml.quick.QContext");
            packages.add("com.jxml.quick.QPE");
        }
        
        if (printPrint)
            packages.add("java.io.PrintWriter");
            
        if (printClone || setParents || printPrint || printXPath) {
            packages.add("java.util.ArrayList");
        }
        
        getPackages(packages);
        if (packages.size() > nb_known) {
            for (int i=nb_known; i<packages.size(); i++)
                pw.println("import " + (String)packages.get(i) + ";");
            pw.println("");
        }
        
        if (importToAdd != null)
	{
            pw.println("import " + importToAdd + ";");
	    pw.println();
	}
    }
    
    public void makeFields() {
        ArrayList otherfields = fields;
        fields = new ArrayList();
        if (attributes != null)
            attributes.makeFields(fields, true);
        if (selseq != null)
            selseq.makeFields(fields, false);
        if (otherfields != null)
            fields.addAll(otherfields);
        fields_inited = true;
    }
    
    public void printFieldDeclarations(PrintWriter pw) {
        for (int i=0; i<fields.size(); i++)
            if (parentElement == null ||
                !parentElement.fieldExists(getField(i).name))
                getField(i).printDeclaration(pw);
        pw.println("");
    }
    
    public void printConstructors(PrintWriter pw, String classname, boolean quick_class) {
        if (textConstructor) {
            pw.println("    public "+classname+"(String text) {");
            if (quick_class &&
                (parentElement == null || !parentElement.textConstructor))
                pw.println("        this.text = text;");
            else
                pw.println("        super(text);");
            pw.println("    }");
	    pw.println();
            if (quick_class) {
                pw.println("    public String toString() {");
                pw.println("        return(text);");
                pw.println("    }");
		pw.println();
            }
        } else {
            pw.println("    public "+classname+"() {");
            pw.println("    }");
	    pw.println();
        }
        
        boolean optelem = false;
        boolean reqelem = false;
        // full constructor
        if (printFullConst && fields != null && (fields.size() > 0 || wild || bimodal)) {
            pw.println("    /** full constructor */");
            pw.print("    public " + classname + "(");
            String dec = null;
            if (textConstructor || bimodal)
                dec = "String text";
            if (setParents) {
                if (dec == null)
                    dec = "";
                else
                    dec += ", ";
                dec += "QGBaseIntf qParent";
            }
            if (wild) {
                if (dec == null)
                    dec = "";
                else
                    dec += ", ";
                dec += "String name";
            }
            ClassField field;
            for (int i=0; i<fields.size(); i++) {
                field = getField(i);
                if (field.optional)
                    optelem = true;
                if (dec == null)
                    dec = "";
                else
                    dec += ", ";
                dec += getClassName(field.classpath) + " " + field.name;
            }
            if (dec != null)
                pw.print(dec);
            pw.println(") {");
            if (quick_class) {
                if (textConstructor || bimodal)
                    pw.println("        this.text = text;");
                if (setParents)
                    pw.println("        this.qParent = qParent;");
                if (wild)
                    pw.println("        this.name = name;");
                for (int i=0; i<fields.size(); i++) {
                    field = getField(i);
                    if (field.optional)
                        optelem = true;
                    else
                        reqelem = true;
                    String ind = "        ";
                    if (field.classpath.equals("java.util.ArrayList")) {
                        pw.println(ind + "if (" + field.name + " != null)");
                        ind += "    ";
                    }
                    pw.println(ind + "this." + field.name + " = " + field.name + ";");
                }
            } else {
                /*
                pw.print("        super(");
                dec = null;
                if (textConstructor || bimodal)
                    dec = "text";
                if (setParents) {
                    if (dec == null)
                        dec = "";
                    else
                        dec += ", ";
                    dec += "qParent";
                }
                if (wild) {
                    if (dec == null)
                        dec = "";
                    else
                        dec += ", ";
                    dec += "name";
                }
                for (int i=0; i<fields.size(); i++) {
                    field = getField(i);
                    if (field.optional)
                        optelem = true;
                    else
                        reqelem = true;
                    if (dec == null)
                        dec = "";
                    else
                        dec += ", ";
                    dec += field.name;
                }
                if (dec != null)
                    pw.print(dec);
                pw.println(");");
                */
                
                // this new version is more flexible:
                
                if (textConstructor || bimodal)
                    pw.println("        setQText(text);");
                if (setParents)
                    pw.println("        setXParent(qParent);");
                if (wild)
                    pw.println("        setQName(name);");
                for (int i=0; i<fields.size(); i++) {
                    field = getField(i);
                    if (field.optional)
                        optelem = true;
                    else
                        reqelem = true;
                    String ind = "        ";
                    if (field.classpath.equals("java.util.ArrayList")) {
                        pw.println(ind + "if (" + field.name + " != null)");
                        ind += "    ";
                    }
                    if (field.property)
                        pw.println(ind + "set" + upFirst(field.name) + "(" + field.name + ");");
                    else
                        pw.println(ind + "this." + field.name + " = " + field.name + ";");
                }
            }
            pw.println("    }");
	    pw.println();
        }

        // constructor with only required elements
        if (printFullConst && fields != null && (fields.size() > 0 || wild || bimodal) && optelem && reqelem) {
            pw.println("    /** minimum constructor */");
            pw.print("    public " + classname + "(");
            String dec = null;
            if (textConstructor || bimodal)
                dec = "String text";
            if (setParents) {
                if (dec == null)
                    dec = "";
                else
                    dec += ", ";
                dec += "QGBaseIntf qParent";
            }
            if (wild) {
                if (dec == null)
                    dec = "";
                else
                    dec += ", ";
                dec += "String name";
            }
            ClassField field;
            for (int i=0; i<fields.size(); i++) {
                field = getField(i);
                if (!field.optional) {
                    if (dec == null)
                        dec = "";
                    else
                        dec += ", ";
                    dec += getClassName(field.classpath) + " " + field.name;
                }
            }
            if (dec != null)
                pw.print(dec);
            pw.println(") {");
            if (quick_class) {
                if (textConstructor || bimodal)
                    pw.println("        this.text = text;");
                if (setParents)
                    pw.println("        this.qParent = qParent;");
                if (wild)
                    pw.println("        this.name = name;");
                for (int i=0; i<fields.size(); i++) {
                    field = getField(i);
                    if (!field.optional) {
                        String ind = "        ";
                        if (field.classpath.equals("java.util.ArrayList")) {
                            pw.println(ind + "if (" + field.name + " != null)");
                            ind += "    ";
                        }
                        pw.println(ind + "this." + field.name + " = " + field.name + ";");
                    }
                }
            } else {
                /*
                pw.print("        super(");
                dec = null;
                if (textConstructor || bimodal)
                    dec = "text";
                if (setParents) {
                    if (dec == null)
                        dec = "";
                    else
                        dec += ", ";
                    dec += "qParent";
                }
                if (wild) {
                    if (dec == null)
                        dec = "";
                    else
                        dec += ", ";
                    dec += "name";
                }
                for (int i=0; i<fields.size(); i++) {
                    field = getField(i);
                    if (!field.optional) {
                        if (dec == null)
                            dec = "";
                        else
                            dec += ", ";
                        dec += field.name;
                    }
                }
                if (dec != null)
                    pw.print(dec);
                pw.println(");");
                */
                
                // this new version is more flexible:
                
                if (textConstructor || bimodal)
                    pw.println("        setQText(text);");
                if (setParents)
                    pw.println("        setXParent(qParent);");
                if (wild)
                    pw.println("        setQName(name);");
                for (int i=0; i<fields.size(); i++) {
                    field = getField(i);
                    if (!field.optional) {
                        if (field.classpath.equals("java.util.ArrayList"))
                            pw.println("        if (" + field.name + " != null)    ");
                        if (field.property)
                            pw.println("        set" + upFirst(field.name) + "(" + field.name + ");");
                        else
                            pw.println("        this." + field.name + " = " + field.name + ";");
                    }
                }
            }
            pw.println("    }");
	    pw.println();
        }
    }
    
    public void printClassMethods(PrintWriter pw) {
        if (bimodal && !intf) {
            pw.println("    public String getQText() {");
            pw.println("        return(text);");
            pw.println("    }");
	    pw.println();
            pw.println("    public void setQText(String text) {");
            pw.println("        this.text = text;");
            pw.println("    }");
	    pw.println();
        }
        if (wild && !intf) {
            pw.println("    public String getQName() {");
            pw.println("        return(name);");
            pw.println("    }");
	    pw.println();
            pw.println("    public void setQName(String name) {");
            pw.println("        this.name = name;");
            pw.println("    }");
	    pw.println();
        }
        
        for (int i=0; i<fields.size(); i++)
            if (parentElement == null ||
                !parentElement.fieldExists(getField(i).name)) {
                getField(i).printMethod(pw, intf);
                if (printMoreMethods)
                    getField(i).printMoreMethods(pw, intf);
            }

        if (printQAware && !intf) {
            pw.println("    public void endElement(String tagName, QContext outputContext) throws QPE {");
            if (setParents) {
                pw.println("        /* setup parent links */");
                pw.println("        setXParentLinks();");
            }
	    pw.println();
            pw.println("        /* perform integrity checks */");
            pw.println("        ");
            pw.println("    }");
	    pw.println();
        }
        if (printPrint && !intf) {
            pw.println("    public void print(PrintWriter pw, String indent) {");
            pw.println("        pw.println(indent + \"element " + tagName + "\");");
            for (int i=0; i<fields.size(); i++)
                getField(i).printPrint(pw);
            if (textConstructor || bimodal)
                pw.println("        pw.println(indent + \"text = \" + text);");
            if (wild)
                pw.println("        pw.println(indent + \"name = \" + name);");
            pw.println("    }");
	    pw.println();
        }
        if ((printXPath || setParents) && !intf)
            printXMethods(pw);
    }
    
    public void printCloneMethod(PrintWriter pw) {
        if (intf) {
            pw.println("    public Object clone();");
	    pw.println();
            return;
        }
        if (abst)
            return;
        String classname = getClassName(targetClass);
        pw.println("    public Object clone() {");
        String newobjname = "new" + upFirst(classname);
        pw.print("        " + classname + " " + newobjname + " = new " + classname);
        if (textConstructor)
            pw.println("(text);");
        else
            pw.println("();");
        if (bimodal)
            pw.println("        " + newobjname + ".setQText(text);");
        if (wild)
            pw.println("        " + newobjname + ".name = name;");
        for (int i=0; i<fields.size(); i++)
            getField(i).printClone(pw, newobjname);
        if (setParents) {
            pw.println("        " + newobjname + ".setXParent(null);");
            pw.println("        " + newobjname + ".setXParentLinks();");
	    pw.println();
        }
        pw.println("        return(" + newobjname + ");");
        pw.println("    }");
	pw.println();
    }
    
    public void printXMethods(PrintWriter pw) {
        if (abst) {
            pw.println("    public abstract ArrayList getXChildren(String childTagName);");
            pw.println();
            pw.println("    public abstract ArrayList getAllXChildren();");
            pw.println();
            pw.println("    public abstract Object getXAttribute(String attName);");
            pw.println();
        } else {
            pw.println("    public ArrayList getXChildren(String childTagName) {");
            pw.println("        ArrayList r = new ArrayList();");
            for (int i=0; i<fields.size(); i++)
                if (!getField(i).attribute)
                    getField(i).printgetXChildren(pw);
            pw.println("        return(r);");
            pw.println("    }");
            pw.println();
            
            pw.println("    public ArrayList getAllXChildren() {");
            pw.println("        ArrayList r = new ArrayList();");
            for (int i=0; i<fields.size(); i++)
                if (!getField(i).attribute)
                    getField(i).printgetAllXChildren(pw);
            pw.println("        return(r);");
            pw.println("    }");
            pw.println();
            
            pw.println("    public Object getXAttribute(String attName) {");
            for (int i=0; i<fields.size(); i++)
                if (getField(i).attribute)
                    getField(i).printgetXAttribute(pw);
            pw.println("        return(null);");
            pw.println("    }");
            pw.println();
        }
    }

    public ClassField getField(int i) {
        return((ClassField)fields.get(i));
    }
    
    /* test if field exists in this element or a parent element */
    public boolean fieldExists(String fieldName) {
        if (!fields_inited)
            makeFields();
        for (int i=0; i<fields.size(); i++)
            if (fieldName.equals(getField(i).name))
                return(true);
        if (parentElement != null)
            return(parentElement.fieldExists(fieldName));
        return(false);
    }
    
    /**
     * for abstract classes or interfaces, return the list of subclasses
     * that are not abstract. Otherwise return this.
     * The goal is to get the classes linked to XML elements (for the xPath
     * method).
     */
    public ArrayList subXChildren() {
        ArrayList r = new ArrayList();
        if (!abst && !intf)
            r.add(this);
        else {
            for (int i=0; i<subclasses.size(); i++) {
                Element sub = (Element)subclasses.get(i);
                r.addAll(sub.subXChildren());
            }
        }
        return(r);
    }
    
    public static String typeToJava(String type) {
        if (type == null)
            return(null);
        else if (type.equals("PCDATA"))
            return("java.lang.String");
        else if (type.equals("CDATA"))
            return("java.lang.String");
        else if (type.equals("idref"))
            return("java.lang.Object");
        else if (type.equals("int"))
            return("java.lang.Integer");
        else if (type.equals("short"))
            return("java.lang.Short");
        else if (type.equals("long"))
            return("java.lang.Long");
        else if (type.equals("byte"))
            return("java.lang.Byte");
        else if (type.equals("boolean"))
            return("java.lang.Boolean");
        else if (type.equals("float"))
            return("java.lang.Float");
        else if (type.equals("double"))
            return("java.lang.Double");
        else if (type.equals("string"))
            return("java.lang.String");
        else if (type.equals("base64"))
            return("[B");
        else if (type.equals("url"))
            return("java.lang.String");
        else if (type.equals("char"))
            return("java.lang.Character");
        else if (type.equals("BigDecimal"))
            return("java.lang.String");
        else
            return(null);
    }
    
    public static String getClassName(String accessclass) {
        if (accessclass == null)
            return(null);
        if (accessclass.lastIndexOf('.') != -1)
            accessclass = accessclass.substring(accessclass.lastIndexOf('.')+1);
        if (accessclass.equals("Boolean"))
            accessclass = "boolean";
        else if (accessclass.equals("Integer"))
            accessclass = "int";
        else if (accessclass.equals("Long"))
            accessclass = "long";
        else if (accessclass.equals("Short"))
            accessclass = "short";
        else if (accessclass.equals("Float"))
            accessclass = "float";
        else if (accessclass.equals("Byte"))
            accessclass = "byte";
        else if (accessclass.equals("Double"))
            accessclass = "double";
        else if (accessclass.equals("Character"))
            accessclass = "char";
        else if (accessclass.equals("[B"))
            accessclass = "byte[]";
        return(accessclass);
    }
    
    public static boolean isJavaType(String accessclass) {
        if (accessclass == null)
            return(false);
        if (accessclass.lastIndexOf('.') != -1)
            accessclass = accessclass.substring(accessclass.lastIndexOf('.')+1);
        if (accessclass.equals("Boolean"))
            return(true);
        else if (accessclass.equals("Integer"))
            return(true);
        else if (accessclass.equals("Long"))
            return(true);
        else if (accessclass.equals("Short"))
            return(true);
        else if (accessclass.equals("Float"))
            return(true);
        else if (accessclass.equals("Byte"))
            return(true);
        else if (accessclass.equals("Double"))
            return(true);
        else if (accessclass.equals("Character"))
            return(true);
        else if (accessclass.equals("[B"))
            return(true);
        else
            return(false);
    }
    
    public static String transformTargetClass(String tc) {
        if (tc.equals("com.jxml.quick.tf.QStringTF"))
            return("java.lang.String");
        else if (tc.equals("com.jxml.quick.tf.QBooleanTF"))
            return("java.lang.Boolean");
        else
            return(tc);
    }
    
    private static String upFirst(String s) {
        return(s.substring(0,1).toUpperCase() + s.substring(1));
    }
    
}

