/*
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License");  you may not use this file except in 
 * compliance with the License.  You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
 * the specific language governing rights and limitations under the License.
 *
 * The Original Code is Protege-2000.
 *
 * The Initial Developer of the Original Code is Stanford University. Portions
 * created by Stanford University are Copyright (C) 2007.  All Rights Reserved.
 *
 * Protege was developed by Stanford Medical Informatics
 * (http://www.smi.stanford.edu) at the Stanford University School of Medicine
 * with support from the National Library of Medicine, the National Science
 * Foundation, and the Defense Advanced Research Projects Agency.  Current
 * information about Protege can be obtained at http://protege.stanford.edu.
 *
 */

package edu.stanford.smi.protege.code.generator.wrapping;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.Date;
import java.util.Iterator;

import edu.stanford.smi.protege.model.Cls;
import edu.stanford.smi.protege.model.KnowledgeBase;
import edu.stanford.smi.protege.model.Slot;


public class JavaCodeGenerator {

    private KnowledgeBase kb;

    private JavaCodeGeneratorOptions options;

    private static final boolean TRANSITIVE = true;
    private static final boolean ALL_PROPERTIES = TRANSITIVE;
    private static final boolean ONLY_LOCAL_PROPERTIES = ! TRANSITIVE;


    public JavaCodeGenerator(KnowledgeBase kb, JavaCodeGeneratorOptions options) {
        this.kb = kb;
        this.options = options;
        File folder = options.getOutputFolder();
        if (folder != null && !folder.exists()) {
            folder.mkdirs();
        }
        String pack = options.getPackage();
        if (pack != null) {
            pack = pack.replace('.', '/');
            File file = folder == null ? new File(pack) : new File(folder, pack);
            file.mkdirs();
            File f = new File(file, "impl");
            f.mkdirs();
        }
        else {
            File file = folder == null ? new File("impl") : new File(folder, "impl");
            file.mkdirs();
        }
    }


    public void createAll() throws IOException {
        for (Object element : options.getClses()) {
            Cls aClass = (Cls) element;

            if (!aClass.isSystem()) {
            	createInterface(aClass);
            	createImplementation(aClass);
            }
        }
        createFactoryClass();
        createOntologyJavaMappingClass();
    }


    private void createOntologyJavaMappingClass() throws IOException {
    	File file = getInterfaceFile("OntologyJavaMapping");
    	FileWriter fileWriter = new FileWriter(file);
    	PrintWriter printWriter = new PrintWriter(fileWriter);

    	printInterfacePackageStatement(printWriter);
    	printWriter.println("import " + options.getPackage() + ".impl.*;");
    	printWriter.println("import edu.stanford.smi.protege.code.generator.wrapping.OntologyJavaMappingUtil;");
    	printWriter.println();
        printWriter.println("/**");
        printWriter.println(" * Generated by Protege (http://protege.stanford.edu).");
        printWriter.println(" *");
        printWriter.println(" * @version generated on " + new Date());
        printWriter.println(" */");
        printWriter.println("public class OntologyJavaMapping {");
        printWriter.println();
        printWriter.println("    public static void initMap() {");

    	for (Cls cls : options.getClses()) {
    		if (cls.isSystem()) { continue;}
    		printWriter.println("        OntologyJavaMappingUtil.add(\"" +
    				cls.getName() + "\", " +
    				getInterfaceName(cls) + ".class, " + getImplementationName(cls) + ".class);");
    	}

    	printWriter.println("    }");
    	printWriter.println("}");
    	fileWriter.close();
    }


    public void createFactoryClass() throws IOException {
        String localName = options.getFactoryClassName();
        if (localName != null && localName.length() > 0) {
            File file = getInterfaceFile(localName);
            FileWriter fileWriter = new FileWriter(file);
            PrintWriter printWriter = new PrintWriter(fileWriter);
            printFactoryClass(printWriter);
            fileWriter.close();
        }
    }


