Extensions API

Robert Grider edited this page Nov 29, 2016 · 49 revisions
Clone this wiki locally

For background information on extensions in NetLogo, see the Extensions section of the User Manual.

Several sample extensions are included with NetLogo. The full Java (or Scala) source code for all of them is hosted on GitHub. Most are in the public domain. Some are copyrighted, but under an open-source license.

Many extensions developed by users are available for download from the Extensions page. Most of them include source code.

To discuss NetLogo development, including usage of NetLogo APIs, browse or join the netlogo-devel group, or come chat with us in our gitter room.

Compatibility

This page covers writing extensions for the NetLogo 6.0 (Beta) and later releases. For authoring extensions for NetLogo 5.3.1 and earlier, please consult this older version of this page.

Writing Extensions

This page introduces this facility for Java programmers. We'll assume that you know the Java language and related tools and practices.

Our API's are also usable from other languages for the Java Virtual Machine, such as Scala. Following the Java information is a section on how to write an extension in Scala. If you have a choice between using Scala and using Java, we recommend using Scala as several sections of the API reference Scala library classes which are much easier to use from scala.

Summary

A NetLogo extension consists of a folder with the following contents:

Required:

  • A JAR file with the same name as the extension, the following contents:
    • one or more classes that implements org.nlogo.api.Primitive
    • a main class that implements org.nlogo.api.ClassManager
    • a NetLogo extension manifest file, with the following four tags:
      • Manifest-Version, always 1.0
      • Extension-Name, the name of the extension.
      • Class-Manager, the fully-qualified name of a class implementing org.nlogo.api.ClassManager.
      • NetLogo-Extension-API-Version, the version of NetLogo Extension API for which this JAR is intended. If a user opens the extension with NetLogo that has a different Extension API version, a warning message is issued. To tell which version of the Extension API your NetLogo supports, choose the "About NetLogo" item in the "Help" menu and then click on the System tab. Or, you can launch NetLogo.jar with the --extension-api-version argument.

Optional:

  • One or more additional JAR files which the extension requires.
  • A lib directory with any required native libraries.
  • One or more NetLogo models demonstrating how the extension is used.
  • A src directory containing the source code for the model
  • Documentation.

To build your extension, you must include NetLogo.jar in your class path. In addition, the lib directory (also from the NetLogo distribution) must be in same location as NetLogo.jar; it contains additional libraries used by NetLogo.jar.

The NetLogo Extension sbt plugin

While a NetLogo extension can be built from any Java build system such as Ant, Gradle, or Maven, the CCL uses and recommends sbt along with the NetLogo Extension Plugin. This plugin handles the following steps:

  • Adding the appropriate NetLogo jar based on the netLogoVersion setting in your build.sbt.
  • Configuring sbt to attach the appropriate manifest to jars as part of the package task.
  • Packaging your extension and any jar dependencies into a .zip file which can be distributed to users. Alternatively, you may elect to have it relocate the .jar and any dependencies into the project root.

Since the CCL uses the NetLogo extension plugin, it will always be up-to-date with the latest build steps necessary for using an extension without requiring user intervention.

Tutorial

Let's write an extension that provides a single reporter called first-n-integers.

