/*
 * Copyright 2010 JBoss Inc
 *
 * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.drools.ide.common.server.rules;

import java.beans.Introspector;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

import org.drools.base.ClassTypeResolver;
import org.drools.compiler.DrlParser;
import org.drools.compiler.DroolsError;
import org.drools.compiler.DroolsParserException;
import org.drools.core.util.asm.ClassFieldInspector;
import org.drools.ide.common.client.modeldriven.FieldAccessorsAndMutators;
import org.drools.ide.common.client.modeldriven.MethodInfo;
import org.drools.ide.common.client.modeldriven.ModelField.FIELD_CLASS_TYPE;
import org.drools.ide.common.client.modeldriven.SuggestionCompletionEngine;
import org.drools.ide.common.server.util.ClassMethodInspector;
import org.drools.ide.common.server.util.DataEnumLoader;
import org.drools.ide.common.server.util.SuggestionCompletionEngineBuilder;
import org.drools.lang.api.impl.FieldDescrBuilderImpl;
import org.drools.lang.descr.AnnotationDescr;
import org.drools.lang.descr.GlobalDescr;
import org.drools.lang.descr.ImportDescr;
import org.drools.lang.descr.PackageDescr;
import org.drools.lang.descr.PatternDescr;
import org.drools.lang.descr.TypeDeclarationDescr;
import org.drools.lang.descr.TypeFieldDescr;
import org.drools.lang.dsl.DSLMappingEntry;
import org.drools.lang.dsl.DSLMappingFile;
import org.drools.lang.dsl.DSLTokenizedMappingFile;
import org.drools.rule.ImportDeclaration;
import org.drools.rule.MapBackedClassLoader;

/**
 * This utility class loads suggestion completion stuff for the package
 * configuration, introspecting from models, templates etc.
 * <p/>
 * This also includes DSL stuff, basically, everything you need to get started
 * with a package. It also validates the package configuration, and can provide
 * errors.
 * <p/>
 * This does NOT validate assets in the package, other then to load up DSLs,
 * models etc as needed.
 * <p/>
 * FYI: the tests for this are in the BRMS module, in context of a full BRMS.
 */
