ftp.nice.ch/Attic/openStep/implementation/gnustep/sources/gstep-base-0.2.7.tgz#/gstep-base-0.2.7/src/Encoder.m

This is Encoder.m in view mode; [Download] [Up]

/* Abstract class for writing objects to a stream
   Copyright (C) 1996 Free Software Foundation, Inc.
   
   Written by:  Andrew Kachites McCallum <mccallum@gnu.ai.mit.edu>
   Created: February 1996, with core from Coder, created 1994.
   
   This file is part of the GNUstep Base Library.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.
   
   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.
   
   You should have received a copy of the GNU Library General Public
   License along with this library; if not, write to the Free
   Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
   */ 

#include <gnustep/base/preface.h>
#include <gnustep/base/Coder.h>
#include <gnustep/base/CoderPrivate.h>
#include <gnustep/base/MemoryStream.h>
#include <gnustep/base/StdioStream.h>
#include <gnustep/base/BinaryCStream.h>
#include <Foundation/NSArchiver.h>
#include <Foundation/NSException.h>

static int default_format_version;
static id default_stream_class;
static id default_cstream_class;

static int debug_coder = 0;
#define DEFAULT_FORMAT_VERSION 0


/* xxx For experimentation.  The function in objc-api.h doesn't always
   work for objects; it sometimes returns YES for an instance. */
/* But, metaclasses return YES too? */
static BOOL
my_object_is_class(id object)
{
  if (object != nil 
#if NeXT_runtime
      && CLS_ISMETA(((Class)object)->isa)
      && ((Class)object)->isa != ((Class)object)->isa)
#else
      && CLS_ISMETA(((Class)object)->class_pointer)
      && ((Class)object)->class_pointer != ((Class)object)->class_pointer)
#endif
    return YES;
  else
    return NO;
}


@implementation Encoder

+ (void) initialize
{
  if (self == [Encoder class])
    {
      /* This code has not yet been ported to machines for which
	 a pointer is not the same size as an int. */
      assert(sizeof(void*) == sizeof(unsigned)); 

      /* Initialize some defaults. */
      default_stream_class = [MemoryStream class];
      default_cstream_class = [BinaryCStream class];
      default_format_version = DEFAULT_FORMAT_VERSION;
    }
}


/* Default format version, Stream and CStream class handling. */

+ (int) defaultFormatVersion
{
  return default_format_version;
}

+ (void) setDefaultFormatVersion: (int)f
{
  default_format_version = f;
}

+ (void) setDefaultCStreamClass: sc
{
  default_cstream_class = sc;
}

+ defaultCStreamClass
{
  return default_cstream_class;
}

+ (void) setDefaultStreamClass: sc
{
  default_stream_class = sc;
}

+ defaultStreamClass
{
  return default_stream_class;
}

/* xxx This method interface may change in the future. */
- (const char *) defaultDecoderClassname
{
  return "Unarchiver";
}


/* Signature Handling. */

- (void) writeSignature
{
  /* Careful: the string should not contain newlines. */
  [[cstream stream] writeFormat: SIGNATURE_FORMAT_STRING,
		    WRITE_SIGNATURE_FORMAT_ARGS];
}



/* This is the designated initializer for this class. */
- initForWritingToStream: (id <Streaming>) s
       withFormatVersion: (int) version
            cStreamClass: (Class) cStreamClass
    cStreamFormatVersion: (int) cStreamFormatVersion
{
  [super _initWithCStream: [[cStreamClass alloc] 
			    initForWritingToStream: s
			    withFormatVersion: cStreamFormatVersion]
	 formatVersion: version];
  in_progress_table = NULL;
  object_2_xref = NULL;
  object_2_fref = NULL;
  const_ptr_2_xref = NULL;
  fref_counter = 0;
  [self writeSignature];
  return self;
}

/* ..Writing... methods */

- initForWritingToStream: (id <Streaming>) s
	withCStreamClass: (Class) cStreamClass
{
  return [self initForWritingToStream: s
	       withFormatVersion: DEFAULT_FORMAT_VERSION
	       cStreamClass: cStreamClass
	       cStreamFormatVersion: [cStreamClass defaultFormatVersion]];
}

