Package com4j.tlbimp

Source Code of com4j.tlbimp.Generator$LibBinder

package com4j.tlbimp;

import com4j.Com4jObject;
import com4j.GUID;
import com4j.tlbimp.def.ICoClassDecl;
import com4j.tlbimp.def.IConstant;
import com4j.tlbimp.def.IDispInterfaceDecl;
import com4j.tlbimp.def.IEnumDecl;
import com4j.tlbimp.def.IImplementedInterfaceDecl;
import com4j.tlbimp.def.IInterfaceDecl;
import com4j.tlbimp.def.ITypeDecl;
import com4j.tlbimp.def.ITypedefDecl;
import com4j.tlbimp.def.IWTypeLib;
import com4j.tlbimp.def.TypeKind;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

/**
* Type library importer.
*
* <p>
* One instance of this class is created for one invocation of type library generation.
* This object keeps track of what type libraries are compiled into what packages,
* and keeps other "global" (per tlbimp invocation) state.
*
* <p>
* The actual details of the generation is delegated to other generator objects.
*
* @author Kohsuke Kawaguchi (kk@kohsuke.org)
*/
public final class Generator {
    private final CodeWriter writer;

    private final ReferenceResolver referenceResolver;

    /**
     * Errors should be reported to this object.
     * Always non-null.
     */
    protected final ErrorListener el;

    final DefaultMethodFinder dmf = new DefaultMethodFinder();

    /**
     * Locale for manipulating strings.
     * Always non-null.
     */
    final Locale locale;

    /**
     * {@link IWTypeLib}s specified to the {@link #generate(IWTypeLib)} method.
     */
    private final Set<LibBinder> generatedTypeLibs = new HashSet<LibBinder>();

    public Generator( CodeWriter writer, ReferenceResolver resolver, ErrorListener el, Locale locale ) {
        this.el = el;
        this.writer = writer;
        this.referenceResolver = resolver;
        this.locale = locale;
    }

    /**
     * Call this method repeatedly to generate classes from each type library.
     */
    public void generate( IWTypeLib lib ) throws BindingException, IOException {
        LibBinder tli = getTypeLibInfo(lib);
        if(referenceResolver.suppress(lib))
            return; // skip code generation
        if(generatedTypeLibs.add(tli))
            tli.generate();
    }

    /**
     * Finally call this method to wrap things up.
     *
     * <p>
     * In particular this generates the ClassFactory class.
     */
    public void finish() throws IOException {
        //Map<String,Set<TypeLibInfo>> byPackage = new HashMap<String,Set<TypeLibInfo>>();
        //for( TypeLibInfo tli : generatedTypeLibs ) {
        //    Set<TypeLibInfo> s = byPackage.get(tli.packageName);
        //    if(s==null)
        //        byPackage.put(tli.packageName,s=new HashSet<TypeLibInfo>());
        //    s.add(tli);
        //}

        // for( Map.Entry<String,Set<TypeLibInfo>> e : byPackage.entrySet() ) {
        for( Package pkg : packages.values() ) {
            LibBinder lib1 = pkg.typeLibs.iterator().next();

            if(referenceResolver.suppress(lib1.lib))
                continue;

            // generate ClassFactory
            IndentingWriter o = pkg.createWriter(lib1,"ClassFactory.java");
            lib1.generateHeader(o);

            o.printJavadoc("Defines methods to create COM objects");
            o.println("public abstract class ClassFactory {");
            o.in();

            o.println("private ClassFactory() {} // instanciation is not allowed");
            o.println();

            for( LibBinder lib : pkg.typeLibs ) {
                int len = lib.lib.count();
                for( int i=0; i<len; i++ ) {
                    ICoClassDecl t = lib.lib.getType(i).queryInterface(ICoClassDecl.class);
                    if(t==null)     continue;
                    if(!t.isCreatable())    continue;

                    declareFactoryMethod(o, t);
                    t.dispose();
                }
            }

            o.out();
            o.println("}");
            o.close();
        }
    }