    private void printFactoryClass(PrintWriter printWriter) {
        printInterfacePackageStatement(printWriter);
        printWriter.println("import java.util.*;");
        printWriter.println("import " + options.getPackage() + ".impl.*;");
        printWriter.println("import " + options.getPackage() + ".OntologyJavaMapping;");
        printWriter.println("import edu.stanford.smi.protege.model.*;");
        printWriter.println("import edu.stanford.smi.protege.code.generator.wrapping.OntologyJavaMappingUtil;");
        printWriter.println();
        printWriter.println("/**");
        printWriter.println(" * Generated by Protege (http://protege.stanford.edu).");
        printWriter.println(" *");
        printWriter.println(" * @version generated on " + new Date());
        printWriter.println(" */");
        printWriter.println("public class " + options.getFactoryClassName() + " {");
        printWriter.println("    static { OntologyJavaMapping.initMap(); }");
        printWriter.println();
        printWriter.println("    private KnowledgeBase kb;");
        printWriter.println();
        printWriter.println("    public " + options.getFactoryClassName() + "(KnowledgeBase kb) {");
        printWriter.println("        this.kb = kb;");
        printWriter.println("    }");
        for (Cls cls : options.getClses()) {
            if (!cls.isSystem()) {
	            String interfaceName = getInterfaceName(cls);
	            printWriter.println();
	            printWriter.println();
	            printWriter.println("    // ***** Class " + cls.getName() + " *****");
	            printWriter.println();
	            printWriter.println("    public Cls get" + interfaceName + "Class() {");
	            printWriter.println("        final String name = \"" + cls.getName() + "\";");
	            printWriter.println("        return kb.getCls(name);");
	            printWriter.println("    }");
	            printWriter.println();
	            if (!cls.isAbstract()) {
	            	printWriter.println("    public " + interfaceName + " create" + interfaceName + "(String name) {");
	            	printWriter.println("        Cls cls = get" + interfaceName + "Class();");
	            	printWriter.println("        Instance inst = cls.createDirectInstance(name);");
	            	printWriter.println("        return new " + getImplementationName(cls) + "(inst);");
	            	printWriter.println("    }");
	            	printWriter.println();
	            }
	            printWriter.println("    public " + interfaceName + " get" + interfaceName + "(String name) {");
	            printWriter.println("        return OntologyJavaMappingUtil.getSpecificObject(kb, kb.getInstance(name), " +
	            		interfaceName + ".class);");
	            printWriter.println("    }");

	            String collectionType  = options.getSetMode() ? "Set" : "Collection";
	            String collectionClass = options.getSetMode() ? "HashSet" : "ArrayList";

	            printWriter.println();
	            printWriter.println("    public " + collectionType + "<" + interfaceName + "> getAll" + interfaceName + "Objects() {");
	            printWriter.println("        return getAll" + interfaceName + "Objects(false);");
	            printWriter.println("    }");
	            printWriter.println();
	            printWriter.println("    public " + collectionType + "<" + interfaceName + "> getAll" + interfaceName + "Objects(boolean transitive) {");
	            printWriter.println("        " + collectionType + "<" + interfaceName + "> result = new " + collectionClass + "<" + interfaceName + ">();");
	            printWriter.println("        final Cls cls = get" + interfaceName + "Class();");
	            printWriter.println("        for (Object element : transitive ? cls.getInstances() : cls.getDirectInstances()) {");
	            printWriter.println("            Instance inst = (Instance) element;");
	            printWriter.println("            result.add(OntologyJavaMappingUtil.getSpecificObject(kb, inst, " +
	            		interfaceName + ".class));");
	            printWriter.println("        }");
	            printWriter.println("        return result;");
	            printWriter.println("    }");
            }
        }

        printWriter.println();
        printWriter.println();
        printWriter.println("    // ***** Getter methods for slots *****");
        for (Iterator it = kb.getSlots().iterator(); it.hasNext();) {
            Slot slot = (Slot) it.next();
            if (!slot.isSystem()) {
                SlotCode slotCode = new SlotCode(slot, false);
                String name = slotCode.getUpperCaseJavaName();
	            printWriter.println();
                printWriter.println("    public Slot get" + name + "Slot() {");
                printWriter.println("        final String name = \"" + slot.getName() + "\";");
                printWriter.println("        return kb.getSlot(name);");
                printWriter.println("    }");
            }
        }

        printWriter.println("}");
    }


    private void printAdditionalFactoryMehods(PrintWriter printWriter) throws IOException {
    	InputStream ins = JavaCodeGenerator.class.getResourceAsStream("resources/FactoryAdditionalMethods.template");
    	DataInputStream in = new DataInputStream(ins);
    	BufferedReader br = new BufferedReader(new InputStreamReader(in));

    	String strLine;

    	while ((strLine = br.readLine()) != null)   {
    		printWriter.println(strLine);
    	}

    	in.close();
    }


