http://www.developer.com/

Back to article

BREW & J2ME: Let's Be Friends!


December 4, 2003

Abstract

This series targets mobile developers more accustomed to J2ME than BREW or even BREW developers interested in lighter and more efficient code production. Largely inspired by the Java GUI model, the present BREW_J2ME framework deals with what's known in J2ME as a "high-level interface." It is far from being an exact J2ME match—reasons will be discussed in the article—but exhibits a similar domain abstraction.

We will begin by quickly discussing the gap between BREW and J2ME and how we can narrow the distance, by providing insight into the design process. A thorough presentation of the framework follows.

An Overview

Since the beginning, Qualcomm tried to position BREW as language neutral, with C/C++ as the best choice—other options being always available for application development [1]. From a technical standpoint, the theory is perfectly plausible—a new language can be always implemented as a static extension, wrapping native BREW functionality. As an added bonus, being an extension, it can immediately reap all the benefits of BREW Distribution System (BDS). This is on the bright side. But what's left in the shadows?

Such an implementation will be inherently slower (all the OS calls are mediated by BREW) and bulkier than the same implementation directly on top of the OS. Now, speaking of J2ME: BREW offers much more functionality than standard MIDP (even 2.0)—meaning that probably a non-standard API will be offered to fill the gap.

But, of course, choosing one language over another is mostly a business decision and there is no point in comparing their merits. What's more attractive is to offer Java developers a more familiar way of writing BREW applications using C++. Obviously, this endeavor implies knowing the limits and differences between J2ME and BREW from the point of view of development.

Some differences stem from the Java vs. C++ architectural debate—for example, memory manipulation, multiple inheritance, type safeness, generics, and so forth. There is another category derived mostly from BREW limitations—prominent examples are lack of support for static variables, level of support for C++, cooperative multitasking, developer not insulated from watchdog activity, and the like. Of course, Java on top of BREW means that somehow Java has to absorb all these differences. A third group of dissimilarities is due to a basic design decision such as event model, components, and so forth. And of course, as mentioned before, J2ME is a platform-independent set of specifications with no counterpart for some BREW functionality.

Design

Our declared goal is to ease the creation of "high-level interface" code and associated logic, making life easier for Java developers. This means that, theoretically at least, we could be able to write code like this:

List* l = new List("Title");
l->setCommandListener(myListUsage);
String a[] = {"1","2","3","4"};
l->append(a);
Display.getDisplayable()->setCurrent(l);:

As we could see in the overview, there are various forces influencing the design. Here are some examples in no particular order: no built-in garbage collector; no static variables = no singletons in the sense of [2]; no root Object but safe type generics, and so forth. Considering all these factors, it becomes clear that code as above cannot be written freely in BREW. Reasons might be:

  1. There is no List.
  2. 'new' has to paired by 'delete'.
  3. There is no string or wide string available.
  4. Placing an array of AECHARs directly on the stack might be dangerous.
  5. There are no listeners.
  6. Singletons are difficult to implement.

Providing the best possible solutions to problems of this kind is what BREW_J2ME framework does.

One of the most important decisions is related to memory management and objects lifetime. If there is no delete, who's responsible for the destruction of our widgets? There are obvious answers, such as stack-based objects and smart pointers for a scope-defined life span or reference counting (that happens to coincide with BREW solution). One solution, used in previous articles, is to register objects and bind their lifetime to the life of the registrar. This implies an additional layer (the registry) and has the advantage of keeping track of all the resources in one place. There is another subtler advantage. too. A C++ application created directly on top of BREW is not a first-rank C++ object but merely a POD structure. That's why real C++ manipulation explicitly requires such an insulation layer. We will call our registry DisplayableRegistry.

Our registry will mainly keep track of displayable resources, implementing IDisplayable interface:

struct IDisplayable
{
  virtual bool onCmd() = 0;
  virtual bool containsItem(int idx) = 0;
  virtual int getID() const = 0;
  virtual IControl* getControl() const = 0;
  virtual ~IDisplayable()
  {}
};

An IDisplayable is somehow equivalent with an IControl in BREW, even if it exhibits additional functionality.

