/**
   * Do a recursive find and replace of objects pointed to by this object.
   *
   * @since v1.0
   * @param objectText Canonical string representation of the portion we want to replace.
   * @param replacement object we want to replace this portion with. A replacement will occur if a
   *     portion of the structure is found with a match of the encoded text with objectText and with
   *     the same class as replacement.
   * @param matchSubstring is true if we want to match objectText as a substring of the encoded
   *     target text. (i.e. an object is a candidate for replacement if objectText is a substring of
   *     candidate.encode() && candidate.class.equals(replacement.class) otherwise the match test is
   *     an equality test.)
   */
  public void replace(String objectText, GenericObjectList replacement, boolean matchSubstring)
      throws IllegalArgumentException {

    if (objectText == null || replacement == null) {
      throw new IllegalArgumentException("null argument!");
    }
    Class replacementClass = replacement.getClass();
    Class myclass = getClass();
    Field[] fields = myclass.getDeclaredFields();
    for (int i = 0; i < fields.length; i++) {
      Field f = fields[i];
      Class fieldType = f.getType();
      if (!getClassFromName(SIP_PACKAGE + ".GenericObject").isAssignableFrom(fieldType)
          && !getClassFromName(SIP_PACKAGE + ".GenericObjectList").isAssignableFrom(fieldType)) {
        continue;
      } else if ((f.getModifiers() & Modifier.PRIVATE) == Modifier.PRIVATE) {
        continue;
      }
      try {
        if (fieldType.equals(replacementClass)) {
          if (GenericObject.isMySubclass(replacementClass)) {
            GenericObject obj = (GenericObject) f.get(this);
            if (!matchSubstring) {
              if (objectText.compareTo(obj.encode()) == 0) {
                f.set(this, replacement);
              }
            } else {
              if (obj.encode().indexOf(objectText) >= 0) {
                f.set(this, replacement);
              }
            }
          } else if (GenericObjectList.isMySubclass(replacementClass)) {
            GenericObjectList obj = (GenericObjectList) f.get(this);
            if (!matchSubstring) {
              if (objectText.compareTo(obj.encode()) == 0) {
                f.set(this, replacement);
              }
            } else {
              if (obj.encode().indexOf(objectText) >= 0) {
                f.set(this, replacement);
              }
            }
          }

        } else if (getClassFromName(SIP_PACKAGE + ".GenericObject").isAssignableFrom(fieldType)) {
          GenericObject g = (GenericObject) f.get(this);
          g.replace(objectText, replacement, matchSubstring);
        } else if (getClassFromName(SIP_PACKAGE + ".GenericObjectList")
            .isAssignableFrom(fieldType)) {
          GenericObjectList g = (GenericObjectList) f.get(this);
          g.replace(objectText, replacement, matchSubstring);
        }
      } catch (IllegalAccessException ex) {
        InternalErrorHandler.handleException(ex);
      }
    }
  }
 /**
  * An introspection based predicate matching using a template object. Allows for partial match of
  * two protocl Objects.
  *
  * @other the match pattern to test against. The match object has to be of the same type (class).
  *     Primitive types and non-sip fields that are non null are matched for equality. Null in any
  *     field matches anything. Some book-keeping fields are ignored when making the comparison.
  */
 public boolean match(Object other) {
   if (other == null) return true;
   if (!this.getClass().equals(other.getClass())) return false;
   GenericObject that = (GenericObject) other;
   Class myclass = this.getClass();
   Field[] fields = myclass.getDeclaredFields();
   Class hisclass = other.getClass();
   Field[] hisfields = hisclass.getDeclaredFields();
   for (int i = 0; i < fields.length; i++) {
     Field f = fields[i];
     Field g = hisfields[i];
     // Only print protected and public members.
     int modifier = f.getModifiers();
     if ((modifier & Modifier.PRIVATE) == Modifier.PRIVATE) continue;
     Class fieldType = f.getType();
     String fieldName = f.getName();
     if (fieldName.compareTo("stringRepresentation") == 0) {
       continue;
     }
     if (fieldName.compareTo("indentation") == 0) {
       continue;
     }
     if (fieldName.compareTo("inputText") == 0) {
       continue;
     }
     try {
       // Primitive fields are printed with type: value
       if (fieldType.isPrimitive()) {
         String fname = fieldType.toString();
         if (fname.compareTo("int") == 0) {
           if (f.getInt(this) != g.getInt(that)) return false;
         } else if (fname.compareTo("short") == 0) {
           if (f.getShort(this) != g.getShort(that)) return false;
         } else if (fname.compareTo("char") == 0) {
           if (f.getChar(this) != g.getChar(that)) return false;
         } else if (fname.compareTo("long") == 0) {
           if (f.getLong(this) != g.getLong(that)) return false;
         } else if (fname.compareTo("boolean") == 0) {
           if (f.getBoolean(this) != g.getBoolean(that)) return false;
         } else if (fname.compareTo("double") == 0) {
           if (f.getDouble(this) != g.getDouble(that)) return false;
         } else if (fname.compareTo("float") == 0) {
           if (f.getFloat(this) != g.getFloat(that)) return false;
         }
       } else {
         Object myObj = f.get(this);
         Object hisObj = g.get(that);
         if (hisObj == myObj) return true;
         else if (hisObj != null && myObj == null) return false;
         else if (GenericObject.isMySubclass(myObj.getClass())
             && !((GenericObject) myObj).match(hisObj)) return false;
         else if (hisObj instanceof java.lang.String && myObj instanceof java.lang.String) {
           if (((String) myObj).compareToIgnoreCase((String) hisObj) != 0) return false;
         } else if (GenericObjectList.isMySubclass(myObj.getClass())
             && !((GenericObjectList) myObj).match(hisObj)) return false;
       }
     } catch (IllegalAccessException ex1) {
       InternalErrorHandler.handleException(ex1);
     }
   }
   return true;
 }