    public void createImplementation(Cls cls) throws IOException {
        File file = getImplementationFile(cls);
        FileWriter fileWriter = new FileWriter(file);
        PrintWriter printWriter = new PrintWriter(fileWriter);
        printImplementationCode(printWriter, cls);
        fileWriter.close();
    }


    public void printImplementationCode(PrintWriter printWriter, Cls aClass) {
        ClsCode code = new ClsCode(aClass);
        boolean considerAllProperties = hasMultipleSuperclasses(aClass) ? ALL_PROPERTIES : ONLY_LOCAL_PROPERTIES;
        if (options.getPackage() != null) {
            printWriter.println("package " + options.getPackage() + ".impl;");
        }
        else {
            printWriter.println("package impl;");
        }
        printWriter.println();
        printWriter.println("import edu.stanford.smi.protege.code.generator.wrapping.AbstractWrappedInstance;");
        printWriter.println("import edu.stanford.smi.protege.model.*;");
        perhapsAddImportJavaUtil(code, printWriter, considerAllProperties);
        String pack = options.getPackage();
        if (pack != null) {
        	printWriter.println("import " + pack + "." + getInterfaceNamePossiblyAbstract(aClass) + ";");
            printWriter.println("import " + pack + ".*;");
            printWriter.println();
        }
        printWriter.println("/**");
        printWriter.println(" * Generated by Protege (http://protege.stanford.edu).");
        printWriter.println(" * Source Class: " + aClass.getName());
        printWriter.println(" *");
        printWriter.println(" * @version generated on " + new Date());
        printWriter.println(" */");
        String implementationName = getImplementationNamePossiblyAbstract(aClass);
        //String abstractString = aClass.isAbstract() ? "abstract" : "";
        //printWriter.println("public " + abstractString + " class " + implementationName + getImplementationExtendsCode(aClass));
        printWriter.println("public class " + implementationName + getImplementationExtendsCode(aClass));
        printWriter.println("         implements " + getInterfaceNamePossiblyAbstract(aClass) + " {");
        printConstructors(printWriter, implementationName);
        for (Object element : code.getSlotCodes(considerAllProperties)) {
            SlotAtClassCode slotCode = (SlotAtClassCode) element;
            printImplementationSlotCode(printWriter, slotCode);
        }
        printWriter.println("}");
    }


    private void printConstructors(PrintWriter printWriter, String implementationName) {
        printWriter.println();
        printWriter.println("    public " + implementationName + "(Instance instance) {");
        printWriter.println("        super(instance);");
        printWriter.println("    }");
        printWriter.println();
        printWriter.println();
        printWriter.println("    public " + implementationName + "() {");
        printWriter.println("    }");
    }


    private String getImplementationExtendsCode(Cls aClass) {
        String str = " extends ";
        String base = getBaseImplementation(aClass);
        if (base == null) {
            return str + "AbstractWrappedInstance";
        }
        else {
            return str + base;
        }
    }


    public void createInterface(Cls aClass) throws IOException {

        File baseFile = getInterfaceFile(aClass);
        FileWriter fileWriter = new FileWriter(baseFile);
        PrintWriter printWriter = new PrintWriter(fileWriter);
        printInterfaceCode(printWriter, aClass);
        fileWriter.close();

        //if (options.getAbstractMode()) {
        //    createUserInterface(aClass);
        //}
    }


    private void createUserInterface(Cls aClass) throws IOException {
        String localName = getInterfaceName(aClass);
        File file = getInterfaceFile(localName);
        if (!file.exists()) {
            FileWriter fileWriter = new FileWriter(file);
            PrintWriter printWriter = new PrintWriter(fileWriter);
            printUserInterfaceCode(printWriter, aClass);
            fileWriter.close();
        }
    }


