How to serialize a map that contains Image objects? - javafx

I'm creating an image gallery, which contains photo albums and each album contains pictures. These items are stored in a HashMap like this
HashMap<Album, ArrayList<Picture>> albums=new HashMap<>();
the problem starts when trying to serialize the map, because every Picture object contains an Image Object, so I can pick this Image and create an ImageView more easily for my app, the Picture constructor looks like this:
public Picture(String name,String place, String description, Image image)
I always get this exception:
java.io.NotSerializableException: javafx.scene.image.Image
Is there any way to make my Pictures serializable?

You need to customize the serialization of Picture. To customize serialization of an object you use the following two methods:
void readObject(ObjectInputStream) throws ClassNotFoundException, IOException
void writeObject(ObjectOutputStream) throws IOException
These methods can have any access modifier but are typically(?) private.
If you have the following class:
public class Picture implements Serializable {
private final String name;
private final String place;
private final String description;
private transient Image image;
public Picture(String name, String place, String description, Image image) {
this.name = name;
this.place = place;
this.description = description;
this.image = image;
}
public String getName() {
return name;
}
public String getPlace() {
return place;
}
public String getDescription() {
return description;
}
public Image getImage() {
return image;
}
}
You have at least three options regarding the serialization of the Image.
Serialize the location of the image.
private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException {
in.defaultReadObject();
String url = (String) in.readObject();
if (url != null) {
image = new Image(url);
}
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObjet();
out.writeObject(image == null ? null : image.getUrl());
}
The Image#getUrl() method was added in JavaFX 9.
Serialize the pixel data of the Image via the PixelReader. When deserializing you'll use a PixelWriter.
private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException {
in.defaultReadObject();
if (in.readBoolean()) {
int w = in.readInt();
int h = in.readInt();
byte[] b = new byte[w * h * 4];
in.readFully(b);
WritableImage wImage = new WritableImage(w, h);
wImage.getPixelWriter().setPixels(0, 0, w, h, PixelFormat.getByteBgraInstance(), b, 0, w * 4);
image = wImage;
}
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeBoolean(image != null);
if (image != null) {
int w = (int) image.getWidth();
int h = (int) image.getHeight();
byte[] b = new byte[w * h * 4];
image.getPixelReader().getPixels(0, 0, w, h, PixelFormat.getByteBgraInstance(), b, 0, w * 4);
out.writeInt(w);
out.writeInt(h);
out.write(b);
}
}
WARNING: Serializing the pixel data in this way is saving the image in an uncompressed format. Images can be quite large which may cause problems for you when using this approach.
This is dependent on the PixelReader being available, which is not always the case. If you read the documentation of Image#getPixelReader() you'll see (emphasis mine):
This method returns a PixelReader that provides access to read the pixels of the image, if the image is readable. If this method returns null then this image does not support reading at this time. This method will return null if the image is being loaded from a source and is still incomplete {the progress is still <1.0) or if there was an error. This method may also return null for some images in a format that is not supported for reading and writing pixels to.
Outside still-loading and errors, some non-exhaustive testing indicates animated GIFs do not have an associated PixelReader.
Serialize the actual image file (I don't recommend this one, reasons below).
private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException {
in.defaultReadObject();
if (in.readBoolean()) {
byte[] bytes = new byte[in.readInt()];
in.readFully(bytes);
image = new Image(new ByteArrayInputStream(bytes));
}
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeBoolean(image != null);
if (image != null) {
byte[] bytes;
try (InputStream is = new URL(image.getUrl()).openStream()) {
bytes = is.readAllBytes();
}
out.writeInt(bytes.length);
out.write(bytes);
}
}
This assumes the Image wasn't loaded from a resource (or at least that the URL has a scheme).
However, as I said I don't recommend this approach. For one, it only allows you to serialize the Picture instance one time because the original URL is lost after deserialization. You could get around this by storing the location separately but then you might as well use option #1. Then there's also the fact you open up an InputStream and read from it during serialization; this could be quite unexpected behavior for a developer serializing Picture instances.
Some notes:
There may be room for optimizations in the code above.
Options #1 and #3 don't take the requested width and height of the image into account. This may lead to having a larger image in memory after deserialization. You can modify the code to fix this.
Your Picture class seems like a model class. If that's the case, it may be better to simply store the location of the image in a field rather than the Image itself (this can also make customizing the serialization unneeded); then have other code responsible for loading the actual Image (e.g. a cache) based on the location stored in Picture. Either that or allow for lazily loading the Image in the Picture instance.
The point is to avoid loading Images when you don't need them. For instance, what if part of your UI only wants to display a list of available pictures by name. If you have thousands of Pictures you'll want to avoid loading thousands of Images as you could very easily run out of memory (at even just a few dozen images).

Related

Save object list in Xamarin Essentials

my first question is here
however since I was advised that questions should not change the original matter I created a new one.
I am saving user settings and I would like to save it in the list, I have had a look on setting by James however I found that that its not possible to save it in the list. So ia have decided to use Xamarin Essentials.
First I tried to save only a string value, which after some struggle I managed to work out and now I am trying to save an object
static void AddToList(SettingField text)
{
var savedList = new List<SettingField>(Preference.SavedList);
savedList.Add(text);
Preference.SavedList = savedList;
}
private void ExecuteMultiPageCommand(bool value)
{
var recognitionProviderSettings = new RecognitionProviderSettings
{SettingFields = new List<SettingField>()};
var set = new SettingField()
{
ProviderSettingId = "test",
Value = "test"
};
AddToList(set);
NotifyPropertyChanged("IsMultiPage");
}
and then the sterilization and des
public static class Preference
{
private static SettingField _settingField;
public static List<SettingField> SavedList
{
get
{
//var savedList = Deserialize<List<string>>(Preferences.Get(nameof(SavedList), "tesr"));
var savedList = Newtonsoft.Json.JsonConvert.DeserializeObject<SettingField>(Preferences.Get(nameof(SavedList), _settingField)) ;
SavedList.Add(savedList);
return SavedList ?? new List<SettingField>();
}
set
{
var serializedList = Serialize(value);
Preferences.Set(nameof(SavedList), serializedList);
}
}
static T Deserialize<T>(string serializedObject) => JsonConvert.DeserializeObject<T>(serializedObject);
static string Serialize<T>(T objectToSerialize) => JsonConvert.SerializeObject(objectToSerialize);
}
}
But Preferences.Get doesn't take object, is there any other way how can I save my setting to a object list? Please advise
I would recommend you to use SecureStorage. You can save your strings only into it. So the place where you have serilized your object as json. Just convert your json to string with .ToString() and save it into secure storage.
You may continue saving your serialized json object as string in Shared preferences but it is recommended to use SecureStorage Instead.

