Source Code
cs_UniqueNameManager.h
  Prev   Next
/*
 * cs_UniqueNameManager.h
 *
 *
 * Copyright (c) 2005 Classical Software. All rights reserved.
 *
 * This software is released under the Creative Commons Attribution License, version 2.0
 * http://creativecommons.org/licenses/by/2.0/legalcode
 */

#import "cs_Utils.h"



/*
 * **************************************
 *                                      *
 *     cs_UniqueNamedObjectProtocol     *
 *                                      *
 * **************************************
 *
 * Objects (most of the time) should respond to this protocol.
 *
 * Groups of objects that are named usually maintain their own
 * names (and so will already implement "-name" and "-setName:",
 * but if for some reason they don't the cs_UniqueNameManager
 * will save the names internally and can be queried with
 * "-nameOfObject:". (In this case the caller needs to use
 * "-setShouldStoreObjectNamesInManager: YES".)
 *
 * Objects MUST implement "-proposeNameForDetailLevel:existingNames:"
 * which generates a new name at the given "level".
 */
@protocol cs_UniqueNamedObjectProtocol

- (NSString *)  name;
- (void)        setName: (NSString *) s;

- (NSString *)  proposeNameForDetailLevel: (int) i  existingNames: (NSSet *) s;

@end


/*
 * **************************************
 *                                      *
 *         cs_UniqueNameManager         *
 *                                      *
 * **************************************
 */
@interface cs_UniqueNameManager: NSObject
{
    NSMutableArray          *all_objects_;
    NSMutableSet            *all_names_;
    NSMutableDictionary     *objects_grouped_by_name_;
    NSMutableDictionary     *names_by_object_;
    BOOL                     needs_updating_;
    BOOL                     should_store_object_names_in_manager_;
}

+ (void)                    produceNamesUsingObjects:  (NSArray             *) a;
+ (id)                      managerWithObjects:        (NSArray             *) a;
+ (id)                      manager;

- (NSMutableArray *)        allObjects;
- (void)                    setAllObjects:             (NSMutableArray      *) a;
- (void)                    mergeObjects:              (NSArray             *) a;

- (NSMutableSet *)          allNames;
- (void)                    setAllNames:               (NSMutableSet        *) s;

- (NSMutableDictionary *)   objectsGroupedByName;
- (void)                    setObjectsGroupedByName:   (NSMutableDictionary *) d;

- (NSMutableDictionary *)   namesByObject;
- (void)                    setNamesByObject:          (NSMutableDictionary *) d;

- (BOOL)                    needsUpdating;
- (void)                    setNeedsUpdating:          (BOOL) b;

- (BOOL)                    shouldStoreObjectNamesInManager;
- (void)                    setShouldStoreObjectNamesInManager: (BOOL) b;

- (void)                    updateIfNeeded;
- (void)                    update;
- (void)                    groupObjectsByName;
- (void)                    calcUniqueNamesForObjects: (NSArray *) a;

- (NSString *)              guaranteedNameOfObject:    (id) o;
- (NSString *)              nameOfObject:              (id) o;
- (void)                    saveName:                  (NSString *) s  forObject: (id) o;
- (NSString *)              keyForObject:              (id) o;

@end
cs_UniqueNameManager.m
/*
 * cs_UniqueNameManager.m
 *
 * Given an set of objects (either initially or continually added/
 * removed) a cs_UniqueNameManager manages the process of producing
 * names for all objects such that they are unique across the entire
 * set - that is, no two objects have the same name.
 *
 * Note that it does this by sending each object "-proposeNameForDetailLevel:existingNames:"
 * to which the object responds with a new name for that "level".
 *
 * If the manager finds duplicate names at level 0 it starts over by
 * asking each object to generate a name for level 1, and so on.
 *
 *
 * Copyright (c) 2005 Classical Software. All rights reserved.
 *
 * This software is released under the Creative Commons Attribution License, version 2.0
 * http://creativecommons.org/licenses/by/2.0/legalcode
 */

#import "cs_Error.h"
#import "cs_UniqueNameManager.h"
#import "cs_Utils.h"


/*
 * --------------------------   D e b u g g i n g   --------------------------
 */
//#define DBG__TELL_NAME_GENS