    public void printInterfaceCode(PrintWriter printWriter, Cls aClass) {
        ClsCode code = new ClsCode(aClass);
        printInterfacePackageStatement(printWriter);
        perhapsAddImportJavaUtil(code, printWriter, ONLY_LOCAL_PROPERTIES);
        printWriter.println("/**");
        printWriter.println(" * Generated by Protege (http://protege.stanford.edu).");
        printWriter.println(" * Source Class: " + aClass.getName());
        printWriter.println(" *");
        printWriter.println(" * @version generated on " + new Date());
        printWriter.println(" */");
        printWriter.println("public interface " + getInterfaceNamePossiblyAbstract(aClass) + getInterfaceExtendsCode(aClass) + " {");

        for (Iterator<SlotAtClassCode> it = code.getSlotCodes(ONLY_LOCAL_PROPERTIES).iterator(); it.hasNext();) {
            SlotAtClassCode slotCode = it.next();
            createInterfaceSlotCode(printWriter, slotCode);
            if (it.hasNext()) {
                printWriter.println();
            }
        }

        printWriter.println();
        printWriter.println("    void delete();");
        printWriter.println("}");
    }


    private void printInterfacePackageStatement(PrintWriter printWriter) {
        if (options.getPackage() != null) {
            printWriter.println("package " + options.getPackage() + ";");
            printWriter.println();
        }
    }


    public void printUserInterfaceCode(PrintWriter printWriter, Cls cls) {
        printInterfacePackageStatement(printWriter);
        printWriter.println("public interface " + getInterfaceName(cls) + " extends " + getInterfaceNamePossiblyAbstract(cls) + " {");
        printWriter.println("}");
    }


    private String getInterfaceExtendsCode(Cls aClass) {
        String str = " extends ";
        String base = getBaseInterface(aClass);
        if (base == null) {
            return "";
        }
        else {
            return str + base;
        }
    }


    public void printImplementationSlotCode(PrintWriter printWriter, SlotAtClassCode slotCode) {
        Slot slot = slotCode.getSlot();
        String name = slotCode.getUpperCaseJavaName();
        String javaGetTypeName = getJavaTypeName(slotCode, false, slotCode.isMultiple());
        String javaSetTypeName = getJavaTypeName(slotCode, true, slotCode.isMultiple());
        String simpleJavaTypeName = slotCode.getJavaType();
        boolean multiple = slotCode.isMultiple();

        String getSlotCall = "get" + name + "Slot()";

        printWriter.println();
        printWriter.println("    // Slot " + slot.getName());
        printWriter.println();
        printWriter.println("    public " + javaGetTypeName + " get" + name + "() {");
        if (multiple) { //multiple cardinality
        	printWriter.println("        Collection protegeValues = getWrappedProtegeInstance().getOwnSlotValues(" + getSlotCall + ");");

        	printWriter.println("        " + javaGetTypeName + " values = new " + (options.getSetMode() ?
        			"HashSet" : "ArrayList") + "<" + slotCode.getJavaType(true) + ">();");

        	if (slotCode.isCustomType()) {
        		printWriter.println("        Cls cls = getKnowledgeBase().getCls(\"" + slotCode.getRangeClsName() + "\");");
        	}

        	printWriter.println("        for (Object object : protegeValues) {");

        	if (slotCode.isCustomType()) {
        		printWriter.println("            if (object instanceof Instance && ((Instance)object).hasType(cls)) {");
        		printWriter.println("                values.add(new " + getImplementationName(slotCode.getRangeCls()) + "((Instance)object));");
        	} else {
        		printWriter.println("            if (object instanceof " + javaGetTypeName + ") {");
        		printWriter.println("                values.add((" + javaGetTypeName + ") object);");
        	}
        	printWriter.println("            }"); //end if
        	printWriter.println("        }"); //end for
        	printWriter.println("        return values;");
        }
        else { //single cardinality
            if (slotCode.isCustomType()) {
            	printWriter.println("        Object object = getWrappedProtegeInstance().getOwnSlotValue(" + getSlotCall + ");");
            	printWriter.println("        Cls cls = getKnowledgeBase().getCls(\"" + slotCode.getRangeClsName() + "\");");
            	printWriter.println("        if (object instanceof Instance && ((Instance)object).hasType(cls)) {");
            	printWriter.println("            return new " + getImplementationName(slotCode.getRangeCls()) + "((Instance)object);");
            	printWriter.println("        }"); //end if
            	printWriter.println("        return null;");

            }
            else {
            	if (slotCode.needsPrimitiveMethod()) {
            		String javaType = slotCode.getJavaType(true);
            		printWriter.println("        " + javaType + " value = (" + javaType + ") getWrappedProtegeInstance().getOwnSlotValue(" + getSlotCall + ");");
            		printWriter.println("        return value == null ? null :");
            		printWriter.println("            (" + javaType + ") value." + slotCode.getPrimitiveMethod() + ";");
            	} else {
            		printWriter.println("        return (" + javaGetTypeName + ") getWrappedProtegeInstance().getOwnSlotValue(" + getSlotCall + ");");
            	}
            }
        }
        printWriter.println("    }"); //end getXYZ() method

        printWriter.println();
        printWriter.println();
        printWriter.println("    public Slot get" + name + "Slot() {");
        printWriter.println("        final String name = \"" + slot.getName() + "\";");
        printWriter.println("        return getKnowledgeBase().getSlot(name);");
        printWriter.println("    }");

        printWriter.println();
        printWriter.println();
        printWriter.println("    public boolean has" + name + "() {");
        printWriter.println("        return hasSlotValues(" + getSlotCall + ");");
        printWriter.println("    }");

        String varName = "new" + name;
        if (multiple) {
            printWriter.println();
            printWriter.println();
            printWriter.println("    public void add" + name + "(" + simpleJavaTypeName + " " + varName + ") {");
            printWriter.println("        addSlotValue(" + getSlotCall + ", " + varName + ");");
            printWriter.println("    }");

            printWriter.println();
            printWriter.println();
            String oldVarName = "old" + name;
            printWriter.println("    public void remove" + name + "(" + simpleJavaTypeName + " " + oldVarName + ") {");
            printWriter.println("        removeSlotValue(" + getSlotCall + ", " + oldVarName + ");");
            printWriter.println("    }");
        }

        printWriter.println();
        printWriter.println();
        printWriter.println("    public void set" + name + "(" + javaSetTypeName + " " + varName + ") {");
        if (multiple) {
            printWriter.println("        setSlotValues(" + getSlotCall + ", " + varName + ");");
        }
        else {
            if (slotCode.isPrimitive()) {
                String t = null;
                if ("int".equals(javaGetTypeName)) {
                    t = "java.lang.Integer";
                }
                else if ("boolean".equals(javaGetTypeName)) {
                    t = "java.lang.Boolean";
                }
                else {
                    t = "java.lang.Float";
                }
                printWriter.println("        setSlotValue(" + getSlotCall + ", new " + t + "(" + varName + "));");
            }
            else {
                printWriter.println("        setSlotValue(" + getSlotCall + ", " + varName + ");");
            }
        }
        printWriter.println("    }");
    }