    /**
     * Returns the primary interface for the given co-class.
     *
     * @return
     *      null if none is found.
     */
    ITypeDecl getDefaultInterface( ICoClassDecl t ) {
        final int count = t.countImplementedInterfaces();
        // look for the default interface first.
        for( int i=0; i<count; i++ ) {
            IImplementedInterfaceDecl impl = t.getImplementedInterface(i);
            if(impl.isSource())
                continue;
            IInterfaceDecl c = getCustom(impl);
            if(impl.isDefault() && c!=null)
                return c;
        }

        // if none is found, look for any non-source interface
        Set<IInterfaceDecl> types = new HashSet<IInterfaceDecl>();
        for( int i=0; i<count; i++ ) {
            IImplementedInterfaceDecl impl = t.getImplementedInterface(i);
            if(impl.isSource())
                continue;
            IInterfaceDecl c = getCustom(impl);
            if(c !=null)
                types.add(c);
        }

        if(types.isEmpty())
            return null;

        // if T1 and T2 are in the set and T1 derives from T2, eliminate T2
        // (since returning T1 is better)
        Set<IInterfaceDecl> bases = new HashSet<IInterfaceDecl>();
        for (IInterfaceDecl ii : types) {
            for( int i=0; i<ii.countBaseInterfaces(); i++)
                bases.add(ii.getBaseInterface(i).queryInterface(IInterfaceDecl.class));
        }
        types.removeAll(bases);

        return types.iterator().next();
    }

    private IInterfaceDecl getCustom(IImplementedInterfaceDecl impl) {
        ITypeDecl t = impl.getType();
        IInterfaceDecl ii = t.queryInterface(IInterfaceDecl.class);
        if(ii!=null)
            return ii;    // this is a custom interface

        IDispInterfaceDecl di = t.queryInterface(IDispInterfaceDecl.class);
        if(di.isDual())
            return di.getVtblInterface();

        return null;   // disp-only. can't handle it now.
    }

    private void declareFactoryMethod(IndentingWriter o, ICoClassDecl t) {
        ITypeDecl p = getDefaultInterface(t);

        String primaryIntf = Com4jObject.class.getName(); // default interface name
        try {
            if(p!=null)
                primaryIntf = getTypeName(p);
        } catch( BindingException e ) {
            e.addContext("class factory for coclass "+t.getName());
            el.error(e);
        }

        o.println();
        o.printJavadoc(t.getHelpString());

        o.printf("public static %1s create%2s() {", primaryIntf, t.getName() );
        o.println();
        o.in();

        o.printf("return COM4J.createInstance( %1s.class, \"%2s\" );",
            primaryIntf, t.getGUID());
        o.println();

        o.out();
        o.println("}");
//
//
//        o.println(t.getHelpString());
//        o.println(t.getName());
//        int count = t.countImplementedInterfaces();
//        for( int j=0; j<count; j++ ) {
//            IImplementedInterfaceDecl impl = t.getImplementedInterface(j);
//            o.printf("%1s def:%2b src:%3b rst:%4b\n",
//                impl.getType().getName(),
//                impl.isDefault(),
//                impl.isSource(),
//                impl.isRestricted());
//            impl.dispose();
//        }
    }


    /**
     * All {@link Package}s keyed by their names.
     */
    private final Map<String,Package> packages = new HashMap<String, Package>();

    private Package getPackage(String name) {
        Package p = packages.get(name);
        if(p==null)
            packages.put(name,p=new Package(name));
        return p;
    }

    /**
     * Represents a Java package.
     */
    private final class Package {
        /**
         * Java package name of this type library.
         * Can be empty but never null.
         */
        final String name;

        /**
         * Short filenames that are generated into this package.
         * <p>
         * Used to detect collisions. The value is the type that
         * generated it.
         */
        private final Map<String,LibBinder> fileNames = new HashMap<String,LibBinder>();

        /**
         * Type libraries generated into this package.
         */
        final Set<LibBinder> typeLibs = new HashSet<LibBinder>();

        public Package(String name) {
            this.name = name;
        }

        private File getDir() {
            if(isRoot())
                return new File(".");
            else
                return new File(name.replace('.',File.separatorChar));
        }

        /**
         * True if this is the root package.
         */
        public boolean isRoot() {
            return name.equals("");
        }

        /**
         * Creates an {@link IndentingWriter} for a given file in this package.
         *
         * @param fileName
         *      such as "Foo.java"
         */
        public IndentingWriter createWriter(LibBinder lib, String fileName) throws IOException {
            LibBinder tli = fileNames.get(fileName);
            if(tli!=null)
                el.error(new BindingException(Messages.FILE_CONFLICT.format(
                    fileName, tli.lib.getName(), lib.lib.getName(), name )));
            else
                fileNames.put(fileName,lib);
            return writer.create(new File(getDir(),fileName));
        }

    }

    /**
     * Type library information.
     */
    private final Map<IWTypeLib,LibBinder> typeLibs = new HashMap<IWTypeLib,LibBinder>();