XStream OutOfMemoryError when generating xml

I have a class defined to store configuration data for my application. I want to save the instances of this out to xml and use XStream for this. But I keep getting outofmemory errors when I try to write an instance.
Here is my class definition:
public class Eol_Target_Variable {
String name;
String alias;
long value;
long default_val;
int size;
int scaling;
int div;
Boolean read_access;
Boolean write_access;
public Eol_Target_Variable(String arg_name, String arg_alias, int arg_value, int arg_size, int arg_scaling,int arg_div)
{
name = arg_name;
alias = arg_alias;
value = arg_value;
default_val = 0;
scaling = arg_scaling;
div = arg_div;
size = arg_size;
read_access = true;
write_access = true;
}
/**
* #return the name
*/
public String getName() {
return name;
}
/**
* #param name the name to set
*/
public void setName(String name) {
this.name = name;
}
...etc for all standard getters and setters
Here is my handler for exporting a single object to xml
public void importConfiguration() {
XStream xstream = new XStream(new DomDriver());
Eol_Target_Variable myvar = new Eol_Target_Variable("jamie", "xtracold", 1977, 16, 1, 1);
String myxml = xstream.toXML(myvar);
System.out.print(myxml);
}
Every time I get "Exception in thread "JavaFX Application Thread" java.lang.OutOfMemoryError: Java heap space" thrown. I cannot see why such a simple class would throw the out of memory error. I have managed to output simple String objects using XStream so the library is working, it is just this custom class that seems to cause problems.
I have also tried to increase the heap allocated at startup with the VM arguments -Xms512m -Xmx1024m but that makes no difference.
Thanks
Jamie
Here is the new class declaration
#XStreamAlias("targetVar")
public class Eol_Target_Variable {
String name;
String alias;
long value;
#XStreamAlias("default")
long default_val;
int size;
int scaling;
int div;
#XStreamOmitField
Node node;
#XStreamOmitField
Boolean read_access;
#XStreamOmitField
Boolean write_access;
public Eol_Target_Variable(String arg_name, String arg_alias, int arg_value, int arg_size, int arg_scaling,int arg_div, Node arg_node)
{
name = arg_name;
alias = arg_alias;
value = arg_value;
default_val = 0;
scaling = arg_scaling;
div = arg_div;
size = arg_size;
node = arg_node;
read_access = true;
write_access = true;
}
I also used a different parser as the basic DOMParser never handled the massive amount of data. When I changed to StaxDriver was at least able to see the streams of text in the debug output as XStream traversed the whole scene graph.
XStream xstream = new XStream(new StaxDriver());
xstream.processAnnotations(Eol_Target_Variable.class);
I don't pretend to fully understand why declaring the class inlined causes problems, but I can reason as to why asking XStream to parse a complete Node might cause issues.
If anyone has any experience with XStream and complex data structures, that are declared inside a JavaFX app I would welcome their input.

how to get text from textview using espresso

I want get text string shown in a textview in LinearLayout. can espresso do that? If not, is there other way to do that or can I use android api in espresso test case? I am using API 17 18 or newer, espresso 1.1(It should be the latest one.). I have no clue about this. Thanks.
The basic idea is to use a method with an internal ViewAction that retrieves the text in its perform method. Anonymous classes can only access final fields, so we cannot just let it set a local variable of getText(), but instead an array of String is used to get the string out of the ViewAction.
String getText(final Matcher<View> matcher) {
final String[] stringHolder = { null };
onView(matcher).perform(new ViewAction() {
#Override
public Matcher<View> getConstraints() {
return isAssignableFrom(TextView.class);
}
#Override
public String getDescription() {
return "getting text from a TextView";
}
#Override
public void perform(UiController uiController, View view) {
TextView tv = (TextView)view; //Save, because of check in getConstraints()
stringHolder[0] = tv.getText().toString();
}
});
return stringHolder[0];
}
Note: This kind of view data retrievers should be used with care. If you are constantly finding yourself writing this kind of methods, there is a good chance, you're doing something wrong from the get go. Also don't ever access the View outside of a ViewAssertion or ViewAction, because only there it is made sure, that interaction is safe, as it is run from UI thread, and before execution it is checked, that no other interactions meddle.
If you want to check text value with another text, you can create Matcher. You can see my code to create your own method:
public static Matcher<View> checkConversion(final float value){
return new TypeSafeMatcher<View>() {
#Override
protected boolean matchesSafely(View item) {
if(!(item instanceof TextView)) return false;
float convertedValue = Float.valueOf(((TextView) item).getText().toString());
float delta = Math.abs(convertedValue - value);
return delta < 0.005f;
}
#Override
public void describeTo(Description description) {
description.appendText("Value expected is wrong");
}
};
}

How could I insert a string into the response stream anywhere I want?

There may be an easy way to do this but I can't see it...
I created a simple Http Module that starts a timer on the PreRequestHandler and stops the timer on the PostRequestHandler to calculate the time it took the page to load.
I then create some simple html and write my results to Response.Write. Since I'm doing this in the PostRequestHandler it's adding my results after the </html> tag. That's fine for testing but I need in a scenario where the page needs to validate.
I can't seem to figure out how I could manipulate the Response object to insert my results before the </body> tag. Response.Write and Response.Output.Write don't have that flexibility and I couldn't see a way to work with the Response as a string. Am I missing something easy?
To do this, you'd have to implement your own stream object and use that as a filter for your response.
For isntance:
public class TimerStream : Stream
{
private Stream inner { get; set; }
private StringBuilder responseHtml;
public TimerStream(Stream inputStream) {
inner = inputStream;
responseHtml = new StringBuilder();
// Setup your timer
}
/* Filter overrides should pass through to inner, all but Write */
public override void Write(byte[] buffer, int offset, int count)
{
string bufferedHtml = System.Text.UTF8Encoding.UTF8.GetString (buffer, offset, count);
Regex endTag = new Regex ("</html>", RegexOptions.IgnoreCase);
if (!endTag.IsMatch (bufferedHtml))
{
responseHtml.Append(bufferedHtml);
}
else
{
// insert timer html into buffer, then...
responseHtml.Append (bufferedHtml);
byte[] data = System.Text.UTF8Encoding.UTF8.GetBytes (responseHtml.ToString ());
inner.Write (data, 0, data.Length);
}
}
}
Then, in your HttpModule, you'd add this to your BeginRequest:
// Change the Stream filter
HttpResponse response = context.Response;
response.Filter = new TimerStream(context.Response.Filter);

How to deal with Number precision in Actionscript?

I have BigDecimal objects serialized with BlazeDS to Actionscript. Once they hit Actionscript as Number objects, they have values like:
140475.32 turns into 140475.31999999999998
How do I deal with this? The problem is that if I use a NumberFormatter with precision of 2, then the value is truncated to 140475.31. Any ideas?
This is my generic solution for the problem (I have blogged about this here):
var toFixed:Function = function(number:Number, factor:int) {
return Math.round(number * factor)/factor;
}
For example:
trace(toFixed(0.12345678, 10)); //0.1
Multiply 0.12345678 by 10; that gives us 1.2345678.
When we round 1.2345678, we get 1.0,
and finally, 1.0 divided by 10 equals 0.1.
Another example:
trace(toFixed(1.7302394309234435, 10000)); //1.7302
Multiply 1.7302394309234435 by 10000; that gives us 17302.394309234435.
When we round 17302.394309234435 we get 17302,
and finally, 17302 divided by 10000 equals 1.7302.
Edit
Based on the anonymous answer below, there is a nice simplification for the parameter on the method that makes the precision much more intuitive. e.g:
var setPrecision:Function = function(number:Number, precision:int) {
precision = Math.pow(10, precision);
return Math.round(number * precision)/precision;
}
var number:Number = 10.98813311;
trace(setPrecision(number,1)); //Result is 10.9
trace(setPrecision(number,2)); //Result is 10.98
trace(setPrecision(number,3)); //Result is 10.988 and so on
N.B. I added this here just in case anyone sees this as the answer and doesn't scroll down...
Just a slight variation on Frasers Function, for anyone who is interested.
function setPrecision(number:Number, precision:int) {
precision = Math.pow(10, precision);
return (Math.round(number * precision)/precision);
}
So to use:
var number:Number = 10.98813311;
trace(setPrecision(number,1)); //Result is 10.9
trace(setPrecision(number,2)); //Result is 10.98
trace(setPrecision(number,3)); //Result is 10.988 and so on
i've used Number.toFixed(precision) in ActionScript 3 to do this: http://livedocs.adobe.com/flex/3/langref/Number.html#toFixed%28%29
it handles rounding properly and specifies the number of digits after the decimal to display - unlike Number.toPrecision() that limits the total number of digits to display regardless of the position of the decimal.
var roundDown:Number = 1.434;
// will print 1.43
trace(roundDown.toFixed(2));
var roundUp:Number = 1.436;
// will print 1.44
trace(roundUp.toFixed(2));
I converted the Java of BigDecimal to ActionScript.
We had no choices since we compute for financial application.
http://code.google.com/p/bigdecimal/
You can use property: rounding = "nearest"
In NumberFormatter, rounding have 4 values which you can choice: rounding="none|up|down|nearest". I think with your situation, you can chose rounding = "nearest".
-- chary --
I discovered that BlazeDS supports serializing Java BigDecimal objects to ActionScript Strings as well. So if you don't need the ActionScript data to be Numbers (you are not doing any math on the Flex / ActionScript side) then the String mapping works well (no rounding weirdness). See this link for the BlazeDS mapping options: http://livedocs.adobe.com/blazeds/1/blazeds_devguide/help.html?content=serialize_data_2.html
GraniteDS 2.2 has BigDecimal, BigInteger and Long implementations in ActionScript3, serialization options between Java / Flex for these types, and even code generation tools options in order to generate AS3 big numbers variables for the corresponding Java ones.
See more here: http://www.graniteds.org/confluence/display/DOC22/2.+Big+Number+Implementations.
guys, just check the solution:
protected function button1_clickHandler(event:MouseEvent):void
{
var formatter:NumberFormatter = new NumberFormatter();
formatter.precision = 2;
formatter.rounding = NumberBaseRoundType.NEAREST;
var a:Number = 14.31999999999998;
trace(formatter.format(a)); //14.32
}
I ported the IBM ICU implementation of BigDecimal for the Actionscript client. Someone else has published their nearly identical version here as a google code project. Our version adds some convenience methods for doing comparisons.
You can extend the Blaze AMF endpoint to add serialization support for BigDecimal. Please note that the code in the other answer seems incomplete, and in our experience it fails to work in production.
AMF3 assumes that duplicate objects, traits and strings are sent by reference. The object reference tables need to be kept in sync while serializing, or the client will loose sync of these tables during deserialization and start throwing class cast errors, or corrupting the data in fields that don't match, but cast ok...
Here is the corrected code:
public void writeObject(final Object o) throws IOException {
if (o instanceof BigDecimal) {
write(kObjectType);
if(!byReference(o)){ // if not previously sent
String s = ((BigDecimal)o).toString();
TraitsInfo ti = new TraitsInfo("java.math.BigDecimal",false,true,0);
writeObjectTraits(ti); // will send traits by reference
writeUTF(s);
writeObjectEnd(); // for your AmfTrace to be correctly indented
}
} else {
super.writeObject(o);
}
}
There is another way to send a typed object, which does not require Externalizable on the client. The client will set the textValue property on the object instead:
TraitsInfo ti = new TraitsInfo("java.math.BigDecimal",false,false,1);
ti.addProperty("textValue");
writeObjectTraits(ti);
writeObjectProperty("textValue",s);
In either case, your Actionscript class will need this tag:
[RemoteClass(alias="java.math.BigDecimal")]
The Actionscript class also needs a text property to match the one you chose to send that will initialize the BigDecimal value, or in the case of the Externalizable object, a couple of methods like this:
public function writeExternal(output:IDataOutput):void {
output.writeUTF(this.toString());
}
public function readExternal(input:IDataInput):void {
var s:String = input.readUTF();
setValueFromString(s);
}
This code only concerns data going from server to client. To deserialize in the other direction from client to server, we chose to extend AbstractProxy, and use a wrapper class to temporarily store the string value of the BigDecimal before the actual object is created, due to the fact that you cannot instantiate a BigDecimal and then assign the value, as the design of Blaze/LCDS expects should be the case with all objects.
Here's the proxy object to circumvent the default handling:
public class BigNumberProxy extends AbstractProxy {
public BigNumberProxy() {
this(null);
}
public BigNumberProxy(Object defaultInstance) {
super(defaultInstance);
this.setExternalizable(true);
if (defaultInstance != null)
alias = getClassName(defaultInstance);
}
protected String getClassName(Object instance) {
return((BigNumberWrapper)instance).getClassName();
}
public Object createInstance(String className) {
BigNumberWrapper w = new BigNumberWrapper();
w.setClassName(className);
return w;
}
public Object instanceComplete(Object instance) {
String desiredClassName = ((BigNumberWrapper)instance).getClassName();
if(desiredClassName.equals("java.math.BigDecimal"))
return new BigDecimal(((BigNumberWrapper)instance).stringValue);
return null;
}
public String getAlias(Object instance) {
return((BigNumberWrapper)instance).getClassName();
}
}
This statement will have to execute somewhere in your application, to tie the proxy object to the class you want to control. We use a static method:
PropertyProxyRegistry.getRegistry().register(
java.math.BigDecimal.class, new BigNumberProxy());
Our wrapper class looks like this:
public class BigNumberWrapper implements Externalizable {
String stringValue;
String className;
public void readExternal(ObjectInput arg0) throws IOException, ClassNotFoundException {
stringValue = arg0.readUTF();
}
public void writeExternal(ObjectOutput arg0) throws IOException {
arg0.writeUTF(stringValue);
}
public String getStringValue() {
return stringValue;
}
public void setStringValue(String stringValue) {
this.stringValue = stringValue;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
}
We were able to reuse one of the available BigDecimal.as classes on the web and extended blazeds by sublassing from AMF3Output, you'll need to specify your own endpoint class in the flex xml files, in that custom endpoint you can insert your own serializer that instantiates an AMF3Output subclass.
public class EnhancedAMF3Output extends Amf3Output {
public EnhancedAMF3Output(final SerializationContext context) {
super(context);
}
public void writeObject(final Object o) throws IOException {
if (o instanceof BigDecimal) {
write(kObjectType);
writeUInt29(7); // write U290-traits-ext (first 3 bits set)
writeStringWithoutType("java.math.BigDecimal");
writeAMFString(((BigDecimal)o).toString());
} else {
super.writeObject(o);
}
}
}
as simple as that! then you have native BigDecimal support using blazeds, wooohoo!
Make sure your BigDecimal as3 class implements IExternalizable
cheers, jb
Surprisingly the round function in MS Excel gives us different values then you have presented above.
For example in Excel
Round(143,355;2) = 143,36
So my workaround for Excel round is like:
public function setPrecision(number:Number, precision:int):Number {
precision = Math.pow(10, precision);
const excelFactor : Number = 0.00000001;
number += excelFactor;
return (Math.round(number * precision)/precision);
}
If you know the precision you need beforehand, you could store the numbers scaled so that the smallest amount you need is a whole value. For example, store the numbers as cents rather than dollars.
If that's not an option, how about something like this:
function printTwoDecimals(x)
{
printWithNoDecimals(x);
print(".");
var scaled = Math.round(x * 100);
printWithNoDecimals(scaled % 100);
}
(With however you print with no decimals stuck in there.)
This won't work for really big numbers, though, because you can still lose precision.
You may vote and watch the enhancement request in the Flash PLayer Jira bug tracking system at https://bugs.adobe.com/jira/browse/FP-3315
And meanwhile use the Number.toFixed() work-around see :
(http://livedocs.adobe.com/flex/3/langref/Number.html#toFixed%28%29)
or use the open source implementations out there : (http://code.google.com/p/bigdecimal/) or (http://www.fxcomps.com/money.html)
As for the serialization efforts, well, it will be small if you use Blazeds or LCDS as they do support Java BigDecimal serialization (to String) cf. (http://livedocs.adobe.com/livecycle/es/sdkHelp/programmer/lcds/wwhelp/wwhimpl/common/html/wwhelp.htm?context=LiveDocs_Parts&file=serialize_data_3.html)
It seems more like a transport problem, the number being correct but the scale ignored. If the number has to be stored as a BigDecimal on the server you may want to convert it server side to a less ambiguous format (Number, Double, Float) before sending it.

Resources