StringTokenizer

I just can’t believe this kind of functionality isn’t built in but so it is. The code should be pretty straight forward and well commented.

A StringTokenizer is used to split a string of text up into smaller bits delimeted by the chosen delimeter. A StringTokenizer really shines when it comes to parsing comma-separated files or the like.

Feel free to use the code but please let me know if you do. The code is provided as-is and I accept no responsibility what so ever.

'*** Constants ***
Private Const STANDARD_DELIMETER$ = ";"

'/**
' * StringTokenizer class to help break a string into tokens. A token is the
' * string between delimeters (defaults to semi-colon). The class handles
' * lines ending in a delimeter if you set the EndingDelimeter property
' * to True.
' *
' * Standard usage:
' * 1. Dim a new StringTokenizer using the source to parse as an argument.
' * 2. (Optional) Change the delimeter using the Delimter property.
' * 3. (Optional) Change the EndingDelimeter property.
' * 4. Call the HasNextElement() method and the NextElement() functions to
' *    loop the string.
' *
' * Example 1:
' * Dim t1 As New StringTokenizer(";123;456;789;")
' * While t1.HasNextElement()
' *   Print t1.NextElement()
' * Wend
' *
' * Output:
' *  - 
' *  - 123
' *  - 456
' *  - 789
' *  - 
' *
' * Example 2:
' * Dim t2 As New StringTokenizer(";123;456;789;")
' * t2.EndingDelimeter = True
' * While t2.HasNextElement()
' *   Print t2.NextElement()
' * Wend
' *
' * Output:
' *  - 
' *  - 123
' *  - 456
' *  - 789
' *
' * Example 3:
' * Dim t3 As New StringTokenizer("123;456%789;012")
' * t3.Delimeter = "%"
' * While t3.HasNextElement()
' *   Print t3.NextElement()
' * Wend
' *
' * Output:
' *  - 123;456
' *  - 789;012
' *
' * @version 1.0 (16 December 2004)
' * @author lekkim@it-inspiration.dk
' * @author it-inspiration aps
' */
Public Class StringTokenizer
   'declarations
   Private pSource As String
   Private pDelimeter As String
   Private pStart As Long
   Private pDelim As Long
   Private pEndingDelimeter As Boolean

   Public Sub New(source As String)
      Me.pSource = source
      Me.pStart = 0
      Me.pDelim = 0
      Me.pEndingDelimeter = False
      Me.pDelimeter = STANDARD_DELIMETER
   End Sub

   Public Property Set EndingDelimeter As Boolean
      Me.pEndingDelimeter = EndingDelimeter
   End Property

   Public Property Set Delimeter As String
      Me.pDelimeter = Left(Delimeter, 1)
   End Property

   Public Function HasNextElement() As Boolean
      'declarations
      Dim index As Long

      'does the source have contents
      If Len(Me.pSource) = 0 Then
         'nope - return false
         HasNextElement = False
         Exit Function
      End If

      'have we been looking for a delimeter before ?
      If Me.pStart = 0 And Me.pDelim = 0 Then
         'nope - does the source start with a delimeter ?
         If Left(Me.pSource, 1) = Me.pDelimeter Then
            'the first element is empty
            Me.pDelim = 1

            'set start
            Me.pStart = 1
         Else
            'find the first delimeter
            index = Instr(1, Me.pSource, Me.pDelimeter)

            'did we find a delimeter
            If index > 1 Then
               'yes we did - set the index of the delimeter
               Me.pDelim = index

               'set start
               Me.pStart = 1
            Else
               'no we didn't only one element
               Me.pDelim = 0
               Me.pStart = 1
            End If
         End If

         'return true
         HasNextElement = True
         Exit Function
      Else
         'we have been looking before - move the pointers
         Me.pStart = Me.pDelim + 1

         'have we reached the end of the source string ?
         If Me.pStart > Len(Me.pSource) Then
            'yes we have - return false
            HasNextElement = False
            Exit Function
         Elseif Me.pStart = Len(Me.pSource) Then
            'see if the last character is a delimeter
            If Right(Me.pSource, 1) = Me.pDelimeter Then
               'the last character is a delimeter - should the class add
               'an empty element at the end
               If Me.pEndingDelimeter Then
                  'the lines end in a delimeter
                  HasNextElement = False
               Else
                  'we should signal an empty element
                  Me.pDelim = Me.pStart
                  HasNextElement = True
               End If
            Else
               'the is a one character element at the end
               Me.pStart = Len(Me.pSource)
               Me.pDelim = 0
               HasNextElement = True
            End If
         Else
            'just look for the next delimeter
            index = Instr(Me.pStart, Me.pSource, Me.pDelimeter)

            'did we find a delimeter
            If index > Me.pStart Then
               'we found a delimter
               Me.pDelim = index
               HasNextElement = True
            Elseif index = 0 And Me.pStart < Len(Me.pSource) Then
               'there is one more element
               Me.pDelim = Len(Me.pSource)
               HasNextElement = True
            Else
               'no more delimeters
               HasNextElement = False
            End If
         End If
      End If
   End Function

   Public Function NextElement() As String

      'if the delimeter is 0 there is only one element to return
      If Me.pDelim = 0 Then
         NextElement = Mid(Me.pSource, Me.pStart)
         Exit Function
      Elseif Me.pDelim = Me.pStart Then
         'return an empty element
         NextElement = ""
         Exit Function
      Elseif Me.pDelim = Len(Me.pSource) Then
         'does the source end with a delimeter
         If Me.pEndingDelimeter Then
            'the line is supposed to end with a delimeter so we remove it
            NextElement = Mid(Me.pSource, Me.pStart, Len(Me.pSource) - Me.pStart)
         Else
            'the line should not end with a delimeter so if it does so we need to
            'return the rest of the line minus 1
            If Right(Me.pSource, 1) = Me.pDelimeter Then
               NextElement = Mid(Me.pSource, Me.pStart, Len(Me.pSource) - Me.pStart)
               Me.pDelim = Me.pDelim - 1
            Else
               NextElement = Mid(Me.pSource, Me.pStart)
            End If
         End If
      Else
         NextElement = Mid(Me.pSource, Me.pStart, Me.pDelim-Me.pStart)
      End If

   End Function