#ifdef DBG__TELL_NAME_GENS
#define DBG_TELL_START()                printf("--- UniqNmGen ---  %s\n", [[self description] lossyCString] );
#define DBG_TELL_GROUPED()              printf(">>> grouped: %s\n", [[self description] lossyCString] );
#define DBG_TELL_RECALCD_GROUP(n)       printf(">>> recalc'd '%s' grp %s\n", [n lossyCString], [[self description] lossyCString] )
#define DBG_TELL_RESULT()               printf(">>> result: %s\n", [[self description] lossyCString] )
#else
#define DBG_TELL_START()
#define DBG_TELL_GROUPED()
#define DBG_TELL_RECALCD_GROUP(n)
#define DBG_TELL_RESULT()
#endif
/*
 * --------------------------   e n d   d e b u g   --------------------------
 */



/*
 * **********************************************************************************************
 *                                                                                              *
 *                                         cs_UniqueNameManager                                 *
 *                                                                                              *
 * **********************************************************************************************
 */
@implementation cs_UniqueNameManager

/*
 * produceNamesUsingObjects:
 */

+ (void) produceNamesUsingObjects: (NSArray *) a
{
    cs_UniqueNameManager    *mgr = [self managerWithObjects: a];
    [mgr update];
}


/*
 * managerWithObjects:
 */

+ (id) managerWithObjects: (NSArray *) a
{
    cs_UniqueNameManager    *mgr = [self manager];
    NSMutableArray          *ma  = (NSMutableArray *) a;
    
    if( [ma isKindOfClass: [NSMutableArray class]] == NO )
         ma = [NSMutableArray arrayWithObjects: a];
    
    [mgr setAllObjects: ma];
    
    return mgr;
}


/*
 * manager
 */

+ (id) manager
{
    return [[[self alloc] init] autorelease];
}


/*
 * init
 */

- (id) init
{
    if( (self = [super init]) )
    {
        [self setObjectsGroupedByName: [NSMutableDictionary dictionary]];
        [self setAllNames:             [NSMutableSet        set       ]];
        [self setNamesByObject:        [NSMutableDictionary dictionary]];
    }
    return self;
}


/*
 * dealloc
 */
- (void) dealloc
{
    [self setAllObjects:            nil];
    [self setObjectsGroupedByName:  nil];
    [self setAllNames:              nil];
    [self setNamesByObject:         nil];
    
    [super dealloc];
}


#pragma mark -
/*
 * **********************************************************************************************
 */

/*
 * allObjects
 */

- (NSMutableArray *) allObjects
{
    return all_objects_;
}


/*
 * setAllObjects:
 */

- (void) setAllObjects: (NSMutableArray *) d
{
    if(  all_objects_ != d )
    {
        [all_objects_ release];
         all_objects_ = [d retain];
        
        [self setNeedsUpdating: YES];
    }
}


/*
 * mergeObjects:
 */

- (void) mergeObjects: (NSArray *) new_objs
{
    NSMutableArray  *all     = [self allObjects];
    int              started = [all count];
    NSEnumerator    *enumr   = [new_objs objectEnumerator];
    id               obj;
    
    while( (obj = [enumr nextObject]) )
    {
        if( [all containsObject: obj] == NO )
            [all addObject: obj];
    }
    
    if( [all count] > started )
        [self setNeedsUpdating: YES];
}


/*
 * allNames
 *
 * Return:       { "abc", "def", "g" } 
 */

- (NSMutableSet *) allNames
{
    return all_names_;
}


/*
 * setAllNames:
 */

- (void) setAllNames: (NSMutableSet *) s
{
    if(  all_names_ != s )
    {
        [all_names_ release];
         all_names_ = [s retain];
    }
}


/*
 * objectsGroupedByName
 *
 *                        Name   Objects
 *                      -------- -----------
 * Return:       dict { { "abc", [o1, o2] },
 *                      { "def", [o3] },
 *                      { "g",   [o4, o5] }
 *                    } 
 */

- (NSMutableDictionary *) objectsGroupedByName
{
    return objects_grouped_by_name_;
}


/*
 * setObjectsGroupedByName:
 */

- (void) setObjectsGroupedByName: (NSMutableDictionary *) d
{
    if(  objects_grouped_by_name_ != d )
    {
        [objects_grouped_by_name_ release];
         objects_grouped_by_name_ = [d retain];
    }
}


/*
 * namesByObject
 *
 *                        Name   Objects
 *                      -------- -----------
 * Return:       dict { { "abc", @"0x1234" },
 *                      { "def", @"32456"  },
 *                      { "g",   @"68793"  }
 *                    } 
 */

- (NSMutableDictionary *) namesByObject
{
    return names_by_object_;
}


/*
 * setNamesByObject:
 */

- (void) setNamesByObject: (NSMutableDictionary *) d
{
    if(  names_by_object_ != d )
    {
        [names_by_object_ release];
         names_by_object_ = [d retain];
    }
}


