BrowserFunction does not execute "JavaScript " code - javascript

Why does BrowserFunction does not execute my Javascript code?
Java Code:
public void createBrowserFunctions() {
new BrowserFunction(mapView_Browser, "appi") {
public Object function(Object[] arguments) {
syncCenter((Double) arguments[0], (Double) arguments[1]);
syncZoom((Double) arguments[2]);
syncRotation((Double) arguments[3]);
return null;
}
};
}
private void syncCenter(final Double latitude, final Double longitude) {
LatLng newCenter = new LatLng(latitude.doubleValue(), longitude.doubleValue());
CENTER = newCenter;
fireCenterChanged();
}
private void syncZoom(final Double zoom) {
int newZoom = zoom.intValue();
if (newZoom != this.ZOOM) {
this.ZOOM = newZoom;
fireZoomChanged();
}
}
private void syncCenter(final Double latitude, final Double longitude) {
LatLng newCenter = new LatLng(latitude.doubleValue(), longitude.doubleValue());
CENTER = newCenter;
fireCenterChanged();
}
JavaScript Code:
window._handleBoundsChanged = function() {
if( !_blockEvents ) {
appi( 10, 15, 20, 32 );
}
};
Console logs at the time of execution " _handleBoundsChanged ();" :
osgi> !SESSION 2015-07-20 08:34:10.848 -----------------------------------------------
eclipse.buildId=unknown
java.version=1.8.0_45
java.vendor=Oracle Corporation
BootLoader constants: OS=win32, ARCH=x86_64, WS=win32, NL=pl_PL
Command-line arguments: -dev
!ENTRY org.eclipse.rap.ui 4 0 2015-07-20 08:34:21.540
!MESSAGE Unhandled event loop exception
!STACK 0
org.eclipse.swt.SWTException: Failed to evaluate Javascript expression
at org.eclipse.swt.browser.Browser.createException(Browser.java:606)
at org.eclipse.swt.browser.Browser.evaluate(Browser.java:344)
at hc.tool.mapviewer.rap.OLMap.setZoom(OLMap.java:67)
at hc.tool.mapviewer.rap.View$1.widgetSelected(View.java:58)
at org.eclipse.swt.widgets.TypedListener.handleEvent(TypedListener.java:262)
at org.eclipse.swt.widgets.EventTable.sendEvent(EventTable.java:86)
at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:708)
at org.eclipse.swt.widgets.Widget.notifyListeners(Widget.java:610)
at org.eclipse.swt.widgets.Display.executeNextEvent(Display.java:1216)
at org.eclipse.swt.widgets.Display.runPendingMessages(Display.java:1197)
at org.eclipse.swt.widgets.Display.safeReadAndDispatch(Display.java:1180)
at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:1172)
at org.eclipse.ui.internal.Workbench.runEventLoop(Workbench.java:2733)
at org.eclipse.ui.internal.Workbench.runUI(Workbench.java:2694)
at org.eclipse.ui.internal.Workbench.access$5(Workbench.java:2530)
at org.eclipse.ui.internal.Workbench$5.run(Workbench.java:701)
at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:337)
at org.eclipse.ui.internal.Workbench.createAndRunWorkbench(Workbench.java:684)
at org.eclipse.ui.PlatformUI.createAndRunWorkbench(PlatformUI.java:157)
at hc.tool.mapviewer.rap.Application.start(Application.java:18)
at org.eclipse.rap.ui.internal.application.EntryPointApplicationWrapper.createUI(EntryPointApplicationWrapper.java:38)
at org.eclipse.rap.rwt.internal.lifecycle.RWTLifeCycle.createUI(RWTLifeCycle.java:171)
at org.eclipse.rap.rwt.internal.lifecycle.RWTLifeCycle$UIThreadController.run(RWTLifeCycle.java:283)
at java.lang.Thread.run(Unknown Source)
at org.eclipse.rap.rwt.internal.lifecycle.UIThread.run(UIThread.java:104)
I am eager to method provided data from JavaScript to Java.
I count on your help , thanks.

So the evaluation is failing at Browser:344, after OLMap:67 is called. You should dig into those two spots to see what is going on.
I am ready to bet that OLMap:67 is your function(Object []), and that the ultimate cause is that you are attempting to cast Object (possibly some kind of JSObject) to java Double without any type of conversion.

Related

Requesting code review for simple EIP712 (Ethereum) validation scheme

