/* EOTemplateGenerator.m created by mgentry on Mon 14-Jun-1999 */
/*-
 * Copyright (c) 2002-2006 Carl Lindberg and Mike Gentry
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#import "EOTemplateGenerator.h"
#import <EOAccess/EOAccess.h>
//#import <EOModeler/EOModelExtensions.h>
#import "MiscMergeTemplate.h"
#import "MiscMergeCommandBlock.h"
#import "MiscMergeEngine.h"
#import "NSString+MiscAdditions.h"
#import "EOAccessAdditions.h"
#import "FoundationAdditions.h"
#import "SystemType.h"

#ifdef MACOS_X
//#import <Foundation/NSJavaSetup.h>
#endif

static BOOL _SystemFrameworkExists(NSString *framework)
{
    NSString *root = @"/System";
    NSString *path;

#ifdef WIN32
    root = [[[NSProcessInfo processInfo] environment] objectForKey:@"NEXT_ROOT"];
#endif
    path = [root stringByAppendingPathComponent:@"Library/Frameworks"];
    path = [path stringByAppendingPathComponent:framework];
    return [[NSFileManager defaultManager] fileExistsAtPath:path];
}

BOOL IsEOF5()
{
    return _SystemFrameworkExists(@"JavaEOControl.framework");
}
BOOL IsEOF52()
{
    return _SystemFrameworkExists(@"JavaEORuleSystem.framework");
}

@implementation EOTemplateGenerator

- init
{
    BOOL isEOF52 = IsEOF52();
    BOOL isEOF5  = isEOF52 || IsEOF5();
    BOOL isEOF4  = isEOF5  || (NSClassFromString(@"EOEvent") != nil);  //EOEvent added in 4.5
    NSString *defaultJavaTemplate = nil;
    NSString *defaultJavaStubTemplate = nil;
    EOModelGroup *defaultGroup;

    [super init];

#ifdef MACOS_X
    /*
     * Some EOAdapter frameworks use this environment variable in their
     * link settings; if set to something else, by a developer's build environment
     * say, it can mess up OSX's dyld.
     */
    setenv("NEXT_ROOT", "", 1);

    /* Avoid an EOControlJava.dylib warning */
   //    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_initJava:)
   //          name:NSJavaDidSetupVirtualMachineNotification object:nil];
#endif

    /* Fix valueType of 'c' to generate as java.lang.Boolean */
   //    if (isEOF52)
   //        [EOGeneratorAttributePoser poseAsClass:[EOAttribute class]];

    /* Set our own default group. The +globalModelGroup does some command line
       parsing, which certain parameter values can mess up causing exceptions to
       be thrown. We don't want any of the self-discovery of models anyways, so
       just avoid it. */
    defaultGroup = [[EOModelGroup alloc] init];
    [defaultGroup setDelegate:self];
    [EOModelGroup setDefaultGroup:defaultGroup];

    javaMode = isEOF5;
    javaClientMode = NO;
    verboseMode = NO;
    forceOverwrites = NO;
    useJavaPackagePaths = NO;
    destination = @".";
    classPrefix = @"_";

    mainHeaderTemplate = isEOF4? @"ObjCHeader.eotemplate" : @"ObjCHeaderEOF3.eotemplate";
    mainSourceTemplate = isEOF4? @"ObjCSource.eotemplate" : @"ObjCSourceEOF3.eotemplate";
    stubHeaderTemplate = @"ObjCSubclassHeader.eotemplate";
    stubSourceTemplate = @"ObjCSubclassSource.eotemplate";
#ifdef EOF2_ONLY
    mainJavaTemplate   = @"JavaSourceEOF2.eotemplate";
    stubJavaTemplate   = @"JavaSubclassSourceEOF2.eotemplate";
#else
    mainJavaTemplate   = isEOF52? @"JavaSourceEOF52.eotemplate" :
                         isEOF5?  @"JavaSourceEOF5.eotemplate" :
                         isEOF4?  @"JavaSourceEOF4.eotemplate" : 
                                  @"JavaSourceEOF3.eotemplate";
    stubJavaTemplate   = isEOF5?  @"JavaSubclassSourceEOF5.eotemplate" :
                         isEOF4?  @"JavaSubclassSourceEOF4.eotemplate" :
                                  @"JavaSubclassSourceEOF3.eotemplate";
    defaultJavaTemplate = mainJavaTemplate;
    defaultJavaStubTemplate = stubJavaTemplate;