    public void createInterfaceSlotCode(PrintWriter printWriter, SlotAtClassCode slotCode) {
        Slot property = slotCode.getSlot();
        String name = slotCode.getUpperCaseJavaName();

        printWriter.println();
        printWriter.println("    // Slot " + property.getName());
        printWriter.println();
        printWriter.println("    " + getJavaTypeName(slotCode, false, slotCode.isMultiple()) + " get" + name + "();");

        printWriter.println();
        printWriter.println("    boolean has" + name + "();");

        if (slotCode.isMultiple()) {
        	String simpleJavaTypeName = slotCode.getJavaType();
            printWriter.println();
            printWriter.println("    void add" + name + "(" + simpleJavaTypeName + " new" + name + ");");
            printWriter.println();
            printWriter.println("    void remove" + name + "(" + simpleJavaTypeName + " old" + name + ");");
        }

        printWriter.println();
        printWriter.println("    void set" + name + "(" + getJavaTypeName(slotCode, true, slotCode.isMultiple()) + " new" + name + ");");
    }


    private String getJavaTypeName(SlotAtClassCode slotCode, boolean useExtends, boolean onlyNonPrimitiveTypes) {
    	String simpleJavaTypeName = slotCode.getJavaType(slotCode.isMultiple());
        if (slotCode.isMultiple()) {
        	if (!slotCode.isCustomType()) {
        		useExtends = false;
        	}
        	String genericsString = simpleJavaTypeName.equals("Object") ?
        			"<?>" : useExtends ?
        					"<? extends " + simpleJavaTypeName + ">" : "<" + simpleJavaTypeName + ">";
            return options.getSetMode() ? "Set" + genericsString : "Collection" + genericsString;
        }
        else {
            return simpleJavaTypeName;
        }
    }


