Using Java

Use the Java API to interact with Kanzi through a platform-agnostic Java API

When you want to create applications that are tightly integrated with the Android APIs, use the Java programming language with the Kanzi Android framework. See Developing with the Kanzi Android framework and Kanzi Java API reference.

Using properties

Java API classes contain static members for built-in property types such as Node.OpacityProperty. To read and write these properties:

// Access a node from the view.
Node2D node = view.getRoot();

// Set property.
node.setProperty(Node.OpacityProperty, 0.5);

// Get property.
float opacity = node.getProperty(Node.OpacityProperty);

To access a property by name, create an instance of DynamicPropertyType or AbstractPropertyType using a full name of a property type:

// Use DynamicPropertyType to access a property type when the type is known.
DynamicPropertyType<Boolean> visibleProperty =
    new DynamicPropertyType("Node.Visible", Boolean.class);
node.setProperty(visibleProperty, false);

// Use AbstractPropertyType to access a property type when the type is not known.
AbstractPropertyType visibleProperty = new AbstractPropertyType("Node.Visible");
node.setProperty(visibleProperty, false);

See Property system and Reference for showing Kanzi Engine plugin custom types in Kanzi Studio.

Using messages

Java API classes contain static members for built-in message types such as Button2D.ClickMessage. To dispatch these messages:

// Access a node from the view.
Node2D node = view.getRoot();

node.dispatchMessage(Button2D.ClickMessage, new MessageArguments());

To dispatch a message by name, create an instance of AbstractMessageType using the full name of a message type:

// Get reference to a message type.
AbstractMessageType messageType = new AbstractMessageType("Message.MyMessage");
DynamicPropertyType<Boolean> argumentProperty =
    new DynamicPropertyType("MyMessage.Argument", Boolean.class);

// Access a node from the view.
Node2D node = view.getRoot();

// Construct message arguments.
MessageArguments args = new MessageArguments();

// Set message arguments.
args.setArgument(argumentProperty, false);

// Send the message.
node.dispatchMessage(messageType, args);

To perform some functionality when a node receives a message, add a message handler to the node:

// Add message subscription.
button.addMessageHandler(Button2D.ToggleStateMessage,
             new Node.MessageSubscriptionFunction()
             {
                @Override
                public void handle(MessageArguments messageArguments)
                {
                    // Extract arguments.
                    int toggleState = messageArguments.getArgument(Button2D.ToggleStateProperty);
                }
             });

See Using messages.

Object lifetime

The ObjectRef class controls object lifetime in Java. The native backing for the object is not reclaimed until the ObjectRef is either explicitly closed, or allowed to be garbage collected. Native objects can also have other native owners that are managed by the native portion of the Kanzi Engine. Therefore an ObjectRef does not exclusively control the lifetime of the object.

It is good practice to close all ObjectRef instances when the application no longer needs them.

Kanzi automatically wraps all newly created objects in an ObjectRef instance as the sole owner.

When you remove all references to an object, and no native references exist, the object is immediately reclaimed. If application code attempts to access the object in this state, Kanzi throws a StaleObjectException to indicate that the object is no longer valid. To prevent this, you can wrap the object in a new ObjectRef instance before that object becomes stale. The new instance extends the lifetime and prevents the native object from being reclaimed.

// Create an EmptyNode2D node.
ObjectRef<EmptyNode2D> nodeRef = EmptyNode2D.create(domain, "My Empty Node");

// Access the EmptyNode2D instance from within the ObjectRef.
EmptyNode2D node = nodeRef.get();

// Use the node

// Close the ObjectRef.
nodeRef.close();

// If you try to access the node variable after closing its ObjectRef,
// Kanzi can throw a StaleObjectException.
node.setName("New Name");

To automatically manage the ObjectRef instance, use the Java try-with-resources statement:

// Create an EmptyNode2D node.
try (ObjectRef<EmptyNode2D> nodeRef = EmptyNode2D.create(domain, "My Empty Node"))
{
    // Access the EmptyNode2D instance from within the ObjectRef.
    EmptyNode2D node = nodeRef.get();

    // Use the node

    // Kanzi automatically closes the nodeRef on the next line.
}

Exceptions

Kanzi uses checked exceptions to indicate recoverable errors. For example, if the resource is not found when you call the Node.acquireResource method, Kanzi throws the ObjectNotFoundException exception.

Kanzi Engine exceptions thrown by native code are packagaged into instances of NativeException. It is not expected for Java code to handle these, instead allow them to propagate up the stack so that Kanzi retranslates it to a native exception that Kanzi Engine can handle them.

Platform-specific code

Java plugins can run on multiple platforms, such as Kanzi Android framework and in the Kanzi Studio Preview. Make sure that the application runs platform-specific code only when it is running on a compatible platform. For example, call the Android API only when the application is running in an Android environment.

To detect the environment, use the getPlatformContext() method:

if (Platform.isAndroid())
{
    // Initialize the code path for when the plugin runs on Android.
    Object context = domain.getPlatformContext();
}
else
{
    // Initialize the code path for when the plugin runs in the Kanzi Studio Preview.
}

Creating a custom node type

You can use Java to extend the functionality of Kanzi Engine by creating a custom node type.

