Unable to store Espresso Failure Screenshots on android 10 devices - kotlin

Since the improved privacy changes on Android 10 Android 10 Privacy changes, I've noticed that my screenshot failure test watcher rule in Kotlin, that extends the Espresso BasicScreenCaptureProcessor no longer saves failure screenshots because I am using the deprecated getExternalStoragePublicDirectory on Android 10.
The concept currently implement is very similar to How to take screenshot at the point where test fail in Espresso?
class TestScreenCaptureProcessor : BasicScreenCaptureProcessor() {
init {
this.mDefaultScreenshotPath = File(
File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
"Failure_Screenshots"
).absolutePath
)
}
As seen in other posts, I could use the getInstrumentation().getTargetContext().getApplicationContext().getExternalFilesDir(DIRECTORY_PICTURES)
that would store the file in - /sdcard/Android/data/your.package.name/files/Pictures directory, but the connectedAndroidTest gradle task deletes the app at the end along with it the folders listed above.
I wondered if anyone else had come across something similar and has considered a way of storing failure screenshots on Android 10, in a location that will not be deleted when the tests have finished running & somewhere that Espresso Instrumentation tests can access.
My UI tests are run on a variety of devices, so need a generic way of storing the files is required to suite all models.

After a lot of research I found a way to save screenshots in kotlin based on the SDK version using MediaStore.
/**
* storeFailureScreenshot will store the bitmap based on the SDK level of the
* device. Due to security improvements and changes to how data can be accessed in
* SDK levels >=29 Failure screenshots will be stored in
* sdcard/DIRECTORY_PICTURES/Failure_Screenshots.
*/
fun storeFailureScreenshot(bitmap: Bitmap, screenshotName: String) {
val contentResolver = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext.contentResolver
// Check SDK version of device to determine how to save the screenshot.
if (android.os.Build.VERSION.SDK_INT >= 29) {
useMediaStoreScreenshotStorage(
contentValues,
contentResolver,
screenshotName,
SCREENSHOT_FOLDER_LOCATION,
bitmap
)
} else {
usePublicExternalScreenshotStorage(
contentValues,
contentResolver,
screenshotName,
SCREENSHOT_FOLDER_LOCATION,
bitmap
)
}
}
/**
* This will be used by devices with SDK versions >=29. This is to overcome scoped
* storage considerations now in the SDK version listed to help limit file
* clutter. A Uniform resource identifier (Uri) is used to insert bitmap into
* the gallery using the contentValues previously specified. The contentResolver
* provides application access to content model to access and publish data in a
* secure manner, using MediaStore collections to do so. Files will
* be stored in sdcard/Pictures
*/
private fun useMediaStoreScreenshotStorage(
contentValues: ContentValues,
contentResolver: ContentResolver,
screenshotName: String,
screenshotLocation: String,
bitmap: Bitmap
) {
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, "$screenshotName.jpeg")
contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + screenshotLocation)
val uri: Uri? = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
if (uri != null) {
contentResolver.openOutputStream(uri)?.let { saveScreenshotToStream(bitmap, it) }
contentResolver.update(uri, contentValues, null, null)
}
}
/**
* Method to access internal storage on a handset with SDK version below 29.
* Directory will be in sdcard/Pictures. Relevant sub directories will be created
* & screenshot will be stored as a .jpeg file.
*/
private fun usePublicExternalScreenshotStorage(
contentValues: ContentValues,
contentResolver: ContentResolver,
screenshotName: String,
screenshotLocation: String,
bitmap: Bitmap
) {
val directory = File(
Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES + screenshotLocation).toString())
if (!directory.exists()) {
directory.mkdirs()
}
val file = File(directory, "$screenshotName.jpeg")
saveScreenshotToStream(bitmap, FileOutputStream(file))
val values = contentValues
contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
}
/**
* Assigns the assignments about the Image media including, image type & date
* taken. Content values are used so the contentResolver can interpret them. These
* are applied to the contentValues object.
*/
val contentValues = ContentValues().apply {
put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis())
}
/**
* Compresses the bitmap object to a .jpeg image format using the specified
* OutputStream of bytes.
*/
private fun saveScreenshotToStream(bitmap: Bitmap, outputStream: OutputStream) {
outputStream.use {
try {
bitmap.compress(Bitmap.CompressFormat.JPEG, 50, it)
} catch (e: IOException) {
Timber.e("Screenshot was not stored at this time")
}
}
}
Used in conjunction with a TestWatcher that takes a screenshot on a UI test failure. This is then added to the test class as a rule.
private val deviceLanguage = Locale.getDefault().language
/**
* Finds current date and time & is put into format of Wed-Mar-06-15:52:17.
*/
fun getDate(): String = SimpleDateFormat("EEE-MMMM-dd-HH:mm:ss").format(Date())
/**
* ScreenshotFailureRule overrides TestWatcher failed rule and instead takes a
* screenshot using the UI Automation takeScreenshot method and the
* storeFailureScreenshot to decide where to store the bitmap when a failure
* occurs.
*/
class ScreenshotFailureRule : TestWatcher() {
override fun failed(e: Throwable?, description: Description) {
val screenShotName = "$deviceLanguage-${description.methodName}-${getDate()}"
val bitmap = getInstrumentation().uiAutomation.takeScreenshot()
storeFailureScreenshot(bitmap, screenShotName)
}
}
file is stored in sdcard/Pictures/Failure_Screenshots with a name of
en-testMethodName-Day-Month-Date-HH_MM_SS
Rule called using:
val screenshotFailureRule = ScreenshotFailureRule()