#endif

    models = [[NSMutableArray alloc] init];
    searchPaths = [[NSMutableArray alloc] init];
    entityNames = [[NSMutableArray alloc] init];
    entityWildcards = [[NSMutableArray alloc] init];
    userVariables = [[NSMutableDictionary alloc] init];

    [self processCommandLineArguments];

    if (outputEncoding == 0)
        outputEncoding = [NSString defaultCStringEncoding];
    if (templateEncoding == 0)
        templateEncoding = outputEncoding;

    /* 
     * If Java client was chosen, and the templates were not overridden on the
     * command line, then use the default JavaClient templates. Use == instead
     * of isEqual: because we want to check reference equality, not just string
     * equality.
     */
    if (javaClientMode)
    {
        if (mainJavaTemplate == defaultJavaTemplate)
            mainJavaTemplate = isEOF52? @"JavaClientSourceEOF52.eotemplate" :
                               isEOF5?  @"JavaClientSourceEOF5.eotemplate" :
                               isEOF4?  @"JavaClientSourceEOF4.eotemplate" : 
                                        @"JavaClientSourceEOF3.eotemplate";
        if (stubJavaTemplate == defaultJavaStubTemplate)
            stubJavaTemplate = isEOF5? @"JavaClientSubclassSourceEOF5.eotemplate" : 
                                       @"JavaClientSubclassSourceEOF4.eotemplate";
    }

    return self;
}

/*
 * When loading the Java runtime (sometimes needed for prototypes in JDBC models), a weird
 * error message "EOControlJava:_RemoveMethodListContainingSelector -- failed to find method
 * list containing validateValue:forKey:" is printed to the console.  Forcibly loading the
 * EOControlJava library here appears to avoid the problem.  The message was harmless but
 * caused needless worrying (and tech support emails ;-) ).  The problem only seems to happen
 * on MacOS X.
 */
- (void)_initJava:(NSNotification *)note
{
#ifdef MACOS_X
    NS_DURING
    id runtimeClass = NSClassFromString(@"com/apple/cocoa/foundation/NSRuntime");
    [runtimeClass performSelector:@selector(loadLibrary:) withObject:@"EOControlJava"];
    NS_HANDLER
    NS_ENDHANDLER
#endif
}

- (void)displayHelpAndExit:(int)exitStatus
{
    const char *appName = [[[NSProcessInfo processInfo] processName] cString];

    printf("Usage: %s -model eoModelName.eomodeld [entities ...]\n\n", appName);
    printf("Options:\n"
           "  -objc                    Output ObjC source code instead of Java\n"
           "  -java                    Output Java source code instead of ObjC\n"
           "  -javaclient              Output JavaClient source code (implies -java)\n"
           "  -model <model>           Output entities from <model>\n"
           "  -refmodel <model>        Load model, do not generate code\n"
           "  -force                   Force overwrite of read-only files\n"
           "  -destination <dir>       Output directory for generated files\n"
           "  -subclassDestination <dir> Directory for stub subclass files\n"
           "  -prefix <string>         Prefix for generated classname, default is _\n"
           "  -packagedirs             Create Java-style package directory tree\n"
           "  -encoding <enc>          Character encoding used to generate files\n"
           "  -templateEncoding <enc>  Character encoding used to read template files\n"               
           "  -filenameTemplate <tmpl> Inline MiscMerge template for filename pattern\n"
           "  -define-<key> <value>    Add <key> to runtime variable list\n"
           "  -templatedir <dir>       Add <dir> to template search path\n"
           "  -sourceTemplate <file>   Use different template file\n"
           "  -headerTemplate             \"\n"
           "  -subclassSourceTemplate     \"\n"
           "  -subclassHeaderTemplate     \"\n"
           "  -javaTemplate               \"\n"
           "  -subclassJavaTemplate       \"\n"
           "  -verbose                 Verbose output\n"
           "  -version                 Print version number and exit\n"
           "  -define-<key> <value>    Add <key> to runtime variable list\n"
           "  -help                    This help\n");

    exit(exitStatus);
}

- (void)addModelFromPath:(NSString *)path shouldProcess:(BOOL)shouldProcess
{
    EOModel *model = nil;

    NS_DURING
        model = [[EOModel alloc] initWithContentsOfFile:path];
    NS_HANDLER
        Printf(@"Error loading eomodel file '%@': %@", path, [localException reason]);
        return;
    NS_ENDHANDLER

    if ([[EOModelGroup defaultGroup] modelNamed:[model name]] == nil)
        [[EOModelGroup defaultGroup] addModel:model];

    if (model && shouldProcess)
        [models addObject:model];

    [model release];
}