- initForWritingToStream: (id <Streaming>) s
{
  return [self initForWritingToStream: s
	       withCStreamClass: [[self class] defaultCStreamClass]];
}

- initForWritingToFile: (id <String>) filename
     withFormatVersion: (int) version
          cStreamClass: (Class) cStreamClass
  cStreamFormatVersion: (int) cStreamFormatVersion
{
  return [self initForWritingToStream: [StdioStream 
					 streamWithFilename: filename
					 fmode: "w"]
	       withFormatVersion: version
	       cStreamClass: cStreamClass
	       cStreamFormatVersion: cStreamFormatVersion];
}

- initForWritingToFile: (id <String>) filename
      withCStreamClass: (Class) cStreamClass
{
  return [self initForWritingToStream: [StdioStream 
					 streamWithFilename: filename
					 fmode: "w"]
	       withCStreamClass: cStreamClass];
}

- initForWritingToFile: (id <String>) filename
{
  return [self initForWritingToStream: 
		 [StdioStream streamWithFilename: filename
			      fmode: "w"]];
}

+ newWritingToStream: (id <Streaming>)s
{
  return [[self alloc] initForWritingToStream: s];
}

+ newWritingToFile: (id <String>)filename
{
  return [self newWritingToStream:
		 [StdioStream streamWithFilename: filename
			      fmode: "w"]];
}

+ (BOOL) encodeRootObject: anObject
		 withName: (id <String>) name
		 toStream: (id <Streaming>)stream
{
  id c = [[self alloc] initForWritingToStream: stream];
  [c encodeRootObject: anObject withName: name];
  [c close];
  [c release];
  return YES;
}

+ (BOOL) encodeRootObject: anObject 
  	         withName: (id <String>) name
                   toFile: (id <String>) filename
{
  return [self encodeRootObject: anObject
	       withName: name
	       toStream: [StdioStream streamWithFilename: filename
				      fmode: "w"]];
}


/* Functions and methods for keeping cross-references
   so objects aren't written/read twice. */

/* These _coder... methods may be overriden by subclasses so that 
   cross-references can be kept differently.

   For instance, ConnectedCoder keeps cross-references to const
   pointers on a per-Connection basis instead of a per-Coder basis.
   We avoid encoding/decoding the same classes and selectors over and
   over again.
*/
- (unsigned) _coderCreateReferenceForObject: anObj
{
  unsigned xref;
  if (!object_2_xref)
    {
      object_2_xref = 
	NSCreateMapTable (NSNonOwnedPointerOrNullMapKeyCallBacks,
			  NSIntMapValueCallBacks, 0);
    }
  xref = NSCountMapTable (object_2_xref) + 1;
  NSMapInsert (object_2_xref, anObj, (void*)xref);
  return xref;
}

- (unsigned) _coderReferenceForObject: anObject
{
  if (object_2_xref)
    return (unsigned) NSMapGet (object_2_xref, anObject);
  else
    return 0;
}


/* Methods for handling constant pointers */
/* By overriding the next three methods, subclasses can change the way
   that const pointers (like SEL, Class, Atomic strings, etc) are
   archived. */

- (unsigned) _coderCreateReferenceForConstPtr: (const void*)ptr
{
  unsigned xref;
  if (!const_ptr_2_xref)
    const_ptr_2_xref = 
      NSCreateMapTable (NSNonOwnedPointerOrNullMapKeyCallBacks,
			NSIntMapValueCallBacks, 0);

  xref = NSCountMapTable (const_ptr_2_xref) + 1;
  assert (! NSMapGet (const_ptr_2_xref, (void*)xref));
  NSMapInsert (const_ptr_2_xref, ptr, (void*)xref);
  return xref;
}

- (unsigned) _coderReferenceForConstPtr: (const void*)ptr
{
  if (const_ptr_2_xref)
    return (unsigned) NSMapGet (const_ptr_2_xref, ptr);
  else
    return 0;
}


/* Methods for forward references */