    public String getBaseImplementation(Cls aClass) {
    	String resultString = "";
        for (Object element : aClass.getDirectSuperclasses()) {
            Cls superclass = (Cls) element;
            String name = getSystemJavaClass(superclass);
            if (name != null) {
                return "Default" + name;
            }
            if (superclass instanceof Cls && !kb.getRootCls().equals(superclass)) {
            	//if first superclass found
            	if (resultString.equals("")) {
					resultString = getImplementationName(superclass);
				} else {
					return null;
				}
            }
        }
        //if no superclass found
        if (resultString.equals("")) {
			return null;
		} else {
			return resultString;
		}
    }


    public String getBaseInterface(Cls cls) {
    	String resultString = "";
        for (Object element : cls.getDirectSuperclasses()) {
            Cls superclass = (Cls) element;
            String name = getSystemJavaClass(superclass);
            if (name != null) {
                return name;
            }
            else if (superclass instanceof Cls && !kb.getRootCls().equals(superclass)) {
            	resultString += (resultString.equals("") ? "" : ", ") + getInterfaceName(superclass);
            }
        }
        if (resultString.equals("")) {
			return null;
		} else {
			return resultString;
		}
    }


    public File getImplementationFile(Cls aClass) {
        String localName = getImplementationNamePossiblyAbstract(aClass);
        return getImplementationFile(localName);
    }


    private File getImplementationFile(String localName) {
        String pack = options.getPackage();
        if (pack != null) {
            pack = pack.replace('.', '/') + "/";
        }
        else {
            pack = "";
        }
        return new File(options.getOutputFolder(), pack + "impl/" + localName + ".java");
    }


    public String getImplementationName(Cls aClass) {
        return "Default" + getInterfaceName(aClass);
    }


    public String getImplementationNamePossiblyAbstract(Cls aClass) {
        return "Default" + getInterfaceNamePossiblyAbstract(aClass);
    }


    public File getInterfaceFile(Cls aClass) {
        String localName = getInterfaceNamePossiblyAbstract(aClass);
        return getInterfaceFile(localName);
    }


    private File getInterfaceFile(String localName) {
        String pack = options.getPackage();
        if (pack != null) {
            pack = pack.replace('.', '/') + "/";
        }
        else {
            pack = "";
        }
        return new File(options.getOutputFolder(), pack + localName + ".java");
    }


    public String getInterfaceNamePossiblyAbstract(Cls aClass) {
        String str = new ClsCode(aClass).getJavaName();
        //if (options.getAbstractMode()) {
        //    str += "_";
        //}
        return str;
    }


    public String getInterfaceName(Cls aClass) {
        return new ClsCode(aClass).getJavaName();
    }


    private String getSystemJavaClass(Cls superclass) {
        if (superclass.equals(kb.getSystemFrames().getStandardSlotMetaCls())) {
            return "Slot";
        }
        else if (superclass.equals(kb.getSystemFrames().getStandardClsMetaCls())) {
            return "Cls";
        }
        return null;
    }


    public static String getValidJavaName(String name) {
        for (int i = 1; i < name.length(); i++) {
            char c = name.charAt(i);
            if (!Character.isJavaIdentifierPart(c)) {
                name = name.replace(c, '_');
            }
        }
        return name;
    }


    private void perhapsAddImportJavaUtil(ClsCode code, PrintWriter printWriter, boolean transitive) {
        for (Object element : code.getSlotCodes(transitive)) {
            SlotAtClassCode propertyAtClassCode = (SlotAtClassCode) element;
            if (propertyAtClassCode.isMultiple()) {
                printWriter.println("import java.util.*;");
                printWriter.println();
                return;
            }
        }
    }

    public boolean hasMultipleSuperclasses(Cls aClass) {
    	boolean superclassFound = false;
        for (Object element : aClass.getDirectSuperclasses()) {
            Cls superclass = (Cls) element;
            String name = getSystemJavaClass(superclass);
            if (name != null) {
                return false;
            }
            if (superclass instanceof Cls && !kb.getRootCls().equals(superclass)) {
            	//if first superclass found
            	if (superclassFound == false) {
					superclassFound = true;
				} else {
					return true;
				}
            }
        }

        return false;
    }

}
