EMMA Coverage Report (generated Sat Jul 01 16:38:45 PDT 2006)
[all classes][jade.jademx.util]

COVERAGE SUMMARY FOR SOURCE FILE [AclMsgCmp.java]

nameclass, %method, %block, %line, %
AclMsgCmp.java100% (2/2)100% (72/72)82%  (2029/2476)87%  (461.1/530)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class AclMsgCmp$Variable100% (1/1)100% (5/5)65%  (81/125)84%  (27/32)
mappedValue (): String 100% (1/1)35%  (16/46)56%  (5/9)
AclMsgCmp$Variable (AclMsgCmp, String, String): void 100% (1/1)80%  (56/70)95%  (19/20)
getMap (): Map 100% (1/1)100% (3/3)100% (1/1)
getName (): String 100% (1/1)100% (3/3)100% (1/1)
getType (): String 100% (1/1)100% (3/3)100% (1/1)
     
class AclMsgCmp100% (1/1)100% (67/67)83%  (1948/2351)87%  (434.1/498)
compareEncoding (ACLMessage, ACLMessage): String 100% (1/1)43%  (13/30)80%  (4/5)
iterToArrayList (Iterator): ArrayList 100% (1/1)50%  (15/30)71%  (5/7)
compareContent (ACLMessage, ACLMessage, Properties): String 100% (1/1)54%  (237/439)70%  (59.8/86)
strEqualsNullOK (String, String): boolean 100% (1/1)55%  (18/33)36%  (4/11)
dateEqualNullOK (Date, Date): boolean 100% (1/1)73%  (24/33)59%  (6.5/11)
strEqualsIgnoreCaseNullOK (String, String): boolean 100% (1/1)73%  (24/33)59%  (6.5/11)
compareUserProperties (ACLMessage, ACLMessage): String 100% (1/1)74%  (80/108)85%  (17/20)
compareAidSet (Iterator, Iterator, String): String 100% (1/1)75%  (169/224)84%  (27/32)
cmpCaseInsConsStrSlot (String, Map, String, String): String 100% (1/1)85%  (104/123)88%  (14/16)
getHap (AID): String 100% (1/1)89%  (16/18)80%  (4/5)
compareSender (ACLMessage, ACLMessage): String 100% (1/1)90%  (130/145)92%  (23/25)
strMismatchMsg (String, int, String): String 100% (1/1)94%  (114/121)89%  (25/28)
compare (ACLMessage, ACLMessage, Properties): String 100% (1/1)95%  (174/184)91%  (28.3/31)
<static initializer> 100% (1/1)100% (7/7)100% (2/2)
AclMsgCmp (): void 100% (1/1)100% (74/74)100% (23/23)
compare (ACLMessage, ACLMessage): String 100% (1/1)100% (6/6)100% (1/1)
compareConversationId (ACLMessage, ACLMessage): String 100% (1/1)100% (10/10)100% (1/1)
compareEnvelope (ACLMessage, ACLMessage): String 100% (1/1)100% (5/5)100% (1/1)
compareInReplyTo (ACLMessage, ACLMessage): String 100% (1/1)100% (10/10)100% (1/1)
compareLanguage (ACLMessage, ACLMessage): String 100% (1/1)100% (30/30)100% (5/5)
compareOntology (ACLMessage, ACLMessage): String 100% (1/1)100% (30/30)100% (5/5)
comparePerformative (ACLMessage, ACLMessage): String 100% (1/1)100% (28/28)100% (5/5)
compareProtocol (ACLMessage, ACLMessage): String 100% (1/1)100% (30/30)100% (5/5)
compareReceiver (ACLMessage, ACLMessage): String 100% (1/1)100% (8/8)100% (1/1)
compareReplyBy (ACLMessage, ACLMessage): String 100% (1/1)100% (30/30)100% (5/5)
compareReplyTo (ACLMessage, ACLMessage): String 100% (1/1)100% (8/8)100% (1/1)
compareReplyWith (ACLMessage, ACLMessage): String 100% (1/1)100% (10/10)100% (1/1)
filterEmbeddedAddresses (String): String 100% (1/1)100% (80/80)100% (22/22)
globalizeContentAgentIds (String, String): String 100% (1/1)100% (90/90)100% (24/24)
hasAtPos (char [], int, char []): boolean 100% (1/1)100% (36/36)100% (12/12)
isCmpContent (): boolean 100% (1/1)100% (3/3)100% (1/1)
isCmpConversationId (): boolean 100% (1/1)100% (3/3)100% (1/1)
isCmpEncoding (): boolean 100% (1/1)100% (3/3)100% (1/1)
isCmpEnvelope (): boolean 100% (1/1)100% (3/3)100% (1/1)
isCmpInReplyTo (): boolean 100% (1/1)100% (3/3)100% (1/1)
isCmpLanguage (): boolean 100% (1/1)100% (3/3)100% (1/1)
isCmpOntology (): boolean 100% (1/1)100% (3/3)100% (1/1)
isCmpPerformative (): boolean 100% (1/1)100% (3/3)100% (1/1)
isCmpProtocol (): boolean 100% (1/1)100% (3/3)100% (1/1)
isCmpReceiver (): boolean 100% (1/1)100% (3/3)100% (1/1)
isCmpReplyBy (): boolean 100% (1/1)100% (3/3)100% (1/1)
isCmpReplyTo (): boolean 100% (1/1)100% (3/3)100% (1/1)
isCmpReplyWith (): boolean 100% (1/1)100% (3/3)100% (1/1)
isCmpSender (): boolean 100% (1/1)100% (3/3)100% (1/1)
isCmpUserProperties (): boolean 100% (1/1)100% (3/3)100% (1/1)
isIgnoreContentAIDAddresses (): boolean 100% (1/1)100% (3/3)100% (1/1)
isNewlinesNormalized (): boolean 100% (1/1)100% (3/3)100% (1/1)
normalizeNewlines (String): String 100% (1/1)100% (46/46)100% (10/10)
setCmpContent (boolean): void 100% (1/1)100% (4/4)100% (2/2)
setCmpConversationId (boolean): void 100% (1/1)100% (4/4)100% (2/2)
setCmpEncoding (boolean): void 100% (1/1)100% (4/4)100% (2/2)
setCmpEnvelope (boolean): void 100% (1/1)100% (4/4)100% (2/2)
setCmpInReplyTo (boolean): void 100% (1/1)100% (4/4)100% (2/2)
setCmpLanguage (boolean): void 100% (1/1)100% (4/4)100% (2/2)
setCmpOntology (boolean): void 100% (1/1)100% (4/4)100% (2/2)
setCmpPerformative (boolean): void 100% (1/1)100% (4/4)100% (2/2)
setCmpProtocol (boolean): void 100% (1/1)100% (4/4)100% (2/2)
setCmpReceiver (boolean): void 100% (1/1)100% (4/4)100% (2/2)
setCmpReplyBy (boolean): void 100% (1/1)100% (4/4)100% (2/2)
setCmpReplyTo (boolean): void 100% (1/1)100% (4/4)100% (2/2)
setCmpReplyWith (boolean): void 100% (1/1)100% (4/4)100% (2/2)
setCmpSender (boolean): void 100% (1/1)100% (4/4)100% (2/2)
setCmpUserProperties (boolean): void 100% (1/1)100% (4/4)100% (2/2)
setIgnoreContentAIDAddresses (boolean): void 100% (1/1)100% (4/4)100% (2/2)
setNewlinesNormalized (boolean): void 100% (1/1)100% (4/4)100% (2/2)
strToVarStrList (String, Properties): List 100% (1/1)100% (159/159)100% (32/32)
stringToMessage (String): ACLMessage 100% (1/1)100% (14/14)100% (2/2)