For convenience, every application inherits from an IMidlet abstract class, responsible for declaring and implementing dummy application-level event callbacks (this subject will be discussed later), as well as providing access to the unique instance of DisplayableRegistry. This solves another issue—Singleton implementation. Please note that the mechanism supplied by IMidlet and dynamic polymorphism is not necessary, per se—static polymorphism can do the trick, too. Making every component aware of the application is somehow equivalent with giving them a context.

class IMidlet
{
public:
  DisplayableRegistry* getDisplayable() const
  {
    return rr_;
  }
  void setRegistry(DisplayableRegistry* rr)
  {
    rr_ = rr;
  }
  virtual bool onStart()
  {
    return true;
  }
  virtual bool onStop()
  {
    return true;
  }
  virtual bool onSuspend()
  {
    return false;
  }
  virtual bool onResume()
  {
    return false;
  }
  virtual ~IMidlet()
  {}
private:
  DisplayableRegistry* rr_;
};

This is a possible implementation of DisplayableRegistry:

class DisplayableRegistry
{
public:
  int registerResource(IDisplayable* resource)
  {
    for(int i = resources_.size() - 1; i >= 0; --i)
    {
      if (resources_[i] && resources_[i] == resource)
      {
        return i;
      }
    }
    resources_.append(resource);
    return resources_.size() - 1;
  }

  bool unregisterResource(int uid)
  {
    if (resources_[uid] )
    {
      delete resources_[uid];
      resources_[uid] = 0;
      return true;
    }
    return false;
  }
  IDisplayable* getRegistered(int uid) const
  {
    return resources_.isEmpty() || ((resources_.size()-1) < uid)
                                   ? 0 : resources_[uid] ;
  }
  void setCurrent(IDisplayable* resource )
  {
    setCurrentImpl(resource);
  }
  int getNextAvailableID() 
  {
    ++itemID_;
    return itemID_;
  }
  bool onCmd(int itemID, long data) const
  {
    int id = INDEX_OUT_OF_BOUNDS;
    for(int i = 0, sz = resources_.size(); i< sz; ++i)
    {
      if (resources_[i]->containsItem(itemID))
      {
        IDisplayable* d = resources_[i];
        return resources_[i]->onCmd();
      }

    }
    return false;
    
    Midlet* getApp() const
  {
    return m_;
  }
  bool isHandled(AEEEvent eCode, uint16 wParam,
                 uint32 dwParam) const
  {
    for(int i = 0, sz = resources_.size(); i< sz; ++i)
    {
      IControl* c = resources_[i]->getControl();
      if (c && ICONTROL_HandleEvent(c,eCode, wParam, dwParam))
        return true;
    }
    return(false);
  }
  ~DisplayableRegistry()
  {
    delete m_;
    unregisterResources();
  }
  DisplayableRegistry(IMidlet* m):itemID_(100), m_(m)
  {
    m_->setRegistry(this);
  }
private:
  void unregisterResources()
  {
    for(int i=0, sz = resources_.size(); i < sz; ++i)
    {
      delete resources_[i];
      resources_[i] = 0;
    }
  }

  void eraseAll() const
  {
    for(int i=0, sz = resources_.size(); i < sz; ++i)
    {
      IControl* c = resources_[i]->getControl();
      ICONTROL_SetActive(c,false);
    }
    IDISPLAY_ClearScreen(getDisplay());
  }

  void setCurrentImpl(IDisplayable* resource)
  {
    eraseAll();
    ICONTROL_SetActive(resource->getControl(),true);
    IDISPLAY_UpdateEx(getDisplay(), false);
  }
private:
  int itemID_;
  IMidlet* m_;
private:
  DisplayableRegistry( const DisplayableRegistry &value );
  const DisplayableRegistry &operator =
        ( const DisplayableRegistry &rhs );
};

And, finally, making the registration process as well as the GUID-based BREW initialization process transparent requires an additional construct—a factory. Our first line of code is now:

List* l = List::getList("Title", this);

What's a List?

BREW doesn't provide a List, but a MenuCtl. The short answer is "a List is a IDisplayable exposing MenuCtl functionality."

template <class T, 
          class P=ListStrategy, 
          class E = ErrorHandler>
class ListImpl : public IDisplayable
{
typedef bool (T::*FNC)(ListImpl<T,P,E>*);
public:
  static ListImpl* getList(
           const WString& title, T* t)
  {
    ListImpl* l = createList(t);
    if (l)
    {
      l->setTitle(title);
      l->setFullScreen();
    }
    return l;
  }