/*
 * needsUpdating
 */

- (BOOL) needsUpdating
{
    return needs_updating_;
}


/*
 * setNeedsUpdating:
 */

- (void) setNeedsUpdating: (BOOL) b
{
    needs_updating_ = b;
}


/*
 * shouldStoreObjectNamesInManager
 */

- (BOOL) shouldStoreObjectNamesInManager
{
    return should_store_object_names_in_manager_;
}


/*
 * setShouldStoreObjectNamesInManager:
 */

- (void) setShouldStoreObjectNamesInManager: (BOOL) b
{
    should_store_object_names_in_manager_ = b;
}


#pragma mark -
/*
 * **********************************************************************************************
 */

/*
 * updateIfNeeded
 */

- (void) updateIfNeeded
{
    if( [self needsUpdating] )
    {
        [self setNeedsUpdating: NO];
        [self update];
    }
}


/*
 * update
 */

- (void) update
{
    DBG_TELL_START();
    
    [self groupObjectsByName];
    
    DBG_TELL_GROUPED();
    
    NSMutableDictionary *obj_groups = [self objectsGroupedByName];
    NSEnumerator        *enumr      = [obj_groups keyEnumerator];
    NSMutableSet        *all_nms    = [self allNames];
    NSMutableArray      *group;
    NSString            *nm;
    
    while( (nm = [enumr nextObject]) )
    {
        /*
         * Get the array of objects that all have this name:
         *
         *      { "abc", [o1, o2] }
         */
        group = [obj_groups objectForKey: nm];
        
        /*
         * Only one object in this group - and thus using this name? 
         *
         *      { "def", [o3] }
         */
        if( [group count] == 1 )
        {
            id <cs_UniqueNamedObjectProtocol>    obj = [group objectAtIndex: 0];
            [self saveName: nm  forObject: obj];
        }
        
        /*
         * Several objects are using this name, so need to have
         * them make new names for themselves.
         *
         *      { "abc", [o1, o2] }   ->   { "abc #1", [o1] },
         *                                 { "abc #2", [o2] }
         */
        else
        {
            [self calcUniqueNamesForObjects: group];
            [obj_groups removeObjectForKey: nm];
            [all_nms    removeObject:       nm];
            DBG_TELL_RECALCD_GROUP( nm );
        }
    }
    DBG_TELL_RESULT();
}


/*
 * groupObjectsByName
 */

- (void) groupObjectsByName
{
    NSMutableDictionary *obj_groups = [self objectsGroupedByName];
    NSMutableArray      *objs       = [self allObjects];
    NSEnumerator        *enumr      = [objs objectEnumerator];
    NSMutableSet        *all_nms    = [self allNames];
    NSMutableArray      *a;
    id <cs_UniqueNamedObjectProtocol>    obj;
    
    [obj_groups removeAllObjects];
    [all_nms    removeAllObjects];
    
    while( (obj = [enumr nextObject]) )
    {
        NSString    *nm = [self nameOfObject: obj];
        
        if( nm == nil )
        {
            nm = [obj proposeNameForDetailLevel: 0  existingNames: nil];
            [self saveName: nm  forObject: obj];
        }
        [all_nms addObject: nm];
        
        if( (a = [obj_groups objectForKey: nm]) == nil )
        {
            a = [NSMutableArray arrayWithObject: obj];
            [obj_groups setObject: a  forKey: nm];
        }
        else
            [a addObject: obj];
    }
}


/*
 * calcUniqueNamesForObjects:
 */

- (void) calcUniqueNamesForObjects: (NSArray *) objs_with_same_nm
{
    NSMutableDictionary *obj_groups = [self objectsGroupedByName];
    NSMutableSet        *all_nms    = [self allNames];
    NSMutableSet        *new_nms    = [NSMutableSet setWithCapacity: [objs_with_same_nm count]];
    int                  lvl        = 1;
    NSString            *nm;
    int                  k, n;
    NSMutableArray      *a;
    id <cs_UniqueNamedObjectProtocol>    obj;
    
    for(k=0,n=[objs_with_same_nm count]; k < n; k++)
    {
        obj = [objs_with_same_nm objectAtIndex: k];
        nm  = [obj proposeNameForDetailLevel: lvl  existingNames: all_nms];
        
        if( [all_nms member: nm] )                      // this name already exists
        {                                               // 
            lvl++;                                      // then go to the next detail level
            k = -1;                                     // start over with the objs
            [all_nms minusSet: new_nms];                //
            [new_nms removeAllObjects];                 // (clear prev produced labels)
        }
        else
        {
            [new_nms addObject: nm];                    // brand new name, so remember it
            [all_nms addObject: nm];                    //
            [self saveName: nm  forObject: obj];        // let the obj keep it's copy, too
        }
    }
    
    /*
     * Now encorporate them back into the "all names" dict
     */
    for(k=0; k < n; k++)
    {
        obj = [objs_with_same_nm objectAtIndex: k];
        nm  = [self nameOfObject: obj];
        a   = [NSMutableArray arrayWithObject: obj];
        [obj_groups setObject: a  forKey: nm];
    }
}