1// jademx - JADE management using JMX
2// Copyright 2004-2005 Caboodle Networks, Inc.
3//
4// This library is free software; you can redistribute it and/or
5// modify it under the terms of the GNU Lesser General Public
6// License as published by the Free Software Foundation; either
7// version 2.1 of the License, or (at your option) any later version.
8//
9// This library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12// Lesser General Public License for more details.
13//
14// You should have received a copy of the GNU Lesser General Public
15// License along with this library; if not, write to the Free Software
16// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
17 
18package jade.jademx.util;
19 
20import jade.core.AID;
21import jade.core.CaseInsensitiveString;
22import jade.lang.acl.ACLCodec;
23import jade.lang.acl.ACLMessage;
24import jade.lang.acl.StringACLCodec;
25import jade.util.leap.Iterator;
26import jade.util.leap.Properties;
27import java.io.StringReader;
28import java.io.StringWriter;
29import java.util.Arrays;
30import java.util.ArrayList;
31import java.util.Collections;
32import java.util.Date;
33import java.util.Enumeration;
34import java.util.HashMap;
35import java.util.LinkedList;
36import java.util.List;
37import java.util.ListIterator;
38import java.util.Map;
39import java.util.NoSuchElementException;
40 
41 
42/**
43 * Comparator for two ACL messages for equality, possibly filtering fields.
44 * This object retains state from one invocation to the next, so that
45 * temporary artifacts such as HAP of the AID or the conversation ID
46 * do not cause false comparison failures, but are instead handled intelligently
47 * so that sets of names must match from one run to the next.
48 * <br/>
49 * !!!MAYBE: allow custom comparison methods: 
50 *           particularly content and user-def-props
51 * <br/>
52 * !!!UNIMPLEMENTED: envelope comparison
53 * <br/>
54 * @author David Bernstein, <a href="http://www.caboodlenetworks.com"
55 *  >Caboodle Networks, Inc.</a>
56 */
57public class AclMsgCmp {
58    
59    // All maps are from left compare arg to right compare arg and
60    // canonically store both key and value in lower case.
61    // Note that toLowerCase() is influenced by the current locale:
62    // in Turkish 'I' -> '\u0131' (dotless small i) and 
63    // '\u0130' (dotted I) -> 'i'; otherwise it's pretty safe.
64    // ?an enhancement for case-sensitive maps?
65    
66    /** map Home Agent Platform (HAP) of Agent Identifier (AID) */
67    private Map hapMap = new HashMap();
68    
69    /** map conversation identifiers */
70    private Map conversationIdMap = new HashMap();
71    
72    /** map reply-with/in-reply-to identifiers */
73    private Map replyIdMap = new HashMap();
74    
75    /** map other identifiers consistently in one or more comparisons */
76    private Map consistentMap = new HashMap();
77    
78    // external names of maps to use for variables
79    // these must map exactly to the enumeration of allowed values
80    // in typeType in jadeunit XML schema.
81    // ?perhaps should just have consistent and random?
82    
83    /** name of AID map */
84    public final static String AID_MAP = "AID";
85    
86    /** name of reply-id map */
87    public final static String REPLY_ID_MAP = "reply-id";
88    
89    /** name of conversation-id map */
90    public final static String CONVERSATION_ID_MAP = "conversation-id";
91    
92    /** name of consistent map */
93    public final static String CONSISTENT_MAP = "consistent";
94    
95    /** name of random map (that is, no map) */
96    public final static String RANDOM_MAP = "random";
97    
98    
99    // !!!TBA???!!!
100    // set by caller:
101    // private XXX content-matching-object
102    // null means string/byte comparison?
103    // defined by local interface?
104    // define a default fipa-sl content comparator?
105    
106    //
107    // CONSTRUCTOR
108    //
109    
110    /**
111     * Create new comparator for ACL messages.
112     */
113    public AclMsgCmp() {
114    }
115    
116    //
117    // HELPER METHODS
118    //
119    
120    /**
121     * convenience method to compare 2 str's (case-sensitive) with null args ok.
122     * @param s1 first string to compare
123     * @param s2 first string to compare
124     * @return whether strings equal: both null is considered equal
125     */
126    private boolean strEqualsNullOK( String s1, String s2 ) {
127        boolean equal;
128        if ( ( null == s1 ) && ( null == s2 ) ) {
129            equal = true;
130        }
131        else if ( ( null == s1 ) && ( null != s2 ) ) {
132            equal = false;
133        }
134        else if ( ( null != s1 ) && ( null == s2 ) ) {
135            equal = false;
136        }
137        else {
138            equal = s1.equals( s2 );
139        }
140        return equal;
141    }
142    
143    /**
144     * convenience method to compare two strs (case-insensitive)w/ null args ok.
145     * @param s1 first string to compare
146     * @param s2 first string to compare
147     * @return whether strings equal: both null is considered equal
148     */
149    private boolean strEqualsIgnoreCaseNullOK( String s1, String s2 ) {
150        boolean equal;
151        if ( ( null == s1 ) && ( null == s2 ) ) {
152            equal = true;
153        }
154        else if ( ( null == s1 ) && ( null != s2 ) ) {
155            equal = false;
156        }
157        else if ( ( null != s1 ) && ( null == s2 ) ) {
158            equal = false;
159        }
160        else {
161            equal = CaseInsensitiveString.equalsIgnoreCase( s1, s2 );
162        }
163        return equal;
164    }
165    
166    /**
167     * convenience method to compare two dates with null arguments ok.
168     * @param d1 first date to compare
169     * @param d2 first date to compare
170     * @return whether dates equal: both null is considered equal
171     */
172    private boolean dateEqualNullOK( Date d1, Date d2 ) {
173        boolean equal;
174        if ( ( null == d1 ) && ( null == d2 ) ) {
175            equal = true;
176        }
177        else if ( ( null == d1 ) && ( null != d2 ) ) {
178            equal = false;
179        }
180        else if ( ( null != d1 ) && ( null == d2 ) ) {
181            equal = false;
182        }
183        else {
184            equal = d1.equals( d2 );
185        }
186        return equal;
187    }
188    
189    /**
190     * for some reason jade.core.AID.getHap() is not public.
191     * if HAP was never set, then AID version returns the name, which
192     * is a mess, so instead we return the empty string for that case
193     * and handle it permissively.
194     * @param aid AID to get HAP for
195     * @return HAP for given AID
196     */
197    private String getHap( AID aid ) {
198        String name = aid.getName();
199        int atPos = name.lastIndexOf('@');
200        if ( atPos == -1 ) {
201            return "";
202        }
203        else {
204            return name.substring(atPos + 1);
205        }
206    }
207    
208    /**
209     * Convert a set represented by a LEAP Iterator to an ArrayList
210     * @param iter iterator to convert
211     * @return ArrayList representing objects iterated over
212     */
213    private ArrayList iterToArrayList( Iterator iter ) {
214        ArrayList al = new ArrayList();
215        while ( iter.hasNext() ) {
216            try {
217                al.add( iter.next() );
218            }
219            catch ( NoSuchElementException nsee ) {
220                throw new RuntimeException( "exception AID set iterator:"+
221                        nsee.getMessage(), nsee );
222            }
223        }
224        return al;
225    }
226    
227    // "&#64;" is HTML for "@", stay safe from javadoc...
228    /**
229     * Compare a slot represented by an Iterator over AID objects.
230     * <p>
231     * <b>N.B.</b> For a series of objects of form <code>x&#64;y</code>, it is 
232     * not always possible to precisely prove or disprove isomorphism.
233     * <i>Proof by counterexample:</i>
234     * </p>
235     * <table>
236     * <code>
237     * <tr><td>a&#64;x</td><td>a&#64;i</td></tr>
238     * <tr><td>b&#64;x</td><td>b&#64;i</td></tr>
239     * <tr><td>a&#64;y</td><td>a&#64;j</td></tr>
240     * <tr><td>b&#64;y</td><td>b&#64;j</td></tr>
241     * </code>
242     * </table>
243     * <p>
244     * So, instead, a series of heuristics is used that should do well
245     * in common usage.  The return value is optimistic, i.e. a false-negative
246     * (i.e. erroneous inequality) will never be returned, but a false-positive
247     * (i.e. erroneous equality) might.
248     * </p>
249     * <p>
250     * Method visibility is package instead of private solely for unit
251     * testing purposes.
252     * </p>
253     * @param iter1 first iteration to compare
254     * @param iter2 second iteration to compare
255     * @param slotName FIPA slot name
256     * @return null if equal, descriptive text if not
257     */
258    /*private*/String compareAidSet( Iterator iter1, 
259                                                   Iterator iter2, 
260                                                   String   slotName ) {
261        ArrayList al1 = iterToArrayList( iter1 );
262        ArrayList al2 = iterToArrayList( iter2 );
263        int size1 = al1.size();
264        int size2 = al2.size();
265        // compare cardinality of sets
266        if ( size1 != size2 ) {
267            return
268            "size of first " + slotName + " set (" + size1 + ")" +
269            " is not equal to " +
270            "size of second " + slotName + " set (" + size2 + ")";
271        }
272        // same size, now sort lists.  after that, if the sets are isomorphic,
273        // then at each index the local names should match (this is the same
274        // as making sure that the cardinality of each local name is the same
275        // in each set).  further more, for singleton local names, then we can
276        // use the usual HAP mapping.
277        Collections.sort( al1 );
278        Collections.sort( al2 );
279        try {
280            for ( int i = 0; i < size1; i++ ) {
281                AID aid1 = (AID)al1.get( i );
282                AID aid2 = (AID)al2.get( i );
283                String local1 = aid1.getLocalName();
284                String local2 = aid2.getLocalName();
285                if ( !CaseInsensitiveString.equalsIgnoreCase( local1, local2 )){
286                    return 
287                    "after sorting " + slotName + " sets, at index (0-based) " +
288                    i + ", first local name of \"" + local1 + 
289                    "\" is different from second local name of \"" + local2 +
290                    "\"";
291                }
292                if ( ( ( i == 0 ) ||
293                       ( !local1.equalsIgnoreCase( 
294                               (((AID)al1.get(i-1))).getLocalName()) ) 
295                     )
296                     &&
297                     ( ( i == ( size1-1 ) ) ||
298                       ( !local1.equalsIgnoreCase( 
299                               (((AID)al1.get(i+1))).getLocalName()) ) 
300                     )
301                   ) {
302                    // this is a singleton case of local name, HAPs must match.
303                    // BUT, if HAP is missing, then be permissive about it and
304                    // assume that there was a sniffing impedance mismatch
305                    // between using the sniffer agent and our sniffing.
306                    String hap1   = getHap( aid1 );
307                    String hap2   = getHap( aid2 );
308                    if ( !hap1.equals("") && !hap2.equals("") ) {
309                        String hap1Canonical = hap1.toLowerCase();
310                        String hap2Canonical = hap2.toLowerCase();
311                        try {
312                            if ( hapMap.containsKey( hap1Canonical ) ) {
313                                if ( !hap2Canonical.equals( 
314                                        hapMap.get( hap1Canonical ) ) ) {
315                                    return
316                                    "for " + slotName + "local name \"" + 
317                                    local1 + "\", " +
318                                    "first message HAP \"" + 
319                                    hap1 +
320                                    "\" does not match second message HAP \"" +
321                                    hap2 +
322                                    "\"";
323                                }
324                                // else HAPs matched
325                            }
326                            else {
327                                // new HAPs, put in hapMap
328                                hapMap.put( hap1Canonical, hap2Canonical );
329                            }
330                        }
331                        catch ( Exception e ) {
332                            throw new RuntimeException( 
333                                    "exception referencing HAP map:"+
334                                    e.getMessage(), e );
335                        }
336                    }
337                }
338            }
339        }
340        catch ( IndexOutOfBoundsException ioobe ) {
341            throw new RuntimeException( 
342                    "exception accessing AID set:"+ioobe.getMessage(), ioobe );
343        }
344        // !!! FIXME TBD:
345        // could run through the lists again, looking for non-singleton
346        // local names, using the HAP mapping gathered so far...
347        return null;
348    }
349    
350    
351    /**
352     * Implementation method for comparison of slots that are case-insensitive
353     * and can vary, but must be consistent across sessions.
354     * @param slotName name of slot being compared
355     * @param slotMap map used for consistency checking
356     * @param value1 first slot value to compare
357     * @param value2 second slot value to compare
358     * @return null if equal, descriptive text if not
359     */
360    private String cmpCaseInsConsStrSlot( String slotName,
361            Map    slotMap,
362            String value1,
363            String value2 ) {
364        // handle null values
365        // if id1 is in map, see that id2 is its value, else error
366        // else if id1 is not in map, enter it with id2 as its value. no error
367        if ( ( null == value1 ) && ( null == value2 ) ) {
368            // OK: counts as equal - intentional NOP
369        }
370        else if ( ( null == value1 ) && ( null != value2 ) ) {
371            return 
372            "first message " + slotName + " is null and does not match second" +
373            " message " + slotName + "\"" + value2 + "\"";
374        }
375        else if ( ( null != value1 ) && ( null == value2 ) ) {
376            return 
377            "second message " + slotName + " is null and does not match first" +
378            " message sender\"" + value1 + "\"";
379        }
380        else {
381            String convId1Canonical = value1.toLowerCase();
382            String convId2Canonical = value2.toLowerCase();
383            try {
384                if ( slotMap.containsKey( convId1Canonical ) ) {
385                    if(!convId2Canonical.equals(slotMap.get(convId1Canonical))){
386                        return
387                        "first message " + slotName + " \"" + 
388                        value1 +
389                        "\" does not match second  message " + 
390                        slotName + " \"" +
391                        value2 +
392                        "\"";
393                    }
394                    // else IDs matched
395                }
396                else {
397                    // new IDs, put in slotMap
398                    slotMap.put( convId1Canonical, convId2Canonical );
399                }
400            } 
401            catch ( Exception e ) {
402                throw new RuntimeException( "exception referencing " + 
403                                            slotName + " map:"+e.getMessage(), 
404                                            e );
405            }
406        }
407        // match
408        return null;
409        
410    }
411    
412    //
413    // SLOT COMPARISON FLAGS, METHODS, AND FLAG GET/SET
414    //
415    
416    // SENDER
417    
418    /** whether to compare sender slots */
419    private boolean cmpSender = true;
420    
421    /**
422     * set sender comparison flag (default is true)
423     * @param doCompare
424     * @see #compare( ACLMessage, ACLMessage )
425     */
426    public void setCmpSender( boolean doCompare ) {
427        cmpSender = doCompare;
428    }
429    
430    /**
431     * get sender comparison flag (default is true)
432     * @return sender comparison flag
433     * @see #compare( ACLMessage, ACLMessage )
434     */
435    public boolean isCmpSender() {
436        return cmpSender;
437    }
438    
439    
440    /**
441     * Compare just the sender slot of two messages.
442     * @param msg1 first message to compare
443     * @param msg2 second message to compare
444     * @return null if equal, descriptive text if not
445     * @see #compare( ACLMessage, ACLMessage )
446     */
447    public String compareSender( ACLMessage msg1, ACLMessage msg2 ) {
448        // get 1st and 2nd AIDs, splitting into local and hap, handling null
449        // if local names don't case-insensitive match, return error
450        // else if hap1 is in map, see that hap2 is its value, else error
451        // else if hap1 is not in map, enter it with hap2 as its value. no error
452        AID sender1 = msg1.getSender();
453        AID sender2 = msg2.getSender();
454        if ( ( null == sender1 ) && ( null == sender2 ) ) {
455            // OK: counts as equal - intentional NOP
456        }
457        else if ( ( null == sender1 ) && ( null != sender2 ) ) {
458            return 
459            "first message sender is null and does not match second" +
460            " message sender\"" + sender2 + "\"";
461        }
462        else if ( ( null != sender1 ) && ( null == sender2 ) ) {
463            return 
464            "second message sender is null and does not match first" +
465            " message sender\"" + sender1 + "\"";
466        }
467        else {
468            String local1 = sender1.getLocalName();
469            String local2 = sender2.getLocalName();
470            // If HAP is missing, then be permissive about it and
471            // assume that there was a sniffing impedance mismatch
472            // between using the sniffer agent and our sniffing.
473            String hap1   = getHap( sender1 );
474            String hap2   = getHap( sender2 );
475            if ( !hap1.equals("") && !hap2.equals("") ) {
476                String hap1Canonical = hap1.toLowerCase();
477                String hap2Canonical = hap2.toLowerCase();
478                try {
479                    if (!CaseInsensitiveString.equalsIgnoreCase(local1,local2)){
480                        return 
481                        "first message sender local name \"" + 
482                        local1 +
483                        "\" does not match second message sender local name \"" 
484                        + local2 +
485                        "\"";
486                    }
487                    else if ( hapMap.containsKey( hap1Canonical ) ) {
488                        if ( !hap2Canonical.equals( hapMap.get( 
489                                                          hap1Canonical ) ) ) {
490                            return
491                            "first message sender HAP \"" + 
492                            hap1 +
493                            "\" does not match second message sender HAP \"" +
494                            hap2 +
495                            "\"";
496                        }
497                        // else HAPs matched
498                    }
499                    else {
500                        // new HAPs, put in hapMap
501                        hapMap.put( hap1Canonical, hap2Canonical );
502                    }
503                } 
504                catch ( Exception e ) {
505                    throw new RuntimeException( "exception referencing HAP map:"
506                                                +e.getMessage(), e );
507                }
508            }
509        }
510        // match
511        return null;
512    }
513    
514    
515    // RECEIVER
516    
517    /** whether to compare receiver slots */
518    private boolean cmpReceiver = true;
519    
520    /**
521     * set receiver comparison flag (default is true)
522     * @param doCompare
523     * @see #compare( ACLMessage, ACLMessage )
524     */
525    public void setCmpReceiver( boolean doCompare ) {
526        cmpReceiver = doCompare;
527    }
528    
529    /**
530     * get receiver comparison flag (default is true)
531     * @return receiver comparison flag
532     * @see #compare( ACLMessage, ACLMessage )
533     */
534    public boolean isCmpReceiver() {
535        return cmpReceiver;
536    }
537    
538    
539    /**
540     * Compare just the receiver slot of two messages.
541     * @param msg1 first message to compare
542     * @param msg2 second message to compare
543     * @return null if equal, descriptive text if not
544     * @see #compare( ACLMessage, ACLMessage )
545     */
546    public String compareReceiver( ACLMessage msg1, ACLMessage msg2 ) {
547        return compareAidSet( msg1.getAllReceiver(),
548                              msg2.getAllReceiver(),
549                              "receiver" );
550    }
551    
552    // REPLY-TO
553    
554    /** whether to compare reply-to slots */
555    private boolean cmpReplyTo = true;
556    
557    /**
558     * set reply-to comparison flag (default is true)
559     * @param doCompare
560     * @see #compare( ACLMessage, ACLMessage )
561     */
562    public void setCmpReplyTo( boolean doCompare ) {
563        cmpReplyTo = doCompare;
564    }
565    
566    /**
567     * get reply-to comparison flag (default is true)
568     * @return reply-to comparison flag
569     * @see #compare( ACLMessage, ACLMessage )
570     */
571    public boolean isCmpReplyTo() {
572        return cmpReplyTo;
573    }
574    
575    
576    /**
577     * Compare just the reply-to slot of two messages.
578     * @param msg1 first message to compare
579     * @param msg2 second message to compare
580     * @return null if equal, descriptive text if not
581     * @see #compare( ACLMessage, ACLMessage )
582     */
583    public String compareReplyTo( ACLMessage msg1, ACLMessage msg2 ) {
584        return compareAidSet( msg1.getAllReplyTo(),
585                              msg2.getAllReplyTo(),
586                              "reply-to" );
587    }
588    
589    // PERFORMATIVE
590    
591    /** whether to compare performative slots */
592    private boolean cmpPerformative = true;
593    
594    /**
595     * set performative comparison flag (default is true)
596     * @param doCompare
597     * @see #compare( ACLMessage, ACLMessage )
598     */
599    public void setCmpPerformative( boolean doCompare ) {
600        cmpPerformative = doCompare;
601    }
602    
603    /**
604     * get performative comparison flag (default is true)
605     * @return performative comparison flag
606     * @see #compare( ACLMessage, ACLMessage )
607     */
608    public boolean isCmpPerformative() {
609        return cmpPerformative;
610    }
611    
612    
613    /**
614     * Compare just the performative slot of two messages.
615     * @param msg1 first message to compare
616     * @param msg2 second message to compare
617     * @return null if equal, descriptive text if not
618     * @see #compare( ACLMessage, ACLMessage )
619     */
620    public String comparePerformative( ACLMessage msg1, ACLMessage msg2 ) {
621        int performative1 = msg1.getPerformative();
622        int performative2 = msg2.getPerformative();
623        if ( performative1 != performative2 ) {
624            return
625            "first message performative " +
626            ACLMessage.getPerformative( performative1 ) +
627            " not equal to " +
628            "second message performative " +
629            ACLMessage.getPerformative( performative2 );
630        }
631        else {
632            return null;
633        }
634    }
635    
636    // CONTENT
637    
638    /** whether to compare content slots */
639    private boolean cmpContent = true;
640    
641    /** 
642     * whether to ignore :addresses inside agent-identifier in content.
643     * N.B.: no effect if content is byte sequence instead of string.
644     */
645    private boolean ignoreContentAIDAddresses = true;
646    
647    /**
648     * set content comparison flag (default is true)
649     * @param doCompare
650     * @see #compare( ACLMessage, ACLMessage )
651     */
652    public void setCmpContent( boolean doCompare ) {
653        cmpContent = doCompare;
654    }
655    
656    /**
657     * get content comparison flag (default is true)
658     * @return content comparison flag
659     * @see #compare( ACLMessage, ACLMessage )
660     */
661    public boolean isCmpContent() {
662        return cmpContent;
663    }
664    
665    
666    /**
667     * given an ACLMessage content string, globalize embedded AIDs.
668     * is protected instead of private only for testing purposes.
669     * @param originalContent content string that want to filter
670     * @param hap home agent platform <em>NOT</em> incl'g <code>"&#64;"</code>
671     * @return filtered version of input
672     */
673    public String globalizeContentAgentIds( String originalContent,
674                                            String hap ) {
675        StringBuffer filteredContent = new StringBuffer();
676        char originalChars[] = originalContent.toCharArray();
677        int origLen = originalChars.length;
678        //
679        // AID in content looks like:
680        // (agent-identifier :name df@picturebook:1098/JADE 
681        //                   :addresses (sequence http://picturebook:7778/acc))
682        // not including resolvers and addresses may not be there
683        //
684        // algorithm:
685        //
686        // at each character in input
687        // if character is not start of agent identifier
688        //   append it to result
689        // else
690        //   append agent id start to result
691        //   move forward to character following agent start
692        //   at each char following agent start
693        //     if character is '@'
694        //       found HAP: append it
695        //       break out back to main loop
696        //     elsif character terminates local name ( ')' or whitespace )
697        //       append HAP
698        //       append terminating character
699        //       break out back to main loop
700        //     else
701        //       append this character in local name
702        for ( int i = 0; i < origLen; ) {
703            if ( !hasAtPos( originalChars, i, EMBEDDED_AID_START_ARR ) ) {
704                filteredContent.append( originalChars[i] );
705                i++;
706            }
707            else {
708                filteredContent.append( EMBEDDED_AID_START_STR );
709                i += EMBEDDED_AID_START_ARR.length;
710                while ( i < origLen ) {
711                    char c = originalChars[i];
712                    i++;
713                    if ( '@' == c ) {
714                        filteredContent.append( c );
715                        break;
716                    }
717                    else if ( ( ')'  == c ) || 
718                              ( ' '  == c ) || 
719                              ( '\t' == c ) || 
720                              ( '\n' == c ) || 
721                              ( '\r' == c ) ) {
722                        filteredContent.append( '@' );
723                        filteredContent.append( hap );
724                        filteredContent.append( c );
725                        break;
726                    }
727                    else {
728                        filteredContent.append( c );
729                    }
730                }
731            }
732        }
733        // System.err.println(
734        //    "***AclMsgCmp.globalizeContentAgentIds(): converted\n"+
735        // originalContent+
736        // " to\n"+
737        // filteredContent.toString());
738        return filteredContent.toString();
739    }
740    
741    /**
742     * set content AID address ignoring flag (default is true)
743     * @param ignore
744     * @see #compare( ACLMessage, ACLMessage )
745     */
746    public void setIgnoreContentAIDAddresses( boolean ignore ) {
747        ignoreContentAIDAddresses = ignore;
748    }
749    
750    /**
751     * get content AID address ignoring flag (default is true)
752     * @return content AID address ignoring
753     * @see #compare( ACLMessage, ACLMessage )
754     */
755    public boolean isIgnoreContentAIDAddresses() {
756        return ignoreContentAIDAddresses;
757    }
758    
759    
760    /**
761     * Represent a part of a content string that can change, maybe consistently.
762     */
763    protected class Variable {
764        /** name of variable is string in message known to change */
765        private String name;
766        /** type of variable is string representing group of vars, e.g. AID */
767        private String type;
768        /** map to use for this variable */
769        private Map    map = null;
770        /**
771         * construct with name and type.
772         * @param name string in message known to change
773         * @param type group of variables with consistent mapping
774         */
775        public Variable( String name, String type ) {
776            this.name = name;
777            this.type = type;
778            if ( type.equals( AID_MAP ) ) {
779                map = hapMap;
780            }
781            else if ( type.equals( REPLY_ID_MAP ) ) {
782                map = replyIdMap;
783            }
784            else if ( type.equals( CONVERSATION_ID_MAP ) ) {
785                map = conversationIdMap;
786            }
787            else if ( type.equals( CONSISTENT_MAP ) ) {
788                map = consistentMap;
789            }
790            else if ( type.equals( RANDOM_MAP ) ) {
791                // NOP, no mapping done
792            }
793            else {
794                throw new IllegalArgumentException( 
795                                       "unknown type argument \""+type+"\"");
796            }
797        }
798        /**
799         * return mapped value for this variable, if any
800         * @return mapped value for this variable, if any
801         */
802        public String mappedValue() {
803            String s = null;
804            if ( null != map ) {
805                try {
806                    s = (String)map.get( name );
807                }
808                catch ( NullPointerException npe ) {
809                    throw new RuntimeException( "map null pointer exception:"+
810                            npe.getMessage(), npe );
811                }
812                catch ( ClassCastException cce ) {
813                    throw new RuntimeException( "map class cast exception:"+
814                            cce.getMessage(), cce );
815                }
816            }
817            return s;
818        }
819        /**
820         * get the name for this variable
821         * @return the name for this variable
822         */
823        public String getName() {
824            return name;
825        }
826        /**
827         * get the type for this variable
828         * @return the type for this variable
829         */
830        public String getType() {
831            return type;
832        }
833        /**
834         * get the map for this variable
835         * @return the map for this variable
836         */
837        public Map getMap() {
838            return map;
839        }
840    }
841    
842    /**
843     * @param s string to convert
844     * @param props mapping from variable string names to variable groups
845     * @return List of String and Variable objects representing input string
846     */
847    protected List strToVarStrList( String s, java.util.Properties props ) {
848        
849        // algorithm:
850        //
851        // start with an empty list
852        // at each character in input string
853        //   if remaining string begins a property key
854        //     create a new Variable and append to list, moving str index fwd
855        //   else (* in a constant string *)
856        //     if no string being built currently
857        //       create it at end of list
858        //     add character to string
859        
860        List l = new LinkedList(); // return this
861        
862        if ( null == s ) {
863            return l;
864        }
865        
866        int sLen = s.length();
867        
868        StringBuffer sb = null; // hold a string being constructed
869        
870        // set up variable names in arrays so a bit more efficient and readable
871        int varCount = ( ( null == props ) ? 0 : props.size() );
872        Object vars[] = ( ( 0 == varCount )
873                ? new Object[0]
874                             : props.entrySet().toArray() );
875        String varNames[] = new String[varCount];
876        String varTypes[] = new String[varCount];
877        int varNameLen[] = new int[varCount];
878        for ( int i = 0; i < varCount; i++ ) {
879            varTypes[i] = (String)((Map.Entry)vars[i]).getValue();
880            varNames[i] = (String)((Map.Entry)vars[i]).getKey();
881            varNameLen[i] = varNames[i].length();
882        }
883        
884        // loop through each character in input string
885        for ( int i = 0; i < sLen; i++ ) {
886            boolean atNewVar = false;
887            // check each var name to see if it's occurred in input string
888            for ( int j = 0; j < varCount; j++ ) {
889                if ( s.regionMatches( i, varNames[j], 0, varNameLen[j] ) ) {
890                    // if were constructing a new string, append it to list
891                    if ( null != sb ) {
892                        l.add( sb.toString() );
893                        sb = null;
894                    }
895                    // create a new variable and append to list
896                    atNewVar = true;
897                    l.add( new Variable( varNames[j], varTypes[j] ) );
898                    // move index forward, accounting for loop index
899                    i += varNameLen[j]-1;
900                    break;
901                }
902            }
903            // if this character doesn't start a new variable
904            if ( !atNewVar ) {
905                // construct new place to build string if at its first front
906                if ( null == sb ) {
907                    sb = new StringBuffer();
908                }
909                // append this character
910                sb.append( s.charAt( i ) );
911            }
912        }
913        
914        // if were constructing a new string, append it to list
915        if ( null != sb ) {
916            l.add( sb.toString() );
917        }
918        
919        // return the constructed list
920        //         try {
921        //             for ( int i = 0; i < l.size(); i++ ) {
922        //                 Object o = l.get(i);
923        //                 if ( o instanceof Variable ) {
924        //                     Variable v = (Variable)o;
925        //                     o = "Variable("+v.getName()+","+v.getType()+")";
926        //                 }
927        //          System.err.println("AclMsgCmp.strToVarStrList(): item "+i+"="+o);
928        //             }
929        //         }
930        //         catch ( Exception e ) {
931        //         throw new RuntimeException( "exception referencing variable/string"+
932        //                                         " list item:" + e.getMessage(), e );
933        //         }
934        return l;
935    }
936    
937    /** whether to normalize newlines */
938    private boolean newlinesNormalized = true;
939    
940    /**
941     * set newline normalization flag (default is true)
942     * @param normalize
943     * @see #normalizeNewlines
944     */
945    public void setNewlinesNormalized( boolean normalize ) {
946        newlinesNormalized = normalize;
947    }
948    
949    /**
950     * get newline normalization flag (default is true)
951     * @return newline normalization flag
952     * @see #normalizeNewlines
953     */
954    public boolean isNewlinesNormalized() {
955        return newlinesNormalized;
956    }
957    
958    /**
959     * normalize CRLF to LF.
960     * in general, i'm in favor of using regular expressions;
961     * in this case it was easier to just write it.
962     * @param s string to convert
963     * @return input string with CRLF sequences converted to LF
964     */
965    private String normalizeNewlines( String s ) {
966        StringBuffer sb = new StringBuffer();
967        int sLen = s.length();
968        for ( int i = 0; i < sLen; i++ ) {
969            char c = s.charAt( i );
970            if ( ( '\r' == c ) && (sLen > (i+1)) && ( '\n' == s.charAt(i+1) ) ){
971                sb.append( '\n' );
972                i++;
973            }
974            else {
975                sb.append( c );
976            }
977        }
978        return sb.toString();
979    }
980    
981    
982    /** 
983     * content in embedded content AID starts with this: as string.
984     * I would have preferred to use string "( agent-identifier " as the marker
985     * for the beginning of an AID, but when embedded in content strings, there
986     * is sometimes a space between "(" and "agent-identifier" and sometimes 
987     * not.  so instead I'm keying off the AID token and the :name slot token, 
988     * which appears to always exist and be the first slot emitted.
989     */
990    private final static String EMBEDDED_AID_START_STR = 
991        "agent-identifier :name ";
992    
993    /** content in embedded content AID starts with this: as character array */
994    private final static char EMBEDDED_AID_START_ARR[] = 
995        EMBEDDED_AID_START_STR.toCharArray();
996    
997    /** content in embedded content AID address starts with this */
998    private final static char EMBEDDED_ADDR_START[] = 
999        " :addresses (sequence ".toCharArray();
1000    
1001    /** right parenthesis */
1002    private final static char RPAREN = ')';
1003    
1004    /**
1005     * see whether given substring is at given position in given string to test.
1006     * is protected instead of private only for testing purposes.
1007     * @param s string to see whether has embedded substring
1008     * @param pos position in s to check for embedded substring
1009     * @param sub substring to look for
1010     * @return whether substring is embedded in string at position
1011     */
1012    protected boolean hasAtPos( char s[], int pos, char sub[] ) {
1013        boolean answer = true;
1014        int subLen = sub.length;
1015        if ( pos + subLen > s.length ) {
1016            answer = false;
1017        }
1018        else {
1019            int sI = pos;
1020            int subI = 0;
1021            for ( ; subI < subLen; sI++, subI++ ) {
1022                if ( s[sI] != sub[subI] ) {
1023                    answer = false;
1024                    break;
1025                }
1026            }
1027        }
1028        return answer;
1029    }
1030    
1031    /**
1032     * given an ACLMessage content string, remove embedded AID addresses.
1033     * is protected instead of private only for testing purposes.
1034     * @param originalContent content string that want to filter
1035     * @return filtered version of input
1036     */
1037    protected String filterEmbeddedAddresses( String originalContent ) {
1038        StringBuffer filteredContent = new StringBuffer();
1039        char originalChars[] = originalContent.toCharArray();
1040        int origLen = originalChars.length;
1041        //
1042        // AID in content looks like:
1043        // (agent-identifier :name df@picturebook:1098/JADE 
1044        //                   :addresses (sequence http://picturebook:7778/acc))
1045        // not including resolvers
1046        //
1047        // algorithm:
1048        //
1049        // at each character in input
1050        // if character is not start of agent identifier
1051        //   append it to result
1052        // else
1053        //   append agent id start to result
1054        //   move forward to character following agent start
1055        //   at each char while haven't hit end of agent id (right paren)
1056        //     if character isn't beginning of agent id address slot
1057        //       append it to result
1058        //     else
1059        //       move forward to char after address slot start
1060        //       for each char up to and including end of address slot (')')
1061        //         step over it
1062        for ( int i = 0; i < origLen; ) {
1063            if ( !hasAtPos( originalChars, i, EMBEDDED_AID_START_ARR ) ) {
1064                filteredContent.append( originalChars[i] );
1065                i++;
1066            }
1067            else {
1068                filteredContent.append( EMBEDDED_AID_START_STR );
1069                i += EMBEDDED_AID_START_ARR.length;
1070                while ( ( i < origLen ) && ( originalChars[i] != RPAREN ) ) {
1071                    if ( !hasAtPos( originalChars, i, EMBEDDED_ADDR_START ) ) {
1072                        filteredContent.append( originalChars[i] );
1073                        i++;
1074                    }
1075                    else {
1076                        // in addresses slot
1077                        i += EMBEDDED_ADDR_START.length;
1078                        while ( ( i < origLen ) && 
1079                                ( originalChars[i] != RPAREN ) ) {
1080                            i++;
1081                        }
1082                        i++; // go past ')'
1083                    }
1084                }
1085            }
1086        }
1087        // System.err.println("***AclMsgCmp.filterEmbeddedAddresses(): cvt'd\n"+
1088        // originalContent+
1089        // " to\n"+
1090        // filteredContent.toString());
1091        return filteredContent.toString();
1092    }
1093    
1094    /**
1095     * return friendly error when couldn't find expected string in content.
1096     * @param content2 new content string verifying against golden
1097     * @param secondStrPos index into current location within content2
1098     * @param s golden substring exp'd found at content2.substring(secondStrPos)
1099     * @return message describing error
1100     */
1101    private String strMismatchMsg( String content2, 
1102            int secondStrPos,
1103            String s ) {
1104        int MAX_ERR_SUBSTR_LEN = 20;
1105        // try to give a message about precisely which character doesn't match
1106        int sLen = s.length();
1107        int mismatchOffset;
1108        char expectedChar = ' '; // keep compiler from complaining about uninit
1109        char foundChar    = ' '; // keep compiler from complaining about uninit
1110        StringBuffer expected = new StringBuffer();
1111        StringBuffer found    = new StringBuffer();
1112        for ( mismatchOffset = 0; mismatchOffset < sLen; mismatchOffset++ ) {
1113            expectedChar = s.charAt( mismatchOffset );
1114            foundChar    = content2.charAt( secondStrPos + mismatchOffset );
1115            if ( foundChar != expectedChar ) {
1116                int sIndex = mismatchOffset;
1117                int c2Index = mismatchOffset + secondStrPos;
1118                int c2Len = content2.length();
1119                for ( int i = 0; i < MAX_ERR_SUBSTR_LEN; i++ ) {
1120                    if ( sIndex >= sLen ) {
1121                        break;
1122                    }
1123                    if ( c2Index >= c2Len ) {
1124                        break;
1125                    }
1126                    expected.append( s.charAt( sIndex++ ) );
1127                    found.append( content2.charAt( c2Index++ ) );
1128                }
1129                if ( ( sIndex+1 ) < sLen ) {
1130                    expected.append( "..." );
1131                }
1132                if ( ( c2Index+1 ) < c2Len ) {
1133                    found.append( "..." );
1134                }
1135                break;
1136            }
1137        }
1138        if ( mismatchOffset >= sLen ) {
1139            throw new RuntimeException(
1140                    "couldn't find mismatching character in string mismatch");
1141        }
1142        //         return "at position (0-based) " + mismatchOffset +
1143        //             " of second content string expected '" +
1144        //             expectedChar + "' but found '" + foundChar + "'";
1145        return "at position (0-based) " + mismatchOffset +
1146        " of second content string expected \"" +
1147        expected + "\" but found \"" + found + "\"";
1148    }
1149    
1150    /**
1151     * Compare just the content slot of two messages.
1152     * @param msg1 first message to compare
1153     * @param msg2 second message to compare
1154     * @param vars mapping from variable string names to variable groups
1155     * @return null if equal, descriptive text if not
1156     * @see #compare( ACLMessage, ACLMessage )
1157     */
1158    public String compareContent( ACLMessage msg1, 
1159                                  ACLMessage msg2, 
1160                                  java.util.Properties vars ) {
1161        
1162        // 1st, an easy check for identical bytes if content is a byte sequence
1163        boolean isByteSequence1 = msg1.hasByteSequenceContent();
1164        boolean isByteSequence2 = msg2.hasByteSequenceContent();
1165        if ( isByteSequence1 != isByteSequence2 ) {
1166            return "first message does " + 
1167            ( isByteSequence1 ? "" : "not " ) +
1168            "have byte sequence content but second message does" +
1169            ( isByteSequence2 ? "" : " not" );
1170        }
1171        if ( isByteSequence1 ) {
1172            String diffMsg = ( ( Arrays.equals( msg1.getByteSequenceContent(), 
1173                    msg2.getByteSequenceContent() ) )
1174                    ? null
1175                            : "different byte sequence content" );
1176            //!!! make this nicer with message about where content differs
1177            return diffMsg;
1178        }
1179        
1180        // OK, content is a string, so:
1181        //
1182        // algorithm:
1183        // check for easy cases of one or both content being null
1184        // if ignoring embedded agent identifier addresses
1185        //   remove them from content strings
1186        // convert first message into list of variables and constant strings
1187        // walking that list
1188        //   if at constant string
1189        //     do a string comparison against next characters in 2nd msg content
1190        //   else (* at a variable *)
1191        //     if variable is already mapped
1192        //       if next characters in 2nd msg str don't match
1193        //         return difference
1194        //       else
1195        //         advance 2nd msg str past the match
1196        //     else (* at a variable not yet mapped *)
1197        //       if at last item of variable/string list
1198        //         map variable to remaining characters of 2nd string
1199        //       elsif following item is string or mapped variable
1200        //         compare substr before next known chars, ret'g diff if found
1201        //       else (* adjacent unmapped variables *)
1202        //         assemble sublist of unmapped variables
1203        //         (* this could miss some mapping changes *)
1204        //         move 2nd string to next known (i.e. str or mapped var)
1205        
1206        String content1 = msg1.getContent();
1207        String content2 = msg2.getContent();
1208        
1209        if ( ( null == content1 ) && ( null == content2 ) ) {
1210            return null;
1211        }
1212        else if ( ( null == content1 ) && ( null != content2 ) ) {
1213            return "second message has content but first does not";
1214        }
1215        else if ( ( null != content1 ) && ( null == content2 ) ) {
1216            return "first message has content but second does not";
1217        }
1218        
1219        if ( ignoreContentAIDAddresses ) {
1220            content1 = filterEmbeddedAddresses( content1 );
1221            content2 = filterEmbeddedAddresses( content2 );
1222        }
1223        if ( newlinesNormalized ) {
1224            content1 = normalizeNewlines( content1 );
1225            content2 = normalizeNewlines( content2 );
1226        }
1227        
1228        List varStrList = strToVarStrList( content1, vars );
1229        
1230        int secondStrPos = 0;
1231        ListIterator li = varStrList.listIterator(); 
1232        while ( li.hasNext() ) {
1233            try {
1234                Object listItem = li.next();
1235                if ( listItem instanceof String ) {
1236                    String s = (String)listItem;
1237                    int sLen = s.length();
1238                    if ( !content2.regionMatches( secondStrPos, s, 0, sLen ) ) {
1239                        return strMismatchMsg( content2, secondStrPos, s );
1240                    }
1241                    secondStrPos += sLen;
1242                }
1243                else {
1244                    Variable v = (Variable)listItem;
1245                    String mappedValue = v.mappedValue();
1246                    if ( null != mappedValue ) {
1247                        // variable is already mapped
1248                        int mValLen = mappedValue.length();
1249                        if ( !content2.regionMatches( secondStrPos, mappedValue, 
1250                                0, mValLen ) ) {
1251                            // msg2 content doesn't match this variable
1252                            return "at character " + secondStrPos + 
1253                            " of second content string, found \"" +
1254                            content2.substring( secondStrPos, 
1255                                                secondStrPos+mValLen ) +
1256                            "\" instead of expected value \"" + mappedValue +
1257                            "\" to match first content string value \"" +
1258                            v.getName() + "\"";
1259                        }
1260                        else {
1261                            // second content string matched, advance index
1262                            secondStrPos += mValLen;
1263                        }
1264                    }
1265                    else {
1266                        // variable not yet mapped or not mappable
1267                        Map map = v.getMap();
1268                        if ( !li.hasNext() ) {
1269                            // at last item of variable/string list, so map
1270                            // variable to remaining substring 
1271                            try {
1272                // System.err.println("AclMsgCmp.compareContent(): mapping \""+ 
1273                                //  v.getName() +"\" to \""+
1274                                //  content2.substring(secondStrPos)+"\"...");
1275                                map.put( v.getName(), 
1276                                         content2.substring( secondStrPos ) );
1277                            }
1278                            catch ( Exception e ) {
1279                                throw new RuntimeException( 
1280                                        "exception referencing "+
1281                                        v.getType() + " map:" +
1282                                        e.getMessage(), e );
1283                            }
1284                        }
1285                        else {
1286                            listItem = li.next();
1287                            String s = ( ( listItem instanceof String )
1288                                    ? ( (String)listItem )
1289                                    : ((Variable)listItem).mappedValue() );
1290                            if ( null != s ) {
1291                                // now, s is the string expected in content2 
1292                                // after this variable so look for it, if find 
1293                                // it then substring before is mapped to 
1294                                // variable, else return a difference
1295                                // message
1296                                
1297                                int sLen = s.length();
1298                                int tooFar = content2.length() - sLen + 1;
1299                                int mapStart = secondStrPos;
1300                                while ( true ) {
1301                                    if ( secondStrPos >= tooFar ) {
1302                                        return 
1303                                        "mismatched content discovered at " +
1304                                        "point where expecting \"" +
1305                                        ( ( sLen > 20 )
1306                                           ? ( s.substring( 0, 20 ) + "...\"" )
1307                                           : s ) + "\"";
1308                                    }
1309                                    if ( content2.regionMatches( secondStrPos, 
1310                                                                 s,
1311                                                                 0, sLen ) ) {
1312                                        // we've re-synchronized the strings
1313                                        // so enter the substring preceding this
1314                                        // match as the map for the previous var
1315                                        if ( null != map ) {
1316                                            try {
1317                                                String mapTo = 
1318                                                    content2.substring(mapStart,
1319                                                            secondStrPos);
1320                 // System.err.println("AclMsgCmp.compareContent(): mapping \""+
1321                                    //  v.getName() +"\" to \""+ mapTo+"\"...");
1322                                                map.put( v.getName(), mapTo );
1323                                                secondStrPos += sLen;
1324                                            }
1325                                            catch ( Exception e ) {
1326                                                throw new RuntimeException( 
1327                                                       "exception referencing "+
1328                                                       v.getType() + " map:" +
1329                                                       e.getMessage(), e );
1330                                            }
1331                                        }
1332                                        break; // out of eternal loop
1333                                    }
1334                                    else {
1335                                        secondStrPos++;
1336                                    }
1337                                }
1338                                
1339                            }
1340                            else {
1341                                // multiple unmapped variables in row
1342                                //
1343                                // can't positively match these, so skip them
1344                                // until reach case where at end (in which case
1345                                // return with no error) or next item is either
1346                                // a string or mapped variable (in which case go
1347                                // looking for a match to recalibrate the index
1348                                // into the second content string)
1349                                while ( true ) {
1350                                    if ( !li.hasNext() ) {
1351                                        // ran off end with unmapped var's, 
1352                                        // return no diff
1353                                        return null;
1354                                    }
1355                                    listItem = li.next();
1356                                    if ( ( listItem instanceof Variable ) &&
1357                                            ( ((Variable)listItem).mappedValue() 
1358                                                    == null ) ) {
1359                                        // unmapped variable, skip over
1360                                    }
1361                                    else {
1362                                        // at either a string or mapped variable
1363                                        // get our bearings again and try to find
1364                                        // this string in remaining part of content2
1365                                        s = ( ( listItem instanceof String ) 
1366                                         ? (String)listItem
1367                                         : ((Variable)listItem).mappedValue() );
1368                                        
1369                                        int sLen = s.length();
1370                                        int tooFar = content2.length() - sLen;
1371                                        while ( true ) {
1372                                            if ( secondStrPos >= tooFar ) {
1373                                                return 
1374                                                "mismatched content discovered"+
1375                                                " at point where expecting \"" +
1376                                                ( ( sLen > 20 )
1377                                            ? ( s.substring( 0, 20 ) + "...\"" )
1378                                            : s ) + "\"";
1379                                            }
1380                                            if ( content2.regionMatches( 
1381                                                    secondStrPos, s,
1382                                                    0, sLen ) ) {
1383                                                // we've re-sync'd the strings
1384                                            }
1385                                            else {
1386                                                secondStrPos++;
1387                                            }
1388                                        }
1389                                        
1390                                        
1391                                    }
1392                                }
1393                            }
1394                        }
1395                    }
1396                    
1397                }
1398            }
1399            catch ( NoSuchElementException nsee ) {
1400                throw new RuntimeException( "variable/string list exception:"+
1401                        nsee.getMessage(), nsee );
1402            }
1403            catch ( ClassCastException cce ) {
1404                throw new RuntimeException( "map class cast exception:"+
1405                        cce.getMessage(), cce );
1406            }
1407        }
1408        
1409        // no differences found, so return null to indicate equality
1410        return null;
1411    }
1412    
1413    
1414    
1415    // LANGUAGE
1416    
1417    /** whether to compare language slots */
1418    private boolean cmpLanguage = true;
1419    
1420    /**
1421     * set language comparison flag (default is true)
1422     * @param doCompare
1423     * @see #compare( ACLMessage, ACLMessage )
1424     */
1425    public void setCmpLanguage( boolean doCompare ) {
1426        cmpLanguage = doCompare;
1427    }
1428    
1429    /**
1430     * get language comparison flag (default is true)
1431     * @return language comparison flag
1432     * @see #compare( ACLMessage, ACLMessage )
1433     */
1434    public boolean isCmpLanguage() {
1435        return cmpLanguage;
1436    }
1437    
1438    
1439    /**
1440     * Compare just the language slot of two messages.
1441     * @param msg1 first message to compare
1442     * @param msg2 second message to compare
1443     * @return null if equal, descriptive text if not
1444     * @see #compare( ACLMessage, ACLMessage )
1445     */
1446    public String compareLanguage( ACLMessage msg1, ACLMessage msg2 ) {
1447        String language1 = msg1.getLanguage();
1448        String language2 = msg2.getLanguage();
1449        if ( !strEqualsIgnoreCaseNullOK( language1, language2 ) ) {
1450            return
1451            "first message language \"" + language1 +
1452            "\" not equal to " +
1453            "second message language \"" + language2 + "\"";
1454        }
1455        else {
1456            return null;
1457        }
1458    }
1459    
1460    // ENCODING
1461    
1462    /** whether to compare encoding slots */
1463    private boolean cmpEncoding = true;
1464    
1465    /**
1466     * set encoding comparison flag (default is true)
1467     * @param doCompare
1468     * @see #compare( ACLMessage, ACLMessage )
1469     */
1470    public void setCmpEncoding( boolean doCompare ) {
1471        cmpEncoding = doCompare;
1472    }
1473    
1474    /**
1475     * get encoding comparison flag (default is true)
1476     * @return encoding comparison flag
1477     * @see #compare( ACLMessage, ACLMessage )
1478     */
1479    public boolean isCmpEncoding() {
1480        return cmpEncoding;
1481    }
1482    
1483    
1484    /**
1485     * Compare just the encoding slot of two messages.
1486     * @param msg1 first message to compare
1487     * @param msg2 second message to compare
1488     * @return null if equal, descriptive text if not
1489     * @see #compare( ACLMessage, ACLMessage )
1490     */
1491    public String compareEncoding( ACLMessage msg1, ACLMessage msg2 ) {
1492        String encoding1 = msg1.getEncoding();
1493        String encoding2 = msg2.getEncoding();
1494        if ( !strEqualsIgnoreCaseNullOK( encoding1, encoding2 ) ) {
1495            return
1496            "first message encoding \"" + encoding1 +
1497            "\" not equal to " +
1498            "second message encoding \"" + encoding2 + "\"";
1499        }
1500        else {
1501            return null;
1502        }
1503    }
1504    
1505    // ONTOLOGY
1506    
1507    /** whether to compare ontology slots */
1508    private boolean cmpOntology = true;
1509    
1510    /**
1511     * set ontology comparison flag (default is true)
1512     * @param doCompare
1513     * @see #compare( ACLMessage, ACLMessage )
1514     */
1515    public void setCmpOntology( boolean doCompare ) {
1516        cmpOntology = doCompare;
1517    }
1518    
1519    /**
1520     * get ontology comparison flag (default is true)
1521     * @return ontology comparison flag
1522     * @see #compare( ACLMessage, ACLMessage )
1523     */
1524    public boolean isCmpOntology() {
1525        return cmpOntology;
1526    }
1527    
1528    
1529    /**
1530     * Compare just the ontology slot of two messages.
1531     * @param msg1 first message to compare
1532     * @param msg2 second message to compare
1533     * @return null if equal, descriptive text if not
1534     * @see #compare( ACLMessage, ACLMessage )
1535     */
1536    public String compareOntology( ACLMessage msg1, ACLMessage msg2 ) {
1537        String ontology1 = msg1.getOntology();
1538        String ontology2 = msg2.getOntology();
1539        if ( !strEqualsIgnoreCaseNullOK( ontology1, ontology2 ) ) {
1540            return
1541            "first message ontology \"" + ontology1 +
1542            "\" not equal to " +
1543            "second message ontology \"" + ontology2 + "\"";
1544        }
1545        else {
1546            return null;
1547        }
1548    }
1549    
1550    // PROTOCOL
1551    
1552    /** whether to compare protocol slots */
1553    private boolean cmpProtocol = true;
1554    
1555    /**
1556     * set protocol comparison flag (default is true)
1557     * @param doCompare
1558     * @see #compare( ACLMessage, ACLMessage )
1559     */
1560    public void setCmpProtocol( boolean doCompare ) {
1561        cmpProtocol = doCompare;
1562    }
1563    
1564    /**
1565     * get protocol comparison flag (default is true)
1566     * @return protocol comparison flag
1567     * @see #compare( ACLMessage, ACLMessage )
1568     */
1569    public boolean isCmpProtocol() {
1570        return cmpProtocol;
1571    }
1572    
1573    
1574    /**
1575     * Compare just the protocol slot of two messages.
1576     * @param msg1 first message to compare
1577     * @param msg2 second message to compare
1578     * @return null if equal, descriptive text if not
1579     * @see #compare( ACLMessage, ACLMessage )
1580     */
1581    public String compareProtocol( ACLMessage msg1, ACLMessage msg2 ) {
1582        String protocol1 = msg1.getProtocol();
1583        String protocol2 = msg2.getProtocol();
1584        if ( !strEqualsIgnoreCaseNullOK( protocol1, protocol2 ) ) {
1585            return
1586            "first message protocol \"" + protocol1 +
1587            "\" not equal to " +
1588            "second message protocol \"" + protocol2 + "\"";
1589        }
1590        else {
1591            return null;
1592        }
1593    }
1594    
1595    
1596    // CONVERSATION-ID
1597    
1598    /** whether to compare conversation-id slots */
1599    private boolean cmpConversationId = true;
1600    
1601    /**
1602     * set conversation-id comparison flag (default is true)
1603     * @param doCompare
1604     * @see #compare( ACLMessage, ACLMessage )
1605     */
1606    public void setCmpConversationId( boolean doCompare ) {
1607        cmpConversationId = doCompare;
1608    }
1609    
1610    /**
1611     * get conversation-id comparison flag (default is true)
1612     * @return conversation-id comparison flag
1613     * @see #compare( ACLMessage, ACLMessage )
1614     */
1615    public boolean isCmpConversationId() {
1616        return cmpConversationId;
1617    }
1618    
1619    
1620    /**
1621     * Compare just the conversation-id slot of two messages.
1622     * @param msg1 first message to compare
1623     * @param msg2 second message to compare
1624     * @return null if equal, descriptive text if not
1625     * @see #compare( ACLMessage, ACLMessage )
1626     */
1627    public String compareConversationId( ACLMessage msg1, ACLMessage msg2 ) {
1628        // delegate work to method refactoring out algorithm for similar slots
1629        return cmpCaseInsConsStrSlot( "conversation-id", 
1630                conversationIdMap,
1631                msg1.getConversationId(),
1632                msg2.getConversationId() );
1633    }
1634    
1635    // IN-REPLY-TO
1636    
1637    /** whether to compare in-reply-to slots */
1638    private boolean cmpInReplyTo = true;
1639    
1640    /**
1641     * set in-reply-to comparison flag (default is true)
1642     * @param doCompare
1643     * @see #compare( ACLMessage, ACLMessage )
1644     */
1645    public void setCmpInReplyTo( boolean doCompare ) {
1646        cmpInReplyTo = doCompare;
1647    }
1648    
1649    /**
1650     * get in-reply-to comparison flag (default is true)
1651     * @return in-reply-to comparison flag
1652     * @see #compare( ACLMessage, ACLMessage )
1653     */
1654    public boolean isCmpInReplyTo() {
1655        return cmpInReplyTo;
1656    }
1657    
1658    
1659    /**
1660     * Compare just the in-reply-to slot of two messages.
1661     * @param msg1 first message to compare
1662     * @param msg2 second message to compare
1663     * @return null if equal, descriptive text if not
1664     * @see #compare( ACLMessage, ACLMessage )
1665     */
1666    public String compareInReplyTo( ACLMessage msg1, ACLMessage msg2 ) {
1667        // delegate work to method refactoring out algorithm for similar slots
1668        return cmpCaseInsConsStrSlot( "in-reply-to", 
1669                replyIdMap,
1670                msg1.getInReplyTo(),
1671                msg2.getInReplyTo() );
1672    }
1673    
1674    // REPLY-WITH
1675    
1676    /** whether to compare reply-with slots */
1677    private boolean cmpReplyWith = true;
1678    
1679    /**
1680     * set reply-with comparison flag (default is true)
1681     * @param doCompare
1682     * @see #compare( ACLMessage, ACLMessage )
1683     */
1684    public void setCmpReplyWith( boolean doCompare ) {
1685        cmpReplyWith = doCompare;
1686    }
1687    
1688    /**
1689     * get reply-with comparison flag (default is true)
1690     * @return reply-with comparison flag
1691     * @see #compare( ACLMessage, ACLMessage )
1692     */
1693    public boolean isCmpReplyWith() {
1694        return cmpReplyWith;
1695    }
1696    
1697    
1698    /**
1699     * Compare just the reply-with slot of two messages.
1700     * @param msg1 first message to compare
1701     * @param msg2 second message to compare
1702     * @return null if equal, descriptive text if not
1703     * @see #compare( ACLMessage, ACLMessage )
1704     */
1705    public String compareReplyWith( ACLMessage msg1, ACLMessage msg2 ) {
1706        // delegate work to method refactoring out algorithm for similar slots
1707        return cmpCaseInsConsStrSlot( "reply-with", 
1708                replyIdMap,
1709                msg1.getReplyWith(),
1710                msg2.getReplyWith() );
1711    }
1712    
1713    // REPLY-BY
1714    
1715    /** whether to compare reply-by slots */
1716    private boolean cmpReplyBy = true;
1717    
1718    /**
1719     * set reply-by comparison flag (default is true)
1720     * @param doCompare
1721     * @see #compare( ACLMessage, ACLMessage )
1722     */
1723    public void setCmpReplyBy( boolean doCompare ) {
1724        cmpReplyBy = doCompare;
1725    }
1726    
1727    /**
1728     * get reply-by comparison flag (default is true)
1729     * @return reply-by comparison flag
1730     * @see #compare( ACLMessage, ACLMessage )
1731     */
1732    public boolean isCmpReplyBy() {
1733        return cmpReplyBy;
1734    }
1735    
1736    
1737    /**
1738     * Compare just the reply-by slot of two messages.
1739     * @param msg1 first message to compare
1740     * @param msg2 second message to compare
1741     * @return null if equal, descriptive text if not
1742     * @see #compare( ACLMessage, ACLMessage )
1743     */
1744    public String compareReplyBy( ACLMessage msg1, ACLMessage msg2 ) {
1745        Date replyBy1 = msg1.getReplyByDate();
1746        Date replyBy2 = msg2.getReplyByDate();
1747        if ( !dateEqualNullOK( replyBy1, replyBy2 ) ) {
1748            return
1749            "first message reply-by \"" + replyBy1 +
1750            "\" not equal to " +
1751            "second message reply-by \"" + replyBy2 + "\"";
1752        }
1753        else {
1754            return null;
1755        }
1756    }
1757    
1758    // USER PROPERTIES
1759    
1760    /** whether to compare user properties slots */
1761    private boolean cmpUserProperties = true;
1762    
1763    /**
1764     * set user properties comparison flag (default is true)
1765     * @param doCompare
1766     * @see #compare( ACLMessage, ACLMessage )
1767     */
1768    public void setCmpUserProperties( boolean doCompare ) {
1769        cmpUserProperties = doCompare;
1770    }
1771    
1772    /**
1773     * get user properties comparison flag (default is true)
1774     * @return user properties comparison flag
1775     * @see #compare( ACLMessage, ACLMessage )
1776     */
1777    public boolean isCmpUserProperties() {
1778        return cmpUserProperties;
1779    }
1780    
1781    
1782    /**
1783     * Compare just the user properties slot of two messages.
1784     * @param msg1 first message to compare
1785     * @param msg2 second message to compare
1786     * @return null if equal, descriptive text if not
1787     * @see #compare( ACLMessage, ACLMessage )
1788     */
1789    public String compareUserProperties( ACLMessage msg1, ACLMessage msg2 ) {
1790        Properties userProperties1 = msg1.getAllUserDefinedParameters();
1791        Properties userProperties2 = msg2.getAllUserDefinedParameters();
1792        Enumeration names1 = userProperties1.propertyNames();
1793        while ( names1.hasMoreElements() ) {
1794            String name1;
1795            try {
1796                name1 = (String)names1.nextElement();
1797            }
1798            catch ( NoSuchElementException nsee ) {
1799                throw new RuntimeException( 
1800                        "user property enumeration exception:"+
1801                        nsee.getMessage(), nsee );
1802            }
1803            String value1 = (String)userProperties1.get( name1 );
1804            if ( !userProperties2.containsKey( name1 ) ) {
1805                return "user property " + name1 + 
1806                " found in first message not" +
1807                " found in second message";
1808            }
1809            String value2 = (String)userProperties2.get( name1 );
1810            if ( !strEqualsNullOK( value1, value2 ) ) {
1811                return "for user property name " + name1 + 
1812                ", first message value \"" +
1813                value1 + "\" not equal to second message value \"" +
1814                value2 + "\"";
1815            }
1816        }
1817        int size1 = userProperties1.size();
1818        int size2 = userProperties2.size();
1819        if ( size1 != size2 ) {
1820            return "first message has " + size1 + 
1821            " property(ies) but second message has " + size2;
1822        }
1823        return null;
1824    }
1825    
1826    // ENVELOPE
1827    
1828    /** whether to compare envelope slots */
1829    private boolean cmpEnvelope = false;
1830    
1831    /**
1832     * set envelope comparison flag (default is false)
1833     * @param doCompare
1834     * @see #compare( ACLMessage, ACLMessage )
1835     */
1836    public void setCmpEnvelope( boolean doCompare ) {
1837        cmpEnvelope = doCompare;
1838    }
1839    
1840    /**
1841     * get envelope comparison flag (default is false)
1842     * @return envelope comparison flag
1843     * @see #compare( ACLMessage, ACLMessage )
1844     */
1845    public boolean isCmpEnvelope() {
1846        return cmpEnvelope;
1847    }
1848    
1849    
1850    /**
1851     * Compare just the envelope slot of two messages.
1852     * NOT YET IMPLEMENTED - ALWAYS THROWS EXCEPTION
1853     * @param msg1 first message to compare
1854     * @param msg2 second message to compare
1855     * @return null if equal, descriptive text if not
1856     * @see #compare( ACLMessage, ACLMessage )
1857     */
1858    public String compareEnvelope( ACLMessage msg1, ACLMessage msg2 ) {
1859        throw new RuntimeException("AclMsgCmp.compareEnvelope() not yet implemented");
1860        //!!!IMPLEMENT ME!!!
1861        //return null;
1862    }
1863    
1864    
1865    //
1866    // ENTIRE MESSAGE COMPARISON
1867    //
1868    
1869    /**
1870     * Easy version of compare() with no variable properties.
1871     * @param msg1 first message to compare
1872     * @param msg2 second message to compare
1873     * @return null if equal, descriptive text if not
1874     */
1875    public String compare( ACLMessage msg1, ACLMessage msg2 ) {
1876        return compare( msg1, msg2, null );
1877    }
1878    
1879    /**
1880     * Compare two ACL messages for equality, possibly filtering fields.
1881     * If comparing a series of messages, it is required that all messages
1882     * from one conversation be the first parameter in each call, and all
1883     * messages from the other conversation be the second parameter in each
1884     * call.  This is so pieces of messages that are transiently consistent,
1885     * such as the HAP or conversation ID, can be tracked.  So, this implements
1886     * a kind of isomorphism check rather than simple equality.  In general,
1887     * FIPA names and words are case-insensitive also.  The method may return
1888     * on the first difference found.  Callers should not depend on that or
1889     * on any particular order of slot comparison.  Comparisons of a given
1890     * slot is governed by individual slot comparison flags, settable, all
1891     * with default true.
1892     * @param msg1 first message to compare
1893     * @param msg2 second message to compare
1894     * @param vars mapping from variable string names to variable groups
1895     * @return null if equal, descriptive text if not
1896     * @see #setCmpSender( boolean )
1897     * @see #isCmpSender()
1898     * @see #compareSender( ACLMessage, ACLMessage )
1899     * @see #setCmpReceiver( boolean )
1900     * @see #isCmpReceiver()
1901     * @see #compareReceiver( ACLMessage, ACLMessage )
1902     * @see #setCmpReplyTo( boolean )
1903     * @see #isCmpReplyTo()
1904     * @see #setCmpPerformative( boolean )
1905     * @see #isCmpPerformative()
1906     * @see #comparePerformative( ACLMessage, ACLMessage )
1907     * @see #setCmpContent( boolean )
1908     * @see #isCmpContent()
1909     * @see #compareContent( ACLMessage, ACLMessage, Properties )
1910     * @see #setCmpLanguage( boolean )
1911     * @see #isCmpLanguage()
1912     * @see #compareLanguage( ACLMessage, ACLMessage )
1913     * @see #setCmpEncoding( boolean )
1914     * @see #isCmpEncoding()
1915     * @see #compareEncoding( ACLMessage, ACLMessage )
1916     * @see #setCmpOntology( boolean )
1917     * @see #isCmpOntology()
1918     * @see #compareOntology( ACLMessage, ACLMessage )
1919     * @see #setCmpProtocol( boolean )
1920     * @see #isCmpProtocol()
1921     * @see #compareProtocol( ACLMessage, ACLMessage )
1922     * @see #setCmpConversationId( boolean )
1923     * @see #isCmpConversationId()
1924     * @see #compareConversationId( ACLMessage, ACLMessage )
1925     * @see #setCmpInReplyTo( boolean )
1926     * @see #isCmpInReplyTo()
1927     * @see #compareInReplyTo( ACLMessage, ACLMessage )
1928     * @see #setCmpReplyWith( boolean )
1929     * @see #isCmpReplyWith()
1930     * @see #compareReplyWith( ACLMessage, ACLMessage )
1931     * @see #setCmpReplyBy( boolean )
1932     * @see #isCmpReplyBy()
1933     * @see #compareReplyBy( ACLMessage, ACLMessage )
1934     * @see #setCmpUserProperties( boolean )
1935     * @see #isCmpUserProperties()
1936     * @see #compareUserProperties( ACLMessage, ACLMessage )
1937     * @see #setCmpEnvelope( boolean )
1938     * @see #isCmpEnvelope()
1939     * @see #compareEnvelope( ACLMessage, ACLMessage )
1940     */
1941    public String compare( ACLMessage msg1, 
1942                           ACLMessage msg2, 
1943                           java.util.Properties vars ) {
1944        
1945        // this is the descriptive text returned of the 1st difference found
1946        String diffMsg = null;
1947        
1948        // compare sender (AID)
1949        if ( cmpSender && 
1950             ( null != ( diffMsg = compareSender( msg1, msg2 ) ) ) ) {
1951            // stop comparison here
1952        }
1953        
1954        // compare receivers (AID[])
1955        else if ( cmpReceiver && 
1956                  ( null != ( diffMsg = compareReceiver( msg1, msg2 ) ) ) ) {
1957            // stop comparison here
1958        }
1959        
1960        // compare reply-to (AID[])
1961        else if ( cmpReplyTo && 
1962                  ( null != ( diffMsg = compareReplyTo( msg1, msg2 ) ) ) ) {
1963            // stop comparison here
1964        }
1965        
1966        // compare performative (int)
1967        else if ( cmpPerformative && 
1968                  ( null != ( diffMsg = comparePerformative( msg1, msg2 ) ) ) ){
1969            // stop comparison here
1970        }
1971        
1972        // compare language (String)
1973        else if ( cmpLanguage && 
1974                  ( null != ( diffMsg = compareLanguage( msg1, msg2 ) ) ) ) {
1975            // stop comparison here
1976        }
1977        
1978        // compare encoding (String)
1979        else if ( cmpEncoding && 
1980                  ( null != ( diffMsg = compareEncoding( msg1, msg2 ) ) ) ) {
1981            // stop comparison here
1982        }
1983        
1984        // compare ontology (String)
1985        else if ( cmpOntology && 
1986                  ( null != ( diffMsg = compareOntology( msg1, msg2 ) ) ) ) {
1987            // stop comparison here
1988        }
1989        
1990        // compare protocol (String)
1991        else if ( cmpProtocol && 
1992                  ( null != ( diffMsg = compareProtocol( msg1, msg2 ) ) ) ) {
1993            // stop comparison here
1994        }
1995        
1996        // compare conversation id
1997        else if ( cmpConversationId && 
1998                  ( null != ( diffMsg = compareConversationId( msg1, msg2 ) ))){
1999            // stop comparison here
2000        }
2001        
2002        // compare in-reply-to
2003        else if ( cmpInReplyTo && 
2004                  ( null != ( diffMsg = compareInReplyTo( msg1, msg2 ) ) ) ) {
2005            // stop comparison here
2006        }
2007        
2008        // compare reply-with
2009        else if ( cmpReplyWith && 
2010                  ( null != ( diffMsg = compareReplyWith( msg1, msg2 ) ) ) ) {
2011            // stop comparison here
2012        }
2013        
2014        // compare reply-by (Date)
2015        else if ( cmpReplyBy && 
2016                  ( null != ( diffMsg = compareReplyBy( msg1, msg2 ) ) ) ) {
2017            // stop comparison here
2018        }
2019        
2020        // compare user properties (key/value pairs)
2021        else if ( cmpUserProperties && 
2022                  ( null != ( diffMsg = compareUserProperties( msg1, msg2 ) ))){
2023            // stop comparison here
2024        }
2025        
2026        // compare envelope
2027        else if ( cmpEnvelope && 
2028                  ( null != ( diffMsg = compareEnvelope( msg1, msg2 ) ) ) ) {
2029            // stop comparison here
2030        }
2031        
2032        // compare content
2033        //
2034        // content mapping is last because it's the most difficult and its
2035        // results can be influenced by correspondences found by previous
2036        // compare routines that are more deterministic.
2037        else if ( cmpContent && 
2038                  ( null != ( diffMsg = compareContent( msg1, msg2, vars ) ) )){
2039            // stop comparison here
2040        }
2041        
2042        // return descriptive text returned of the 1st difference found
2043        // null if none found
2044        return diffMsg;
2045        
2046    }
2047    
2048    // method to make it easier to construct message from text.
2049    
2050    /**
2051     * Convenience method to convert string to ACLMessage.
2052     * @param s string to convert to ACLMessage
2053     * @return ACLMessage version of input string
2054     * @throws jade.lang.ACLCodec.CodecException converting string to message
2055     */
2056    public static ACLMessage stringToMessage( String s ) 
2057      throws ACLCodec.CodecException {
2058        StringACLCodec sac = new StringACLCodec( new StringReader( s ),
2059                new StringWriter() );
2060        return sac.decode();
2061    }
2062    
2063    
2064    // methods for customizing comparison
2065    // !!!???!!! to customize comparison, override individual compare methods?
2066    
2067}

[all classes][jade.jademx.util]
EMMA 2.0.5312 (C) Vladimir Roubtsov