  virtual ~ListImpl()
  {
    if (list_) IMENUCTL_Release(list_);
  }

  void append(const WString& item)
  {
    if (!rr_)
      return;
    int id = rr_->getNextAvailableID();
    IMENUCTL_AddItem(list_, 0, 0, id, 
                  const_cast<AECHAR*>
                    (item.toCharArray()), 0);
    indxs_.append(id);
  }

  void append(const WString items[]s, int sz)
  {
    for(int i=0; i < sz; ++i)
    {
      append(items[i]);
    }
  }

  virtual IControl* getControl() const
  {
    return reinterpret_cast<IControl*>(list_);
  }

  bool onCmd()
  {
    if (fnc_ && t_)
      return (*t_.*fnc_)(this);
    return false;
  }

  int getSelectedIndex() const
  {
    return selected_;
  }

  virtual bool containsItem(int ix)
  {
    selected_ = getIDImpl(ix);
    return (selected_ != INDEX_OUT_OF_BOUNDS);
  }

  virtual int getID() const
  {
    return id_;
  }

  void setCbk(FNC f)
  {
    fnc_ = f;
  }

  void setSelection(int id)
  {
    int lid = IMENUCTL_GetItemID(list_, id);
    IMENUCTL_SetSel(list_, lid);
  }

  int size() const
  {
    return indxs_.size();
  }

  void setTitle(const WString& title)
  {
    if (title.length())
      IMENUCTL_SetTitle(list_, NULL, 0, const_cast<AECHAR*>
                       (title.toCharArray()));
  }

private:
  
  ListImpl( T* t, AEECLSID cid ): list_(0), t_(t) , rr_(0),
          shell_(getShell()), selected_(0)
  {
    if (shell_)
      ISHELL_CreateInstance(shell_, cid, (void **)&list_);
    if (!list_)
    {
      E::onMemAllocError(WString(__FILE__),
                         WString((long)__LINE__));
      return;
    }

    rr_ = t_->getDisplayable();
    if (rr_)
      id_ = rr_->registerResource(this);
  }

  static ListImpl* createList( T* t, AEECLSID cid =
                                     AEECLSID_MENUCTL)
  {
    ListImpl* l = new ListImpl( t, cid);
    if (!l->list_)
    {
      delete l;
      return 0;
    }
    return l;
  }

  void setFullScreen();

  int getIDImpl(int ix);

private:
  IMenuCtl *  list_;
  FNC fnc_;
  T* t_;
  DisplayableRegistry* rr_;
  int selected_;
  int id_;
  IShell * shell_;
  BrewVector<int> indxs_;
};

The next concern is event handling. Apparently, there are important differences between BREW and Java: a unique ID, event loop mechanism versus an implicit, observer-based one. I say "apparently" because one can move freely from one mechanism to the other. Even more: J2ME, for example, shares one of the weaknesses of BREW—the command listener implementation is a close relative of the BREW event loop—usually a hard-to-maintain, huge "switch." On top of this, listeners are weak-typed constructs, exposing a Displayable interface that has to be cast by the user. Our framework offers a safer approach with type safe, J2SE style listeners. For example, the ListImpl class defines a listener:

typedef bool (T::*FNC)(ListImpl<T,P,E>*);

implemented in:

