Questions re Additional Java Files

User avatar
honki-bobo
Posts: 83
Joined: Sat Nov 09, 2019 1:18 pm

Re: Questions re Additional Java Files

Post by honki-bobo » Sat May 23, 2020 9:05 pm

The preparation was this:

I have implemented an "adapter class" in Eclipse with interface, abstract class and a stub implementation as part of my library that basically implements all methods of a VoltageModule, but does not extend it. The library also includes voltage.jar in the classpath, since I need a lot of Voltage classes.

Then I have made a template project in VMD that already has a background image, lables, my company logo and name at the bottom, etc. This template project includes my library as external Jar file and has just a few lines of code that basically call all methods in my adapter class. For instance

Code: Select all

@Override
public boolean Notify( VoltageComponent component, ModuleNotifications notification, double doubleValue, long longValue, int x, int y, Object object )
{
	// add your own code here
	return this.moduleAdapter.Notify(component, notification, doubleValue, longValue, x, y, object);
}
When I create a new module, first thing I do is I save this template project in a new folder and give it the name of the module I want to create, let's say Sample Delay. In VMD I do the layout of all GUI components and also set their parameters, like min, max, default value, visibility and all that stuff.

Back in Eclipse I create a new project for Sample Delay and include my library and the voltage.jar in the classpath of that project. Then I create the same package that is used in VMD for Sample Delay in the src folder, in my case it's com.monkeybusinessaudio.sampledelay and create the class VMA_SampeDelay.java in this package (VMA for VoltageModuleAdapter). This class extends the abstract adapter class which in turn implements the adapter's interface, so Eclipse takes care of all the method creation for me. After the class is created I save it and add it as external Java file back in the VMD project.

I call the adapter class' constructor in the Initialize() method in the VMD project. My adapter class also has a addComponent method to which I pass all GUI components. It gets called for each component right after the constructor call. This method puts each component into a hashmap according to its class and uses the componentName attribute as a key for the object in the hashmap. I have taken care of this in the abstract class, so I only had to implement this once. This gives me a very convenient way to access all Voltage Components that I have added to the module in VMD. Finally, I call the Initialize() method of my adapter object and that's basically it.

From there on I almost only work in Eclipse for the GUI behaviour and all audio DSP code. When I want to compile my code, I save the java file, press CTRL+A and CTRL+C in Eclipse, go to VMD and select my VMA java file in the editor, press CTRL+A, wait a few seconds for VMD to digest this (yes, it somehow needs its time, haven't tested V2 yet), press CTRL+V and then hit CTRL+F5 to run and test it. Then I go back to Eclipse... hack, hack, hack... CTRL+A,CTRL+C, back to VMD, CTRL+A, wait, CTRL+V, CTRL+F5... check, check, check... back to Eclipse and so on and so forth.

That is basically my workflow. It might seem a bit complicated and over-engineered, but (at least with V1 of VMD) it is much less of a hazzle and way more comfortable to work in a fully featured IDE that has been developed by a lot of people over a very long time specifically for this purpose. No offense intended against CA! VMD is absolutely great for the GUI part of things, it's just that I am way more comfortable working in Eclipse. Also, I haven't tried V2 yet. It might as well be that VMD got more responsive with V2 and also offers some IDE features that I just don't want to miss. But since I still have a few projects that I'm working on I do not dare to upgrade just now.
Image
Monkey Business Audio
Modules - Forum

terrymcg
Posts: 50
Joined: Mon Apr 08, 2019 12:35 am

Re: Questions re Additional Java Files

Post by terrymcg » Sat Jun 20, 2020 5:01 am

AndyMac wrote:
Sun May 10, 2020 6:28 am
The reason I was using their logging API was so that it also shows up in the debug window when testing, and so it can be switched on or off through VM settings.

I'm going to setup a return object for these so that I can pick up errors and log them in the main code. A bit of a simplistic workaround, and the 'scaffolding' in the main code is a bit more, but not too much, but means that the more complex shared code is still separated properly.
A module I'm working on now has so much of its "brain" in an external JAR that what had been a long term desire for a solution to the "logging from an external JAR" problem became an urgent need. While it may be trivial exercise for the more Java-minded among us, it took some head-sratching for me to come up with a workable solution so I thought I'd share it here, for discussion and/or to save others some of the hassle :)

Like the solution Andrew mentions, this has feet on both sides, in VMD and in the external JAR.

First, on the 'external JAR' side of things (via Eclipse in my case), I defined a 'VoltageLogger' interface as follows:

Code: Select all