- (void)processCommandLineArguments
{
    NSEnumerator *argEnum = [[[NSProcessInfo processInfo] arguments] objectEnumerator];
    NSString *currArg;

    [argEnum nextObject]; //skip argv[0]

    while (currArg = [argEnum nextObject])
    {
        if ([@"-verbose" hasPrefix:currArg])
        {
            verboseMode = YES;
        }
        else if ([@"-help" hasPrefix:currArg])
        {
            [self displayHelpAndExit:0];
        }
        else if ([@"-model" hasPrefix:currArg])
        {
            [self addModelFromPath:[argEnum nextObject] shouldProcess:YES];
        }
        else if ([@"-objc" hasPrefix:currArg])
        {
            javaMode = javaClientMode = NO;
        }
        else if ([@"-java" hasPrefix:currArg])
        {
            javaMode = YES;
        }
        else if ([@"-javaclient" hasPrefix:currArg])
        {
#ifdef EOF2_ONLY
            Printf(@"ERROR: JavaClient not supported by EOF 2.x.");
#else
            javaMode = javaClientMode = YES;
#endif
        }
        else if ([@"-force" hasPrefix:currArg])
        {
            forceOverwrites = YES;
        }
        else if ([@"-wildcard" hasPrefix:currArg]) //needed?
        {
            [entityWildcards addObject:[argEnum nextObject]];
        }
        else if ([@"-prefix" hasPrefix:currArg])
        {
            classPrefix = [[argEnum nextObject] retain];
        }
        else if ([@"-destination" hasPrefix:currArg])
        {
            destination = [[argEnum nextObject] retain];
        }
        else if ([@"-subclassDestination" hasPrefix:currArg])
        {
            subclassDestination = [[argEnum nextObject] retain];
        }
        else if ([@"-packagedirs" hasPrefix:currArg])
        {
            useJavaPackagePaths = YES;
        }
        else if ([@"-templatedir" hasPrefix:currArg] || [@"-Templatedir" hasPrefix:currArg])
        {
            NSString *path = [argEnum nextObject];
            if (![[NSFileManager defaultManager] directoryExistsAtPath:path])
                Printf(@"Warning: Template search directory %@ does not exist", path);
            [searchPaths addObject:path];
        }
        else if ([@"-sourceTemplate" hasPrefix:currArg])
            mainSourceTemplate = [[argEnum nextObject] retain];
        else if ([@"-headerTemplate" hasPrefix:currArg])
            mainHeaderTemplate = [[argEnum nextObject] retain];
        else if ([@"-subclassSourceTemplate" hasPrefix:currArg])
            stubSourceTemplate = [[argEnum nextObject] retain];
        else if ([@"-subclassHeaderTemplate" hasPrefix:currArg])
            stubHeaderTemplate = [[argEnum nextObject] retain];
        else if ([@"-javaTemplate" hasPrefix:currArg])
            mainJavaTemplate = [[argEnum nextObject] retain];
        else if ([@"-subclassJavaTemplate" hasPrefix:currArg])
            stubJavaTemplate = [[argEnum nextObject] retain];

        else if ([@"-filenameTemplate" hasPrefix:currArg])
        {
            MiscMergeTemplate *filenameTemplate = [[MiscMergeTemplate alloc] init];
            
            [filenameTemplate setStartDelimiter:@"{" endDelimiter:@"}"];
            [filenameTemplate parseString:[argEnum nextObject]];
            
            filenameEngine = [[MiscMergeEngine alloc] initWithTemplate:filenameTemplate];
            [filenameTemplate release];
        }
        else if ([@"-encoding" hasPrefix:currArg])
        {
            NSString *enc = [argEnum nextObject];
            outputEncoding = [NSString stringEncodingNamed:enc];
            if (outputEncoding == 0) Printf(@"Warning: unknown encoding '%@'", enc);
        }
        else if ([@"-templateEncoding" hasPrefix:currArg])
        {
            NSString *enc = [argEnum nextObject];
            templateEncoding = [NSString stringEncodingNamed: enc];
            if (templateEncoding == 0) Printf(@"Warning: unknown encoding '%@'", enc);
        }
        else if ([@"-refmodel" hasPrefix:currArg])
        {
            [self addModelFromPath:[argEnum nextObject] shouldProcess:NO];
        }
        else if ([@"-version" hasPrefix:currArg])
        {
            Printf(@"EOGenerator version 1.7");
            exit(0);
        }
        else if ([currArg hasPrefix:@"-define-"])
        {
            NSString *key = [currArg substringFromIndex:8];
            NSString *value = [argEnum nextObject];

            if ([key length] > 0 && value)
                [userVariables setObject:value forKey:key];
        }
        else if ([currArg hasPrefix:@"-"])
        {
            Printf(@"Unknown option: %@", currArg);
            [self displayHelpAndExit:1];
        }
        else
        {
            if ([currArg containsString:@".eomodel"])
                [self addModelFromPath:currArg shouldProcess:YES];
            else if ([currArg containsString:@"*"] || [currArg containsString:@"?"])
                [entityWildcards addObject:currArg];
            else
                [entityNames addObject:currArg];
        }		
    }

    if ([models count] == 0)
    {
        Printf(@"Unspecified EOModel file.  Use -h to see help.");
        exit(1);
    }

    if (![[NSFileManager defaultManager] directoryExistsAtPath:destination])
    {
        Printf(@"Destination directory (%@) does not exist.", destination);
        exit(1);
    }

    if (subclassDestination &&
        ![[NSFileManager defaultManager] directoryExistsAtPath:subclassDestination])
    {
        Printf(@"Subclass destination directory (%@) does not exist.", subclassDestination);
        exit(1);
    }

    [self addDefaultSearchPaths];
}