  bool myListUsage( List* l)
  {
    int pos = l->getSelectedIndex();
    return (pos == INDEX_OUT_OF_BOUNDS) ?
            false : BuildCommand(pos), true;
  }

and registered like this:

l->setCommandListener(myListUsage);

Java has a more elegant getSelected() mechanism instead of unique labels to be passed to IMENUCTL_AddItem. This can be easily implemented by using the ubiquitous DisplayableRegistry, this time generating unique numbers to be used internally as IDs. Please note the use of a policy to faster retrieve the IDs in some particular cases.

A string implementation supporting AECHARs and CHARs was discussed extensively in [3].

A striking difference between BREW and Java is error handling, when RTTI based exception handling is not available or might be considered too expensive to be used in BREW. Emulating a try/catch mechanism using setjmp/longjmp is not directly applicable due to problems in destructing auto objects, but other techniques are available (see [4], [5]). As a convenience, we provided an ErrorHandler as a policy—a way to gracefully provide error tracking information.

Wrapping Up

Let's write an application from scratch, using the presented technology.

The framework comes encapsulated in cppapp and brewJ2ME. An application is a class—be it Midlet in file MyApp.h. These are the steps to be followed:

  1. Include "MyApp.h" in cppapp.h.
  2. Create a Midlet class inheriting from IMidlet.
  3. Define type APPLICATION_TYPE as Midlet.

And here is the resulting skeleton:

class Midlet : public IMidlet
{
};
typedef Midlet APPLICATION_TYPE ;

Our simple application will deal with two lists, each one having four items—{"1","2","3","4"} and {"A","B","C","D"}. Pressing "1," for example, takes us to the correspondent "A," pressing "C" switches to "3," and so on. This logic is embedded in the event handlers: my14ListUsage and myADListUsage.

class Midlet : public IMidlet
{
typedef ListImpl<Midlet> List;
private:
  bool my14ListUsage( List* l)
  {
    int pos = l->getSelectedIndex();
    return (pos == INDEX_OUT_OF_BOUNDS) ?
            false : BuildADList(pos), true;
  }
  bool myADListUsage( List* l) 
  {
    int pos = l->getSelectedIndex();
    return  (pos == INDEX_OUT_OF_BOUNDS)  ?
             false : Build14List(pos), true;
  }
  List * Init14List()
  {
    List* l = List::getList("List14", this);
    if (!l) return 0;
    l->setCbk(my14ListUsage);
    WString a[] = {"1","2","3","4"};
    l->append(a,SIZE_OF(a));
    return l;
  }
  List * InitADList()
  {
    List* l = List::getList("ListAD", this);
    if (!l) return 0;
    l->setCbk(myADListUsage);
    WString a[] = {"A","B","C","D"};
    l->append(a,SIZE_OF(a));
    return l;
  }
  void BuildADList(int ix )
  {
    if (!lAD_)
    {
      lAD_ =InitADList();
    }
    BuildList(lAD_, ix);
  }
  void Build14List(int ix )
  {
    if (!l14_)
    {
      l14_ = Init14List();
    }
    BuildList(l14_, ix);
  }
  void BuildList(List* l , int ix)
  {
    if (!l || (l->size()-1)<ix) return;
    l->setSelection(ix);
    getDisplayable()->setCurrent(l);
  }
public:
  virtual bool onStart() 
  {
    l14_ = 0;
    lAD_ = 0;
    BuildADList(2);
    return true;
  }
private:
  List* l14_;
  List* lAD_;
};

What's really interesting, other than the striking simplicity of code, is the clarity of event handling.

Where Do We Go from Here?

This first attempt didn't cover important subjects such as multi-control screens, handling events other than EVT_COMMAND, and controlling the registered resources. The next installment will discuss all these topics as well as mechanisms to extend the framework.

Accompanying Source Code

Source code: cppapp.zip - 98 kb

References

  1. BREW and J2METM—A Complete Wireless Solution for Operators Committed to Java—Qualcomm http://www.qualcomm.com/brew/images/about/pdf/brew_j2me.pdf
  2. Design Patterns: Elements of Reusable Object-Oriented Software—ErichGamma, RichardHelm, RalphJohnson, and John Vlissides, Addison-Wesley, 1994
  3. String in BREW Revisited—a BrewString Generalizationhttp://www.developer.com/ws/brew/article.php/2229051
  4. Emulating C++ Exception Handling—Gregory Colvin, C/C++ Users Journal, December 1994
  5. For Brew Developers, There Are New Kids in Town: IThread & IRscPool, Part 2http://www.developer.com/ws/brew/article.php/3105131

About the Author

Radu Braniste is Director of Technology at Epicad. He can be contacted at rbraniste@epicad.com

# # #

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date