#pragma mark -
/*
 * **********************************************************************************************
 */

/*
 * When saving object names ourself we use a dictionary, 
 * indexed by each object (actually a string containing
 * it's address, e.g.: @"0x657498").
 *
 * Otherwise each object keeps it's own name.
 */

/*
 * guaranteedNameOfObject:
 *
 * This object should have a name (it either keeps the name
 * itself or we have a big table with the obj & it's name),
 * but if somehow it doesn't we'll make up one.
 */

- (NSString *) guaranteedNameOfObject: (id) obj
{
    NSString    *nm = [self nameOfObject: obj];
    
    if( [nm length] == 0 )
    {
        if( obj == nil )
            nm = @"0x0";
        else
            nm = [NSString stringWithFormat: @"%@ %p", [obj class], obj];
    }
    
    return nm;
}


/*
 * nameOfObject:
 */

- (NSString *) nameOfObject: (id) obj
{
    NSString    *nm = @"?";
    
    if( [self shouldStoreObjectNamesInManager] )                // are we storing the names?
    {
        NSMutableDictionary *d = [self namesByObject];          // the get it
        NSString            *s = [self keyForObject: obj];      //
        nm = [d objectForKey: s];                               //
    }
    else if( [obj respondsToSelector: @selector( name )] )      // Nope, the obj SHOULD have it
    {
        nm = [obj name];                                        // they do, so get it
    }
    else                                                        // NO!
        [self INTERNAL_ERROR: @"%@ %p does not respond to 'name' (obj=%@)", [obj class], obj, obj];
    
    return nm;
}


/*
 * saveName: forObject:
 */

- (void) saveName: (NSString *) nm
        forObject: (id        ) obj
{
    if( [self shouldStoreObjectNamesInManager] )                // are we storing the names?
    {
        NSMutableDictionary *d = [self namesByObject];          // then save it away 
        NSString            *s = [self keyForObject: obj];
        [d setObject: nm  forKey: s];                           //
    }
    else if( [obj respondsToSelector: @selector( setName: )] )  // this obj SHOULD be saving it
    {
        [(id<cs_UniqueNamedObjectProtocol>)obj setName: nm];
    }
    else                                                        // they aren't, trouble
        [self INTERNAL_ERROR: @"%@ %p does not respond to 'setName' (obj=%@)", [obj class], obj, obj];
}


/*
 * keyForObject:
 *
 * Return a (simple) unique key for this object:
 *
 *      "0x12345678"        (it's address as a string)
 */

- (NSString *) keyForObject: (id) obj
{
    return [NSString stringWithFormat: @"%p", obj];
}


#pragma mark -
/*
 * **********************************************************************************************
 */

/*
 * description
 */

- (NSString *) description
{
    NSDictionary    *obj_groups = [self objectsGroupedByName];
    NSMutableString *s          = [NSMutableString stringWithFormat: @"%@: %d groups", [self class], [obj_groups count]];
    
    if( [self needsUpdating] )
        [s appendString: @" (needs updating)"];
    
    NSEnumerator    *enumr = [obj_groups keyEnumerator];
    int              k     = 0;
    id               obj;
    NSString        *nm;
    
    [s appendFormat: @"\nGrp# Name"];
    while( (nm = [enumr nextObject]) )
    {
        NSMutableArray  *a = [obj_groups objectForKey: nm];
        [s appendFormat: @"\n  %2d ", k++];
        
        if( [a count] == 1 )
        {
            obj = [a objectAtIndex: 0];
            nm  = [self nameOfObject: obj];
            [s appendFormat: @"%@", nm];
        }
        else
        {
            int j, n;
            for(j=0,n=[a count]; j < n; j++)
            {
                obj = [a objectAtIndex: j];
                nm  = [self nameOfObject: obj];
                [s appendFormat: @"%s%@", j > 0 ? ", " : "", nm];
            }
        }
    }
    
    return s;
}


@end