package com.blackcloudindustries.log;

public interface VoltageLogger {
	public void logMsg(Object obj, String msg);
	public void logErrorMsg(Object obj, String msg);
	public void logMsg(String msg);
	public void logErrorMsg(String msg);
}

And then in (external) Classes that I'm interested in getting messages from, I add these 'extra' bits:

Code: Select all

import com.blackcloudindustries.log.VoltageLogger;	// adjust package name as needed!

public class MyExternalClass {

	VoltageLogger logger = null;                    // logger instance
	
	// connect the global log receiver
	public void connectLogger(VoltageLogger logger) {
		if (this.logger == null) {                             // only connect once
			this.logger = logger;                          // set the logger instance
			this.logger.logMsg(this, this.toString());     // optional "hello world" message, adjust as needed
		}
	}
	
	public void methodOne() {
		if (logger != null)                     // if we're connected to the logger...
			logger.logMsg(this, String.format("A message from 0x%08x", System.identityHashCode(this)));	// insert your message here
			
		// do something useful
	}
	
	public void methodTwo() {
		if (logger != null)                      // if we're connected to the logger...
			logger.logMsg(this, String.format("B message from 0x%08x", System.identityHashCode(this)));	// insert your message here!
	
		// do something useful
	}
}

Finally, the 'User Imports', 'Initialize()' and 'User Variables & Functions' sections of the project (in VMD) get the following additions:

Code: Select all

// Add your own imports here
import com.blackcloudindustries.log.VoltageLogger;		// adjust package name as needed!
[ . . . ]
//-------------------------------------------------------------------------------
@Override
public void Initialize()
{
	globalLogger = new LogReciever();
	anInstanceOfMyExternalClass = new MyExternalClass();            // can be inside the JAR instead, for deeply buried stuff
	anInstanceOfMyExternalClass.connectLogger(globalLogger);	// can be inside the JAR instead, for deeply buried stuff
[ . . . ]
// Add your own variables and functions here
MyClass anInstanceOfMyExternalClass;
LogReciver globalLogger;

public class LogReciever implements VoltageLogger {
	// decorated log message
	public void logMsg(Object obj, String msg) {
		
		// if the message isn't wrapped in "{}", add 'em (cheap test)
		if (msg.substring(0, 1).equals("{"))
	  		Log(getRef(obj) + " " + msg );
		else
			Log(getRef(obj) + " { " + msg + " }");	
	}
	
	// decorated error message
	public void logErrorMsg(Object obj, String msg) {
		LogError(getRef(obj) + " " + msg);		
	}
	
	// naked log message
	public void logMsg(String msg) {
     		Log(msg);
   	}
   	
	// naked error message
   	public void logErrorMsg(String msg) {
		LogError(msg);
   	}
}
	
// return the passed object's class and method names
private String getRef(Object obj) {
	String method = "";

	for (StackTraceElement e : Thread.currentThread().getStackTrace()) {
		if (e.getClassName().equals(obj.getClass().getName())) {
			method = e.getMethodName();
			break;
		}
	}

	// adjust returned string format to suit your needs
	return obj.getClass().getSimpleName() + "[" + method + "]";
//	return obj.getClass().getSimpleName() + "::" + method;
}
The 'getRef' code is adapted from a StackOverflow answer (of course ;)), and still needs a bit of work (it has trouble finding some method names). It's only decorative and can be omitted/avoided via the 'naked' methods


It doesn't matter if your instance is inside the JAR or not, as long as you can find somewhere to call 'connectLogger(globalLogger)' for your interesting class(es), you'll get messages like this in VMD's Output pane, from inside the external JAR, with the class and method name being automagically appended via the 'decorated' logMsg(obj, msg) method:

Code: Select all

BasicClock[connectLogger] { this:0x6405bf03 knob:703 led { null } basicclock { this:0x6405bf03 tics:0 period:30000 running:false observers { 0x52c50f04 (0x437322be) } } }
Note that elsewhere I've overridden the default toString() method for the class in this example. And the example illustrates another problem with 'getRef' - BasicClock is actually the super-class of the object sending the message. But, for the moment it's good enough for me and means I'm not typing class/method names all the time :)

It's obviously at least as costly as calling Log/LogError directly, so using it in busy segments of code will significantly distort module performance, but so do Log/LogError and there are times when there's no other way. Don't want the messages? Just omit the call(s) to connectLogger().

I'd be interested in hearing any suggested improvements and/or if you find it useful (it's been a life-saver for my module :) ).

Cheers,
--
Terry McG

Post Reply