To create a custom node type:

  1. Create a class in a Java project.

    class MyNode2D extends Node2D
    {
    }
    
  2. Kanzi requires these items for each custom node:

    • Metaclass. The metaclass must be present in this form:

      @Metadata
      public static final Metaclass metaclass = new Metaclass("Example.MyNode2D", MyNode2D.class);
      

      You can provide additional metadata with the @EditorInfo annotation. See Reference for showing Kanzi Engine plugin custom types in Kanzi Studio.

    • Construction. Construction of derived types must follow this pattern with a static create method and a private constructor with the specific arguments. The implementation of the create method is usually a call to the createDerived method on the type that you are extending.

    static public ObjectRef<MyNode2D> create(Domain domain, String name)
    {
        return Node2D.createDerived(domain, name, metaclass);
    }
    
    private MyNode2D(Domain domain, long nativeNode, Metaclass metaclass)
    {
        super(domain, nativeNode, metaclass);
    }
    
    • (Optional) Custom initialization. During the constructor Kanzi does not fully initialize custom Kanzi Java objects. Place custom initialization code in the initialize() method.

    @Override
    protected void initialize()
    {
        super.initialize();
    
        // Add custom initialization code after calling super.initialize()
    }
    
  3. (Optional) Create custom property types.

    @Metadata
    @EditorInfo(displayName = "Custom Property")
    public static final PropertyType<String> = CustomProperty =
        new PropertyType("MyNode2D.Custom", String.class);
    
    // Properties can also have an automatically generated name.
    // In this case 'MyNode2D.Other'.
    @Metadata
    public static final PropertyType<String> = OtherProperty = new PropertyType(String.class);
    
  4. (Optional) Create custom message types.

    • Define the message type.

      @Metadata
      public static final MessageType ExampleMessage =
          new MessageType("Message.MyNode2D.Example", MessageRouting.MessageRoutingBubbling);
      
    • Add a message handler.

      // Add message subscription.
      addMessageHandler(MyNode2D.ExampleMessage,
                new Node.MessageSubscriptionFunction()
                {
                   @Override
                   public void handle(MessageArguments messageArguments)
                   {
                       ...
                   }
                });
      
  5. (Optional) Define custom node behavior by overriding relevant methods.

    @Override
    protected void arrangeOverride(Vector2 actualSize)
    {
       super.arrangeOverride(actualSize);
    
       ...
    }
    

Creating a custom resource manager protocol

To load resources using Java, implement a custom resource manager protocol.

ResourceManager resourceManager = domain.getResourceManager();
resourceManager.registerProtocolHandler("MyProtocol",
    new ResourceManager.ProtocolHandler()
    {
        @Override
        public Result handle(ResourceManager resourceManager, String url, String protocol,
                             String hostname, String path) {
            if (some condition)
            {
                // Load a resource synchronously.
                Resource resource = ...;
                return new Result(resource);
            }
            else
            {
                // Use load task for asynchronous loading.
                return new ResourceManager.LoadTask() {
                    @Override
                    public void loadFunction(ResourceManager resourceManager) {
                        // Perform thread-independent loading.
                    }

                    @Override
                    public void finishFunction(ResourceManager resourceManager) {
                        // Create and store the resource.
                    }

                    @Override
                    public Resource getResult() {
                        // Return the created resource.
                    }
                };
            }
        }
    });

In case of failure, the resource protocol handle can throw an exception, which the Kanzi Resource Manager handles.

Creating a data source

A data source enables you to access third-party data from your Kanzi application.

To create a data source:

  1. Create the DataSource class.

    import com.rightware.kanzi.DataSource;
    
    class MyDataSource extends DataSource
    {
        @Metadata
        public static final Metaclass metaclass =
            new Metaclass("MyPlugin.MyDataSource", MyDataSource.class);
    
        public static ObjectRef<MyDataSource> create(Domain domain, String name)
        {
            return DataSource.createDerived(domain, name, MyDataSource.metaclass);
        }
    
        private MyDataSource(Domain domain, long nativeHandle, Metaclass metaclass)
        {
            super(domain, nativeHandle, metaclass);
        }
    
        @Override
        protected void initialize()
        {
            super.initialize();
    
            createData();
        }
    }
    
  2. Add the required members, accessors, and the method to initialize the data.

    private ObjectRef<DataObject> mDataRef;
    private DataObject mData;
    private DataObjectBool mMyBool;
    
    public DataObject getData()
    {
        return mData;
    }
    
    private void createData()
    {
        Domain domain = getDomain();
        mDataRef = DataObject.create(domain, "root");
        mData = mDataRef.get();
    
        // Add the remaining structure of the DataSource
        try (ObjectRef<DataObjectBool> objRef = DataObjectBool.create(domain, "MyBoolean", true))
        {
            mMyBool = objRef.get();
            mData.addChild(mMyBool);
        }
    }
    

To learn more about the data sources in Kanzi, see Data sources.

Creating Kanzi Engine Java plugins

You can combine custom Java types into a Kanzi Engine plugin.

To create a Kanzi Engine Java plugin:

  1. Create a class for the plugin.

    class TestPlugin extends Plugin
    {
        @Override
        public String getName()
        {
            return "mytestplugin";
        }
    }
    

    Tip

    The string returned from getName() must match the name of the Jar file.

  2. Add metadata for the custom types.

    Register the metadata of all the custom Kanzi types in that plugin.

    @Override
    public void registerClasses(MetaclassRegistry metaclassRegistry)
    {
        metaclassRegistry.registerClass(ExampleNode2D.class);
    }
    

    Tip

    You can override an existing type by using the overrideClass instead of registerClass().

  3. Load the plugin in your Android application.

    For example, load the plugin during the initialization of an Activity.

    @Override
    protected void onCreate(Bundle icicle)
    {
        ...
    
        try (KanziRuntime.Reference runtimeRef = KanziRuntime.acquire(this))
        {
            runtimeRef.Get().getDomain().registerPlugin(new TestPlugin());
        }
    
        ...
    }
    

To learn more about creating Kanzi Engine Java plugins, see Creating a Java Kanzi Engine plugin manually.