    /**
     * Generates code from a type library.
     *
     * <p>
     * Processing a type library often requires references to other
     * type libraries, and in particular how the type names in other
     * type libraries are bound in Java.
     *
     * <p>
     * An instance of this class is created for each type library
     * (including the one that we are binding.)
     */
    /*package*/ final class LibBinder {
        final IWTypeLib lib;

        /**
         * Java package of this type library.
         */
        final Package pkg;

        /**
         * Every top-level type declaration in this type library and their
         * Java type name.
         */
        private final Map<ITypeDecl,String> aliases = new HashMap<ITypeDecl,String>();

        /**
         * Generated event interfaces,
         * so that we don't generate them as invocable interfaces,
         * and we don't generate them twice.
         */
        private Set<ITypeDecl> eventInterfaces = new HashSet<ITypeDecl>();

        public LibBinder(IWTypeLib lib) throws BindingException {
            this.lib = lib;
            this.pkg = getPackage(referenceResolver.resolve(lib));
            this.pkg.typeLibs.add(this);

            buildSimpleAliasTable();
        }

        /**
         * MIDL often produces typedefs of the form "typedef A B" where
         * B is an enum declaration, and A is the name given by the user in IDL
         * (A is usually cryptic, B is human readable.)
         *
         * <p>
         * I don't know why MIDL behaves in this way, but in this case
         * it's usually desirable as a binding if we use A everywhere in place of B.
         *
         * <p>
         * We build this map B -> A to simply this.
         */
        private void buildSimpleAliasTable() {
            String prefix = pkg.name;
            if(prefix.length()==0prefix += '.';

            int len = lib.count();
            for( int i=0; i<len; i++ ) {
                ITypeDecl t = lib.getType(i);
                if(t.getKind()==TypeKind.ALIAS) {
                    ITypedefDecl typedef = t.queryInterface(ITypedefDecl.class);
                    ITypeDecl def = typedef.getDefinition().queryInterface(ITypeDecl.class);
                    if(def!=null) {
//                    System.out.println(def.getName()+" -> "+typedef.getName());
                        aliases.put( def, typedef.getName() );

                        if(def.getKind()==TypeKind.DISPATCH ) {
                            // if the alias is defined against dispinterface,
                            // also define the same typedef for 'interface'

                            IDispInterfaceDecl dispi = def.queryInterface(IDispInterfaceDecl.class);
                            if(dispi.isDual()) {
                                IInterfaceDecl custitf = dispi.getVtblInterface();
                                aliases.put(custitf, typedef.getName());
                            }
                        }
                    }
                }
                t.dispose();
            }
        }

        /**
         * Gets the simple name of the type (as opposed to FQCN.)
         */
        String getSimpleTypeName(ITypeDecl decl) {
            assert decl.getParent().equals(lib);

            String name;
            if(aliases.containsKey(decl))
                name = aliases.get(decl);
            else
                name = decl.getName();

            // check GUID
            GUID guid = getGUID(decl);

            if(guid!=null && STDOLE_TYPES.contains(guid))
                return "Com4jObject";
            else
                return name;
        }

        private GUID getGUID(ITypeDecl decl) {
            IDispInterfaceDecl di = decl.queryInterface(IDispInterfaceDecl.class);
            if(di!=null)
                return di.getGUID();

            IInterfaceDecl ii = decl.queryInterface(IInterfaceDecl.class);
            if(ii!=null)
                return ii.getGUID();

            return null;
        }

        public IndentingWriter createWriter(String fileName) throws IOException {
            return pkg.createWriter(this,fileName);
        }

        /**
         * Generates all the code from this type library.
         */
        public void generate() throws IOException {
            IWTypeLib tlib = lib;
            generatePackageHtml();

            int len = tlib.count();
            // generate event interface first,
            // so that we don't generate same interface as invocable ones.
            for( int i=0; i<len; i++ ) {
                ITypeDecl t = tlib.getType(i);
                if(t.getKind()== TypeKind.COCLASS) {
                    generateEventsFrom( t.queryInterface(ICoClassDecl.class) );
                }
            }

            for( int i=0; i<len; i++ ) {
                ITypeDecl t = tlib.getType(i);
                switch(t.getKind()) {
                case DISPATCH:
                    generate( t.queryInterface(IDispInterfaceDecl.class) );
                    break;
                case INTERFACE:
                    new CustomInterfaceGenerator(this,t.queryInterface(IInterfaceDecl.class)).generate();
                    break;
                case ENUM:
                    generate( t.queryInterface(IEnumDecl.class) );
                    break;
                }
                t.dispose();
            }
        }

        private void generatePackageHtml() throws IOException {
            PrintWriter o = createWriter("package.html");
            o.println("<html><body>");
            o.printf("<h2>%1s</h2>",lib.getName());
            o.printf("<p>%1s</p>",lib.getHelpString());
            o.println("</html></body>");
            o.close();
        }

        private void generate( IEnumDecl t ) throws IOException {

            // load all the constants first
            int len = t.countConstants();
            IConstant[] cons = new IConstant[len];

            for( int i=0; i<len; i++ )
                cons[i] = t.getConstant(i);

            // check if we need to use ComEnum
            boolean needComEnum = false;
            for( int i=0; i<len; i++ ) {
                if( cons[i].getValue()!=i ) {
                    needComEnum = true;
                    break;
                }
            }

            // generate the prolog
            String typeName = getSimpleTypeName(t);
            IndentingWriter o = createWriter(typeName+".java");
            generateHeader(o);

            o.printJavadoc(t.getHelpString());

            o.printf("public enum %1s ",typeName);
            if(needComEnum)
                o.print("implements ComEnum ");
            o.println("{");
            o.in();

            // generate constants
            for( IConstant con : cons ) {
                o.printJavadoc(con.getHelpString());
                o.print(con.getName());
                if(needComEnum) {
                    o.printf("(%1d),",con.getValue());
                } else {
                    o.print(", // ");
                    o.print(con.getValue());
                }
                o.println();
            }

            if(needComEnum) {
                // the rest of the boiler-plate code
                o.println(";");
                o.println();
                o.println("private final int value;");
                o.println(typeName+"(int value) { this.value=value; }");
                o.println("public int comEnumValue() { return value; }");
            }

            o.out();
            o.println("}");

            // clean up
            for( IConstant con : cons)
                con.dispose();

            o.close();
        }

        private void generate( IDispInterfaceDecl t ) throws IOException {
            // if dual, always prefer the custom binding
            if(eventInterfaces.contains(t))
                return; // avoid generating the same interface twice, once as event, once as normal
            if(t.isDual()) {
                new CustomInterfaceGenerator(this,t.getVtblInterface()).generate();
            } else {
                new DispInterfaceGenerator(this,t).generate();
            }
        }

        /**
         * Generates the event sink interfaces from this object.
         */
        private void generateEventsFrom(ICoClassDecl co) throws IOException {
            int len = co.countImplementedInterfaces();
            for( int i=0; i<len; i++ ) {
                IImplementedInterfaceDecl item = co.getImplementedInterface(i);
                if(item.isSource()) {
                    ITypeDecl it = item.getType();
                    if(eventInterfaces.add(it)) {
                        IDispInterfaceDecl di = it.queryInterface(IDispInterfaceDecl.class);
                        if(di!=null)    // can this ever be null?
                            new EventInterfaceGenerator(this,di).generate();
                    }
                }
            }
        }

        void generateHeader(IndentingWriter o) {
            generateHeader(o,null);
        }
        void generateHeader(IndentingWriter o,String subPackage) {
            if(!pkg.isRoot() || subPackage!=null) {
                if(subPackage==null)    subPackage="";
                else                    subPackage='.'+subPackage;
                o.printf("package %1s%2s;",pkg.name,subPackage);
                o.println();
                o.println();
            }

            o.println("import com4j.*;");
            o.println();
        }

        public final Generator parent() {
            return Generator.this;
        }
    }

    // TODO: map IFont and IPicture correctly
    private static final Set<GUID> STDOLE_TYPES = new HashSet<GUID>(
        Arrays.asList(
            new GUID("00000000-0000-0000-C000-000000000046"),   // IUnknown
            new GUID("00020400-0000-0000-C000-000000000046"),   // IDispatch
            new GUID("BEF6E002-A874-101A-8BBA-00AA00300CAB"),   // IFont
            new GUID("7BF80980-BF32-101A-8BBB-00AA00300CAB")    // IPicture
        ));


    /**
     * Gets the type name for the given declaration.
     * <p>
     * This takes the followings into account:
     *
     * <ul>
     <li>aliases (typedefs)
     *  <li>types in other type libs.
     * </ul>
     */
    /*package*/ String getTypeName(ITypeDecl decl) throws BindingException {
        Generator.LibBinder tli = getTypeLibInfo(decl.getParent());
        String name = tli.pkg.name;
        if(name.length()>0) name+='.';
        name += tli.getSimpleTypeName(decl);
        return name;
    }

    /**
     * Gets or creates a {@link LibBinder} object for the given
     * type library.
     */
    private LibBinder getTypeLibInfo(IWTypeLib p) throws BindingException {
        LibBinder tli = typeLibs.get(p);
        if(tli==null) {
            typeLibs.put(p,tli=new LibBinder(p));
        }
        return tli;
    }
}
TOP

Related Classes of com4j.tlbimp.Generator$LibBinder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.