End Class

Google suggests

Google suggests looks to me to what we really need in our application. in this particular application the user has to select a street from the database without resorting to dropdowns and the like. At the moment this is a accomplished by the user specifying the start of the street name, clicks a button (or presses enter) and a asynchronenous XML query is run the background. This works perfectly but having something like Google suggests would surely help users.

The main issue with the way it currently works is that users need to know the exact spelling of the street (or at least parts of the exact spelling) to find anything. Using something like Google suggests has users could have some help finding the street without needing to know the spelling.

This guy dissected the code and made it more readable.

Thinking about LotusScript

Lately I have been help thinking about the LotusScript language and why it hasn’t matured more over the years. I have two primary grievances:

  • The lack of OO support in the IDE.
  • The lack of helper classes in the API.

The lack of OO support in the IDE

Come on Lotus – when will this happen. How come it is practically easier to develop LotusScript classes in Visual Studio than in Domino Designer. Please step up and add class recognizion to the LotusScript IDE. It is practially imposible to handle classes in Script Libraries and in Agents.

What would be really help would be a simple tree structure showing the class hierarchy in the left hand side – it anything else fails let us have the same UI as in the Java IDE… 🙂

I think it is a real show stopper for the adaption of OO design and programming since programmers new to OOP loose track of the code and the structure. The overview is much better when you use the traditional procedural approach.

The lack of helper classes in the API

I know additional classes has been added in version 6 but how come no collection api or more general string operation classes has been added.

There is a major need for a standardized collection API to help the adoption of OOP. Just see what the Collection API did for Java 2.

AMgr memory consumption part 2

After talking to Lotus Support and monitoring the server more closely it appears not to be the AMgr alone having the problem. The problem appears to be more general. Yesterday afternoon the CA task was at 200 MB memory! An important task but not one one of the busiest I must say…

Number of Lotus Notes seats

Hi Mikkel --

Both vendors have generally gotten away from making
seat counts the focus.  I think Microsoft is claiming
130 million, and IBM claims about 113 million.  The
problem with seat counts is that they are completely
artificial... unaudited numbers.

In terms of numbers to bet on, you could do well with
the Gartner market share numbers.  They report IBM as
having 46% share, Microsoft at 44.2%.  These
percentages are based on revenue, not seats.
Those numbers are reported publicly at
http://www.cmpnetasia.com/ViewArt.cfm?Artid=24452&Catid=8&subcat=83

Hope this helps.

--Ed

Using Velocity in an agent

At work I am using Velocity in a number of web applications and was looking into using it for a newsletter application we have. This would allow authors to write more dynamic newsletters, and insert user-specific information in the subject and body of the newsletter.

I already had an implementation in Java that would replace macros using String operations. While the current solution works flawlessly but I was looking for support for if’s statements etc. which Velocity has.

I turned out that replacing my own implementation was a matter of replacing the Decorator implementation I was using with a new one using Velocity:

import java.io.StringWriter;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import dk.itinspiration.newsletter.*;

public class VelocityMacroDecorator extends Decorator {

  public void decorate(Message msg) {
    try {
      // get the intended recipient
      Person p = msg.getRecipient();

      // get newsletter contents
      String content = msg.getContent();

      // initialize Velocity
      Velocity.init();

      // create and populate context
      VelocityContext ctx = new VelocityContext();
      ctx.put("email", p.getEmail());
      ctx.put("firstname", p.getFirstname());
      ctx.put("lastname", p.getLastname());
      ctx.put("password", p.getPassword());

      // create a string writer for the result
      StringWriter sw = new StringWriter();

      // replace macros
      Velocity.evaluate(ctx, sw, null, content);

      // set email contents
      msg.setContent(sw.toString());
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

This’s it! Surely very simple. Now it is just a matter of populating the VelocityContext with more information from the Person object (the newsletter subscriber) and about the newsletter in general (number of subscribers etc.).

The only caveat is that you must allow the agent to run with restricted operations due to Velocity requesting information about the system (dangerous stuff such as line separator and the like).

A sample newsletter could now be something like the following:

Hello $firstname $lastname
You are subscribed using '$email'
#if ($password != "")
  and your password is '$password'.
#else
  .
#end