- (unsigned) _coderCreateForwardReferenceForObject: anObject
{
  unsigned fref;
  if (!object_2_fref)
    object_2_fref = 
      NSCreateMapTable (NSNonOwnedPointerOrNullMapKeyCallBacks,
			NSIntMapValueCallBacks, 0);
  fref = ++fref_counter;
  assert ( ! NSMapGet (object_2_fref, anObject));
  NSMapInsert (object_2_fref, anObject, (void*)fref);
  return fref;
}

- (unsigned) _coderForwardReferenceForObject: anObject
{
  /* This method must return 0 if it's not there. */
  if (!object_2_fref)
    return 0;
  return (unsigned) NSMapGet (object_2_fref, anObject);
}

- (void) _coderRemoveForwardReferenceForObject: anObject
{
  NSMapRemove (object_2_fref, anObject);
}


/* This is the Coder's interface to the over-ridable
   "_coderPutObject:atReference" method.  Do not override it.  It
   handles the root_object_table. */

- (void) _coderInternalCreateReferenceForObject: anObj
{
  [self _coderCreateReferenceForObject: anObj];
}


/* Handling the in_progress_table.  These are called before and after
   the actual object (not a forward or backward reference) is encoded.

   One of these objects should also call
   -_coderInternalCreateReferenceForObject:.  GNU archiving calls it
   in the first, in order to force forward references to objects that
   are in progress; this allows for -initWithCoder: methods that
   deallocate self, and return another object.  OpenStep-style coding
   calls it in the second, meaning that we never create forward
   references to objects that are in progress; we encode a backward
   reference to the in progress object, and assume that it will not
   change location. */

- (void) _objectWillBeInProgress: anObj
{
  if (!in_progress_table)
    in_progress_table = 
      /* This is "NonOwnedPointer", and not "Object", because
	 with "Object" we would get an infinite loop with distributed
	 objects when we try to put a Proxy in in the table, and
	 send the proxy the -hash method. */ 
      NSCreateMapTable (NSNonOwnedPointerMapKeyCallBacks, 
			NSIntMapValueCallBacks, 0);
  NSMapInsert (in_progress_table, anObj, (void*)1);
}

- (void) _objectNoLongerInProgress: anObj
{
  NSMapRemove (in_progress_table, anObj);
  /* Register that we have encoded it so that future encoding can 
     do backward references properly. */
  [self _coderInternalCreateReferenceForObject: anObj];
}


/* Method for encoding things. */

- (void) encodeValueOfCType: (const char*)type 
   at: (const void*)d 
   withName: (id <String>)name
{
  [cstream encodeValueOfCType:type
	   at:d
	   withName:name];
}

- (void) encodeBytes: (const void *)b
   count: (unsigned)c
   withName: (id <String>)name
{
  /* xxx Is this what we want?  
     It won't be cleanly readable in TextCStream's. */
  [cstream encodeName: name];
  [[cstream stream] writeBytes: b length: c];
}


- (void) encodeTag: (unsigned char)t
{
  if ([cstream respondsToSelector: @selector(encodeTag:)])
    [(id)cstream encodeTag:t];
  else
    [self encodeValueOfCType:@encode(unsigned char) 
	  at:&t 
	  withName:@"Coder tag"];
}

- (void) encodeClass: aClass 
{
  [self encodeIndent];
  if (aClass == Nil)
    {
      [self encodeTag: CODER_CLASS_NIL];
    }
  else
    {
      /* xxx Perhaps I should do classname substitution here. */
      const char *class_name = class_get_class_name (aClass);
      unsigned xref;

      /* Do classname substitution, ala encodeClassName:intoClassName */
      if (classname_2_classname)
	{
	  char *subst_class_name = NSMapGet (classname_2_classname,
					     class_name);
	  if (subst_class_name)
	    {
	      class_name = subst_class_name;
	      aClass = objc_lookup_class (class_name);
	    }
	}

      xref = [self _coderReferenceForConstPtr: aClass];
      if (xref)
	{
	  /* It's already been encoded, so just encode the x-reference */
	  [self encodeTag: CODER_CLASS_REPEATED];
	  [self encodeValueOfCType: @encode(unsigned)
		at: &xref 
		withName: @"Class cross-reference number"];
	}
      else
	{
	  /* It hasn't been encoded before; encode it. */
	  int class_version = class_get_version (aClass);

	  assert (class_name);
	  assert (*class_name);

	  [self encodeTag: CODER_CLASS];
	  [self encodeValueOfCType: @encode(char*)
		at: &class_name
		withName: @"Class name"];
	  [self encodeValueOfCType: @encode(int)
		at: &class_version
		withName: @"Class version"];
	  [self _coderCreateReferenceForConstPtr: aClass];
	}
    }
  [self encodeUnindent];
  return;
}