- (void)addDefaultSearchPaths
{
    NSArray *libraryPaths;
    int i;

#ifdef OPENSTEP_ONLY
    libraryPaths = NSStandardLibraryPaths();
#else
    libraryPaths = NSSearchPathForDirectoriesInDomains(
                        NSAllLibrariesDirectory,
                        NSUserDomainMask|NSLocalDomainMask|NSNetworkDomainMask,
                        YES);
#endif

    for (i=0; i<[libraryPaths count]; i++)
    {
        NSString *libPath = [libraryPaths objectAtIndex:i];
        [searchPaths addObject:[libPath stringByAppendingPathComponent:@"EOGenerator"]];
    }

    [searchPaths addObject:[[NSBundle mainBundle] bundlePath]];

    if (verboseMode)
        Printf(@"Search path is:\n%@", searchPaths);
}

- (NSArray *)searchPaths
{
    return searchPaths;
}

- (EOEntity *)relationship:(EORelationship *)relationship
	failedToLookupDestinationNamed:(NSString *)entityName
{
    Printf(@"ERROR: Failed to find entity named '%@', needed by "
             @"the relationship %@ in entity %@.  Use a -refmodel "
             @"parameter to load the needed model.",
             entityName, [relationship name], [[relationship entity] name]);
    exit(1);
    return nil;
}

/*** GENERATION ***/

- (void)generate
{
    EOModelGroup *modelGroup = [EOModelGroup defaultGroup];
    EOEntity *entity;
    NSString *entityName;
    NSEnumerator *enumerator;

    /* Use specified entities instead of entire EOModel(s) if > 0 */
    if ([entityNames count] > 0 || [entityWildcards count] > 0)
    {
        int i, j;

        /* Add wildcards to entity name list, but only use list of chosen models */
        for (i=0; i<[entityWildcards count]; i++) {
            for (j=0; j<[models count]; j++) {
                NSString *wildcard = [entityWildcards objectAtIndex:i];
                EOModel  *model = [models objectAtIndex:j];
                [entityNames addObjectsFromArray:[model entityNamesMatchingWildcard:wildcard]];
            }
        }

        if ([entityNames count] == 0)
        {
            Printf(@"No entity names matched wildcards %@", entityWildcards);
        }

        enumerator = [entityNames objectEnumerator];
        while (entityName = [enumerator nextObject])
        {
            entity = [modelGroup entityNamed:entityName];
            if (entity == nil)
            {
                Printf(@"Entity %@ not found in model, skipping.", entityName);
            }
            else
            {
                [self generateEntity:entity];
            }
        }
    }
    else
    {
        NSEnumerator *modelEnum = [models objectEnumerator];
        EOModel *model;

        while (model = [modelEnum nextObject])
        {
            enumerator = [[model entities] objectEnumerator];
            while (entity = [enumerator nextObject])
            {
                [self generateEntity:entity];
            }

        }
    }
}