Hello,
I am working on a simple EIP712 whitelist member wallet registration/validation scheme. The nutshell goes (sign typed data -> pass to chain -> extract signing address -> compare to signing address stored on chain).
I have been banging my head against this for a while now. I am not able to get the onchain extracted address to match the signing public address offchain. My eyes are way too close to this problem and I need help looking for something I may have missed. By my best ability, I appear to be adhering to standard, but obviously I am doing something wrong.
I have been referring to the EIP712 standard, the 'Mail' EIP reference implementation here (sol) + here (js), and the msfeldstein reference implementation here (sol) + here (ts).
Constraint
For reasons, I do not wish to use any framework/OpenZeppelin (and I also have tried, but likewise could not get to work.)
Notes
The code presented below is basically the EIP reference implementation whittled down, and made as painfully explicit as possible to make the troubleshooting/review process as easy as possible. I likewise cut out all the other testing console.logs.
My approach has been to generate the v, r, s, and signing public address by running .js and printing to console. I then deploy the .sol to Remix, and manually enter generated values.
I am likewise posting the question on Ethereum Stack Exchange, etc.
Alternative typed-data signing methods/strategies are verymuch welcome.
If you have the time and knowhow, I would appreciate your review of my implementation of the EIP712 standard below.
Clientside:
// using ethereumjs-util 7.1.3
const ethUtil = require('ethereumjs-util');
// using ethereumjs-abi 0.6.9
const abi = require('ethereumjs-abi');
// The purpose of this script is to be painfully explicit for the sake
// of showing work, to ask for help.
// generate keys
prikey = ethUtil.keccakFromString('cow', 256);
signingAddress = ethUtil.privateToAddress(prikey);
// 0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826
// data
const typedData = {
types: {
EIP712Domain: [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' },
],
Validation: [
{ name: 'wallet', type: 'address' },
{ name: 'share', type: 'uint256' },
{ name: 'pool', type: 'uint8' }
],
},
primaryType: 'Validation',
domain: {
name: 'Validator',
version: '1',
chainId: 1,
verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
},
message: {
wallet: '0xeeBA65D9C7E5832918d1F4277DE0a78b78efEC43',
share: 1000,
pool: 5,
},
};
// create domain struct hash
const encodedDomainType = 'EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)';
const domainTypeHash = ethUtil.keccakFromString(encodedDomainType, 256);
var encTypes = [];
var encValues = [];
// add typehash
encTypes.push('bytes32');
encValues.push(domainTypeHash);
// add name
encTypes.push('bytes32');
encValues.push(ethUtil.keccakFromString(typedData.domain.name, 256));
// add version
encTypes.push('bytes32');
encValues.push(ethUtil.keccakFromString(typedData.domain.version, 256));
// add chainId
encTypes.push('uint256');
encValues.push(typedData.domain.chainId);
// add chainId
encTypes.push('address');
encValues.push(typedData.domain.verifyingContract);
// computer final hash
domainStructHash = abi.rawEncode(encTypes, encValues);
// create validation struct hash
const encodedValidationType = 'Validation(address wallet,uint256 share,uint256 pool)';
const validationTypeHash = ethUtil.keccakFromString(encodedValidationType, 256);
encTypes = [];
encValues = [];
// add typehash
encTypes.push('bytes32');
encValues.push(validationTypeHash);
// add wallet address
encTypes.push('address');
encValues.push(typedData.message.wallet);
// add share
encTypes.push('uint256');
encValues.push(typedData.message.share);
// add pool
encTypes.push('uint256');
encValues.push(typedData.message.pool);
// computer final hash
validationStructHash = abi.rawEncode(encTypes, encValues);
// now finally create final signature hash
signatureHash = ethUtil.keccak256(
Buffer.concat([
Buffer.from('1901', 'hex'),
domainStructHash,
validationStructHash,
]),
);
// and finally, sign
signature = ethUtil.ecsign(signatureHash, prikey);
// convert r, s, and signingAddress into hex strings to pass to remix
console.log(signature.v);
var r = ''
function pad2(s) {return s.length < 2 ? "0" + s : s};
for(i = 0; i < signature.r.length; i++) {
r += pad2(signature.r[i].toString(16)); }
console.log('0x' + r); // r bytes
var s = ''
function pad2(s) {return s.length < 2 ? "0" + s : s};
for(i = 0; i < signature.s.length; i++) {
s += pad2(signature.s[i].toString(16)); }
console.log('0x' + s); // s bytes
var str = '';
function pad2(s) {return s.length < 2 ? "0" + s : s};
for(i = 0; i < signingAddress.length; i++) {
str += pad2(signingAddress[i].toString(16)); }
console.log('0x' + str); // signingAddress bytes
On chain:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
contract validateData {
address _validationKey = 0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826;
struct EIP712Domain {
string name;
string version;
uint256 chainId;
address verifyingContract;
}
struct Validation {
address wallet;
uint256 share;
uint256 pool;
}
bytes32 constant EIP712DOMAIN_TYPEHASH = keccak256(
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
);
bytes32 constant VALIDATION_TYPEHASH = keccak256(
"Validation(address wallet,uint256 share,uint256 pool)"
);
bytes32 DOMAIN_SEPARATOR;
constructor () {
DOMAIN_SEPARATOR = hash(EIP712Domain({
name: "Validator",
version: '1',
chainId: 1,
verifyingContract: 0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC
}));
}
function hash(EIP712Domain memory eip712Domain) internal pure returns (bytes32) {
return keccak256(abi.encode(
EIP712DOMAIN_TYPEHASH,
keccak256(bytes(eip712Domain.name)),
keccak256(bytes(eip712Domain.version)),
eip712Domain.chainId,
eip712Domain.verifyingContract
));
}
function hash(Validation calldata validation) internal pure returns (bytes32) {
return keccak256(abi.encode(
VALIDATION_TYPEHASH,
validation.wallet,
validation.share,
validation.pool
));
}
event compare(address sig, address key);
function verify(Validation calldata validation, uint8 v, bytes32 r, bytes32 s) public {
bytes32 digest = keccak256(abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR,
hash(validation)
));
emit compare(ecrecover(digest, v, r, s), _validationKey);
}
}
Thank you for your time and consideration!
Use this contract template to recover your address first, then use it into your contract, when you successfully retrieve the address. I will explain the contract too:
pragma solidity ^0.8.0;
contract SignTest {
address owner = msg.sender;
mapping(uint256 => bool) usedNonces;
function test(uint256 amount, uint256 nonce, bytes memory sig, uint tV, bytes32 tR, bytes32 tS, bytes32 tMsg) public view returns(address) {
bytes32 message = prefixed(keccak256(abi.encodePacked(amount, nonce)));
bytes32 messageWithoutPrefix = keccak256(abi.encodePacked(amount, nonce));
address signer = recoverSigner(messageWithoutPrefix, sig, tV, tR,tS);
return signer;
}
// Signature methods
function splitSignature(bytes memory sig)
public
view
returns (uint8, bytes32, bytes32)
{
require(sig.length == 65, "B");
bytes32 r;
bytes32 s;
uint8 v;
assembly {
// first 32 bytes, after the length prefix
r := mload(add(sig, 32))
// second 32 bytes
s := mload(add(sig, 64))
// final byte (first byte of the next 32 bytes)
v := byte(0, mload(add(sig, 96)))
}
return (v, r, s);
}
function recoverSigner(bytes32 message, bytes memory sig, uint tV, bytes32 tR, bytes32 tS)
public
view
returns (address)
{
uint8 v;
bytes32 r;
bytes32 s;
(v, r, s) = splitSignature(sig);
require(v==tV, "V is not correct");
require(r==tR, "R is not correct");
require(s==tS, "S is not correct");
return ecrecover(message, v, r, s);
}
// Builds a prefixed hash to mimic the behavior of eth_sign.
function prefixed(bytes32 inputHash) public pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", inputHash));
}
}
I use web3 for ecrecover, so, let me provide you a simple example:
let fnSignature = web3.utils.keccak256("setApprovalForAll(address,bool").substr(0,10)
// encode the function parameters and add them to the call data
let fnParams = web3.eth.abi.encodeParameters(
["address","bool"],
[toAddr,permit]
)
calldata = fnSignature + fnParams.substr(2)
console.log(calldata)
First, substr first 4 bytes of function signature, then encode them into fnParams + fnSignature
Second step is to sign your data:
const data = calldata //Retrieved from above code
const NFTAddress = 'Contract address where you sign'
const newSigner = web3.eth.accounts.privateKeyToAccount("Your Priv Key");
const myAccount = web3.eth.accounts.wallet.add(newSigner);
const signer = myAccount.address;
console.log(signer) // display the target address in console ( for better verification )
Use calldata from first function and add it to data
Add the contract address where you want to sign the hash
Then, follow these steps:
let rawData = web3.eth.abi.encodeParameters(
['address','bytes'],
[NFTAddress,data]
);
// hash the data.
let hash = web3.utils.soliditySha3(rawData);
console.log(hash)
// sign the hash.
let signature = web3.eth.sign(hash, signer);
console.log(signature)
Using "hash", go to solidity contract (posted by me) and add retrieved bytes(hash) into function prefixed(bytes32 hash)
Using "signature", go to solidity contract and retrieve v,r,s. Use function splitSignature(bytes32 signature)
Using bytes generated by function prefixed(bytes32 hash), use function recoverSigner(message ( bytes from prefixed(bytes32hash), signature ( from web3 javascript), v ( uint8 from splitSignature), r (bytes32 from splitSignature), s ( bytes32 from splitSignature)
You can also play with the contract and script I provided, but, i prefer to post a step by step guide, easy to understand for everyone.
Happy developing! :)

Loading Images from URL Is Producing Unexpected Results

I am loading images from Wikipedia into a Grid view. For the most part this is working correctly. Because there could possible be up to 200 or more images being loaded I am try to run it in a new thread. I see a definite delay when scrolling from my Album tab to the Artist tab that is loading the images. I am also see some lag as images are still getting load while scrolling up and down the list. Also when I scroll back to the top of the list place holders that previously occupied by the default image because I am unable to get an image from Wikipedia are now occupied by images from another artist.
When I scroll back to the song list and then back to the artist list the view is reset but it still has a lot of delay when going into the artist tab.
This image is what the screen looks like when first entering the Artist tab.
This image is what the screen looks like after scrolling to the bottom of the list and back to the top.
As you can see the <unknow. and AJR have had their default image replaced.
Here is my code that I am calling to load the images from Wikipedia.
#Override
public void onBindViewHolder(#NonNull ARV holder, int position) {
Artist artist = artistList.get(position);
if(artist!=null) {
holder.artistName.setText(artist.artistName);
String bandName = artist.artistName;
bandName = bandName.replace(' ','_');
try {
String imageUrl = cutImg(getUrlSource("https://en.wikipedia.org/w/api.php?action=query&titles="+bandName+"&prop=pageimages&format=json&pithumbsize=250"));
URL url = new URL(imageUrl);
ImageLoader.getInstance().displayImage(imageUrl, holder.artistImage,
new DisplayImageOptions.Builder().cacheInMemory(true).showImageOnLoading(R.drawable.album)
.resetViewBeforeLoading(true).build());
} catch (IOException e) {
e.printStackTrace();
}
/*ImageLoader.getInstance().displayImage(getCoverArtPath(context,artist.id),holder.artistImage,
new DisplayImageOptions.Builder().cacheInMemory(true).showImageOnLoading(R.drawable.album)
.resetViewBeforeLoading(false).build());*/
}
}
private StringBuilder getUrlSource(String site) throws IOException {
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
URL localUrl = null;
localUrl = new URL(site);
URLConnection conn = localUrl.openConnection();
BufferedReader reader = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
String line = "";
String html;
StringBuilder ma = new StringBuilder();
while ((line = reader.readLine()) != null) {
ma.append(line);
Log.i(ContentValues.TAG, "StringBuilder " + ma);
}
Log.i(ContentValues.TAG, "Final StringBuilder " + ma);
return ma;
}
public static String cutImg(StringBuilder split){
int start=split.indexOf("\"source\":")+new String("\"source\":\"").length();
split.delete(0, start);
split.delete(split.indexOf("\""), split.length());
Log.i(ContentValues.TAG, "StringBuilder " + split);
return split.toString();
}
Here is the code that is call the Artist Fragment.
public class ArtistFragment extends Fragment {
int spanCount = 3; // 2 columns
int spacing = 20; // 20px
boolean includeEdge = true;
private RecyclerView recyclerView;
private ArtistAdapter adapter;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_artist, container, false);
recyclerView = view.findViewById(R.id.artistFragment);
recyclerView.setLayoutManager(new GridLayoutManager(getActivity(), 3));
Thread t = new Thread()
{
public void run()
{
// put whatever code you want to run inside the thread here.
new LoadData().execute("");
}
};
t.start();
return view;
}
public class LoadData extends AsyncTask<String, Void, String> {
#Override
protected String doInBackground(String... strings) {
if(getActivity()!=null) {
adapter=new ArtistAdapter(getActivity(),new ArtistLoader().artistList(getActivity()));
}
return "Executed";
}
#Override
protected void onPostExecute(String s) {
recyclerView.setAdapter(adapter);
if(getActivity()!=null) {
recyclerView.addItemDecoration(new GridSpacingItemDecoration(spanCount, spacing, includeEdge));
}
}
#Override
protected void onPreExecute() {
super.onPreExecute();
}
}
}
I have also tried this using Picasso using the following code:
bandName = artist.artistName;
bandName = bandName.replace(' ','_');
try {
String imageUrl = cutImg(getUrlSource("https://en.wikipedia.org/w/api.php?action=query&titles="+bandName+"&prop=pageimages&format=json&pithumbsize=250"));
URL url = new URL(imageUrl);
Picasso.get().load(imageUrl).placeholder(R.drawable.album)
.error(R.drawable.artistdefault).into(holder.artistImage);
} catch (IOException e) {
e.printStackTrace();
}
The results are pretty much the same as when I used Android-Universal-Image-Loader. I have been try for several days to fix this, I have tried several different examples that I found on Stack overflow but none of them seem to resolve the issues I am seeing. I am hoping that someone will be able to identify what I am doing incorrectly.
Thanks in advance.
ArtistFragmentconverted to Kotlin
class ArtistFragment : Fragment() {
var spanCount = 3 // 2 columns
var spacing = 20 // 20px
var includeEdge = true
var retrofit: Retrofit? = null
var wikiService: WikiService? = null
var adapter: ArtistAdapter? = null
private var recyclerView: RecyclerView? = null
private var viewModelJob = Job()
private val viewModelScope = CoroutineScope(Dispatchers.Main + viewModelJob)
private var progress_view: ProgressBar? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_artist, container, false)
recyclerView = view.findViewById(R.id.artistFragment)
recyclerView?.setLayoutManager(GridLayoutManager(activity, 3))
progress_view = view.findViewById(R.id.progress_view)
initWikiService()
initList()
//LoadData().execute("")
return view
}
override fun onDestroy() {
super.onDestroy()
viewModelJob.cancel()
}
private fun initList() {
recyclerView!!.addItemDecoration(GridSpacingItemDecoration(spanCount, spacing, includeEdge))
adapter = ArtistAdapter(this)
adapter?.items = ArrayList()
adapter?.listener = this
recyclerView?.adapter = adapter
viewModelScope.launch {
progress_view.visibility = View.VISIBLE
val wikiPages = getWikiPages()
adapter?.items = wikiPages
progress_view?.visibility = View.GONE
}
}
private suspend fun getWikiPages(): ArrayList<Artist> {
val newItems = ArrayList<Artist>()
withContext(Dispatchers.IO) {
ArtistData.artists.map { artist ->
async { wikiService?.getWikiData(artist) }
}.awaitAll().forEach { response ->
val pages = response?.body()?.query?.pages
pages?.let {
for (page in pages) {
val value = page.value
val id = value.pageid?.toLong() ?: value.title.hashCode().toLong()
val title = value.title ?: "Unknown"
val url = value.thumbnail?.source
newItems.add(Artist(id, title, albumCount = 0, songCount = 0, artistUrl = url!!))
}
}
}
}
return newItems
}
private fun initWikiService() {
retrofit = Retrofit.Builder()
.baseUrl("https://en.wikipedia.org/")
.addConverterFactory(GsonConverterFactory.create())
.build()
wikiService = retrofit?.create(WikiService::class.java)
}
I believe I have resolved most of the issues I was previously seeing I am now down to the following problems:
Artist.item.map { artist -> - Not sure how this should be called, Unresolved reference: item
}.awaitAll().forEach { response -> = forEach is telling me Overload resolution ambiguity. All these functions match.
public inline fun Iterable<TypeVariable(T)>.forEach(action: (TypeVariable(T)) → Unit): Unit defined in kotlin.collections
public inline fun <K, V> Map<out TypeVariable(K), TypeVariable(V)>.forEach(action: (Map.Entry<TypeVariable(K), TypeVariable(V)>) → Unit): Unit defined in kotlin.collections
newItems.add(Artist(id, title, url)) - I know that the variables for the Artist Model need to go here, but when I put them there they are unresolved.
I have reworked the ArtistAdapter not sure if it is correct though.
class ArtistAdapter(private val context: ArtistFragment, private val artistList: List<Artist>?) : RecyclerView.Adapter<ArtistAdapter.ARV>() {
private var dimension: Int = 64
init {
val density = context.resources.displayMetrics.density
dimension = (density * 64).toInt()
hasStableIds()
}
var items: MutableList<Artist> = ArrayList()
set(value) {
field = value
notifyDataSetChanged()
}
var listener: Listener? = null
interface Listener {
fun onItemClicked(item: Artist)
abstract fun ArtistAdapter(context: ArtistFragment): ArtistAdapter
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ARV {
return ARV(LayoutInflater.from(parent.context).inflate(R.layout.artist_gride_item, parent,
false))
}
override fun onBindViewHolder(holder: ARV, position: Int) {
holder.onBind(getItem(position))
}
private fun getItem(position: Int): Artist = items[position]
override fun getItemId(position: Int): Long = items[position].id
override fun getItemCount(): Int {
return artistList?.size ?: 0
}
inner class ARV(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
private val artistNameView: TextView = itemView.findViewById(R.id.artistName)
private val artistAlbumArtView: SquareCellView = itemView.findViewById(R.id.artistAlbumArt)
fun onBind(item: Artist) {
artistNameView.text=item.artistName
if(item.artistURL!=null) {
Picasso.get()
.load(item.artistURL)
.resize(dimension, dimension)
.centerCrop()
.error(R.drawable.artistdefualt)
.into(artistAlbumArtView)
} else {
artistAlbumArtView.setImageResource(R.drawable.artistdefualt)
}
itemView.setOnClickListener(this)
}
override fun onClick(view: View) {
val artistId = artistList!![bindingAdapterPosition].id
val fragmentManager = (context as AppCompatActivity).supportFragmentManager
val transaction = fragmentManager.beginTransaction()
val fragment: Fragment
transaction.setCustomAnimations(R.anim.layout_fad_in, R.anim.layout_fad_out,
R.anim.layout_fad_in, R.anim.layout_fad_out)
fragment = ArtistDetailsFragment.newInstance(artistId)
transaction.hide(context.supportFragmentManager
.findFragmentById(R.id.main_container)!!)
transaction.add(R.id.main_container, fragment)
transaction.addToBackStack(null).commit()
}
}
}
Logcat Snippet
java.lang.NoSuchMethodError: No static method metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; in class Ljava/lang/invoke/LambdaMetafactory; or it
s super classes (declaration of 'java.lang.invoke.LambdaMetafactory' appears in /apex/com.android.art/javalib/core-oj.jar)
at okhttp3.internal.Util.<clinit>(Util.java:87)
at okhttp3.internal.Util.skipLeadingAsciiWhitespace(Util.java:321)
at okhttp3.HttpUrl$Builder.parse(HttpUrl.java:1313)
at okhttp3.HttpUrl.get(HttpUrl.java:917)
at retrofit2.Retrofit$Builder.baseUrl(Retrofit.java:506)
at com.rvogl.androidaudioplayer.fragments.ArtistFragment.initWikiService(ArtistFragment.kt:103)
at com.rvogl.androidaudioplayer.fragments.ArtistFragment.onCreateView(ArtistFragment.kt:43)
It looks to me as a known bug with Picasso.
Try to load default image manually so it won't be replaced with cached one.
Update 14.10.20:
I think the main problem is that you load network content in adapter in rather ineffective way. I suggest to form a list of all urls at first, leaving only image load in adapter.
Also reccomend you to use rerofit2 for network calls and something for async work instead of AsyncTask: rxJava, courutines, flow etc.
I created a sample project to load data async using retrofit2+coroutines.
In activity:
private val viewModelScope = CoroutineScope(Dispatchers.Main)
private fun initWikiService() {
retrofit = Retrofit.Builder()
.baseUrl("https://en.wikipedia.org/")
.addConverterFactory(GsonConverterFactory.create())
.build()
wikiService = retrofit?.create(WikiService::class.java)
}
private fun initList() {
viewModelScope.launch {
val wikiPages = getWikiPages()
adapter?.items = wikiPages
}
}
private val viewModelScope = CoroutineScope(Dispatchers.Main + viewModelJob)
private suspend fun getWikiPages(): ArrayList<Item> {
val newItems = ArrayList<Item>()
withContext(IO) {
ArtistData.artists.map { artist ->
async { wikiService?.getWikiData(artist) }
}.awaitAll().forEach { response ->
val pages = response?.body()?.query?.pages
pages?.let {
for (page in pages) {
val value = page.value
val id = value.pageid?.toLong() ?: value.title.hashCode().toLong()
val title = value.title ?: "Unknown"
val url = value.thumbnail?.source
newItems.add(Item(id, title, url))
}
}
}
}
return newItems
}
In viewHolder:
fun onBind(item: Item) {
if (item.url != null) {
Picasso.get()
.load(item.url)
.resize(dimension, dimension)
.centerCrop()
.error(R.drawable.ic_baseline_broken_image_24)
.into(pictureView)
} else {
pictureView.setImageResource(R.drawable.ic_baseline_image_24)
}
}
In adapter: add hasStableIds() to constructor and override getItemId method:
init {
hasStableIds()
}
override fun getItemId(position: Int): Long = items[position].id
Retrofit Service:
interface WikiService {
#GET("/w/api.php?action=query&prop=pageimages&format=json&pithumbsize=250")
suspend fun getWikiData(#Query("titles") band: String): Response<WikipediaResponse?>
}

Getting touchInput from a touch that starts outside of the Unity player

I have a Unity WebGL player that is embedded in a React application. The React application has drag and drop tiles that can be drug and dropped onto the WebGL player. When the tiles begin to be drug unity starts raycasting so that you can tell what object in the screen you are going to drop onto. All of this works perfectly when using a mouse, but I've noticed Input.touchCount always returns 0 unless the touch originates inside the WebGL player. Does anyone know a fix to this? Been head bashing this one for a moment now...
Here is the raycasting code. Like I said, it works perfectly for a mouse... but I cannot get a touch.position returned.
public void LateUpdate()
{
if (SHOULD_CAST_RAY)
{
// always returning 0
Debug.Log(Input.touchCount);
RaycastHit hit;
Vector3 position = Input.touchSupported
&& Input.touchCount == 1
? new Vector3(Input.GetTouch(0).position.x, Input.GetTouch(0).position.y, 0)
: Input.mousePosition;
if (Physics.Raycast(RigsCamera.ScreenPointToRay(position), out hit, CameraRigControllerScript.CameraDistanceMax * 1.5f, 1 << 10))
{
if (CURRENT_SELECTION == null)
{
CURRENT_SELECTION = UnsafeGetModelInstantiationFromRaycast(hit);
ApplySelectionIndication();
}
else if (!IsAlreadySelected(hit))
{
RemoveSelectionIndication();
CURRENT_SELECTION = UnsafeGetModelInstantiationFromRaycast(hit);
ApplySelectionIndication();
}
return;
}
if (CURRENT_SELECTION != null)
{
RemoveSelectionIndication();
CURRENT_SELECTION = null;
}
}
}
Also, if I touch the screen on the Unity WebGL player and then start dragging one of my React components which sends a message to Unity to start raycasting; I get atouch.position that is at the point I touched and does not move with the finger... The hell?
If you have Unity 5+ version you can use mousePosition for everything, since the Input class handles the Touch (0) and Mouse (0) exactly the same, have you tried that?
Vector3 position = Input.touchSupported && Input.touchCount == 1
? new Vector3(Input.GetTouch(0).position.x, Input.GetTouch(0).position.y, 0)
: Input.mousePosition;
Try changing this to only
Vector3 position = Input.mousePosition;
I posted a solution on the Unity forums in-case there is any update to this thread in the future.
In WebGL, the Unity Input class does not register touch events that originate begin outside of the WebGL player. To solve this problem I used a couple boolean values that are toggled by my React components through the GameInstance.SendMessage method; as well as a Vector3 to store a value sent from React, also through SendMessage. Here are the important c# bits. If anyone has any questions, please ask and I'll walk you through the rest!
bool SHOULD_CAST_RAY;
bool USE_EXTERNAL_ORIGINATING_TOUCH_POS;
Vector3 EXTERNAL_ORIGINATING_TOUCH_POS;
public void LateUpdate()
{
if (SHOULD_CAST_RAY)
{
if (USE_EXTERNAL_ORIGINATING_TOUCH_POS && EXTERNAL_ORIGINATING_TOUCH_POS.z < 0) { return; }
RaycastHit hit;
Vector3 screenPoint = Input.mousePresent ? Input.mousePosition : Vector3.zero;
Vector3 viewportPoint = USE_EXTERNAL_ORIGINATING_TOUCH_POS ? RigsCamera.ScreenToViewportPoint(EXTERNAL_ORIGINATING_TOUCH_POS) : Vector3.zero;
if (Physics.Raycast(
USE_EXTERNAL_ORIGINATING_TOUCH_POS
? RigsCamera.ViewportPointToRay(new Vector3(viewportPoint.x, 1 - viewportPoint.y, 0))
: RigsCamera.ScreenPointToRay(screenPoint),
out hit,
CameraRigControllerScript.CameraDistanceMax * 1.5f,
1 << 10
)) {
if (CURRENT_SELECTION == null)
{
CURRENT_SELECTION = UnsafeGetModelInstantiationFromRaycast(hit);
ApplySelectionIndication();
}
else if (!IsAlreadySelected(hit))
{
RemoveSelectionIndication();
CURRENT_SELECTION = UnsafeGetModelInstantiationFromRaycast(hit);
ApplySelectionIndication();
}
return;
}
if (CURRENT_SELECTION != null)
{
RemoveSelectionIndication();
CURRENT_SELECTION = null;
}
}
}
// The below methods are used to control the raycasting from React through sendMessage
public void ClearExternalOriginatingTouchPosition()
{
EXTERNAL_ORIGINATING_TOUCH_POS = new Vector3(0, 0, -1f);
USE_EXTERNAL_ORIGINATING_TOUCH_POS = false;
}
public void DisableRaycasting()
{
SHOULD_CAST_RAY = false;
RemoveSelectionIndication();
CURRENT_SELECTION = null;
}
public void EnableRaycasting()
{
SHOULD_CAST_RAY = true;
}
public void SetExternalOriginatingTouchPosition(string csv)
{
string[] pos = csv.Split(',');
EXTERNAL_ORIGINATING_TOUCH_POS = new Vector3(float.Parse(pos[0]), float.Parse(pos[1]), 0);
USE_EXTERNAL_ORIGINATING_TOUCH_POS = true;
}

Disappearing data, what's going on? Pebble newbie

Brand new and naive to pebble SDK and C but lots of experience in other languages. Which means I'm probably going to be embarrassed when someone solves this for me very easily but... I have two files in a watchface project based on the tutorials... I'm pulling in different data but it all works just like the getting started tutorial up until the point that the code updates one of the text layers with... nothing. It's got me puzzled becuase the data it's supposed to be putting in there appears fine in the console log so it's definitely getting passed over. Anyways, here's the C
include
#define KEY_MONTHMILES 0
#define KEY_MONTHELEVATION 1
#define KEY_TOTALMILES 2
#define KEY_TOTALELEVATION 3
static Window *s_main_window;
static TextLayer *s_time_layer;
static BitmapLayer *s_background_layer;
static GBitmap *s_background_bitmap;
static TextLayer *s_weather_layer;
static void update_time() {
// Get a tm structure
time_t temp = time(NULL);
struct tm *tick_time = localtime(&temp);
// Create a long-lived buffer
static char buffer[] = "00:00";
// Write the current hours and minutes into the buffer
if(clock_is_24h_style() == true) {
// Use 24 hour format
strftime(buffer, sizeof("00:00"), "%H:%M", tick_time);
} else {
// Use 12 hour format
strftime(buffer, sizeof("00:00"), "%I:%M", tick_time);
}
// Display this time on the TextLayer
text_layer_set_text(s_time_layer, buffer);
}
static void tick_handler(struct tm *tick_time, TimeUnits units_changed) {
update_time();
}
static void inbox_received_callback(DictionaryIterator *iterator, void *context) {
// Store incoming information
static char monthlyMiles_buffer[128];
static char monthlyElevation_buffer[128];
static char yearlyMiles_buffer[128];
static char yearlyElevation_buffer[128];
// Read first item
Tuple *t = dict_read_first(iterator);
// For all items
while(t != NULL) {
// Which key was received?
switch(t->key) {
case KEY_MONTHMILES:
snprintf(monthlyMiles_buffer, sizeof(monthlyMiles_buffer), "%d", (int)t->value->int32);
break;
case KEY_MONTHELEVATION:
snprintf(monthlyElevation_buffer, sizeof(monthlyElevation_buffer), "%d", (int)t->value->int32);
break;
case KEY_TOTALMILES:
snprintf(yearlyMiles_buffer, sizeof(yearlyMiles_buffer), "%d", (int)t->value->int32);
break;
case KEY_TOTALELEVATION:
snprintf(yearlyElevation_buffer, sizeof(yearlyElevation_buffer), "%d", (int)t->value->int32);
break;
default:
APP_LOG(APP_LOG_LEVEL_ERROR, "Key %d not recognized!", (int)t->key);
break;
}
// Look for next item
t = dict_read_next(iterator);
}
text_layer_set_text(s_weather_layer, monthlyMiles_buffer);
}
static void inbox_dropped_callback(AppMessageResult reason, void *context) {
APP_LOG(APP_LOG_LEVEL_ERROR, "Message dropped!");
}
static void outbox_failed_callback(DictionaryIterator *iterator, AppMessageResult reason, void *context) {
APP_LOG(APP_LOG_LEVEL_ERROR, "Outbox send failed!");
}
static void outbox_sent_callback(DictionaryIterator *iterator, void *context) {
APP_LOG(APP_LOG_LEVEL_INFO, "Outbox send success!");
}
static void main_window_load(Window *window) {
// Create GBitmap, then set to created BitmapLayer
s_background_bitmap = gbitmap_create_with_resource(RESOURCE_ID_BG_IMAGE);
s_background_layer = bitmap_layer_create(GRect(0, 0, 144, 168));
bitmap_layer_set_bitmap(s_background_layer, s_background_bitmap);
layer_add_child(window_get_root_layer(window), bitmap_layer_get_layer(s_background_layer));
// Create time TextLayer
s_time_layer = text_layer_create(GRect(0, 60, 144, 50));
text_layer_set_background_color(s_time_layer, GColorClear);
text_layer_set_text_color(s_time_layer, GColorWhite);
layer_add_child(window_get_root_layer(window), text_layer_get_layer(s_time_layer));
// Improve the layout to be more like a watchface
text_layer_set_font(s_time_layer, fonts_get_system_font(FONT_KEY_BITHAM_42_BOLD));
text_layer_set_text_alignment(s_time_layer, GTextAlignmentCenter);
// Make sure the time is displayed from the start
update_time();
// Create temperature Layer
s_weather_layer = text_layer_create(GRect(0, 130, 144, 25));
text_layer_set_background_color(s_weather_layer, GColorClear);
text_layer_set_text_color(s_weather_layer, GColorWhite);
text_layer_set_text_alignment(s_weather_layer, GTextAlignmentCenter);
text_layer_set_text(s_weather_layer, "Loading...");
text_layer_set_font(s_weather_layer, fonts_get_system_font(FONT_KEY_GOTHIC_14));
layer_add_child(window_get_root_layer(window), text_layer_get_layer(s_weather_layer));
}
static void main_window_unload(Window *window) {
// Destroy TextLayer
text_layer_destroy(s_time_layer);
// Destroy GBitmap
gbitmap_destroy(s_background_bitmap);
// Destroy BitmapLayer
bitmap_layer_destroy(s_background_layer);
// Destroy weather elements
text_layer_destroy(s_weather_layer);
}
static void init() {
// Create main Window element and assign to pointer
s_main_window = window_create();
// Set handlers to manage the elements inside the Window
window_set_window_handlers(s_main_window, (WindowHandlers) {
.load = main_window_load,
.unload = main_window_unload
});
// Show the Window on the watch, with animated=true
window_stack_push(s_main_window, true);
// Register with TickTimerService
tick_timer_service_subscribe(MINUTE_UNIT, tick_handler);
// Register callbacks
app_message_register_inbox_received(inbox_received_callback);
app_message_register_inbox_dropped(inbox_dropped_callback);
app_message_register_outbox_failed(outbox_failed_callback);
app_message_register_outbox_sent(outbox_sent_callback);
// Open AppMessage
app_message_open(app_message_inbox_size_maximum(), app_message_outbox_size_maximum());
}
static void deinit() {
// Destroy Window
window_destroy(s_main_window);
}
int main(void) {
init();
app_event_loop();
deinit();
}
and here's the Javascript
var xhrRequest = function (url, type, callback) {
var xhr = new XMLHttpRequest();
xhr.onload = function () {
callback(this.responseText);
};
xhr.open(type, url);
xhr.send();
};
function getStats() {
// Construct URL
var url = "http://crin.co.uk/stravaWatchface/getStats.php";
// Send request to crin.co.uk
xhrRequest(url, 'GET',
function(responseText) {
// responseText contains a JSON object with stat info
var json = JSON.parse(responseText);
// Monthly miles
var monthMiles = json.mM;
console.log("Monthly total miles is: " + monthMiles);
// Monthly Elevation
var monthElevation = json.mE;
console.log("Monthly total elevation gain is: " + monthElevation);
// Total Miles
var totalMiles = json.tM;
console.log("All-time total miles is: " + totalMiles);
// Total Elevation
var totalElevation = json.tE;
console.log("All-time total elevation gain is: " + totalElevation);
// Assemble dictionary using our keys
var dictionary = {
"KEY_MONTHMILES": monthMiles,
"KEY_MONTHELEVATION": monthElevation,
"KEY_TOTALMILES": totalMiles,
"KEY_TOTALELEVATION": totalElevation
};
// Send to Pebble
Pebble.sendAppMessage(dictionary,
function(e) {
console.log("Stats info sent to Pebble successfully!");
},
function(e) {
console.log("Error sending stats info to Pebble!");
}
);
}
);
}
// Listen for when the watchface is opened
Pebble.addEventListener('ready',
function(e) {
console.log("PebbleKit JS ready!");
// Get the initial weather
getStats();
}
);
// Listen for when an AppMessage is received
Pebble.addEventListener('appmessage',
function(e) {
console.log("AppMessage received!");
getStats();
}
);
Anyone spot anything obvious? It compiles and installs fine, but this line...
text_layer_set_text(s_weather_layer, monthlyMiles_buffer);
Is updating the watchface with blank space and not the value it should.
Please help I've lost an afternoon to this.

Java8 JS Nashorn convert array to Java array

How can I convert JS array to native array ?
In Rhino conversion looked like (Scala code):
val eng = (new javax.script.ScriptEngineManager).getEngineByName("JavaScript")
val obj = eng.eval("[1,2,3,4]")
val arr = obj.asInstanceOf[sun.org.mozilla.javascript.internal.NativeArray]
In Nashorn NativeArray absent, and I can't find any documentation on conversion.
From Java (and Scala), you can also invoke convert method on jdk.nashorn.api.scripting.ScriptUtils class. E.g. from Java:
import jdk.nashorn.api.scripting.ScriptUtils;
...
int[] iarr = (int[])ScriptUtils.convert(arr, int[].class)
my Scala is not too fluent, but I believe the equivalent is:
val iarr = ScriptUtils.convert(arr, Array[Int]).asInstanceOf(Array[Int])
Given a JavaScript array, you can convert it to a Java array using the Java.to() method in oracle nashorn engine which is available in jdk 8
Example
var data = [1,2,3,4,5,6];
var JavaArray = Java.to(data,"int[]");
print(JavaArray[0]+JavaArray[1]+JavaArray[2]);
I found a solution that works for Rhino and Nashorn.
First problem, the script writer must not deal with Java Objects!
That leads to a solution, that only uses Java.
Second it must work with Java 8 and previous versions!
final ScriptEngineManager manager = new ScriptEngineManager();
final ScriptEngine engine = manager.getEngineByName("JavaScript");
try {
Object result = convert(engine.eval("(function() {return ['a', 'b'];})()"));
log.debug("Result: {}", result);
result = convert(engine.eval("(function() {return [3, 7.75];})()"));
log.debug("Result: {}", result);
result = convert(engine.eval("(function() {return 'Test';})()"));
log.debug("Result: {}", result);
result = convert(engine.eval("(function() {return 7.75;})()"));
log.debug("Result: {}", result);
result = convert(engine.eval("(function() {return false;})()"));
log.debug("Result: {}", result);
} catch (final ScriptException e) {
e.printStackTrace();
}
private static Object convert(final Object obj) {
log.debug("JAVASCRIPT OBJECT: {}", obj.getClass());
if (obj instanceof Bindings) {
try {
final Class<?> cls = Class.forName("jdk.nashorn.api.scripting.ScriptObjectMirror");
log.debug("Nashorn detected");
if (cls.isAssignableFrom(obj.getClass())) {
final Method isArray = cls.getMethod("isArray");
final Object result = isArray.invoke(obj);
if (result != null && result.equals(true)) {
final Method values = cls.getMethod("values");
final Object vals = values.invoke(obj);
if (vals instanceof Collection<?>) {
final Collection<?> coll = (Collection<?>) vals;
return coll.toArray(new Object[0]);
}
}
}
} catch (ClassNotFoundException | NoSuchMethodException | SecurityException
| IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {}
}
if (obj instanceof List<?>) {
final List<?> list = (List<?>) obj;
return list.toArray(new Object[0]);
}
return obj;
}
Rhino delivers arrays as class sun.org.mozilla.javascript.internal.NativeArray that implement the java.util.List interface, that are easy to handle
13:48:42.400 [main] DEBUG de.test.Tester - JAVASCRIPT OBJECT: class sun.org.mozilla.javascript.internal.NativeArray
13:48:42.405 [main] DEBUG de.test.Tester - Result: [a, b]
13:48:42.407 [main] DEBUG de.test.Tester - JAVASCRIPT OBJECT: class sun.org.mozilla.javascript.internal.NativeArray
13:48:42.407 [main] DEBUG de.test.Tester - Result: [3.0, 7.75]
13:48:42.410 [main] DEBUG de.test.Tester - JAVASCRIPT OBJECT: class java.lang.String
13:48:42.410 [main] DEBUG de.test.Tester - Result: Test
13:48:42.412 [main] DEBUG de.test.Tester - JAVASCRIPT OBJECT: class java.lang.Double
13:48:42.412 [main] DEBUG de.test.Tester - Result: 7.75
13:48:42.414 [main] DEBUG de.test.Tester - JAVASCRIPT OBJECT: class java.lang.Boolean
13:48:42.415 [main] DEBUG de.test.Tester - Result: false
Nashorn returns a JavaScript array as jdk.nashorn.api.scripting.ScriptObjectMirror that unfortunately doesn't implement the List interface.
I solved it using reflection and I'm wondering why Oracle has made this big change.
13:51:02.488 [main] DEBUG de.test.Tester - JAVASCRIPT OBJECT: class jdk.nashorn.api.scripting.ScriptObjectMirror
13:51:02.495 [main] DEBUG de.test.Tester - Nashorn detected
13:51:02.497 [main] DEBUG de.test.Tester - Result: [a, b]
13:51:02.503 [main] DEBUG de.test.Tester - JAVASCRIPT OBJECT: class jdk.nashorn.api.scripting.ScriptObjectMirror
13:51:02.503 [main] DEBUG de.test.Tester - Nashorn detected
13:51:02.503 [main] DEBUG de.test.Tester - Result: [3.0, 7.75]
13:51:02.509 [main] DEBUG de.test.Tester - JAVASCRIPT OBJECT: class java.lang.String
13:51:02.509 [main] DEBUG de.test.Tester - Result: Test
13:51:02.513 [main] DEBUG de.test.Tester - JAVASCRIPT OBJECT: class java.lang.Double
13:51:02.513 [main] DEBUG de.test.Tester - Result: 7.75
13:51:02.520 [main] DEBUG de.test.Tester - JAVASCRIPT OBJECT: class java.lang.Boolean
13:51:02.520 [main] DEBUG de.test.Tester - Result: false
The solution is Java.to function to do conversion:
engine.eval("Java.to(" + script + ",'byte[]')").asInstanceOf[Array[Byte]]
engine.eval("Java.to(" + name + ",'java.lang.String[]')").asInstanceOf[Array[String]]
In case there is array of arrays, we have to process recursivelly:
public class Nashorn {
public static List<String> fromScript(final Object obj){
List<String> returnList = new ArrayList<>();
if(obj==null){
returnList.add("");
return returnList;
}
if (obj instanceof Bindings) {
flatArray(returnList, obj);
return returnList;
}
if (obj instanceof List<?>) {
final List<?> list = (List<?>) obj;
returnList.addAll(list.stream().map(String::valueOf).collect(Collectors.toList()));
return returnList;
}
if(obj.getClass().isArray()){
Object[] array = (Object[])obj;
for (Object anArray : array) {
returnList.add(String.valueOf(anArray));
}
return returnList;
}
returnList.add(String.valueOf(obj));
return returnList;
}
//if we have multiple levels of array, flat the structure
private static void flatArray(List<String> returnList, Object partialArray){
try {
final Class<?> cls = Class.forName("jdk.nashorn.api.scripting.ScriptObjectMirror");
if (cls.isAssignableFrom(partialArray.getClass())) {
final Method isArray = cls.getMethod("isArray");
final Object result = isArray.invoke(partialArray);
if (result != null && result.equals(true)) {
final Method values = cls.getMethod("values");
final Object vals = values.invoke(partialArray);
if (vals instanceof Collection<?>) {
final Collection<?> coll = (Collection<?>) vals;
for(Object el : coll) {
if (cls.isAssignableFrom(el.getClass())) {
flatArray(returnList, el);
}
else{
returnList.add(String.valueOf(el));
}
}
}
}
}
} catch (ClassNotFoundException | NoSuchMethodException | SecurityException
| IllegalAccessException | IllegalArgumentException | InvocationTargetException ignored) {}
}
}
For multidimensional arrays:
You can use this code below to get an array with a dynamic dimension, depending on the object you want to convert. There is a usage example below.
public static Object[] toArray(ScriptObjectMirror scriptObjectMirror)
{
if (!scriptObjectMirror.isArray())
{
throw new IllegalArgumentException("ScriptObjectMirror is no array");
}
if (scriptObjectMirror.isEmpty())
{
return new Object[0];
}
Object[] array = new Object[scriptObjectMirror.size()];
int i = 0;
for (Map.Entry<String, Object> entry : scriptObjectMirror.entrySet())
{
Object result = entry.getValue();
System.err.println(result.getClass());
if (result instanceof ScriptObjectMirror && scriptObjectMirror.isArray())
{
array[i] = toArray((ScriptObjectMirror) result);
}
else
{
array[i] = result;
}
i++;
}
return array;
}
Now, use the method like this:
ScriptObjectMirror som = (ScriptObjectMirror) YOUR_NASHORN_ENGINE.eval("['this', ['tricky', ['method', ['works']], 'perfectly'], ':)']");
// create multi-dimensional array
Object[] obj = toArray(som);
And voila, you got it. Below is an example on how to iterate over such arrays:
public static void print(Object o)
{
if (o instanceof Object[])
{
for (Object ob : (Object[]) o)
{
print(ob);
}
}
else
{
System.out.println(o);
}
}
I success to retrieve java/scala array by using method like traditional netscape.javascript.JSObject in Java8/Scala2.12/Nashorn.
val arr = engine.eval(script) match {
case obj:ScriptObjectMirror =>
if(obj.isArray){
for(i <- 0 until obj.size()) yield obj.getSlot(i)
} else Seq.empty
case unexpected => Seq.empty
}
Using ScriptUtil() may retrieve scalar value such as String, but it seems to cause ClassCastException for Array.

Categories

Resources