Cache tricks
Several years ago, long before Developer Community Portal was launched, I published a series of Caché tricks at one of Czech web sites. In this article, I’m posting translated version of one of them.
Capturing output of someone else’s methods or routines
Suppose you, or someone else created a useful method or routine, that was producing some computation that you’d like to benefit from, but the routine was writing output to process principal device.
You would like to use the data, but you need it not written to a device, but assigned to a variable. And, for any reason, you can’t modify the code. What can you do?
Well, there is a solution to it. A bit tricky, but works very well.
Let’s take an example from not so far past. In Caché 2013 we introduced enhancement to ZEN framework, a class %ZEN.Auxiliary.jsonProvider, that allowed to pass JSON objects between client (browser) and server and vice versa. Unfortunately, the %ObjectToJSON() method was implemented in just that way - it was writing JSON serialized object representation to output device (the TCP / HTTP channel).
So, how to deal with the problem? There is something we call mnemonic space, these spaces are available with Caché IO devices. We can uses these mnemonic spaces to open IO devices. However, online documentation is very concise about mnemonic spaces and is not providing much practical details of use.
Let’s have a look at the following code, that demonstrates one use case.
set pObject=##class(%ZEN.proxyObject).%New() // any class can be here
set pObject.error="my-error-1"
set pObject.description="Error number one"
Set tInitIO = $IO
// we MUST use %ISCJSONStream variable used by mnemonic space ^%ZEN.Auxiliary.jsonProvider.1 as output redirection container
set %ISCJSONStream=##class(%Stream.TmpCharacter).%New()
use tInitIO::("^%ZEN.Auxiliary.jsonProvider.1") // this routine – here as mnemonic space – is created during %ZEN.Auxiliary.jsonProvider class compilation
do ##class(%Library.Device).ReDirectIO(1)
$$$THROWONERROR(tSC,##class(%ZEN.Auxiliary.jsonProvider).%ObjectToJSON(pObject))
If ##class(%Library.Device).ReDirectIO(0) Use tInitIO
do %ISCJSONStream.Rewind()
// display result
w %ISCJSONStream.Read()
// result: { "error":"my-error-1", "description":"Error number one" }
You can, alternatively, write your own redirection routine
// save as MAC routine, e.g. test.mac
// launch by from terminal: d run^test()
run()
Set tInitIO = $IO
// we MUST use variable called %Stream that is used by a mnemonic space created by our routine
set %Stream=##class(%Stream.TmpCharacter).%New()
use tInitIO::("^"_$zname)
do ##class(%Library.Device).ReDirectIO(1)
// call routine that we want to redirect
do demo()
If ##class(%Library.Device).ReDirectIO(0) Use tInitIO
do %Stream.Rewind()
// show result
set res=%Stream.Read() write res
// result: Hi there!
q
demo() public
{
w !,"Hi there!"
}
redirects()
#; Public entry points for redirection
wstr(s) Do %Stream.Write(s) Quit
wchr(a) Do %Stream.Write($char(a)) Quit
wnl Do %Stream.Write($char(13,10)) Quit
wff Do %Stream.Write($char(13,10,13,10)) Quit
wtab(n) New chars Set $piece(chars," ",n+1)="" Do %Stream.Write(chars) Quit
rstr(len,time) Quit ""
rchr(time) Quit ""
Please note that redirects() procedure is not using procedural block and uses public variable %Stream that needs to be initialized before calling code to what we apply redirection.
Hope you find this helpful.
Dan
PS: When I searched community portal, I saw an article that was referring to the redirection, too. However, it was in a different context and was not explaining the power of such redirection.
Maybe it is worth to mention that there is much simpler solution now in 2016 for the object-to-JSON conversion. It is a tiny part of the new powerful DocDM concept (http://localhost:57772/csp/docbook/DocBook.UI.Page.cls?KEY=GJSON_intro#G...).
try this:
set pObject=##class(%Object).%New() ///this is one of the ways to create a DocDM object
pObject.error="my-error-1"
set pObject.description="Error number one"
set string=pObject.$toJSON() ///here you call a system method "toJSON()" via dot dollar syntax
write string
........
However, the output capturing can be useful in other situations (or with the older versions of Cache).
Well, whilst it is true that we have much better ways of handling JSON today, this article mentions is by pure coincidence, driven by the fact that I had that problem some 3 years ago and when searching for solution, I found write redirection code withing the class mentioned in the article.
BTW: there is a %Device class available, that wraps redirection code, so no need to write your own mac code anymore.
That's useful.
Is there any particular reason to use % variable here? I think local variable would be enough.
Sure, thats needs because this variable should be available in any place for this process, just because this code executes in context where works any output commands like write