- (NSString*)classNameForEntity:(EOEntity *)entity
{
    NSString *className = [entity className];
#ifndef EOF2_ONLY
    if (javaClientMode)
        className = [entity clientClassName];
#endif
    return className;
}

- (void)generateEntity:(EOEntity *) entity
{
    NSAutoreleasePool *pool;
    NSString  *entityName = [entity name];
    NSString  *className = [self classNameForEntity:entity];
    
    /* 
     * Ignore EOPrototypes entities, since they're not real entities. Must allow
     * for "EOPrototypes", DB-specific ones like "EOOraclePrototypes",
     * "EOSybasePrototypes", and the "EOPrototypesToHide" one (used to hide
     * default prototypes).
     */
    if (([entityName hasPrefix:@"EO"] && [entityName hasSuffix:@"Prototypes"]) ||
        ([entityName isEqualToString:@"EOPrototypesToHide"]))
    {
        if (verboseMode)
            Printf(@"Ignoring EOPrototypes entity '%@'", entityName);
        return;
    }

    /* If it doesn't have a class name, we can't do too much... */
    if ([className length] == 0)
    {
        Printf(@"Entity '%@' has no class name set; ignoring", entityName);
        return;
    }

    /* Stock EOGenericRecords don't have their own class generated by definition */
    if ([className hasSuffix:@"EOGenericRecord"])
    {
        if (verboseMode)
            Printf(@"Skipping EOGenericRecord entity '%@'", entityName);
        return;
    }

    pool = [[NSAutoreleasePool alloc] init];

    if (javaMode)
    {
        [self generateEntity:entity withTemplate:mainJavaTemplate asPrimary:YES extension:@"java"];
        [self generateEntity:entity withTemplate:stubJavaTemplate asPrimary:NO  extension:@"java"];
    }
    else
    {
        [self generateEntity:entity withTemplate:mainHeaderTemplate asPrimary:YES extension:@"h"];
        [self generateEntity:entity withTemplate:mainSourceTemplate asPrimary:YES extension:@"m"];

        [self generateEntity:entity withTemplate:stubHeaderTemplate asPrimary:NO extension:@"h"];
        [self generateEntity:entity withTemplate:stubSourceTemplate asPrimary:NO extension:@"m"];
    }

    [pool release];
}


- (NSString *)filenameForEntity:(EOEntity *)entity
        primary:(BOOL)primary
        extension:(NSString *)fileExtension
{
    NSString *baseFilename;
    NSString *className;
    NSString *packageName = nil;
    NSString *destDirectory;

    /* Make sure to use the right class names... */
#ifndef EOF2_ONLY
    if (javaClientMode) {
        className = [entity clientClassNameWithoutPackage];
        packageName = [entity clientClassPackageName];
    }
    else
#endif
    if (javaMode) {
        className = [entity classNameWithoutPackage];
        packageName = [entity classPackageName];
    }
    else {
        className = [entity className];
    }

    /* If a special filename template was specified, use that for the class name. */
    if (filenameEngine != nil)
    {
        [filenameEngine setEngineValue:[NSNumber numberWithBool:primary] forKey:@"isSuperclass"];
        [filenameEngine setEngineValue:[NSNumber numberWithBool:!primary] forKey:@"isSubclass"];
        className = [filenameEngine executeWithObject:entity sender:nil];
        
        if (javaMode && [className containsString:@"."])
        {
            NSRange dotRange = [className rangeOfString:@"." options:NSBackwardsSearch];
            packageName = [className substringToIndex:dotRange.location];
            className = [className substringFromIndex:NSMaxRange(dotRange)];
        }
    }
    
    baseFilename = [NSString stringWithFormat:@"%@%@", primary ? classPrefix : @"", className];
    baseFilename = [baseFilename stringByAppendingPathExtension:fileExtension];

    if (!primary && subclassDestination != nil)
        destDirectory = subclassDestination;
    else
        destDirectory = destination;

    /* Append path components for Java package if applicable */
    if (javaMode && useJavaPackagePaths && packageName != nil)
    {
        NSArray *pathComponents = [packageName componentsSeparatedByString:@"."];
        NSString *pathSegment = [NSString pathWithComponents:pathComponents];

        destDirectory = [destDirectory stringByAppendingPathComponent:pathSegment];
        [[NSFileManager defaultManager] deepCreateDirectoryAtPath:destDirectory attributes:nil];
    }

    /* Now put the path in front of the filename. */
    return [destDirectory stringByAppendingPathComponent:baseFilename];
}