- (void) encodeAtomicString: (const char*) sp
   withName: (id <String>) name
{
  /* xxx Add repeat-string-ptr checking here. */
  [self notImplemented:_cmd];
  [self encodeValueOfCType:@encode(char*) at:&sp withName:name];
}

- (void) encodeSelector: (SEL)sel withName: (id <String>) name
{
  [self encodeName:name];
  [self encodeIndent];
  if (sel == 0)
    {
      [self encodeTag: CODER_CONST_PTR_NULL];
    }
  else
    {
      unsigned xref = [self _coderReferenceForConstPtr: sel];
      if (xref)
	{
	  /* It's already been encoded, so just encode the x-reference */
	  [self encodeTag: CODER_CONST_PTR_REPEATED];
	  [self encodeValueOfCType: @encode(unsigned)
		at: &xref
		withName: @"SEL cross-reference number"];
	}
      else
	{
	  const char *sel_name;
	  const char *sel_types;

	  [self encodeTag: CODER_CONST_PTR];

	  /* Get the selector name and type. */
	  sel_name = sel_get_name(sel);
#if NeXT_runtime
	  sel_types = NO_SEL_TYPES;
#else
	  sel_types = sel_get_type(sel);
#endif
#if 1 /* xxx Yipes,... careful... */
	  /* xxx Think about something like this. */
	  if (!sel_types)
	    sel_types = 
	      sel_get_type (sel_get_any_typed_uid (sel_get_name (sel)));
#endif
	  if (!sel_name || !*sel_name)
	    [NSException raise: NSGenericException
			 format: @"ObjC runtime didn't provide SEL name"];
	  if (!sel_types || !*sel_types)
	    [NSException raise: NSGenericException
			 format: @"ObjC runtime didn't provide SEL type"];

	  [self _coderCreateReferenceForConstPtr: sel];
	  [self encodeValueOfCType: @encode(char*) 
		at: &sel_name 
		withName: @"SEL name"];
	  [self encodeValueOfCType: @encode(char*) 
		at: &sel_types 
		withName: @"SEL types"];
	  if (debug_coder)
	    fprintf(stderr, "Coder encoding registered sel xref %u\n", xref);
	}
    }
  [self encodeUnindent];
  return;
}

- (void) encodeValueOfObjCType: (const char*) type 
   at: (const void*) d 
   withName: (id <String>) name
{
  switch (*type)
    {
    case _C_CLASS:
      [self encodeName: name];
      [self encodeClass: *(id*)d];
      break;
    case _C_ATOM:
      [self encodeAtomicString: *(char**)d withName: name];
      break;
    case _C_SEL:
      {
	[self encodeSelector: *(SEL*)d withName: name];
	break;
      }
    case _C_ID:
      [self encodeObject: *(id*)d withName: name];
      break;
    default:
      [self encodeValueOfCType:type at:d withName:name];
    }
}


/* Methods for handling interconnected objects */

- (void) startEncodingInterconnectedObjects
{
  interconnect_stack_height++;
}

- (void) finishEncodingInterconnectedObjects
{
  /* xxx Perhaps we should look at the forward references and
     encode here any forward-referenced objects that haven't been
     encoded yet.  No---the current behavior implements NeXT's
     -encodeConditionalObject: */
  assert (interconnect_stack_height);
  interconnect_stack_height--;
}

/* NOTE: Unlike NeXT's, this *can* be called recursively */
- (void) encodeRootObject: anObj
    withName: (id <String>)name
{
  [self encodeName: @"Root Object"];
  [self encodeIndent];
  [self encodeTag: CODER_OBJECT_ROOT];
  [self startEncodingInterconnectedObjects];
  [self encodeObject: anObj withName: name];
  [self finishEncodingInterconnectedObjects];
  [self encodeUnindent];
}