Related

Getting Profile Photo from Microsoft Graph API for Android

I have recently started working on an android project that uses Microsft authentication and graph API. By following this:
https://learn.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-android
I am able to authenticate and get some data from graph API. Now I want to load the profile photo of Microsoft account in the app. For this purpose, I used ProfilePhoto object with a call back as follows:
graphClient
.me()
.photo()
.buildRequest()
.get(new ICallback<ProfilePhoto>() {
#Override
public void success(ProfilePhoto profilePhoto) {
Log.d(TAG, "Found " + profilePhoto.getRawObject().toString());
}
#Override
public void failure(ClientException ex) {
displayError(ex);
}
});
Here profilePhoto.getRawObject() returns a json file like:
{"#odata.context":"https://graph.microsoft.com/v1.0/$metadata#users('hanzla_hawk%40outlook.com')/photo/$entity","#odata.mediaContentType":"image/png","#odata.mediaEtag":"W/\"8050a078da935403cf67163f23f1baace5c7abf3ff784452cb08c38660308a83\"","id":"default","height":256,"width":256}
With this Json, how can I load the image into an image view? I have previous experience with Picasso and other fake apis. But right now I just dont know what should I pass in the Picasso to load image from this json.
I have just make a call to get a profile photo to show on an Android app using Jetpack Compose views in Kotlin. To achieve it I have followed your question and this tutorial:
https://learn.microsoft.com/en-us/graph/tutorials/android
You almost got it. Just add .content() call between .me().photo() and .buildRequest().
This is my code on my project to get the photo content:
// GET /me/photo/$value (logged in user)
fun getUserPhoto(
onGotPhoto: (ImageBitmap) -> Unit,
onGotError: (Exception) -> Unit
) {
mClient!!.me().photo().content().buildRequest()
.async
.thenAccept { inputStream ->
val bitmap = BitmapFactory.decodeStream(inputStream).asImageBitmap()
onGotPhoto.invoke(bitmap)
}
.exceptionally { processError(it, onGotError) }
}

While running Karate(1.0.1) tests from Spock, System property that was set in mock ends up undefined in karate.properties['message']