public class SuggestionCompletionLoader
        implements
        ClassToGenericClassConverter {

    private final SuggestionCompletionEngineBuilder builder                      = new SuggestionCompletionEngineBuilder();

    private final MapBackedClassLoader              loader;

    private final List<String>                      errors                       = new ArrayList<String>();

    // iterating over the import list
    private final ClassTypeResolver                 resolver;
    private PackageDescr                            pkgDescr;

    /**
     * List of external ImportDescr providers.
     */
    private List<ExternalImportDescrProvider>       externalImportDescrProviders = new ArrayList<ExternalImportDescrProvider>();

    /**
     * Interface used for add external ImportDescr added to
     * SuggestionCompletionEngine Use this to add Fact Types that are not
     * imported by the package.
     */
    public static interface ExternalImportDescrProvider {
        public Set<ImportDescr> getImportDescrs();
    }

    /**
     * This uses the current classes classloader as a base, and jars can be
     * added.
     */
    public SuggestionCompletionLoader() {
        this( null );
    }

    /**
     * This allows a pre existing classloader to be used (and preferred) for
     * resolving types.
     */
    public SuggestionCompletionLoader(ClassLoader classLoader) {
        loader = getMapBackedClassLoader( classLoader );
        resolver = new ClassTypeResolver( new HashSet<String>(),
                                          loader );
    }

    private MapBackedClassLoader getMapBackedClassLoader(ClassLoader classLoader) {
        MapBackedClassLoader mapBackedClassLoader = new MapBackedClassLoader( createClassLoader( classLoader ) );

        return mapBackedClassLoader;
    }

    private ClassLoader createClassLoader(ClassLoader classLoader) {
        if ( classLoader == null ) {
            classLoader = Thread.currentThread().getContextClassLoader();
            if ( classLoader == null ) {
                classLoader = this.getClass().getClassLoader();
            }
        }
        return classLoader;
    }

    /**
     * This will validate, and generate a new engine, ready to go. If there are
     * errors, you can get them by doing getMissingClasses();
     * 
     * @param header
     *            The package configuration file content.
     * @param jars
     *            a list of jars to look inside (pass in empty array if not
     *            needed) this is a list of {@link JarInputStream}
     * @param dsls
     *            any dsl files. This is a list of {@link DSLMappingFile}.
     * @return A SuggestionCompletionEngine ready to be used in anger.
     */
    public SuggestionCompletionEngine getSuggestionEngine(final String header,
                                                          final List<JarInputStream> jars,
                                                          final List<DSLTokenizedMappingFile> dsls) {
        return this.getSuggestionEngine( header,
                                         jars,
                                         dsls,
                                         Collections.<String> emptyList() );
    }

    /**
     * This will validate, and generate a new engine, ready to go. If there are
     * errors, you can get them by doing getMissingClasses();
     * 
     * @param header
     *            The package configuration file content.
     * @param jars
     *            a list of jars to look inside (pass in empty array if not
     *            needed) this is a list of {@link JarInputStream}
     * @param dsls
     *            any dsl files. This is a list of {@link DSLMappingFile}.
     * @param dataEnums
     *            this is a list of String's which hold data enum definitions.
     *            (normally will be just one, but for completeness can load
     *            multiple).
     * @return A SuggestionCompletionEngine ready to be used in anger.
     */
    public SuggestionCompletionEngine getSuggestionEngine(final String header,
                                                          final List<JarInputStream> jars,
                                                          final List<DSLTokenizedMappingFile> dsls,
                                                          final List<String> dataEnums) {
        builder.newCompletionEngine();

        if ( headerNotEmpty( header ) ) {
            processPackageHeader( header,
                                  jars );
        }

        populateDSLSentences( dsls );

        populateDateEnums( dataEnums );

        return builder.getInstance();
    }

    private void populateDateEnums(List<String> dataEnums) {
        for ( String enumFile : dataEnums ) {
            DataEnumLoader enumLoader = new DataEnumLoader( enumFile );
            if ( enumLoader.hasErrors() ) {
                errors.addAll( enumLoader.getErrors() );
            } else {
                builder.addAllDataEnumsList( enumLoader.getData() );
            }
        }
    }

    private boolean headerNotEmpty(String header) {
        return !header.trim().equals( "" );
    }

    private void processPackageHeader(final String header,
                                      final List jars) {
        DrlParser parser = getParser( header );

        logErrors( parser );

        populateEngineBuilder( jars );
    }

    private void populateEngineBuilder(List jars) {
        if ( thereWasNoErrorsAndPackageDescrWasCreated() ) {

            populateModelInfo( jars );

            populateDeclaredFactTypes( jars );

            populateGlobalInfo( jars );

        }
    }

    private void logErrors(DrlParser parser) {
        if ( parser.hasErrors() ) {
            for ( DroolsError droolsError : parser.getErrors() ) {
                this.errors.add( droolsError.getMessage() );
            }
        }
    }

    private DrlParser getParser(String header) {
        DrlParser parser = new DrlParser();
        try {
            pkgDescr = parser.parse( header );
        } catch ( final DroolsParserException e1 ) {
            throw new IllegalStateException( "Serious error, unable to validate package." );
        }
        return parser;
    }

    private boolean thereWasNoErrorsAndPackageDescrWasCreated() {
        return pkgDescr != null;
    }

    private void populateDSLSentences(final List<DSLTokenizedMappingFile> dsls) {

        for ( DSLTokenizedMappingFile file : dsls ) {
            for ( DSLMappingEntry entry : file.getMapping().getEntries() ) {
                if ( entry.getSection() == DSLMappingEntry.CONDITION ) {
                    builder.addDSLConditionSentence( entry.getMappingKey() );
                } else if ( entry.getSection() == DSLMappingEntry.CONSEQUENCE ) {
                    builder.addDSLActionSentence( entry.getMappingKey() );
                } else if ( entry.getSection() == DSLMappingEntry.KEYWORD ) {
                    builder.addDSLMapping( entry );
                } else if ( entry.getSection() == DSLMappingEntry.ANY ) {
                    builder.addDSLConditionSentence( entry.getMappingKey() );
                    builder.addDSLActionSentence( entry.getMappingKey() );
                }
            }
        }

    }

    private void populateGlobalInfo(final List jars) {

        // populating information for the globals
        for ( final Iterator it = pkgDescr.getGlobals().iterator(); it.hasNext(); ) {
            final GlobalDescr global = (GlobalDescr) it.next();
            try {
                final String shortTypeName = getShortNameOfClass( global.getType() );
                final Class< ? > clazz = loadClass( global.getType(),
                                                    jars );
                if ( !this.builder.hasFieldsForType( shortTypeName ) ) {

                    loadClassFields( clazz,
                                     shortTypeName );

                    this.builder.addGlobalType( global.getIdentifier(),
                                                shortTypeName );

                }
                if ( implementsCollection( clazz ) ) {
                    this.builder.addGlobalCollection( global.getIdentifier() );
                }
                this.builder.addGlobalType( global.getIdentifier(),
                                            shortTypeName );
            } catch ( final IOException e ) {
                this.errors.add( "Error while inspecting class for global: " + global.getType() + " error message: " + e.getMessage() );
            }

        }
    }

    private boolean implementsCollection(Class< ? > clazz) {
        return clazz != null && Collection.class.isAssignableFrom( clazz );
    }

    /**
     * Populate the fact type data.
     */
    private void populateModelInfo(final List< ? > jars) {
        List<ImportDescr> imports = new ArrayList<ImportDescr>( pkgDescr.getImports() );

        addAnyExternalImports( imports );

        for ( ImportDescr importDescr : imports ) {
            String className = importDescr.getTarget();
            try {
                addImport( className );
                addFactType( jars,
                             className );
            } catch ( WildCardException e ) {
                this.errors.add( String.format( "Unable to introspect model for wild card imports (%s). Please explicitly import each fact type you require.",
                                                className ) );
            }
        }
    }

    private void populateDeclaredFactTypes(List< ? > jars) {
        for ( TypeDeclarationDescr baseType : pkgDescr.getTypeDeclarations() ) {
            List<TypeDeclarationDescr> th = getDeclaredTypeHierachy( baseType,
                                                                     jars );
            populateDeclaredFactType( th );
        }
    }

    private List<TypeDeclarationDescr> getDeclaredTypeHierachy(TypeDeclarationDescr td,
                                                               List< ? > jars) {
        List<TypeDeclarationDescr> th = new ArrayList<TypeDeclarationDescr>();
        th.add( td );
        TypeDeclarationDescr std;
        while ( (std = getDeclaredSuperType( td )) != null ) {
            th.add( std );
            td = std;
        }

        //If the super-most class has been imported attempt to make a pseudo TypeDeclaration for the imported class
        if ( this.pkgDescr.getImports().size() > 0 ) {
            for ( ImportDescr imp : this.pkgDescr.getImports() ) {
                if ( imp.getTarget().endsWith( "." + td.getTypeName() ) ) {
                    TypeDeclarationDescr pseudoTypeDeclr = makePseudoTypeDeclarationDescrFromSuperClassType( imp.getTarget(),
                                                                                                             jars );
                    if ( pseudoTypeDeclr != null ) {
                        th.add( pseudoTypeDeclr );
                    }
                }
            }

        }
        return th;
    }

    private TypeDeclarationDescr getDeclaredSuperType(TypeDeclarationDescr td) {
        String declaredSuperTypeName = td.getSuperTypeName();
        if ( declaredSuperTypeName == null ) {
            return null;
        } else {
            for ( TypeDeclarationDescr std : pkgDescr.getTypeDeclarations() ) {
                if ( declaredSuperTypeName.equals( std.getTypeName() ) ) {
                    return std;
                }
            }
        }
        return null;
    }

    private TypeDeclarationDescr makePseudoTypeDeclarationDescrFromSuperClassType(String className,
                                                                                  List< ? > jars) {

        Class< ? > clazz = loadClass( className,
                                      jars );

        if ( clazz != null ) {

            Method[] methods = clazz.getMethods();
            Map<String, MethodSignature> methodSignatures = getMethodSignatures( className,
                                                                                 methods );

            TypeDeclarationDescr td = new TypeDeclarationDescr();
            td.setTypeName( className );

            for ( Map.Entry<String, MethodSignature> e : methodSignatures.entrySet() ) {
                if ( e.getValue().accessorAndMutator == FieldAccessorsAndMutators.BOTH ) {
                    String fieldShortName = getShortNameOfClass( e.getKey() );
                    TypeFieldDescr fieldDescr = new TypeFieldDescr( fieldShortName );
                    PatternDescr patternDescr = new PatternDescr( e.getValue().returnType.getName() );
                    fieldDescr.setPattern( patternDescr );
                    td.addField( fieldDescr );
                }
            }
            return td;

        }

        return null;
    }

    private void populateDeclaredFactType(List<TypeDeclarationDescr> th) {

        String declaredType = th.get( 0 ).getTypeName();
        Set<String> declaredTypes = getDeclaredTypes( pkgDescr );
        Map<String, FieldAccessorsAndMutators> accessorsAndMutators = new HashMap<String, FieldAccessorsAndMutators>();
        Map<String, Map<String, String>> annotations = new HashMap<String, Map<String, String>>();
        List<String> fieldNames = new ArrayList<String>();

        //'this' is a special case
        fieldNames.add( "this" );
        this.builder.addFieldType( declaredType + ".this",
                                   SuggestionCompletionEngine.TYPE_THIS,
                                   null );
        accessorsAndMutators.put( declaredType + ".this",
                                  FieldAccessorsAndMutators.ACCESSOR );

        //Other facts and fields in the type hierarchy
        for ( TypeDeclarationDescr typeDeclarationDescr : th ) {

            //Configure annotations
            for ( String annotationName : typeDeclarationDescr.getAnnotationNames() ) {
                AnnotationDescr annotation = typeDeclarationDescr.getAnnotation( annotationName );
                annotations.put( annotationName,
                                 annotation.getValues() );
            }

            //Configure fields
            if ( typeDeclarationDescrHasFields( typeDeclarationDescr ) ) {

                this.builder.addFactType( declaredType,
                                          FIELD_CLASS_TYPE.TYPE_DECLARATION_CLASS );

                //Other fields
                for ( Map.Entry<String, TypeFieldDescr> f : typeDeclarationDescr.getFields().entrySet() ) {
                    String fieldName = f.getKey();
                    fieldNames.add( fieldName );

                    String factField = declaredType + "." + fieldName;
                    accessorsAndMutators.put( factField,
                                              FieldAccessorsAndMutators.BOTH );
                    String fieldClass = f.getValue().getPattern().getObjectType();

                    if ( declaredTypes.contains( fieldClass ) ) {
                        this.builder.addFieldType( declaredType + "." + fieldName,
                                                   fieldClass,
                                                   null );//SuggestionCompletionEngine.TYPE_OBJECT );
                    } else {
                        try {
                            Class< ? > clz = resolver.resolveType( fieldClass );
                            this.builder.addFieldType( declaredType + "." + fieldName,
                                                       translateClassToGenericType( clz ),
                                                       clz );
                        } catch ( ClassNotFoundException e ) {
                            this.errors.add( "Class of field not found: " + fieldClass );
                        }
                    }
                }
            }
        }

        this.builder.addAnnotationsForType( declaredType,
                                            annotations );
        this.builder.addFieldsForType( declaredType,
                                       fieldNames.toArray( new String[fieldNames.size()] ) );
        this.builder.addFieldAccessorsAndMutatorsForField( accessorsAndMutators );

    }

    private Map<String, MethodSignature> getMethodSignatures(String className,
                                                             Method[] methods) {

        Map<String, MethodSignature> methodSignatures = new HashMap<String, MethodSignature>();

        //Determine accessors for methods
        for ( Method method : methods ) {
            boolean addMethod = false;
            String name = method.getName();
            if ( method.getParameterTypes().length > 0 ) {

                //Strip bare mutator name
                if ( name.startsWith( "set" ) ) {
                    addMethod = true;
                    name = Introspector.decapitalize( name.substring( 3 ) );
                }

                if ( addMethod ) {
                    String factField = className + "." + name;
                    if ( !methodSignatures.containsKey( factField ) ) {
                        methodSignatures.put( factField,
                                              new MethodSignature( FieldAccessorsAndMutators.MUTATOR,
                                                                   void.class ) );
                    } else if ( methodSignatures.get( factField ).accessorAndMutator == FieldAccessorsAndMutators.ACCESSOR ) {
                        MethodSignature signature = methodSignatures.get( factField );
                        signature.accessorAndMutator = FieldAccessorsAndMutators.BOTH;
                    }
                }

            } else if ( !method.getReturnType().equals( "void" ) ) {

                //Strip bare accessor name
                if ( name.startsWith( "get" ) ) {
                    addMethod = true;
                    name = Introspector.decapitalize( name.substring( 3 ) );
                } else if ( name.startsWith( "is" ) ) {
                    addMethod = true;
                    name = Introspector.decapitalize( name.substring( 2 ) );
                }

                if ( addMethod ) {
                    String factField = className + "." + name;
                    if ( !methodSignatures.containsKey( factField ) ) {
                        methodSignatures.put( factField,
                                              new MethodSignature( FieldAccessorsAndMutators.ACCESSOR,
                                                                   method.getReturnType() ) );
                    } else if ( methodSignatures.get( factField ).accessorAndMutator == FieldAccessorsAndMutators.MUTATOR ) {
                        MethodSignature signature = methodSignatures.get( factField );
                        signature.accessorAndMutator = FieldAccessorsAndMutators.BOTH;
                    }
                }
            }
        }
        return methodSignatures;
    }

    private Map<String, FieldAccessorsAndMutators> extractFieldAccessorsAndMutators(Map<String, MethodSignature> methodSignatures) {
        Map<String, FieldAccessorsAndMutators> accessorsAndMutators = new HashMap<String, FieldAccessorsAndMutators>();
        for ( Map.Entry<String, MethodSignature> e : methodSignatures.entrySet() ) {
            accessorsAndMutators.put( e.getKey(),
                                      e.getValue().accessorAndMutator );
        }
        return accessorsAndMutators;
    }

    private static class MethodSignature {

        MethodSignature(FieldAccessorsAndMutators accessorAndMutator,
                        Class< ? > returnType) {
            this.accessorAndMutator = accessorAndMutator;
            this.returnType = returnType;
        }

        FieldAccessorsAndMutators accessorAndMutator;
        Class< ? >                returnType;

    }

    private boolean typeDeclarationDescrHasFields(TypeDeclarationDescr typeDeclarationDescr) {
        return typeDeclarationDescr.getFields().size() > 0;
    }

    private Set<String> getDeclaredTypes(PackageDescr pkgDescr) {
        Set<String> declaredTypes = new HashSet<String>();

        for ( TypeDeclarationDescr typeDeclarationDescr : pkgDescr.getTypeDeclarations() ) {
            declaredTypes.add( typeDeclarationDescr.getTypeName() );
        }
        return declaredTypes;
    }

    private void addImport(String className) throws WildCardException {
        if ( isWildCardImport( className ) ) {
            throw new WildCardException();
        } else {
            resolver.addImport( className );
        }
    }

    private void addFactType(List jars,
                             String className) {
        final Class clazz = loadClass( className,
                                       jars );
        if ( clazz != null ) {
            try {

                final String shortTypeName = getShortNameOfClass( clazz.getName() );
                this.builder.addFactType( shortTypeName,
                                          FIELD_CLASS_TYPE.REGULAR_CLASS );
                loadClassFields( clazz,
                                 shortTypeName );

            } catch ( final IOException e ) {
                this.errors.add( String.format( "Error while inspecting the class: %s. The error was: %s",
                                                className,
                                                e.getMessage() ) );
            } catch ( NoClassDefFoundError e ) {
                this.errors.add( String.format( "Unable to find the class: %s which is required by: %s. You may need to add more classes to the model.",
                                                e.getMessage().replace( '/',
                                                                        '.' ),
                                                className ) );
            }

        }
    }

    private boolean isWildCardImport(String className) {
        return className.endsWith( "*" );
    }

    private void addAnyExternalImports(List<ImportDescr> imports) {
        if ( this.externalImportDescrProviders != null ) {
            for ( ExternalImportDescrProvider externalImportDescrProvider : this.externalImportDescrProviders ) {
                imports.addAll( externalImportDescrProvider.getImportDescrs() );
            }
        }
    }

    private Class loadClass(String className,
                            List jars) {
        Class clazz = null;
        try {
            clazz = resolver.resolveType( className );
        } catch ( ClassFormatError e1 ) {
            clazz = loadClass( className,
                               jars,
                               clazz );
        } catch ( ClassNotFoundException e1 ) {
            clazz = loadClass( className,
                               jars,
                               clazz );
        }
        return clazz;
    }

    private Class loadClass(String className,
                            List jars,
                            Class clazz) {
        try {
            addJars( jars );
            clazz = resolver.resolveType( className );
        } catch ( Exception e ) {
            this.errors.add( "Class not found: " + className );
        }
        return clazz;
    }

    private void loadClassFields(final Class< ? > clazz,
                                 final String shortTypeName) throws IOException {
        if ( clazz == null ) {
            return;
        }

        final ClassFieldInspector inspector = new ClassFieldInspector( clazz );
        Set<String> fieldsSet = new TreeSet<String>();
        fieldsSet.addAll( inspector.getFieldNames().keySet() );
        List<String> fields = removeIrrelevantFields( fieldsSet );

        Method[] methods = clazz.getMethods();
        List<String> modifierStrings = new ArrayList<String>();
        Map<String, MethodSignature> methodSignatures = getMethodSignatures( shortTypeName,
                                                                             methods );

        //'this' is a special case
        fields.add( 0,
                    "this" );
        methodSignatures.put( shortTypeName + ".this",
                                  new MethodSignature( FieldAccessorsAndMutators.ACCESSOR,
                                                       clazz ) );
        this.builder.addFieldType( shortTypeName + ".this",
                                   SuggestionCompletionEngine.TYPE_THIS,
                                   clazz );
        this.builder.addFieldsForType( shortTypeName,
                                       fields.toArray( new String[fields.size()] ) );

        //Configure modifiers and accessors
        String[] modifiers = new String[modifierStrings.size()];
        modifierStrings.toArray( modifiers );
        this.builder.addModifiersForType( shortTypeName,
                                          modifiers );
        this.builder.addFieldAccessorsAndMutatorsForField( extractFieldAccessorsAndMutators( methodSignatures ) );

        //Configure other fields
        fields.remove( "this" );
        for ( String field : fields ) {
            final Class< ? > type = inspector.getFieldTypes().get( field );
            final String fieldType = translateClassToGenericType( type );
            this.builder.addFieldType( shortTypeName + "." + field,
                                       fieldType,
                                       type );
            Field f = inspector.getFieldTypesField().get( field );
            this.builder.addFieldTypeField( shortTypeName + "." + field,
                                            f );
        }

        ClassMethodInspector methodInspector = new ClassMethodInspector( clazz,
                                                                         this );

        List<MethodInfo> methodInfos = methodInspector.getMethodInfos();
        for ( MethodInfo mi : methodInfos ) {
            String genericType = mi.getParametricReturnType();
            if ( genericType != null ) {
                this.builder.putParametricFieldType( shortTypeName + "." + mi.getNameWithParameters(),
                                                     genericType );
            }
        }
        this.builder.getInstance().addMethodInfo( shortTypeName,
                                                  methodInfos );
    }

    public String getShortNameOfClass(final String clazz) {
        return clazz.substring( clazz.lastIndexOf( '.' ) + 1 );
    }

    /**
     * This will remove the unneeded "fields" that come from java.lang.Object
     * these are really not needed for the modeller.
     */
    public List<String> removeIrrelevantFields(Collection<String> fields) {
        final List<String> result = new ArrayList<String>();
        for ( String field : fields ) {
            if ( !(field.equals( "class" ) || field.equals( "hashCode" ) || field.equals( "toString" )) ) {
                result.add( field );
            }
        }
        return result;
    }

    /**
     * This will add the given jars to the classloader.
     */
    private void addJars(final List<JarInputStream> jars) throws IOException {
        for ( final Iterator<JarInputStream> it = jars.iterator(); it.hasNext(); ) {
            final JarInputStream jis = it.next();
            JarEntry entry;
            final byte[] buf = new byte[1024];
            int len;
            while ( (entry = jis.getNextJarEntry()) != null ) {
                if ( !entry.isDirectory() && entry.getName().endsWith( ".class" ) ) {
                    final ByteArrayOutputStream out = new ByteArrayOutputStream();
                    while ( (len = jis.read( buf )) >= 0 ) {
                        out.write( buf,
                                   0,
                                   len );
                    }
                    this.loader.addResource( entry.getName(),
                                             out.toByteArray() );
                }
            }

        }
    }

    /* (non-Javadoc)
     * @see org.drools.ide.common.server.rules.ClassToGenericClassConverter#translateClassToGenericType(java.lang.Class)
     */
    //XXX {bauna} field type
    public String translateClassToGenericType(final Class< ? > type) {
        String fieldType = null; // if null, will use standard operators
        if ( type != null ) {
            if ( type.isPrimitive() && (type != boolean.class) ) {
                fieldType = SuggestionCompletionEngine.TYPE_NUMERIC;
            } else if ( Number.class.isAssignableFrom( type ) ) {
                fieldType = SuggestionCompletionEngine.TYPE_NUMERIC;
            } else if ( String.class.isAssignableFrom( type ) ) {
                fieldType = SuggestionCompletionEngine.TYPE_STRING;
            } else if ( Collection.class.isAssignableFrom( type ) ) {
                fieldType = SuggestionCompletionEngine.TYPE_COLLECTION;
            } else if ( Boolean.class.isAssignableFrom( type ) || boolean.class.isAssignableFrom( type ) ) {
                fieldType = SuggestionCompletionEngine.TYPE_BOOLEAN;
            } else if ( Date.class.isAssignableFrom( type ) ) {
                fieldType = SuggestionCompletionEngine.TYPE_DATE; // MN: wait until we support it.
            } else if ( Comparable.class.isAssignableFrom( type ) ) {
                fieldType = SuggestionCompletionEngine.TYPE_COMPARABLE;
            } else {
                try {
                    Class clazz = resolver.resolveType( type.getName() );
                    fieldType = clazz.getSimpleName();
                } catch ( ClassNotFoundException e ) {
                    fieldType = SuggestionCompletionEngine.TYPE_OBJECT;
                }
            }
        }
        return fieldType;
    }

    public void addExternalImportDescrProvider(ExternalImportDescrProvider provider) {
        this.externalImportDescrProviders.add( provider );
    }

    public Set<ImportDescr> getExternalImportDescrs() {
        Set<ImportDescr> result = new HashSet<ImportDescr>();
        for ( ExternalImportDescrProvider externalImportDescrProvider : this.externalImportDescrProviders ) {
            result.addAll( externalImportDescrProvider.getImportDescrs() );
        }

        return result;
    }

    /**
     * @return true if there were errors when processing the package.
     */
    public boolean hasErrors() {
        return (this.errors.size() > 0);
    }

    /**
     * Returns a list of String errors.
     */
    public List<String> getErrors() {
        return this.errors;
    }

    class WildCardException extends Exception {

    }
}
