ArtiSynth Reference Manual

3 Writing and Scanning Components

ArtiSynth model components have the ability to save and restore themselves from persistent storage. They do this by implementing the write() and scan() methods of maspack.util.Scannable, and the postscan() method of ModelComponent:

  // write this component to a PrintWriter:
  void write (PrintWriter pw, NumberFormat fmt, Object ref) throws IOException
  // scan the component from a ReaderTokenizer:
  void scan (ReaderTokenizer rtok, Object ref) throws IOException
  // perform the post-scan pass for this component:
  void postscan (Deque<ScanToken> tokens, CompositeComponent ancestor);
  // normally returns true but can be overridden to return false
  // if for some reason a component should be written to secondary storage
  boolean isWritable();

The operation and implementation of these methods will now be described in detail. A summary of the key points is given in Section 3.5.

3.1 Writing components

The write() method writes information about the component to a PrintWriter, using NumberFormat to format floating point numbers where appropriate. The ref argument is used to provide additional context information for generating the output, and is specifically used to generate path names for other components that are referenced by the component being written (Section 3.1.1).

In general, each component writes out its attributes as a list of name/value pairs, each of the form

  <name>=<value>

with the list itself enclosed between square brackets ’[ ]’ which serve as begin and end delimiters. The value associated with each attribute name may itself be a quantity (such as a vector, matrix, or another component) delimited by square brackets. For example, the output for a Particle component may look like this:

   [ name="primary"
     position=[ 15.0 0.0 10.0 ]
     mass=20.0
     dynamic=false
   ]

The output begins with an openning square bracket, followed by four attribute/value pairs and a closing square bracket. The position attribute is a 3-vector also enclosed between square brackets.