In karate version 0.9.5 I was able to use System.setProperty('message', message) during a mock invocation. Then that property was available inside a feature using karate.properties['message']. I have upgraded to version 1.0.1 and now result of karate.properties['message'] results in undefined
Spock Test code
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ApiTestRunnerSpec extends Specification {
#LocalServerPort
private int port
#SpringBean
MessageLogger messageLogger = Mock()
def "setup"() {
System.out.println("Running on port: " + port)
System.setProperty("server.port", "" + port)
}
def "Run Mock ApiTest"() {
given:
System.setProperty('foo', 'bar')
when:
Results results = Runner.path("classpath:").tags("~#ignore").parallel(5)
then:
results != null
1 * messageLogger.logMessage(_ as String) >> { String message ->
assert message != null
System.setProperty("message", message)
}
}
}
Controller
#RestController
public class MessageController {
#Autowired private MessageLogger messageLogger;
#GetMapping("/message")
public String message() {
String message = "Important Message";
messageLogger.logMessage(message);
return message;
}
}
MessageLogger
#Component
public class MessageLogger {
public void logMessage(String message) {
System.out.println(message);
}
}
karate-config.js
function fn() {
karate.configure('connectTimeout', 10000);
karate.configure('readTimeout', 10000);
karate.configure('ssl', true);
var config = {
localUrl: 'http://localhost:' + java.lang.System.getProperty('server.port'),
};
print('localUrl::::::::::', config.localUrl);
return config;
}
Feature
#mockMessage
#parallel=true
Feature: Test Message
Background:
* url localUrl
Scenario: GET
Given path '/message'
When method get
Then status 200
* print 'foo value ' + karate.properties['foo']
* print 'message value ' + karate.properties['message']
0.9.5
2021-04-28 15:07:51.819 (...) [print] **foo value bar**
2021-04-28 15:07:51.826 (...) [print] **message value Important Message**
1.0.1
2021-04-28 14:36:58.566 (...) [print] **foo value bar**
2021-04-28 14:36:58.580 (...) [print] **message value undefined**
Link to project on github
I cloned your project and noticed a few outdated things (Groovy, Spock and GMaven+ versions). Upgrading them did not change the outcome, I can still reproduce your problem.
A also noticed that in your two branches the POM differs in more than just the Karate version number, also the dependencies differ. If I use the ones from the 1.0.1 branch, tests do not work under 0.9.5 anymore. So I forked your project and sent you two pull requests for each branch with a dependency setup working identically for both Karate versions. Now the branches really just differ in the Karate version number:
https://github.com/kriegaex/spock-karate-example/compare/karate-0.9.5...kriegaex:karate-1.0.1
BTW, for some reason I had to compile your code running JDK 11, JDK 16 did not work. GMaven+ complained about Java 16 groovy class files (bytecode version 60.0), even though GMaven+ should have used target level 11. No idea what this is about. Anyway, on Java 11 I can reproduce your problem. As the Spock version is identical for both branches, I guess the problem is within Karate itself. I recommend to open an issue there, linking to your GitHub project (after you have accepted my PRs). Spock definitely sets the system property, I have added more log output into the stubbing closure order to verify that. Maybe this is an issue concerning how and when Karate communicates with Spock.
Update: Peter Thomas suggested in his answer to store the value to be transferred to the feature in a Java object and access that one from the feature after the Spock test has set it. I guess, he means something like this:
https://github.com/kriegaex/spock-karate-example/commit/ca88e3da
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ApiTestRunnerSpec extends Specification {
#LocalServerPort
private int port
#SpringBean
MessageLogger messageLogger = Mock() {
1 * logMessage(_ as String) >> { String message ->
assert message != null
MessageHolder.INSTANCE.message = message
}
}
def "setup"() {
System.out.println("Running on port: " + port)
System.setProperty("server.port", "" + port)
}
def "Run Mock ApiTest"() {
given:
Results results = Runner
.path("classpath:")
.systemProperty("foo", "bar")
.tags("~#ignore")
.parallel(5)
expect:
results
}
static class MessageHolder {
public static final MessageHolder INSTANCE = new MessageHolder()
private String message
private MessageHolder() {}
String getMessage() {
return message
}
void setMessage(String message) {
this.message = message
}
}
}
#mockMessage
#parallel=true
Feature: Test Message
Background:
* url localUrl
Scenario: GET
Given path '/message'
When method get
Then status 200
* print 'foo value ' + karate.properties['foo']
* def getMessage =
"""
function() {
var MessageHolder = Java.type('com.example.spock.karate.ApiTestRunnerSpec.MessageHolder');
return MessageHolder.INSTANCE.getMessage();
}
"""
* def message = call getMessage {}
* print 'message value ' + message
Update 2: This is the implementation of Peter's second idea to simply access Java system properties via JS. So I simplified the working, but unnecessarily complicated version with the message holder singleton, eliminating it again:
https://github.com/kriegaex/spock-karate-example/commit/e235dd71
Now it simply looks like this (similar to the original Spock specification, only refactored to be a bit less verbose):
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ApiTestRunnerSpec extends Specification {
#LocalServerPort
private int port
#SpringBean
MessageLogger messageLogger = Mock() {
1 * logMessage(_ as String) >> { String message ->
assert message != null
System.setProperty('message', message)
}
}
def "setup"() {
System.out.println("Running on port: " + port)
System.setProperty("server.port", "" + port)
}
def "Run Mock ApiTest"() {
expect:
Runner.path("classpath:").systemProperty("foo", "bar").tags("~#ignore").parallel(5)
}
}
The only important change is in the Karate feature:
#mockMessage
#parallel=true
Feature: Test Message
Background:
* url localUrl
Scenario: GET
Given path '/message'
When method get
Then status 200
* print 'foo value ' + karate.properties['foo']
* def getMessage = function() { return Java.type('java.lang.System').getProperty('message'); }
* print 'message value ' + getMessage()
The Runner "builder" has a .systemProperty() method which is recommended.
Please refer: https://github.com/intuit/karate/wiki/1.0-upgrade-guide#improved-test-suite-builder
So this should work. Else as I said in the comments, please submit a way to replicate.
Results results = Runner.path("classpath:")
.systemProperty("foo", "bar")
.tags("~#ignore").parallel(5)
EDIT: so I can confirm that karate.properties is made immutable at the time the test-suite starts.
So here are the 3 options:
change your test strategy so that all dynamic params are resolved before you start
instead of karate.properties[] do the old-school java.lang.System.getProperty('foo') call in Karate / JS, I'm pretty sure that will work
use a Java singleton to hold shared state for your test-runner and karate-feature