/* These next two methods are the designated coder methods called when
   we've determined that the object has not already been
   encoded---we're not simply going to encode a cross-reference number
   to the object, we're actually going to encode an object (either a
   proxy to the object or the object itself).

   ConnectedCoder overrides _doEncodeObject: in order to implement
   the encoding of proxies. */

- (void) _doEncodeBycopyObject: anObj
{
  id encoded_object, encoded_class;

  /* Give the user the opportunity to substitute the class and object */
  /* xxx Is this the right place for this substitution? */
  if ([[self class] isKindOf: [NSCoder class]]
      && ! [[self class] isKindOf: [NSArchiver class]])
    /* Make sure we don't do this for the Coder class, because
       by default Coder should behave like NSArchiver. */
    {
      encoded_object = [anObj replacementObjectForCoder: (NSCoder*)self];
      encoded_class = [encoded_object classForCoder];
    }
  else
    {
      encoded_object = [anObj replacementObjectForArchiver: (NSArchiver*)self];
      encoded_class = [encoded_object classForArchiver];
    }
  [self encodeClass: encoded_class];
  /* xxx We should make sure it responds to this selector! */
  [encoded_object encodeWithCoder: (id)self];
}

/* This method overridden by ConnectedCoder */
- (void) _doEncodeObject: anObj
{
  [self _doEncodeBycopyObject:anObj];
}


/* This is the designated object encoder */
- (void) _encodeObject: anObj
   withName: (id <String>) name
   isBycopy: (BOOL) bycopy_flag
   isForwardReference: (BOOL) forward_ref_flag
{
  [self encodeName:name];
  [self encodeIndent];
  if (!anObj)
    {
      [self encodeTag:CODER_OBJECT_NIL];
    }
  else if (my_object_is_class(anObj))
    {
      [self encodeTag: CODER_OBJECT_CLASS];
      [self encodeClass:anObj];
    }
  else
    {
      unsigned xref = [self _coderReferenceForObject: anObj];
      if (xref)
	{
	  /* It's already been encoded, so just encode the x-reference */
	  [self encodeTag: CODER_OBJECT_REPEATED];
	  [self encodeValueOfCType: @encode(unsigned)
		at: &xref 
		withName: @"Object cross-reference number"];
	}
      else if (forward_ref_flag
	       || (in_progress_table
		   && NSMapGet (in_progress_table, anObj)))
	{
	  unsigned fref;

	  /* We are going to encode a forward reference, either because 
	     (1) our caller asked for it, or (2) we are in the middle
	     of encoding this object, and haven't finished encoding it yet. */
	  /* Find out if it already has a forward reference number. */
	  fref = [self _coderForwardReferenceForObject: anObj];
	  if (!fref)
	    /* It doesn't, so create one. */
	    fref = [self _coderCreateForwardReferenceForObject: anObj];
	  [self encodeTag: CODER_OBJECT_FORWARD_REFERENCE];
	  [self encodeValueOfCType: @encode(unsigned)
		at: &fref 
		withName: @"Object forward cross-reference number"];
	}
      else
	{
	  /* No backward or forward references, we are going to encode
	     the object. */
	  unsigned fref;

	  /* Register the object as being in progress of encoding.  In
	     OpenStep-style archiving, this method also calls
	     -_coderInternalCreateReferenceForObject:. */
	  [self _objectWillBeInProgress: anObj];

	  /* Encode the object. */
	  [self encodeTag: CODER_OBJECT];
	  [self encodeIndent];
	  if (bycopy_flag)
	    [self _doEncodeBycopyObject:anObj];
	  else
	    [self _doEncodeObject:anObj];	    
	  [self encodeUnindent];

	  /* Find out if this object satisfies any forward references,
	     and encode either the forward reference number, or a
	     zero.  NOTE: This test is here, and not before the
	     _doEncode.., because the encoding of this object may,
	     itself, generate a "forward reference" to this object,
	     (ala the in_progress_table).  That is, we cannot know
	     whether this object satisfies a forward reference until
	     after it has been encoded. */
	  fref = [self _coderForwardReferenceForObject: anObj];
	  if (fref)
	    {
	      /* It does satisfy a forward reference; write the forward 
		 reference number, so the decoder can know. */
	      [self encodeValueOfCType: @encode(unsigned)
		    at: &fref 
		    withName: @"Object forward cross-reference number"];
	      /* Remove it from the forward reference table, since we'll never
		 have another forward reference for this object. */
	      [self _coderRemoveForwardReferenceForObject: anObj];
	    }
	  else
	    {
	      /* It does not satisfy any forward references.  Let the
		 decoder know this by encoding NULL.  Note: in future
		 encoding we may have backward references to this
		 object, but we will never need forward references to
		 this object.  */
	      unsigned null_fref = 0;
	      [self encodeValueOfCType: @encode(unsigned)
		    at: &null_fref 
		    withName: @"Object forward cross-reference number"];
	    }

	  /* We're done encoding the object, it's no longer in progress.
	     In GNU-style archiving, this method also calls
	     -_coderInternalCreateReferenceForObject:. */
	  [self _objectNoLongerInProgress: anObj];
	}
    }
  [self encodeUnindent];
}