Within the write() method, the above output could be produced with code like this:

  import maspack.util.*;
  void write (PrintWriter pw, NumberFormat fmt, Object ref) throws IOException {
     pw.print ("[ ");
     IndentingPrintWriter.addIndentation (2);
     pw.println ("name=" + Write.getQuotedString(myName));
     pw.println ("position=[" + myPosition.toString (fmt) + "]");
     pw.println ("mass=" + fmt.format(myMass));
     pw.println ("dynamic=" + myDynamicP;
     IndentingPrintWriter.addIndentation (-2);
     pw.println ("]");
  }

Output indentation can be controlled by using an IndentingPrintWriter, and IndentingPrintWriter.addIndentation() will increase (or decrease) output indentation if pw is an instance of IndentingPrintWriter.

In practice, components do not generally need to provide explicit code to write out all their attribute values. In particular, any information that is associated with a Property (see the Property section of the Maspack Reference Manual) can be written out automatically using a code fragment of the form:

   getAllPropertyInfo().writeNonDefaultProps (this, pw, fmt);

In addition, any attribute information contained in a component’s superclass will usually be written by that superclass. The default write() definition for a model component is usually looks something like this:

   public void write (PrintWriter pw, NumberFormat fmt, Object ref)
   throws IOException {
      dowrite (pw, fmt, ref);
   }
   protected void dowrite (PrintWriter pw, NumberFormat fmt, Object ref)
   throws IOException {
      CompositeComponent ancestor = ComponentUtils.castRefToAncestor(ref);
      IndentingPrintWriter.printOpening (pw, "[ ");
      IndentingPrintWriter.addIndentation (pw, 2);
      writeItems (pw, fmt, ancestor);
      IndentingPrintWriter.addIndentation (pw, -2);
      pw.println ("]");
   }
   protected void writeItems (
   PrintWriter pw, NumberFormat fmt, CompositeComponent ancestor)
   throws IOException {
      getAllPropertyInfo().writeNonDefaultProps (this, pw, fmt);
   }

The write() and dowrite() methods take care of writting the square brackets and setting up the initial indentation. Then a call to writeItems() prints out all necessary property values. The ancestor argument obtained from ref will be discussed in Section 3.1.1.

If all a component’s attribute information is associated with property values, then it is usually not necessary to provide any component-specific code for writing the component: the default implementations of write() and writeItems() will handle it. If a component does have attribute information that is not associated with a property, then it is usually sufficient to handle this by overriding writeItems(). For example, if a component has a non-property “centroid” attribute, it can be written by an override of writeItems() constructed like this:

   protected void writeItems (
   PrintWriter pw, NumberFormat fmt, CompositeComponent ancestor)
   throws IOException {
      super.writeItems (pw, fmt, ref);
      pw.println ("centroid=[" + getCentroid().toString (fmt) + "]");
   }

3.1.1 Writing references

ArtiSynth components often contain references to other components that are not part of their ancestor hierarchy. For example, a two-point spring will contain references to its two end-point particles, and a FemElement3d will contain references to its nodes. The set of all references refered to by a component is returned by the combination of the component’s getHardReferences() getSoftReferences() methods.

Because they generally reside outside a component’s immediate ancestor hierarchy, information about each reference’s location needs to be explicitly written and scanned as part of writing and scanning a component. The location information is stored using the component’s path with respect to some known ancestor. This ancestor is passed to the component’s write() method through the ref argument, and is cast explicitly to CompositeComponent and passed to writeItems() as the ancestor argument. A component can then use ComponentUtils.getWritePathName() to obtain the path name of each reference with respect to the ancestor, and write this to the output.

As an example, here is a possible implementation of writeItems() for a two-point spring:

   protected void writeItems (
   PrintWriter pw, NumberFormat fmt, CompositeComponent ancestor)
   throws IOException {
      super.writeItems (pw, fmt, ancestor);
      pw.println ("point0=" + ComponentUtils.getWritePathName(ancestor,myPnt0));
      pw.println ("point1=" + ComponentUtils.getWritePathName(ancestor,myPnt1));
   }

This will produce an output like this,

      point0="models/points/0"
      point1="models/points/1"

where models/points/n gives the path name of the n-th point with respect to the ancestor. Alternatively, if a component has a variable number of references, they can be written out as a list between square brackets:

   protected void writeItems (
   PrintWriter pw, NumberFormat fmt, CompositeComponent ancestor)
   throws IOException {
      super.writeItems (pw, fmt, ancestor);
      pw.println ("points=[");
      IndentingPrintWriter.addIndentation (pw, 2);
      for (ModelComponent pnt : myPnts) {
         pw.println (ComponentUtils.getWritePathName(ancestor,pnt));
      }
      IndentingPrintWriter.addIndentation (pw, -2);
      pw.println ("]");
   }

The above code will produce output like this:

      points=[
        "models/points/0"
        "models/points/1"
        "models/points/2"
      ]

The ancestor used for reading and writing references will always be a common ancestor of both the references and the refering component. This may sometimes be the root model (i.e., the top of the hierarchy), but more typically it will be the first common ancestor for which hierarchyContainsReferences() returns true (implying that all references are contained within the ancestors’s descendants). This allows paths to be written more compactly. Most Model components presently enforce the hierarchyContainsReferences() condition.

3.1.2 Writing child components

If a component is a CompositeComponent, then it also needs to write out its child components along with its attribute information. This can be done by recursively calling the children’s write() methods.

If the child component configuration is fixed (i.e., the component does not implement MutableCompositeComponent) and the children are created in the composite’s constructor, then attribute names can be used to delimit each child. For example, suppose a component contains two children: a list of particles and a list of springs. This component could then be written using a code construction such as:

   protected void writeItems (
   PrintWriter pw, NumberFormat fmt, CompositeComponent ancestor)
   throws IOException {
      super.writeItems (pw, fmt, ref);
      pw.print ("particles=");
      myParticles.write (pw, fmt, ref);
      pw.print ("springs=");
      mySprings.write (pw, fmt, ref);
   }

If the composite component has been implemented internally using an instance of ComponentListImpl (Section 1.4), then the above can be written using the latter’s writeComponentsByName() method, which writes each child, using its component name as an attribute name:

   ComponentListImpl myComponents;
   protected void writeItems (
   PrintWriter pw, NumberFormat fmt, CompositeComponent ancestor)
   throws IOException {
      super.writeItems (pw, fmt, ref);
      myComponents.writeComponentsByName (pw, fmt, ancestor);
   }

One the other hand, composite components which are instances of MutableCompositeComponent may not have predetermined component arrangements and so these components cannot be identified by an attribute name. Instead, the components must simply be printed out in sequence. For example, the writeItems() method for a list of particles could be programmed like this:

   protected void writeItems (
   PrintWriter pw, NumberFormat fmt, CompositeComponent ancestor)
   throws IOException {
      super.writeItems (pw, fmt, ref);
      for (int i=0; i<particles.size(); i++) {
         particles.get(i).write (pw, fmt, ref);
      }
   }

This would produce output such as:

  [ position=[ 10.0 0.0 20.0 ]
    mass=20.0
  ]
  [ position=[ 5.0 0.0 10.0 ]
    mass=15.0
  ]
  [ position=[ 15.0 0.0 10.0 ]
    mass=20.0
  ]
  [ position=[ 10.0 0.0 0.0 ]
    mass=15.0
  ]

However, when scanning a MutableCompositeComponent, the scan() method (discussed below) needs to create and scan new child components as it encounters them in the input. It is therefore necessary for scan() to know what class of component to create. Typically, the composite component has a default component type; for example, the default type for ComponentList<Particle> is Particle. However, in some cases the components may be subclasses of the default type, or the default type may be an interface or abstract class and hence not instantiable. In such instances, the output needs to be augmented with class type information, which is placed before the openning ’[’ of the component output. The details of how to do this are beyond the scope of this document. However, if the composite component has been implemented internally using an instance of ComponentListImpl, then one can use the latter’s writeComponents() method to automatically write out all the components, along with the necessary class information:

   ComponentListImpl myComponents;
   protected void writeItems (
   PrintWriter pw, NumberFormat fmt, CompositeComponent ancestor)
   throws IOException {
      super.writeItems (pw, fmt, ref);
      myComponents.writeComponents (pw, fmt, ancestor);
   }

The result may look something like this:

  [ position=[ 10.0 0.0 20.0 ]
    mass=20.0
  ]
  artisynth.core.mechmodels.SpecialParticle [
    position=[ 5.0 0.0 10.0 ]
    mass=15.0
  ]
  [ position=[ 15.0 0.0 10.0 ]
    mass=20.0
  ]
  user.projects.CustomParticle [
    position=[ 10.0 0.0 0.0 ]
    mass=15.0
  ]

In this example, the second and fourth particles have class types that differ from the default and so class information for each is prepended to the output.

3.2 Scanning components

The scan() method reads the component in from a token stream provided by a ReaderTokenizer. This translates the input into a stream of tokens, including words, numbers, and special token characters (such as ’[’, ’]’, and ’=’), which are then used to parse the input. Authors implementing scanning code should have some familiarity with ReaderTokenizer. A description is beyond the scope of this document but good documentation is available in the ReaderTokenizer class header.

The main code inside the default scan() method for a model component looks roughly like this:

   public void scan (ReaderTokenizer rtok, Object ref)
   throws IOException {
      rtok.scanToken (’[’);
      while (rtok.nextToken() != ’]’) {
         rtok.pushBack();
         if (!scanItem (rtok, tokens)) {
            throw new IOException ("Unexpected token: " + rtok);
         }
      }
   }

The method looks for and scans the initial ’[’ character (and will throw an IOException if this is not found). It then reads other tokens (using nextToken()) until the terminating ’]’ character is found. After each token is inspected, it is pushed back into the token stream using pushBack() and scanItem() is called to try and read an individual attribute or subcomponent from the input. If scanItem() cannot match the input to any attributes or child components it returns false.

Note: ReaderTokenizer allows one token of look-ahead, so that any read token can be pushed back once. In particular, in the following sequence, t1 and t2 should be the same:

   t1 = rtok.nextToken();
   rtok.pushBack();
   t2 = rtok.nextToken();

The default implementation of scanItem() provides code for reading property values and looks something like this:

   protected boolean scanItem (ReaderTokenizer rtok, Deque<ScanToken> tokens)
   throws IOException {
      rtok.nextToken();
      // if attribute name is a property name, scan that property:
      if (ScanWriteUtils.scanProperty (rtok, this)) {
         return true;
      }
      rtok.pushBack();
      return false;
   }

The method begins by getting the next token and then calling ScanWriteUtils.scanProperty() to see if the token is a word matching the name of one of the component’s properties. If so, then scanProperty() scans and sets the property value and returns true, and scanItem() itself returns true. Otherwise, scanItem() returns false indicating that it was unable to find a match for the input. The tokens argument is used to store information whose processing must be deferred until the post-scan step, as discussed in Section 3.2.1.

Typically, component implementations will not need to override scan() unless the scanning procedure calls for pre- or post-processing, as in:

   public void scan (ReaderTokenizer rtok, Object ref)
   throws IOException {
      ... do pre-processing here ...
      super.scan (rtok, ref);
      ... do post-processing here ...
   }

Note that if a class makes use of the post-scan step (Section 3.2.1), then it may be necessary to do the post-processing in an override of postscan() instead.

Component implementations often will need to override scanItem() to scan additional attribute information. For example:

  protected boolean scanItem (ReaderTokenizer rtok, Deque<ScanToken> tokens)
  throws IOException {
     rtok.nextToken();
     if (rtok.ttype == ReaderTokenizer.TT_WORD) {
        if (rtok.sval.equals ("attributeXXX") {
           rtok.scanToken (’=’);
           ... scan information for attribute XXX ...
           return true;
        }
     }
     rtok.pushBack();
     return super.scanItem (rtok, tokens);
  }

First, the method gets the next token. Then it checks if its type (ttype) corresponds to a WORD token and if the word’s string value (sval) equals the attribute name attributeXXX. If so, then it scans the ’=’ character following attribute name, scans whatever information is associated with the attribute, and returns true. Otherwise, if no expected attribute name is matched, the current token is pushed back, and the superclass method is called to see if it can match the current input.

Most implementations of ModelComponent provide the convenience method scanAttributeName(rtok, name) which allows the code fragment

     if (rtok.ttype == ReaderTokenizer.TT_WORD) {
        if (rtok.sval.equals ("attributeXXX") {
           rtok.scanToken (’=’);

to be replaced with

      if (scanAttributeName (rtok, "attributeXXX")) {

Employing this in a larger example, we have

  protected boolean scanItem (ReaderTokenizer rtok, Deque<ScanToken> tokens)
  throws IOException {
     rtok.nextToken();
     if (scanAttributeName (rtok, "name"))
        myName = rtok.scanQuotedString();
        return true;
     }
     else if (scanAttributeName (rtok, "position")) {
        myPosition.scan (rtok);
        return true;
     }
     else if (scanAttributeName (rtok, "mass")) {
        myMass = rtok.scanNumber();
        return true;
     }
     else if (scanAttributeName (rtok, "dynamic")) {
        myDynamicP = rtok.scanBoolean();
        return true;
     }
     rtok.pushBack();
     return super.scanItem (rtok, tokens);
  }

Here, if any of the attribute names name, position, mass, or dynamic are matched, then the corresponding string, vector, numeric, or boolean attribute values are scanned using scanQuotedString(), the vector’s own scan() method, scanNumber(), or scanBoolean(). Each of these will throw an IOException if the input token sequence does not match what is expected.

3.2.1 Scanning references and post-scanning

When scanning a component that contains references, the path for each reference is used to locate the referenced component within the component hierarchy. However, this poses a problem: because components are created only as the component hierarchy is recursively scanned, it is possible that some references may not yet exist at the time when the component is scanned. For example, if the points referenced by a two-point spring belong to part of the hierarchy further "to the right" of the spring components, then when the spring is scanned the points won’t yet exist and the scanning method will be unable to find them.

The solution to this problem is to employ a two-step scanning process in which the initial scan is followed by a secondary "post-scan" which can be used to resolve references. Each reference path found during the initial scan is saved for later use in the post-scan step, by which time all components are guaranteed to have been created. Reference information, along with any other information needed for the post-scan step, is saved in a queue of ScanTokens supplied to the scan() method through the ref argument. Several different types of ScanTokens allow different types of information to be stored: StringTokens are use to store attribute names and reference paths; ObjectTokens are use to store object pointers; and special marker tokens, ScanToken.BEGIN and ScanToken.END, can be used as delimiters.

At a minimum, scanning each component causes BEGIN and END tokens to be added to the token queue, with additional tokens added in between as necessary. Revisiting the basic scan() method code shown at the top of Section 3.2, we show the additional code that is needed to handle this:

   public void scan (ReaderTokenizer rtok, Object ref)
   throws IOException {
      Deque<ScanToken> tokens = (Deque<ScanToken>)ref;
      tokens.offer (ScanToken.BEGIN);
      rtok.scanToken (’[’);
      while (rtok.nextToken() != ’]’) {
         rtok.pushBack();
         if (!scanItem (rtok, tokens)) {
            throw new IOException ("Unexpected token: " + rtok);
         }
      }
      tokens.offer (ScanToken.END);
   }

The token queue itself, called tokens, is obtained from the ref argument via an explicit cast. BEGIN and END tokens are added at the beginning and end of the scan. In between, the token queue is passed to scanItem(), which adds addtional tokens when necessary.

Within scanItem(), tokens are added to provide whatever information is needed for the post-scan step. This information is often provided in the form of two or more tokens comprising an attribute name/value pair, so that the post-scan step is not sensitive to input ordering. In this sense, the information stored in the token queue will reflect the same structure as the tokens in the original input.

Consider the first example in 3.1.1 where the reference information for a two-point spring was output as:

      point0="models/points/0"
      point1="models/points/1"

To process this inside scanItem(), we check for the attribute names point0 and point1 and if either is found, we store both the attribute name and the reference path in the token queue using StringTokens. For point0, the corresponding code looks like

      if (scanAttributeName (rtok, "point0")) {
         String refpath = rtok.scanWordOrQuotedString (’"’);
         tokens.offer (new StringToken ("point0"));
         tokens.offer (new StringToken (refpath));
         return true;
      }

Most implementations of ModelComponent provide the convenience method scanAndStoreReference (rtok, name, tokens) which allows this to be compressed into

      if (scanAndStoreReference (rtok, "point0", tokens)) {
         return true;
      }

One may also use ScanWriteUtils.scanAndStoreReference() for the same purpose. The scanItem() method for a two-point spring can then be written as:

   protected boolean scanItem (ReaderTokenizer rtok, Deque<ScanToken> tokens)
   throws IOException {
      rtok.nextToken();
      if (scanAndStoreReference (rtok, "point0", tokens)) {
         return true;
      }
      else if (scanAndStoreReference (rtok, "point1", tokens)) {
         return true;
      }
      rtok.pushBack();
      return super.scanItem (rtok, tokens);
   }

Alternatively, if we have an attribute followed by a list of references enclosed in square brackets, such as

      points=[
        "models/points/0"
        "models/points/1"
        "models/points/2"
      ]

then we want to store a sequence of tokens consisting of the attribute name, a BEGIN token, the reference paths, and an END token:

      "points"
      BEGIN
      "models/points/0"
      "models/points/1"
      "models/points/2"
      END

That can be done by a code sequence that looks like

      if (scanAttributeName (rotk, "points", tokens)) {
         rtok.scanToken (’[’);
         tokens.offer (new StringToken ("points"));
         tokens.offer (ScanToken.BEGIN);
         while (rtok.nextToken() != ’]’) {
            if (rtok.tokenIsWordOrQuotedString ()) {
               tokens.offer (rtok.sval);
            }
            else {
               throw new IOException ("Error: reference path expected");
            }
         }
         tokens.offer (ScanToken.END);
         return true;
      }

and which is available in most ModelComponent implementations via the convenience method scanAndStoreReferences():

      if (scanAndStoreReferences (rtok, "point", tokens)) {
         return true;
      }

One may also use ScanWriteUtils.scanAndStoreReferences() for the same purpose.

3.2.2 Scanning child components

In addition to their attributes, composite components need to scan in their child components. This can be done by recursively calling the children’s scan() methods.

If the child component configuration is fixed (i.e., the component does not implement MutableCompositeComponent), and the children are created in the composite’s constructor and written out using attribute names as delimiters, then these attributes names can be used to drive the scanning. The example composite from Section 3.1.2, comprising a list of particles and a list of springs, could be scanned in using code such as:

  protected boolean scanItem (ReaderTokenizer rtok, Deque<ScanToken> tokens)
  throws IOException {
     rtok.nextToken();
     if (scanAttributeName (rtok, "particles"))
        tokens.offer (new ObjectToken (myParticles));
        myParticles.scan (rtok, tokens);
        return true;
     }
     else if (scanAttributeName (rtok, "springs")) {
        tokens.offer (new ObjectToken (mySprings));
        mySprings.scan (rtok);
        return true;
     }
     rtok.pushBack();
     return super.scanItem (rtok, tokens);
  }

Here, an ObjectToken() identifying each scanned componet is stored on the token queue for later use in the post-scan step. If the composite component has been implemented internally using an instance of ComponentListImpl (Section 1.4), then the above can be written more succinctly using the latter’s scanAndStoreComponentByName() method:

   ComponentListImpl myComponents;
   protected boolean scanItem (ReaderTokenizer rtok, Deque<ScanToken> tokens)
   throws IOException {
      rtok.nextToken();
      if (myComponents.scanAndStoreComponentByName (rtok, tokens)) {
         return true;
      }
      rtok.pushBack();
      return super.scanItem (rtok, tokens);
   }

On the other hand, composite components which are instances of MutableCompositeComponent are written out in sequence, without using attribute names but with possible prefixed information giving information about the component’s class. When scanning in these children, scanItem() must determine the class for the child, create an instance of the child, and then scan it in. The code required for these steps is beyond the scope of this document. However, if the mutable composite has been implemented internally using an instance of ComponentListImpl, then one can use the latter’s methods scanBegin(), scanAndStoreComponent(), and scanEnd() to handle the scanning.

First, scanBegin() is called in an override of scan():

   ComponentListImpl myComponents;
   public void scan(ReaderTokenizer rtok, Object ref) throws IOException {
      myComponents.scanBegin();
      super.scan (rtok, ref);
   }

scanAndStoreComponent() can then be called in scanItem() to handle scanning of individual components:

   protected boolean scanItem (ReaderTokenizer rtok, Deque<ScanToken> tokens)
   throws IOException {
      if (super.scanItem (rtok, tokens)) {
         return true;
      }
      rtok.nextToken();
      return myComponents.scanAndStoreComponent (rtok, tokens);
   }

The method checks to see if the input contains child component information, and if so, scans the information, creates an instance of the component if necessary, stores a copy of the component in the token queue for use in post-scanning, and returns true. Note that in this case one should call scanAndStoreComponent() after the super method to avoid confusing attribute names with class information.

scanAndStoreComponent() will not create a new component if a fixed component (Section 1.4) of the appropriate class already exists at the current list position. Instead, the existing component will be scanned “in place”.

Finally, scanEnd() is called in an override of postscan() method (discussed below):

   public void postscan (
   Deque<ScanToken> tokens, CompositeComponent ancestor) throws IOException {
      super.postscan (tokens, ancestor);
      myComponents.scanEnd();
   }

3.2.3 Post-scanning implementation

Once the token queue has been built by the scan() methods, it is processed in the post-scan step. This is done by each component using a postscan() method that takes as arguments the token queue and the ancestor with respect to which reference paths should be evaluated. The default postscan() method for most components looks something like this:

   public void postscan (
   Deque<ScanToken> tokens, CompositeComponent ancestor) throws IOException {
      ScanWriteUtils.postscanBeginToken (tokens, this);
      while (tokens.peek() != ScanToken.END) {
         if (!postscanItem (tokens, ancestor)) {
            throw new IOException (
               "Unexpected token " + tokens.poll());
         }
      }
      tokens.poll(); // consume the END token
   }

postscanBeginToken() gets the next token on the queue, checks that it is a BEGIN token, and throws an exception if this is not the case. Then the method simply calls postScanItem(), which does the actual token handling work, until a terminating END token is found.

As is the case with scan(), subclasses typically do not need to override postscan(). The exception to this is when post-processing is required after the scan process:

   public void postscan (
   Deque<ScanToken> tokens, CompositeComponent ancestor) throws IOException {
      super.postscan (tokens, ancestor);
      ... do post processing ...
   }

However, any component which adds tokens in its scanItem() method will need to process those tokens in an override of postscanItem(). Tokens can be removed from the queue using the queue’s poll() method, and can be examined (without removing them) using the queue’s peek() method. More usefully, the utility class ScanWriteUtils provides a number of methods for token processing, including:

   boolean postscanAttributeName (tokens, name);
   C postscanReference (tokens, clazz, ancestor);
   C[] postscanReferences (tokens, refs, clazz, ancestor);

ModelComponentBase also makes convenience wrappers for these directly available within the class. postscanAttributeName() checks if the next token in the queue is a StringToken matching name, and if it is, consumes that token and returns true. postscanReference() checks that the next token is a StringToken containing a path reference, finds the component referenced by that path relative to ancestor, checks that it is an instance of clazz, and returns it. postscanReferences() obtains a set of component references described by a sequence of StringTokens located between BEGIN and END tokens, and returns the referenced components in an array. These methods will throw an IOException if they encounter unexpected tokens or if referenced components cannot be found.

Employing these methods to handle the point0, point1 reference example above, we obtain:

   public boolean postscanItem (
   Deque<ScanToken> tokens, CompositeComponent ancestor) throws IOException {
      if (postscanAttributeName (tokens, "point0")) {
         myPnt0 = postscanReference (tokens, Point.class, ancestor);
         return true;
      }
      else if (postscanAttributeName (tokens, "point1")) {
         myPnt1 = postscanReference (tokens, Point.class, ancestor);
         return true;
      }
      return super.postscanItem (tokens, ancestor);
   }

Similarly, the points reference example can be handled as:

   public boolean postscanItem (
   Deque<ScanToken> tokens, CompositeComponent ancestor) throws IOException {
      if (postscanAttributeName (tokens, "points")) {
         Point[] pnts = postscanReferences (tokens, Point.class, ancestor);
         return true;
      }
      return super.postscanItem (tokens, ancestor);
   }

Finally, for composite components, it is necessary to call postscan() for each of their children. For composites implemented using ComponentListImpl, this can be done by calling the latter’s postscanComponent() method. For both CompositeComponent and MutableCompositeComponent implementations, the corresponding code looks like this:

   protected boolean postscanItem (
      Deque<ScanToken> tokens, CompositeComponent ancestor)
      throws IOException {
      if (myComponents.postscanComponent (tokens, ancestor)) {
         return true;
      }
      return super.postscanItem (tokens, ancestor);
   }

postscanComponent() checks to see if the next token is an ObjectToken containing a ModelComponent, and if it is, it removes that token, calls the component’s postscan() method, and returns true.

3.2.4 Post-scanning property values

As described in 3.2, base implementations of scanItem() automatically read in and set property values for the component. However, in a few cases it may be necessary to defer setting the property value until the post-scan step, either because it depends on component references, or requires the component structure to be fully realized. A current example of this is the surfaceRendering property for FemModel.

Deferring the settting of property values until the post-scan step can be done by saving the scanned property values in the token queue, and then actually setting the properties during the post-scan. Two convenience methods, ScanWriteUtils.scanAndStorePropertyValue() and ScanWriteUtils.postscanPropertyValue() allow this to be done fairly easily:

   public boolean scanItem (ReaderTokenizer rtok, Deque<ScanToken> tokens)
      throws IOException {
      rtok.nextToken();
      if (ScanWriteUtils.scanAndStorePropertyValue (
          rtok, this, "surfaceRendering", tokens)) {
         return true;
      }
      else {
         ... remaining scanItem() implementation ...
      }
      rtok.pushBack();
      return super.scanItem (rtok, tokens);
   }
   public boolean postscanItem (
      Deque<ScanToken> tokens, CompositeComponent ancestor) throws IOException {
      if (ScanWriteUtils.postscanPropertyValues (tokens, this, deferredProps)) {
         return true;
      }
      else {
         ... remaining postscanItem() implementation ...
      }
      return false;
   }

Note that it is often not necessary to provide a component-specific implementation of postscanItem() since the defaullt implementation for most components already contains a call to ScanWriteUtils.postscanPropertyValues().

If a property value depends on references, it is also important to ensure that reference information is written out before the property information, so that in the post-scan step, it will be set before the property values. In such cases, that means that writeItems() should be structured as follows:

   protected void writeItems (
      PrintWriter pw, NumberFormat fmt, CompositeComponent ancestor)
      throws IOException {
      ... write out reference information first ...
      super.writeItems (pw, fmt, ancestor);
   }

3.2.5 Invoking the complete scan process

The two-step scanning process means that for the top-level invocation of scan, the application needs to create a token queue, call scan() with this queue as the ref argument, and then call postscan():

      ArrayDeque<ScanToken> tokens = new ArrayDeque<ScanToken>();
      comp.scan (rtok, tokens);
      comp.postscan (tokens, ancestor);

For convenience, the above code fragment is encapsulated into the method ScanWriteUtils.scanfull().

If we are scanning an entire hierarchy from scratch, then comp will be the root component of the hierarchy and ancestor will equal comp. Otherwise, if we are scanning a new sub-hierarchy, then comp will be the root of the sub-hierarchy and ancestor may be some component higher in the existing hierarchy.

3.3 File and token structure

Because Artisynth components are responsible for writing and scanning themselves, there is no mandatory file structure imposed per-se. However, there is a structure that should be adhered to whenever possible. Expressed loosely as a production grammar, with ’*’ expressing repetition of zero or more times, this is:

    component
            ’[’ componentItem* ’]’
    componentItem
            attributePair
            componentSpec
    attributePair
            NAME ’=’ value
    componentSpec
            CLASSINFO component
            component
    value
            componentSpec
            list
            literal
    list
            ’[’ listItem* ’]’
    listItem
            value
            attributePair
    literal
            BOOLEAN
            INTEGER
            FLOAT
            STRING
            WORD

Here, NAME and WORD are identifiers that consist of alphanumerics, ’$’, or ’_’, and do not begin with a digit. CLASSINFO is the classname for a component, or an alias that can be mapped to a classname using ClassAliases.resolveClass().

Within an ArtiSynth file, ’#’ is a comment character, causing all remaining characters on the line to be discarded.

When tokens are saved for the post-scan step, they should arranged in a structure similiar to that used for the file itself:

  componentTokens
        BEGIN itemTokens* END
  itemTokens
        componentSpecTokens
        attributePairTokens
  attributePairTokens
        NAME valueTokens
  componentSpecTokens
        COMPONENT componentTokens
        componentTokens
  valueTokens
        componentSpecTokens
        listTokens
        literalTokens
  listTokens
        BEGIN <listItemTokens>* END
  listItemTokens
        valueTokens
        attributePairTokens
  literalTokens
        STRING
        OBJECT

Here, BEGIN and END are ScanToken.BEGIN and ScanToken.END, NAME is a StringToken with an attribute name as a value, COMPONENT is a ObjectToken with a reference to the object as a value, and STRING and OBJECT are StringToken and ObjectToken, respectively.

3.4 Debugging

Debugging write and scan methods is generally not too difficult because of the ascii nature of the data files. A good first test is to write components out, read them back in, and then write them out a second time and make sure that the second output equals the first. Scan methods will generally throw IOExceptions when unexpected input is encountered, and these usually provide the offending line number.

Problems that occur in post-scan can be slightly harder to solve because the token queue is not normally written out in any place where it can be inspected. To help with this, once can use ScanWriteUtils.setTokenPrinting() to enable the token queue produced by ScanWriteUtils.scanfull() to be printed to the standard output. It is also possible to print a token queue directly using ScanWriteUtils.printTokens().

3.5 Summary

The main points concerning component writing and scanning are as follows:

  1. 1.

    Writing and scanning are done using the component’s write(), scan(), postscan() methods. These methods usually employ writeItems(), scanItem(), and postscanItem() to handle the writing and scanning of individual attributes and child components.

  2. 2.

    Where possible, the structure described in Section 3.3 should be adhered to.

  3. 3.

    Scanning is a two-step process, involving a scan step and a post-scan step. This is to accomodate the fact that some aspects of scanning (most importantly the evaluation of references) cannot be done until the entire component hierarchy has been constructed. Information needed for the post-scan step (such as reference path names) should be stored in the token queue passed to scan() and scanItems().

  4. 4.

    It is usually only necessary for a component implementation to override writeItems(), scanItem(), and postscanItem(). Property values are usually written and scanned automatically by the base implementations of writeItems() and scanItem(). If a component does not contain references or non-property attributes, it may not be necessary for the implementation to override any methods at all.

  5. 5.

    Composite components need to call write(), scan(), and postscan() for their child components. Composites implemented using ComponentListImpl can do this using methods supplied by that class, such as writeComponents(), scanAndStoreComponent(), and postscanComponent().

  6. 6.

    A complete scan operation involves creating a token queue and then calling both scan() and postscan() for the top-level component. This can be done using the convenience method ScanWriteUtils.scanfull().

  7. 7.

    The utility class ScanWriteUtils contains a large number of methods that facilitate writing and scanning.