first-n-integers will take a single numeric input n and report a list of the integers 0 through n - 1. (Of course, you could easily do this just in NetLogo; it's only an example.)

1. Create extension folder

Since an extension is a folder with several items, we first need to create our folder. In this example, it is called example. We will be doing all of our work in that folder. We will also want to create a src/main/java sub-folder to hold our Java code.

2. Write primitives

The primitives are implemented as one or more Java classes. The .java files for these classes should be put in the src/main/java sub-folder.

A command performs an action; a reporter reports a value. To create a new command or reporter, create a class that implements the interface org.nlogo.api.Command or org.nlogo.api.Reporter, which extend org.nlogo.api.Primitive.

DefaultReporter requires that we implement:

Object report (Argument args[], Context context)
  throws ExtensionException;

Since our reporter takes an argument, we also implement:

Syntax getSyntax();

Here's the implementation of our reporter, in a file called src/main/java/IntegerList.java:

import org.nlogo.api.*;
import org.nlogo.core.Syntax;
import org.nlogo.core.SyntaxJ;

public class IntegerList implements Reporter {
  // take one number as input, report a list
  public Syntax getSyntax() {
    return SyntaxJ.reporterSyntax(
    new int[] {Syntax.NumberType()}, Syntax.ListType());
  }
  public Object report(Argument args[], Context context)
      throws ExtensionException {
    // create a NetLogo list for the result
    LogoListBuilder list = new LogoListBuilder();
    int n ;
    // use typesafe helper method from 
    // org.nlogo.api.Argument to access argument
    try {
      n = args[0].getIntValue();  
    }
    catch(LogoException e) {
      throw new ExtensionException( e.getMessage() ) ;
    }
    if (n < 0) {
    // signals a NetLogo runtime error to the modeler
    throw new ExtensionException
      ("input must be positive");
    }
    // populate the list. note that we use Double objects; NetLogo
    // numbers are always Doubles
    for (int i = 0; i < n; i++) {
      list.add(Double.valueOf(i));
    }
    return list.toLogoList();
  }
}

Notes:

  • The number objects we put in the list are Doubles, not Integers. All numbers used as NetLogo values must be of type Double, even if they happen to have no fractional part.
  • To access arguments, use org.nlogo.api.Argument's typesafe helper methods, such as getDoubleValue().
  • Throw org.nlogo.api.ExtensionException to signal a NetLogo runtime error to the modeler.

A Command is just like a Reporter, except that reporters implement Object report(...) while commands implement void perform(...).

2. Write a ClassManager

Each extension must include, in addition to any number of command and reporter classes, a class that implements the interface org.nlogo.api.ClassManager. The ClassManager tells NetLogo which primitives are part of this extension. In simple cases, extend the abstract class org.nlogo.api.DefaultClassManager, which provides empty implementations of the methods from ClassManager that you aren't likely to need.

Here's the class manager for our example extension, src/main/java/SampleExtension.java:

import org.nlogo.api.*;

public class SampleExtension extends DefaultClassManager {
  public void load(PrimitiveManager primitiveManager) {
    primitiveManager.addPrimitive(
      "first-n-integers", new IntegerList());
  }
}

addPrimitive() tells NetLogo that our reporter exists and what its name is.

3. Write a Manifest

The extension must also include a manifest. The manifest is a text file which tells NetLogo the name of the extension and the location of the ClassManager.

The manifest must contain three tags:

  • Extension-Name, the name of the extension.
  • Class-Manager, the fully-qualified name of a class implementing org.nlogo.api.ClassManager.
  • NetLogo-Extension-API-Version, the version of NetLogo Extension API for which this JAR is intended. If a user opens the extension with NetLogo that has a different Extension API version, a warning message is issued. To tell which version of the Extension API your NetLogo supports, choose the "About NetLogo" item in the "Help" menu. Or, you can launch the NetLogo.jar with the --extension-api-version argument.

Note that the API version is rarely the same as the NetLogo version. The NetLogo 6.0 release will have an API version of 6.0, but NetLogo 6.0.1 and quite possible NetLogo 6.1 are also likely to have the API version 6.0 as well. This version number will increment when a backwards-incompatible change is introduced to the org.nlogo.api or org.nlogo.core package.

Here's a manifest for our example extension, manifest.txt:

Manifest-Version: 1.0
Extension-Name: example
Class-Manager: SampleExtension
NetLogo-Extension-API-Version: 6.0

The NetLogo-Extension-API-Version line should match the actual version of NetLogo Extension API you are using.

Make sure even the last line ends with a newline character.

4. Create a JAR

If you're using the NetLogo sbt plugin, you can have sbt create the extension package for you by running sbt package. You will then find a .zip file in the project root, which can be unzipped in the NetLogo extensions directory to install the extension. If you would like to build the jar by hand, the instructions are as follows:

To create an extension's JAR file, first compile your classes as usual, either from the command line or using an IDE.

Important: You must add NetLogo.jar (from the NetLogo distribution) to your classpath when compiling, and the lib directory must be accessible in the same location as NetLogo.jar.

Each of our sample extensions on GitHub includes a command-line build, invoked with the sbt package command.

Here's an example of how compiling your extension might look from the command line if you aren't using sbt or make:

$ mkdir -p classes     # create the classes subfolder if it does not exist
$ javac -classpath NetLogo.jar -d classes src/main/java/IntegerList.java src/main/java/SampleExtension.java

You will need to change the classpath argument to point to the NetLogo.jar file from your NetLogo installation. For example, on Mac OS X you'd do:

javac -classpath "/Applications/NetLogo 6.0-BETA2/Java/netlogo-6.0.0-BETA2.jar" -d classes src/main/java/IntegerList.java src/main/java/SampleExtension.java

This command line will compile the .java and put the .class files in the classes subfolder.

Then create a JAR containing the resulting class files and the manifest. For example:

$ jar cvfm example.jar manifest.txt -C classes .

For information about manifest files, JAR files and Java tools, see Oracle's Java information.

5. Use your extension in a model

To use our example extension, put the example folder in the NetLogo extensions folder, or in the same directory as the model that will use the extension. At the top of the Code tab write:

extensions [example]

Now you can use example:first-n-integers just like it was a built-in NetLogo reporter. For example, select the Interface tab and type in the Command Center:

observer> show example:first-n-integers 5
observer: [0 1 2 3 4]

Scala Tutorial

Now let's rewrite the extension in Scala.

If you are using Scala, you'll need to make sure you are using Scala 2.12.x. (Any version where the first two parts are 2.12). Other versions such as 2.10 or 2.11 will not work.

1. Create extension folder

Let's start with a new folder called, example-scala. Similar to the Java example, create a src/main/scala sub-folder.

2. Write primitives and a Class Manager

We'll put all of the source code in one file. Here's the implementation of our reporter, and our ClassManager, in a file called src/main/scala/IntegerList.scala:

import org.nlogo.api._
import org.nlogo.api.ScalaConversions._
import org.nlogo.core.Syntax._

class SampleScalaExtension extends DefaultClassManager {
  def load(manager: PrimitiveManager) {
    manager.addPrimitive("first-n-integers", new IntegerList)
  }
}

class IntegerList extends Reporter {
  override def getSyntax = reporterSyntax(Array(NumberType), ListType)
  def report(args: Array[Argument], context: Context): AnyRef = {
    val n = try args(0).getIntValue
    catch {
      case e: LogoException =>
    throw new ExtensionException(e.getMessage)
    }
    if (n < 0)
      throw new ExtensionException("input must be positive")
    (0 until n).toLogoList
  }
}

Mostly this is a straightforward, line-by-line translation of the Java version.

One difference is worth noting. In the Java version, we explicitly converted ints to Double objects. As previously mentioned, all numbers used as NetLogo values must be of type Double, even if they happen to have no fractional part. In the Scala version we leverage implicit conversions to do this work for us. We do so by importing org.nlogo.api.ScalaConversions._, which provides us with two new methods via implicit conversions. The first is toLogoList, which converts Scala Seqs to LogoLists as seen in: (0 until n).toLogoList. The second is toLogoObject, which converts any supported Scala value to the appropriate NetLogo type. The conversions provided by toLogoObject are:

  • from scala.Boolean, java.lang.Boolean to java.lang.Boolean
  • from scala.Char, java.lang.Character to String
  • from scala.Byte, java.lang.Byte to java.lang.Double
  • from scala.Short, java.lang.Short to java.lang.Double
  • from scala.Int, java.lang.Integer to java.lang.Double
  • from scala.Float, java.lang.Float to java.lang.Double
  • from scala.Long, java.lang.Long to java.lang.Double
  • from scala.Double to java.lang.Double
  • from scala.Seq to org.nlogo.api.LogoList
  • any already valid recognized type (e.g. String, ExtensionObject) passes through unaltered
  • anything else results in an error

The conversions to LogoList are recursive. Nested collections in the input will be converted to nested LogoLists in which all elements have been converted by toLogoObject. ExtensionObjects, on the other hand, are not recursed into.

Using the toLogoObject conversion is simple. Just call the method on an an Any. Example: 7.toLogoObject

3. Create a JAR

If you're using the NetLogo sbt plugin, you can have sbt create the extension package for you by running sbt package. You will then find a .zip file in the project root, which can be unzipped in the NetLogo extensions directory to install the extension. If you would like to build the jar by hand, the instructions are as follows:

Important: As when compiling Java, you must add NetLogo.jar (from the NetLogo distribution) to your classpath when compiling.

Here's an example of how compiling your extension might look from the command line:

$ mkdir -p classes     # create the classes subfolder if it does not exist
$ scalac -classpath NetLogo.jar -d classes src/main/scala/IntegerList.java

You will need to change the classpath argument to point to the NetLogo.jar file from your NetLogo installation. This command line will compile the .java and put the .class files in the classes subfolder.

Then create a JAR containing the resulting class files and the manifest exactly as was done with the Java classes. For example:

$ jar cvfm example-scala.jar manifest.txt -C classes .

Note that NetLogo includes scala-library.jar, so you do not need to include scala-library.jar alongside your extension jar.

Extension development tips

Instantiation

Your class manager is instantiated in a fresh JVM classloader at the time a model using the extension is loaded.

This is done so that extensions are unloadable, so that when you open a new model, the extensions used by the previous model can be unloaded and the memory they used reclaimed. (The JVM does not allow unloading particular classes; you can only unload an entire classloader at once.)

Classpath

Don't forget to include NetLogo.jar in your class path when compiling. This is the most common mistake made by new extension authors. (If the compiler can't find NetLogo.jar, you'll get error messages about classes in the org.nlogo.api package not being found. If the lib directory isn't in the same location as NetLogo.jar, you'll get errors about other classes not being found.)

If you use sbt or maven, adding the NetLogo jar can be done through your build tool. NetLogo jars from 5.3.1 and later are hosted on bintray. You can view the bintray page for the relevant artifacts here. The bintray page also includes example code for adding NetLogo.jar as a dependency. Note that if you use the NetLogo extension sbt plugin that the NetLogo dependency will be added to your build automatically.

Debugging extensions

There are special NetLogo primitives to help you as you develop and debug your extension. These are considered experimental and may be changed at a later date. (That's why they have underscores in their name.)

  • print __dump-extensions prints information about loaded extensions
  • print __dump-extension-prims prints information about loaded extension primitives
  • __reload-extensions forces NetLogo to reload all extensions the next time you compile your model. Without this command, changes in your extension JAR will not take effect until you open a model or restart NetLogo.

Language Tests

You can run language test from within your extension's sbt session. The NW-Extension uses this. See its build.sbt for an example.

Benchmarking

Similarly, you can run benchmarks from within your extension's sbt session. If you setup language tests for the extension, then just put your benchmark models in the models/test/benchmarks subfolder of your extension's root folder. Then you can run them with:

test:run-main org.nlogo.headless.HeadlessBenchmarker BenchmarkName

Third party JARs

If your extension depends on code stored in a separate JAR, copy the extra JARs into the extension's directory. Whenever an extension is imported, NetLogo makes all the JARs in its folder available to the extension.

If you plan to distribute your extension to other NetLogo users, make sure to provide installation instructions.

Documenting your extension

Note that there is no way for the modeler to get a list of commands and reporters provided by an extension, so it's important that you provide adequate documentation.

Conclusion

Don't forget to consult the NetLogo API Specification for full details on these classes, interfaces, and methods.

You may also find it useful to consult the NetLogo source code on GitHub.

The extensions facility is not yet complete. The API doesn't include everything you might expect to be present. Some facilities exist but are not yet documented. If you don't see a capability you want, please let us know. Do not hesitate to contact us with questions, as we may be able to find a workaround or provide additional guidance where our documentation is thin.

Hearing from users of this API will also allow us to appropriately focus our efforts for future releases. We are committed to making NetLogo flexible and extensible, and we very much welcome your feedback.

Building Pre-Made Extensions with sbt on Windows

At times you may want to download extension code from GitHub and build it yourself (possibly for the purpose of making modifications). The following method will work for extensions built with sbt package, such as any of the bundled NetLogo Extensions. These are attached to the NetLogo GitHub and can be found via searching for NetLogo repositories containing the word "Extension". A few good extensions to look at include:

First, navigate to the extension that you want to download on GitHub. Download the repository as a ZIP file and unzip it. If you wish to modify the code, open the /src directory and modify the text files inside using your text editor or IDE of choice.

Before building the extension, you require certain software packages. Firstly, you need sbt, or the Scala Build Tool, which will allow you to build the extension. Download and run the MSI from the sbt website. This will add sbt to your system path, allowing easy access to running sbt from any Command Prompt or Windows Powershell.

You also need javac.exe to compile the package. To get it, download the Java Development Kit. Once you download the JDK, add the directory which contains javac.exe to your system path: it should be something like C:\Program Files\Java\jdk1.8.0_102\jre\bin. Depending on your OS and the version of java downloaded, it may be in "Program Files (x86)" instead. Be sure the you add contains javac.exe.

Finally, download Git from http://git-scm.com/download/win, and add the directory containing git.exe (C:\Program Files (x86)\Git\bin or similar) to your system path.

You are now prepared to build the extension. Open the Command Prompt (search for "cmd" in the Start Menu). Enter the command "cd [directory]" to navigate to the extension (for example, "cd C:\GoGo Extension"). Then enter "sbt package." Your code should compile at this time. If there are errors in your code, the command prompt will output error messages.

You should end up with a .jar file in the extension directory. Copy this .jar file to the appropriate directory in NetLogo (e.g. C:\Program Files (x86)\NetLogo 6.0\Java\extensions\gogo). The next time you run NetLogo, you should have the new version of the extension.