- (void) encodeObject: anObj
   withName: (id <String>)name
{
  [self _encodeObject:anObj withName:name isBycopy:NO isForwardReference:NO];
}


- (void) encodeBycopyObject: anObj
   withName: (id <String>)name
{
  [self _encodeObject:anObj withName:name isBycopy:YES isForwardReference:NO];
}

- (void) encodeObjectReference: anObj
   withName: (id <String>)name
{
  [self _encodeObject:anObj withName:name isBycopy:NO isForwardReference:YES];
}



- (void) encodeWithName: (id <String>)name
   valuesOfObjCTypes: (const char *)types, ...
{
  va_list ap;

  [self encodeName:name];
  va_start(ap, types);
  while (*types)
    {
      [self encodeValueOfObjCType:types
	    at:va_arg(ap, void*)
	    withName:@"Encoded Types Component"];
      types = objc_skip_typespec(types);
    }
  va_end(ap);
}

- (void) encodeValueOfObjCTypes: (const char *)types
   at: (const void *)d
   withName: (id <String>)name
{
  [self encodeName:name];
  while (*types)
    {
      [self encodeValueOfObjCType:types
	    at:d
	    withName:@"Encoded Types Component"];
      types = objc_skip_typespec(types);
    }
}

- (void) encodeArrayOfObjCType: (const char *)type
   count: (unsigned)c
   at: (const void *)d
   withName: (id <String>)name
{
  int i;
  int offset = objc_sizeof_type(type);
  const char *where = d;

  [self encodeName:name];
  for (i = 0; i < c; i++)
    {
      [self encodeValueOfObjCType:type
	    at:where
	    withName:@"Encoded Array Component"];
      where += offset;
    }
}

- (void) encodeIndent
{
  [cstream encodeIndent];
}

- (void) encodeUnindent
{
  [cstream encodeUnindent];
}

- (void) encodeName: (id <String>)n
{
  [cstream encodeName: n];
}


/* Substituting Classes */

- (id <String>) classNameEncodedForTrueClassName: (id <String>) trueName
{
  [self notImplemented: _cmd];
  return nil;

#if 0
  if (classname_2_classname)
    return NSMapGet (classname_2_classname, [trueName cStringNoCopy]);
  return trueName;
#endif
}

- (void) encodeClassName: (id <String>) trueName
   intoClassName: (id <String>) inArchiveName
{
  [self notImplemented: _cmd];

#if 0
  /* The table should hold char*'s, not id's. */
  if (!classname_2_classname)
    classname_2_classname = 
      NSCreateMapTable (NSObjectsMapKeyCallBacks,
			NSObjectsMapValueCallBacks, 0);
  NSMapInsert (classname_2_classname, trueName, inArchiveName);
#endif
}

- (void) dealloc
{
  if (in_progress_table) NSFreeMapTable (in_progress_table);
  if (object_2_xref) NSFreeMapTable (object_2_xref);
  if (object_2_fref) NSFreeMapTable (object_2_fref);
  if (const_ptr_2_xref) NSFreeMapTable (const_ptr_2_xref);
  if (classname_2_classname) NSFreeMapTable (classname_2_classname);
  [super dealloc];
}

@end

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.