eval 'exec perl -x $0 ${1+"$@"}' # -*-perl-*-
  if 0;
#!perl -w
#
# ======================================================================
# This file is Copyright 1998,1999 by the Purdue Research Foundation and
# may only be used under license.  For terms of the license, see the
# file named COPYRIGHT included with this software release.
# AAFID is a trademark of the Purdue Research Foundation.
# All rights reserved.
# ======================================================================
#
# AAFID::Message package.
#
# AAFID project, COAST Laboratory, CERIAS, 1998-1999.
# 
# Diego Zamboni, Jan 23, 1998.
#
# $Id: Message.pm,v 1.16 1999/09/03 17:08:53 zamboni Exp $
#
# NOTE: This file is in Perl's POD format. For more information, see the
#       perlpod(1) man page.
#

package AAFID::Message;

# The following keeps up with the RCS version number. The self-assignment
# keeps the -w switch from complaining (because $VERSION may not be used
# here, but it is used in our base class).
$VERSION = do { my @r = (q$Revision: 1.16 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; $VERSION = $VERSION;

use vars qw($MSG_NOTYPE
	   );
use strict;
use Carp;

=pod

This package defines the class that is used to represent messages
exchanged in the AAFID system.

=head1 Message types and subtypes

Messages have a defined format. The first field is the message type,
which indicates how the rest of the message will be interpreted. The
currently defined message types are the following.

=over 4

=item NOTYPE

Dummy value used to indicate a message that has not been initialized. It
can also be used to initially fill in the subtype field (see below).

=item CONNECT

The entity sending the message is trying to connect to someone
else. Who the connecting entity is, and some other information, may be
given by the message subtype and the other fields.

=item DISCONNECT

Notification that the entity sending the message is disconnecting from
the recipient. As before, more information is given by the other fields.

=item COMMAND

The message contains a command that should be executed by the recipient
(some authentication should be done first). The command to execute, and
any information necessary for it, is given in the other fields.

=item STATUS_UPDATE

The message contains a status update that the sender wants the recipient
to know about.

=back 4

The message subtype field is normally used to completely specify the type
of message that is being sent, and it has different meanings for different
message types.

=over 4

=item PARENT, CHILD

These are intended for use with a B<CONNECT> (and maybe B<DISCONNECT>)
message, to specify whether the entity that is trying to connect is a
parent or a child.

=item Command name

With a COMMAND message, the message subtype contains the specific command
to execute. Parameters are given as comma-separated C<key => value> pairs
in the DATA field.

=item RESULT

If a command produces a result that needs to be sent back to the entity
that requested the command, a COMMAND message with a RESULT subtype is
generated. The command must have generated a hash that will be sent in
the DATA field, with an additional key called C<Command> that will contain
the name of the command that generated the output.

=back 4

=head1 Creation of an object

Message objects are represented internally by a hash that contains the
different fields, indexed by their names. The currently defined fields 
are the following:

=over 4

=item TYPE

Message type code, as defined above.

=item SUBTYPE

Message subtype code, as defined above.

=item FROM

Identifier of the entity sending the message.

=item _FROMSTDIN

Set to true by the message-reading functions if the message was received
from standard input. If this field is not defined or false, then the message
should have been received from the entity specified in the FROM field.

This field is not represented in the messages that are exchanged between
entities, and is only set internally, because messages received from
STDIN may have a special meaning to the entities. That is why the name
of the field starts with a "_", to indicate that it is internal.

=item _FROMHANDLE

Also an internal field, contains a reference to the handle from which the
message was read.

=item TO

Identifier of the entity to which the message is destined. This element 
should contain a reference to an array, so that multiple recipients can
be specified. It may be empty (denoted by a "-") if there is only one
recipient, and the message is being sent directly to that recipient.

=item TIME

Date and time stamp of the message, represented as the number of seconds
elapsed since January 1, 1970 (as returned by the C<time> function in Perl).

=item DATA

Arbitrary text that can contain more information necessary for the specific
message that is being sent.

=back 4

The C<_init> method initially sets all the fields to be empty. This is
an instance method, and as such expects a reference to the object on which
it was called as the first argument. It will normally be used only by the
constructor.

=cut

$AAFID::Message::MSG_NOTYPE	= "NOTYPE";

sub _init {
    my $self = shift;
    # Initialize the data fields.
    $self->{TYPE}		= $MSG_NOTYPE,
    $self->{SUBTYPE}		= $MSG_NOTYPE,
    $self->{FROM}		= "-";
    $self->{TO}			= "-";
    $self->{DATA}		= undef;
    $self->{TIME}		= time;
}

=pod

One important thing for a message is that it has to be able to travel over
the network. Thus, we define methods for generating a string representation
of a message, and to parse a string representation back into a hash with
the appropriate fields. This is done by the C<toString> and C<fromString>
methods.

In the current implementation, the string format of a message is simply
a line of text, with the fields separated by spaces.

While C<toString> can only be called as an instance method (this is,
on an existing object), C<fromString> can be called either as an
instance or as a class method. In the first case, it redefines the
fields of the object it is called on, and in the second, it acts as
a constructor, returning a new object that contains the appropriate
data.

=cut

sub toString {
    my $self=shift;
    ref($self) || croak "AAFID::Message::toString can only be called as an instance method";
    local $^W=0;
    return "$self->{TYPE} $self->{SUBTYPE} $self->{FROM} $self->{TO} $self->{TIME} $self->{DATA}";
}

sub fromString {
    my $self=shift;
    my @fields=split /\s+/, shift, 6;

    # Sanity checking. The only really mandatory parameter is TYPE.
    # If empty, return nothing.
    return undef if $#fields<0;
    # By default subtype is NOTYPE.
    $fields[1]=$MSG_NOTYPE unless ($fields[1] && ($fields[1] ne "-"));
    # We let FROM be empty, we hope it's ok.
    $fields[2]="-" unless $fields[2];
    # To may be empty, if the message is directed to a single entity and
    # sent through a one-to-one channel.
    $fields[3]="-" unless $fields[3];
    # If DATE is empty or "-", set the message time to the time it is created.
    $fields[4]=time unless ($fields[4] && ($fields[4] ne "-"));

    # If called as instance method, redefine the fields in $self.
    if(ref($self)) {
	($self->{TYPE}, $self->{SUBTYPE}, $self->{FROM}, $self->{TO}, $self->{TIME}, $self->{DATA}) = @fields;
	return $self;
    }
    else {
	# If class method, act as constructor.
	my $class=$self;
	$self={TYPE		=>	$fields[0],
	       SUBTYPE		=>	$fields[1],
	       FROM		=>	$fields[2],
	       TO		=>	$fields[3],
	       TIME		=>	$fields[4],
	       DATA		=>	$fields[5]};
	bless $self, $class;
	return $self;
    }
}

=pod

The constructor for the class, if invoked with one argument, uses that as
an initialization string to parse and extract the information from, as
done in C<fromString>. If no arguments are given, an empty object is
created. Also, the user can directly give the fields and values she wants
to put in there as a hash. The fields that are not specified will be 
initialized to their default values. Examples of message creation are:

    $msg=AAFID::Message->new();	# Empty message.
    $msg=AAFID::Message->new("MSG STYPE $myID $hisID 885843603 message text");
    $msg=AAFID::Message->new(TYPE    => "MSG",
			     SUBTYPE => "STYPE",
			     FROM    => $myID,
			     TO      => $hisID,
			     DATA    => "message text");

=cut

# Constructor.
sub new {
    my $class=shift;
    my $self={};
    my %keys;
    
    if(@_==0) {
	bless $self, $class;
	$self->_init;
    }
    elsif(@_==1) {
	$self=AAFID::Message->fromString(shift);
	# $self returns already blessed.
	return $self;
    }
    elsif((@_ % 2)==0) {
	my %fields=@_;
	bless $self, $class;
	$self->_init;
	map {$self->{$_}=$fields{$_}} keys(%fields);
    }
    else {
	carp "$class: Invalid initialization parameters";
	return undef;
    }
    return $self;
}

=pod

We have some "is" methods for checking for different types and
subtypes of messages.

=cut

sub isType {
  my $self=shift;
  my $type=shift;
  return 0 if !$type;
  return ( uc($self->{TYPE}) eq uc($type) );
}

sub isSubtype {
  my $self=shift;
  my $stype=shift;
  return 0 if !$stype;
  return ( uc($self->{SUBTYPE}) eq uc($stype) );
}

# Synonymous for isType
sub isMessage {
  my $self=shift;
  return $self->isType(@_);
}

sub isCommand {
  my $self=shift;
  my $cmd=shift;
  return 0 if !$cmd;
  return ( ( uc($self->{TYPE}) eq 'COMMAND' ) &&
	   ( uc($self->{SUBTYPE}) eq uc($cmd) ) );
}

1;

#
# $Log: Message.pm,v $
# Revision 1.16  1999/09/03 17:08:53  zamboni
# Changed the start line to something that is path-independent, and
# updated the copyright notice.
#
# Revision 1.15  1999/03/31 07:24:00  zamboni
# - Added isSubtype
#
# Revision 1.14  1998/09/09 03:00:27  zamboni
# * Message.pm:
#   - Moved Log to the end.
#   - Added isType, isMessage and isCommand.
#
# Revision 1.13  1998/06/29 20:11:23  zamboni
# Added copyright message
#
# Revision 1.12  1998/03/13 17:00:25  zamboni
# - Added $VERSION declaration.
#
# Revision 1.11  1998/03/06 07:12:16  zamboni
# - Added "use strict"
# - Removed the "use Exporter", because we are not exporting anything now.
#
# Revision 1.10  1998/03/04 19:32:29  zamboni
# Made the fromString method give default values to any fields that are
# missing or specified as "-".
#
# Also, made the _init method use "-" instead of undef for all fields except
# for DATA.
#
# Revision 1.9  1998/02/26 05:25:24  zamboni
# Corrected name of FROMSTDIN to _FROMSTDIN, and added description of
# field _FROMHANDLE.
#
# Revision 1.8  1998/02/23 05:42:46  zamboni
# Much simplified version. Now message types/subtypes are not defined as
# codes, but instead everything is handled as strings, so that anything can
# be supplied as types or subtypes.
#
# Also, the warning flag is disabled in toString so that undefined fields
# do not produce warnings.
#
# Revision 1.7  1998/02/23 01:30:13  zamboni
# *** empty log message ***
#
# Revision 1.6  1998/02/17 05:09:46  zamboni
# Latest "old" version.
#
# Revision 1.5  1998/01/27 03:33:50  zamboni
# *** empty log message ***
#
# Revision 1.4  1998/01/26 20:55:37  zamboni
# Removed unnecessary "use lib(...)".
#
# Revision 1.3  1998/01/26 20:52:06  zamboni
# Modified the constructor to be able to take field=>value pairs as the
# initialization values. Also, added a TIME field to the message structure.
#
# Revision 1.2  1998/01/26 20:06:55  zamboni
# *** empty log message ***
#
# Revision 1.1  1998/01/23 21:27:11  zamboni
# Initial revision
#
