package com.jasonette.seed.Core; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.net.Uri; import android.os.AsyncTask; import android.util.Log; import com.jasonette.seed.Helper.JasonHelper; import org.apache.commons.lang.StringEscapeUtils; import org.apache.commons.lang.StringUtils; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.IOException; import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; import okhttp3.Call; import okhttp3.Callback; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; public class JasonModel{ public String url; public JSONObject jason; public JSONObject rendered; public JSONObject state; public JSONObject refs; JasonViewActivity view; // Variables public JSONObject var; // $get public JSONObject cache; // $cache public JSONObject params; // $params public JSONObject session; public JSONObject action; // latest executed action public OkHttpClient client; public JasonModel(String url, Intent intent, JasonViewActivity view){ this.url = url; this.view = view; // $params this.params = new JSONObject(); if(intent != null && intent.hasExtra("params")){ try { this.params = new JSONObject(intent.getStringExtra("params")); } catch (Exception e) { Log.d("Error", e.toString()); } } // $get this.var = new JSONObject(); // $cache this.cache = new JSONObject(); SharedPreferences cache_pref = view.getSharedPreferences("cache", 0); if(cache_pref.contains(url)){ String str = cache_pref.getString(url, null); try { this.cache = new JSONObject(str); } catch (Exception e) { Log.d("Error", e.toString()); } } // session SharedPreferences session_pref = view.getSharedPreferences("session", 0); this.session = new JSONObject(); try { URI uri_for_session = new URI(url.toLowerCase()); String session_domain = uri_for_session.getHost(); if (session_pref.contains(session_domain)) { String str = session_pref.getString(session_domain, null); this.session = new JSONObject(str); } } catch (Exception e){ Log.d("Error", e.toString()); }; } public void fetch() { if(url.startsWith("file://")) { fetch_local(); } else { fetch_http(); } } private void fetch_local(){ try { jason = JasonHelper.read_json(url, this.view); if(jason.has("$jason")){ view.build(); } else { Log.d("Error", "Invalid jason"); } } catch (Exception e) { Log.d("Error", e.toString()); } } private void fetch_http(){ try{ Request request; Request.Builder builder = new Request.Builder(); // SESSION HANDLING // Attach Header from Session if(session != null && session.has("header")) { Iterator<?> keys = session.getJSONObject("header").keys(); while (keys.hasNext()) { String key = (String) keys.next(); String val = session.getJSONObject("header").getString(key); builder.addHeader(key, val); } } // Attach Params from Session if (session != null && session.has("body")) { Iterator<?> keys = session.getJSONObject("body").keys(); Uri.Builder b = Uri.parse(url).buildUpon(); while (keys.hasNext()) { String key = (String) keys.next(); String val = session.getJSONObject("body").getString(key); b.appendQueryParameter(key, val); } Uri uri = b.build(); url = uri.toString(); } request = builder .url(url) .build(); client = new OkHttpClient(); client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { e.printStackTrace(); } @Override public void onResponse(Call call, final Response response) throws IOException { if (!response.isSuccessful()) { throw new IOException("Unexpected code " + response); } String res = response.body().string(); refs = new JSONObject(); resolve_and_build(res); } }); } catch (Exception e){ Log.d("Error", e.toString()); } } private void include(String res){ String regex = "\"([+@])\"[ ]*:[ ]*\"(([^\"@]+)(@))?([^\"]+)\""; Pattern require_pattern = Pattern.compile(regex); Matcher matcher = require_pattern.matcher(res); ArrayList<String> urls = new ArrayList<String>(); while (matcher.find()) { //System.out.println("Path: " + matcher.group(3)); // Fetch URL content and cache String matched = matcher.group(5); if(!matched.contains("$document")){ urls.add(matcher.group(5)); } } if(urls.size() > 0) { CountDownLatch latch = new CountDownLatch(urls.size()); ExecutorService taskExecutor = Executors.newFixedThreadPool(urls.size()); for (int i = 0; i < urls.size(); i++) { taskExecutor.submit(new JasonRequire(urls.get(i), latch, refs, client, view)); } try { latch.await(); } catch (Exception e) { Log.d("Error", e.toString()); } } resolve_reference(); } private void resolve_and_build(String res){ try { jason = new JSONObject(res); // "include" handling // 1. check if it contains "+": "..." // 2. if it does, need to resolve it first. // 3. if it doesn't, just build the view immediately // Exclude patterns that start with $ (will be handled by local resolve) String regex = "\"([+@])\"[ ]*:[ ]*\"(([^$\"@]+)(@))?([^$\"]+)\""; Pattern require_pattern = Pattern.compile(regex); Matcher matcher = require_pattern.matcher(res); if (matcher.find()) { // if requires resolution, require first. include(res); } else { // otherwise, resolve local once and then render (for $document) String local_regex = "\"([+@])\"[ ]*:[ ]*\"(([^\"@]+)(@))?([^\"]+)\""; Pattern local_require_pattern = Pattern.compile(local_regex); Matcher local_matcher = local_require_pattern.matcher(res); if (local_matcher.find()) { resolve_local_reference(); } else { if (jason.has("$jason")) { view.build(); } else { } } } } catch (Exception e){ Log.d("Error", e.toString()); } } private void resolve_reference(){ // convert "+": "$document.blah.blah" // to "{{#include $root.$document.blah.blah}}": {} String str_jason = jason.toString(); try { // Exclude a pattern that starts with $ => will be handled by resolve_local_reference String remote_pattern_with_path_str = "\"([+@])\"[ ]*:[ ]*\"(([^$\"@]+)(@))([^\"]+)\""; Pattern remote_pattern_with_path = Pattern.compile(remote_pattern_with_path_str); Matcher remote_with_path_matcher = remote_pattern_with_path.matcher(str_jason); str_jason = remote_with_path_matcher.replaceAll("\"{{#include \\$root[\\\\\"$5\\\\\"].$3}}\": {}"); // Exclude a pattern that starts with $ => will be handled by resolve_local_reference String remote_pattern_without_path_str = "\"([+@])\"[ ]*:[ ]*\"([^$\"]+)\""; Pattern remote_pattern_without_path = Pattern.compile(remote_pattern_without_path_str); Matcher remote_without_path_matcher = remote_pattern_without_path.matcher(str_jason); str_jason = remote_without_path_matcher.replaceAll("\"{{#include \\$root[\\\\\"$2\\\\\"]}}\": {}"); JSONObject to_resolve = new JSONObject(str_jason); refs.put("$document", jason); // parse JasonParser.getInstance(this.view).setParserListener(new JasonParser.JasonParserListener() { @Override public void onFinished(JSONObject resolved_jason) { try { resolve_and_build(resolved_jason.toString()); } catch (Exception e) { Log.d("Error", e.toString()); } } }); JasonParser.getInstance(this.view).parse("json", refs, to_resolve, this.view); } catch (Exception e){ Log.d("Error", e.toString()); } } private void resolve_local_reference(){ // convert "+": "$document.blah.blah" // to "{{#include $root.$document.blah.blah}}": {} String str_jason = jason.toString(); try { String local_pattern_str = "\"[+@]\"[ ]*:[ ]*\"[ ]*(\\$document[^\"]*)\""; Pattern local_pattern = Pattern.compile(local_pattern_str); Matcher local_matcher = local_pattern.matcher(str_jason); str_jason = local_matcher.replaceAll("\"{{#include \\$root.$1}}\": {}"); JSONObject to_resolve = new JSONObject(str_jason); refs.put("$document", jason); // parse JasonParser.getInstance(this.view).setParserListener(new JasonParser.JasonParserListener() { @Override public void onFinished(JSONObject resolved_jason) { try { resolve_and_build(resolved_jason.toString()); } catch (Exception e) { Log.d("Error", e.toString()); } } }); JasonParser.getInstance(this.view).parse("json", refs, to_resolve, this.view); } catch (Exception e){ Log.d("Error", e.toString()); } } public void set(String name, JSONObject data){ if(name.equalsIgnoreCase("jason")) { this.jason = data; } else if(name.equalsIgnoreCase("state")) { try { // Construct variable state (var => $get, cache => $cache, params => $params, etc) // by default, take the inline data if (jason.getJSONObject("$jason").has("head") && jason.getJSONObject("$jason").getJSONObject("head").has("data")) { state = jason.getJSONObject("$jason").getJSONObject("head").getJSONObject("data"); } else { state = new JSONObject(); } if (data instanceof JSONObject) { Iterator<?> keys = data.keys(); while (keys.hasNext()) { String key = (String) keys.next(); Object val = data.get(key); state.put(key, val); } } // merge with passed in data state.put("$get", var); state.put("$cache", cache); state.put("$params", params); } catch (Exception e) { Log.d("Error", e.toString()); } } else { } } }