- (MiscMergeTemplate *)templateForName:(NSString *)name
{
    static NSMutableDictionary *templateDict = nil;
    MiscMergeTemplate *template = [templateDict objectForKey:name];

    if (templateDict == nil)
        templateDict = [[NSMutableDictionary alloc] initWithCapacity:4];

    if (template == nil)
    {	
        NSFileManager *fileManager = [NSFileManager defaultManager];
        NSString *filename = nil;

        if ([fileManager regularFileExistsAtPath:name])
            filename = name;
        else
            filename = [fileManager findFile:name inSearchPath:[self searchPaths]];

        if (filename == nil)
        {
            Printf(@"Could not find template file '%@' in search path", name);
        }
        else
        {
            if (verboseMode)
                Printf(@"Using template at path %@", filename);
            template = [[MiscMergeTemplate alloc] init];
            [template setStartDelimiter:@"<$" endDelimiter:@"$>"];
            [template parseContentsOfFile:filename encoding:templateEncoding];
            [templateDict setObject:template forKey:name];
            [template release];
        }
    }

    return template;
}

- (void)generateEntity:(EOEntity *)entity
		withTemplate:(NSString *)templateName
		asPrimary:(BOOL)primary
		extension:(NSString *)fileExtension
{
    NSFileManager     *fileManager = [NSFileManager defaultManager];
    MiscMergeEngine   *engine;
    NSString          *newContents;
    NSData            *newVersion;
    NSData            *oldVersion;
    NSString          *outputFilename;
    MiscMergeTemplate *template;
    NSEnumerator      *userVariableEnum;
    NSString          *userKey;

    outputFilename = [self filenameForEntity:entity primary:primary extension:fileExtension];

    if (verboseMode)
        Printf(@"Generating class file %@ from template %@", outputFilename, templateName);
    
    /* 
     * If it is not a primary output file (an always generated _ClassName
     * entity), then check to see if the secondary (ClassName, which is a
     * subclass of _ClassName) is present. If the secondary file is present, we
     * DO NOT generate a new file. We only generate this once and the user is
     * free to edit the file afterwards to add business logic.
     */
    if (!primary && [fileManager regularFileExistsAtPath:outputFilename])
    {
        if (verboseMode)
        {
            Printf(@"Subclass %@ already exists at %@; not overwritten",
                     [self classNameForEntity:entity], outputFilename);
        }

        return;  /* we're done. */
    }

    // Locate the template or bail out if it can't be found
    if ((template = [self templateForName:templateName]) == nil)
        return;

    engine = [[MiscMergeEngine alloc] initWithTemplate:template];

	/* Add variables defined on the command line */
    userVariableEnum = [userVariables keyEnumerator];
    while (userKey = [userVariableEnum nextObject])
        [engine setEngineValue:[userVariables objectForKey:userKey] forKey:userKey];

    /* set prefix for stub classes */
    [engine setEngineValue:classPrefix forKey:@"GEN_PREFIX"];

    newContents = [engine executeWithObject:entity sender:nil];
    newVersion = [newContents dataUsingEncoding:outputEncoding];
    oldVersion = [NSData dataWithContentsOfFile:outputFilename];

    if (oldVersion != nil && [oldVersion isEqualToData:newVersion])
    {
        if (verboseMode)
            Printf(@"Output file %@ is identical to generated file; not written",
                     outputFilename);
    }
    else if (!forceOverwrites &&
             [fileManager fileExistsAtPath:outputFilename] &&
             ![fileManager isWritableFileAtPath:outputFilename])
    {
        Printf(@"ERROR: Could not overwrite read-only file '%@'", outputFilename);
    }
    else
    {
        if (![newVersion writeToFile:outputFilename atomically:YES])
        {
            Printf(@"ERROR: Could not write file '%@'", outputFilename);
        }
        else
        {
            /*
             * If we regenerate the main class in ObjC mode, update the timestamp on the
             * subclass so it will be recompiled.
             */
            if (primary && !javaMode)
            {
                NSString *subclassFile = [self filenameForEntity:entity
                                                         primary:NO
                                                       extension:fileExtension];

                if ([fileManager regularFileExistsAtPath:subclassFile])
                    [fileManager touchPath:subclassFile];
            }

            if (verboseMode) {
                Printf(@"Generated output file %@ for entity %@",
                         outputFilename, [entity name]);
            }
            else {
                Printf(@"Generated %@\n", outputFilename);
            }
        }
    }

    [engine release];
}

@end