protected void generateTemplate(Template t, String className,
                                    ClassFile classFile) {
        Class<?> subClass;
        Method subMethod, subContextMethod, subIdMethod, subDetachMethod;
        if (mGenerateSubFormat) {
            subClass = Substitution.class;
            classFile.addInterface(subClass);
            classFile.addInterface(Cloneable.class);
            classFile.addInterface(java.io.Serializable.class);
            try {
                subMethod = subClass.getMethod("substitute");
                subContextMethod = subClass.getMethod
                    ("substitute", new Class[]{Context.class});
                subIdMethod = subClass.getMethod("getIdentifier");
                subDetachMethod = subClass.getMethod("detach");
            }
            catch (NoSuchMethodException e) {
                throw new InternalError(e.toString());
            }
        }
        else {
            subClass = null;
            subMethod = null;
            subContextMethod = null;
            subIdMethod = null;
            subDetachMethod = null;
        }

        // Build the static getTemplateParameterNames method.

        Variable[] params = t.getParams();
        int paramCount = params.length;

        generateTemplateParameters(t, classFile);

        // Done building the static getTemplateParameterNames method.

        // Build the static execute method.

        Modifiers pubstat = new Modifiers();
        pubstat.setPublic(true);
        pubstat.setStatic(true);

        Variable[] allParams;
        if (t.hasSubstitutionParam()) {
            allParams = new Variable[paramCount + 2];
            Type type = new Type(Substitution.class);
            Variable subParam = new Variable(null, SUB_PARAM_NAME, type);
            allParams[paramCount + 1] = subParam;
        }
        else {
            allParams = new Variable[paramCount + 1];
        }

        Type type = new Type(mUnit.getCompiler().getRuntimeContext());
        allParams[0] = new Variable(null, CONTEXT_PARAM_NAME, type);
        allParams[0].setTransient(true);

        for (int i=0; i<paramCount; i++) {
            allParams[i + 1] = params[i];
        }

        TypeDesc[] paramTypes = new TypeDesc[allParams.length];

        for (int i=0; i<allParams.length; i++) {
            if (mGenerateSubFormat) {
                allParams[i].setField(true);
            }
            paramTypes[i] = makeDesc(allParams[i]);
        }

        Type returnType = t.getReturnType();
        TypeDesc returnTypeDesc;
        if (returnType == null) {
            returnTypeDesc = null;
        }
        else {
            returnTypeDesc = makeDesc(returnType);
        }

        MethodInfo mi = classFile.addMethod(pubstat, EXECUTE_METHOD_NAME,
                                            returnTypeDesc, paramTypes);

        mi.addException("java.lang.Exception");
        CodeBuilder builder = new CodeBuilder(mi);

        LocalVariable[] localVars = builder.getParameters();
        // Apply names to the passed in parameters, even though this step
        // is not required.
        for (int i=0; i<localVars.length; i++) {
            localVars[i].setName(allParams[i].getName());
        }

        // inject template body profiling bytecode - start marker
        TypeDesc methodObserverType = makeDesc(MergedClass.InvocationEventObserver.class);
        boolean profilingEnabled = isProfilingEnabled();
        if (profilingEnabled) {
            mGlobalTime = builder.createLocalVariable("startTime", makeDesc(long.class));
            builder.invokeStatic(mUnit.getRuntimeContext().getName(),
                "getInvocationObserver", methodObserverType);
            builder.invokeInterface(methodObserverType.getFullName(), "currentTime",
               TypeDesc.forClass(long.class));
            builder.storeLocal(mGlobalTime);
        }

        if (!mGenerateSubFormat) {
            Visitor gen = new Visitor(allParams, builder.getParameters());
            gen.allowInitializerStatements();
            gen.generateNormalFormat(builder, t);

        }
        else {
            boolean doReturnValue = !(Type.VOID_TYPE.equals(returnType));

            // new <this>(params ...).substitute();
            TypeDesc thisType = makeDesc(className);
            builder.newObject(thisType);
            builder.dup();
            for (int i=0; i<localVars.length; i++) {
                builder.loadLocal(localVars[i]);
            }
            builder.invokeConstructor(paramTypes);

            if (doReturnValue) {
                // We'll need the object again to load the field.
                builder.dup();
            }

            builder.invoke(subMethod);

            Visitor gen = new Visitor(allParams);
            gen.allowInitializerStatements();

            // Generate Substitution.substitute().
            mi = classFile.addMethod(subMethod);
            CodeBuilder subBuilder = new CodeBuilder(mi);
            gen.generateContext(subBuilder);
            Label gotContext = subBuilder.createLabel();
            subBuilder.ifNullBranch(gotContext, false);
            String unSupExName =
                UnsupportedOperationException.class.getName();
            subBuilder.newObject(makeDesc(unSupExName));
            subBuilder.dup();
            subBuilder.invokeConstructor(unSupExName);
            subBuilder.throwObject();
            gotContext.setLocation();
            subBuilder.loadThis();
            gen.generateContext(subBuilder);
            subBuilder.invoke(subContextMethod);
            subBuilder.returnVoid();

            // Generate Substitution.substitute(Context).
            mi = classFile.addMethod(subContextMethod);
            subBuilder = new CodeBuilder(mi);

            String[] subFieldNames = gen.generateSubFormat(subBuilder, t);

            // inject template body profiling bytecode - end and generate event
            if (profilingEnabled) {
                builder.invokeStatic(mUnit.getRuntimeContext().getName(),
                    "getInvocationObserver", methodObserverType);
                builder.loadConstant(mUnit.getName());
                builder.loadConstant(null);
                builder.invokeStatic(mUnit.getRuntimeContext().getName(),
                    "getInvocationObserver", methodObserverType);
                builder.invokeInterface(methodObserverType.getFullName(), "currentTime",
                    makeDesc(long.class));
                builder.loadLocal(mGlobalTime);
                builder.math(Opcode.LSUB);
                builder.invokeInterface(methodObserverType.getFullName(), "invokedEvent", null,
                    new TypeDesc[] { makeDesc(String.class), makeDesc(String.class),
                        makeDesc(long.class) });
            }

            // Finish execute method.
            if (doReturnValue) {
                // If template returns something, return it here by reading
                // from a special field.
                builder.loadField(subFieldNames[1], returnTypeDesc);
                builder.returnValue(makeDesc(returnType.getNaturalClass()));
            }
            else {
                builder.returnVoid();
            }

            // Generate getIdentifier method.
            mi = classFile.addMethod(subIdMethod);
            subBuilder = new CodeBuilder(mi);

            TypeDesc td = makeDesc(SubstitutionId.class);
            subBuilder.newObject(td);
            subBuilder.dup();
            subBuilder.loadThis();
            subBuilder.loadThis();
            subBuilder.loadField(subFieldNames[0], TypeDesc.INT);
            subBuilder.invokeConstructor
                (td.getRootName(),
                 new TypeDesc[]{TypeDesc.OBJECT, TypeDesc.INT});
            subBuilder.returnValue(td);

            // Generate detach method.
            mi = classFile.addMethod(subDetachMethod);
            subBuilder = new CodeBuilder(mi);
            subBuilder.loadThis();
            subBuilder.invokeVirtual("clone", TypeDesc.OBJECT);
            subBuilder.checkCast(thisType);
            LocalVariable sub = subBuilder.createLocalVariable(null, thisType);
            subBuilder.storeLocal(sub);
            subBuilder.loadLocal(sub);
            subBuilder.loadConstant(null);
            subBuilder.storeField(gen.mContextParam.getVariable().getName(),
                                  paramTypes[0]);
            if (t.hasSubstitutionParam()) {
                // Also detach internal substitution parameter.
                subBuilder.loadLocal(sub);
                subBuilder.loadLocal(sub);
                String subFieldName = gen.mSubParam.getVariable().getName();
                TypeDesc subType = makeDesc(subClass);
                subBuilder.loadField(subFieldName, subType);
                subBuilder.invoke(subDetachMethod);
                subBuilder.storeField(subFieldName, subType);
            }
            subBuilder.loadLocal(sub);
            subBuilder.returnValue(TypeDesc.OBJECT);
        }

        // Done building the static execute method.

        // Build the private constructor.

        Modifiers pvt = new Modifiers();
        pvt.setPrivate(true);

        if (mGenerateSubFormat) {
            mi = classFile.addConstructor(pvt, paramTypes);
        }
        else {
            mi = classFile.addConstructor(pvt);
        }

        builder = new CodeBuilder(mi);
        builder.loadThis();
        builder.invokeSuperConstructor();

        if (mGenerateSubFormat) {
            // Copy all the params to fields.

            localVars = builder.getParameters();

            for (int i=0; i<localVars.length; i++) {
                builder.loadThis();
                builder.loadLocal(localVars[i]);
                builder.storeField(allParams[i].getName(), paramTypes[i]);
            }
        }

        builder.returnVoid();

        // Build static initializer, if required.

        if (mInitializerStatements.size() > 0) {
            mi = classFile.addInitializer();
            builder = new CodeBuilder(mi);

            Visitor gen = new Visitor(new Variable[0]);

            for (int i=0; i<mInitializerStatements.size(); i++) {
                Statement stmt = (Statement)mInitializerStatements.get(i);
                gen.generateNormalFormat(builder, stmt);
            }

            builder.returnVoid();
        }

        // Done building static initializer, if required.

        // Create any necessary private fields, after all methods have been
        // defined.
        Iterator<Variable> it = mFields.values().iterator();
        Modifiers flags = new Modifiers();
        flags.setPrivate(true);
        while (it.hasNext()) {
            Variable v = it.next();
            flags.setStatic(v.isStatic());
            flags.setTransient(v.isTransient());
            TypeDesc td = makeDesc(v);
            classFile.addField(flags, v.getName(), td).markSynthetic();
        }
    }