Trying to save dstream chepoints in a location on amazon s3

I want to save chekpoint tests in a location on amazon S3, this is the part of my scala code on DStream,using below format but getting the error..
Exception in thread "main" java.lang.IllegalArgumentException: AWS Access Key ID and Secret Access Key must be specified as the username or password (respectively) of a s3n URL, or by setting the fs.s3n.awsAccessKeyId or fs.s3n.awsSecretAccessKey properties (respectively).
Code:
val creatingFunc = { ()=>
// Create a StreamingContext
val ssc = new StreamingContext(sc, Seconds(batchIntervalSeconds))
val ggsnLines = ssc.fileStream[LongWritable, Text, TextInputFormat]("C:\\Users\\Mbazarganigilani\\Documents\\RA\\GGSN\\Files1",filterF,false)
val ccnLines= ssc.fileStream[LongWritable, Text, TextInputFormat]("C:\\Users\\Mbazarganigilani\\Documents\\RA\\CCN\\Files1",filterF,false)
val probeLines= ssc.fileStream[LongWritable, Text, TextInputFormat]("C:\\Users\\Mbazarganigilani\\Documents\\RA\\Probe\\Files1",filterF,false)
val ggssnArrays=ggsnLines.map(x=>(x._1,x._2.toString())).filter(!_._2.contains("ggsnIPAddress")).map(x=>(x._1,x._2.split(",")))
ggssnArrays.foreachRDD(s=> {
s.collect().take(10).foreach(u=>println(u._2.mkString(",")))
})
ssc.remember(Minutes(1)) // To make sure data is not deleted by the time we query it interactively
ssc.checkpoint("s3n://probecheckpoints/checkpoints")
println("Creating function called to create new StreamingContext")
newContextCreated = true
ssc
}
def main(args:Array[String]): Unit =
{
//the minremeberduration is set to read the previous files from the directory
//the kyroclasses serialization needs to be enabled for the filestream
if (stopActiveContext) {
StreamingContext.getActive.foreach { _.stop(stopSparkContext = false) }
}
// Get or create a streaming context
val hadoopConfiguration:Configuration=new Configuration()
hadoopConfiguration.set("fs.s3n.impl", "org.apache.hadoop.fs.s3native.NativeS3FileSystem")
hadoopConfiguration.set("fs.s3n.awsAccessKeyId", "AKIAIOPSJVBDTEUHUJCQ")
hadoopConfiguration.set("fs.s3n.awsSecretAccessKey", "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")
//val ssc = StreamingContext.getActiveOrCreate(creatingFunc)
val ssc=StreamingContext.getActiveOrCreate("s3n://probecheckpoints/SparkCheckPoints",creatingFunc,hadoopConfiguration,false)
if (newContextCreated) {
println("New context created from currently defined creating function")
} else {
println("Existing context running or recovered from checkpoint, may not be running currently defined creating function")
}
// Start the streaming context in the background.
ssc.start()

How to set the name of a snapshot file

I am developing a J2ME application to run on my W595s Sony Ericsson mobile phone.
My application uses the JSR 135 Mobile Media API and the JSR 234 Advanced Multimedia Supplements API.
My application displays a Form.
The camera video is displayed in a Form's item.
The Form has a command.
The application takes a snapshot when the user activates the command.
The snapshot file is saved to the Picture directory on the memory stick.
Here is the Form's commandAction event listener :
public void commandAction(Command arg0, Displayable arg1) {
m_snapshotControl.setDirectory("e:/Picture");
m_snapshotControl.setFilePrefix("AC");
m_snapshotControl.setFileSuffix(".JPG");
int[]resolutions = m_cameraControl.getSupportedStillResolutions();
int maxValue = (resolutions.length / 2) - 1;
m_cameraControl.setStillResolution(maxValue);
m_snapshotControl.start(1);
}
I ran my application 2 times.
The Picture directory did not contain any snapshot file before the first run.
I did the following actions during each run :
I activated the command
I answered yes to the following rights requesting dialogs :
Allow the application to read user data ?
Allow the application to write user data ?
Allow the application to read user data ?
Allow the application to write user data ?
Allow the application to shoot with the camera ?
Allow the application to read user data ?
Allow the application to write user data ?
The AC0000.jpg snapshot file was created after the first run.
The AC0000.jpg picture file was replaced after the second run.
I do not want my application to replace snapshots taken during past runs.
How can I set the name of a snapshot file before taking the snapshot ?
Is it possible to set the string in between the prefix and the suffix ?
Any help will be greatly appreciated
I haven't tried on this particular range of phone but how about this:
private int iSnapshotCounter = 0;
public void commandAction(Command arg0, Displayable arg1) {
m_snapshotControl.setDirectory("e:/Picture");
m_snapshotControl.setFilePrefix("AC");
m_snapshotControl.setFileSuffix( (++iSnapshotCounter) + ".JPG");
int[]resolutions = m_cameraControl.getSupportedStillResolutions();
int maxValue = (resolutions.length / 2) - 1;
m_cameraControl.setStillResolution(maxValue);
m_snapshotControl.start(1);
}
If that works, you can then decide to include the date and time (say, to the second) in the file name.
Of course, if setFileSuffix() won't allow you to specify more than a file extension, you can try to use the same trick on the prefix string.
You may also need to use JSR-75 to figure out what files already exist in the folder.
I have added a PlayerListener to the SnapshotControl's Player.
My playerUpdate method renames the created Snapshot file when the SHOOTING_STOPPED event occurs.
The new name is made up of the current date and time's parts.
The format of the new name is YYYYMMDD_HHMMSS.jpg.
Here is the playerUpdate method :
public void playerUpdate(Player arg0, String arg1, Object arg2) {
if (arg1.equalsIgnoreCase(SnapshotControl.SHOOTING_STOPPED)) {
FileConnection fconn = null;
try {
fconn = (FileConnection)Connector.open("file:///e:/Picture/" + (String)arg2);
Date now = new Date();
Calendar calendar = Calendar.getInstance();
calendar.setTime(now);
StringBuffer buffer = new StringBuffer();
buffer.append(calendar.get(Calendar.YEAR));
int month = calendar.get(Calendar.MONTH);
month++;
if (month < 10) {
buffer.append(0);
}
buffer.append(month);
int day = calendar.get(Calendar.DAY_OF_MONTH);
if (day < 10) {
buffer.append(0);
}
buffer.append(day);
buffer.append("_");
int hour = calendar.get(Calendar.HOUR_OF_DAY);
if (hour < 10) {
buffer.append(0);
}
buffer.append(hour);
int minute = calendar.get(Calendar.MINUTE);
if (minute < 10) {
buffer.append(0);
}
buffer.append(minute);
int second = calendar.get(Calendar.SECOND);
if (second < 10) {
buffer.append(0);
}
buffer.append(second);
buffer.append(".jpg");
fconn.rename(buffer.toString());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
fconn.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}

Pros & cons bean vs SSJS?

I was trying to build a bean that always retrieves the same document ( a counter document), gets the current value, increment it and save the document with the new value. Finally it should return the value to the calling method and that would get me a new sequential number in my Xpage.
Since the Domino objects cannot be serialized or singleton'ed what's the benefit creating a bean doing this, over creating a SSJS function doing the exact same thing?
My bean must have calls to session, database, view and document, which then will be called every time.
The same within the SSJS-function except for session and database.
Bean:
public double getTransNo() {
try {
Session session = ExtLibUtil.getCurrentSession();
Database db = session.getCurrentDatabase();
View view = db.getView("vCount");
view.refresh();
doc = view.getFirstDocument();
transNo = doc.getItemValueDouble("count");
doc.replaceItemValue("count", ++transNo);
doc.save();
doc.recycle();
view.recycle();
} catch (NotesException e) {
e.printStackTrace();
}
return transNo;
}
SSJS:
function getTransNo() {
var view:NotesView = database.getView("vCount");
var doc:NotesDocument = view.getFirstDocument();
var transNo = doc.getItemValueDouble("count");
doc.replaceItemValue("count", ++transNo);
doc.save();
doc.recycle();
view.recycle();
return transNo;
}
Thank you
Both pieces of code are not good (sorry to be blunt).
If you have one document in your view, you don't need a view refresh which might be queued behind a refresh on another view and be very slow. Presumably you are talking about a single sever solution (since replication of the counter document would for sure lead to conflicts).
What you do in XPages is to create a Java class and declare it as application bean:
public class SequenceGenerator {
// Error handling is missing in this class
private double sequence = 0;
private String docID;
public SequenceGenerator() {
// Here you load from the document
Session session = ExtLibUtil.getCurrentSession();
Database db = session.getCurrentDatabase();
View view = db.getView("vCount");
doc = view.getFirstDocument();
this.sequence = doc.getItemValueDouble("count");
this.docID = doc.getUniversalId();
Utils.shred(doc, view); //Shred currenDatabase isn't a good idea
}
public synchronized double getNextSequence() {
return this.updateSequence();
}
private double updateSequence() {
this.sequence++;
// If speed if of essence I would spin out a new thread here
Session session = ExtLibUtil.getCurrentSession();
Database db = session.getCurrentDatabase();
doc = db.getDocumentByUnid(this.docID);
doc.ReplaceItemValue("count", this.sequence);
doc.save(true,true);
Utils.shred(doc);
// End of the candidate for a thread
return this.sequence;
}
}
The problem for the SSJS code: what happens if 2 users hit that together? At least you need to use synchronized there too. Using a bean makes it accessible in EL too (you need to watch out not to call it too often). Also in Java you can defer the writing back to a different thread - or not write it back at all and in your class initialization code read the view with the actual documents and pick the value from there.
Update: Utils is a class with static methods:
/**
* Get rid of all Notes objects
*
* #param morituri = the one designated to die, read your Caesar!
*/
public static void shred(Base... morituri) {
for (Base obsoleteObject : morituri) {
if (obsoleteObject != null) {
try {
obsoleteObject.recycle();
} catch (NotesException e) {
// We don't care we want go get
// rid of it anyway
} finally {
obsoleteObject = null;
}
}
}
}