1 /**
  2  * @fileOverview A collection of miscellaneous utilities.
  3  */
  4 
  5 if (!appjet) {
  6   throw new Error('appjet library is required for util library.');
  7 }
  8 
  9 /**
 10  * Returns a string version of the MD5 signature of x.
 11  *
 12  * @example print(md5("appjet")); // prints "9b458805f67473b49761c13e48c5de35"
 13  *
 14  * @param {String} x a string
 15  * @return {String} the md5 hash of x
 16  */
 17 function md5(x) {
 18   return appjet._native.md5(x);
 19 }
 20 
 21 /**
 22  * Iterator convenience for JavaScript Objects.
 23  *
 24  * Note that if func returns false, the iteration will be immediately terminated.
 25  * (Returning undefined, or not specifying a return type, does not terminate the iteration).
 26  *
 27  * @example
 28 var pastels = {
 29   red: "#fcc",
 30   green: "#cfc",
 31   blue: "#ccf"
 32 };
 33 eachProperty(pastels, function(key, value) {
 34   print(DIV({style: 'background: '+value+';'}, key));
 35 });
 36  *
 37  * @param {object} obj The object over which to iterate.
 38  * @param {function} func The function to run on each [key,value] pair.
 39  */
 40 function eachProperty(obj, func) {
 41   var r;
 42   for (k in obj) {
 43     if (obj.hasOwnProperty(k)) {
 44       r = func(k,obj[k]);
 45       if (r === false) {
 46 	break;
 47       }
 48     }
 49   }
 50 }
 51 
 52 /**
 53  * Douglas Crockford's "object" function for prototypal inheritance, taken from
 54  * http://javascript.crockford.com/prototypal.html
 55  *
 56  * @param {object} parent The parent object.
 57  * @return {object} A new object whose prototype is parent.
 58  */
 59 function object(parent) {
 60   function f() {};
 61   f.prototype = parent;
 62   return new f();
 63 }
 64 
 65 /**
 66  * Creates an array of the properties of <code>obj</code>,
 67  * <em>not</em> including built-in or inherited properties.  If no
 68  * argument is given, applies to the global object.
 69  *
 70  * @example
 71 // Prints "abc"
 72 keys({a: 1, b: 2, c: 3}).forEach(function(k) {
 73   print(k);
 74 }
 75  *
 76  * @example
 77 // Prints all the functions and object members of the global "appjet" object,
 78 // one per line.
 79 print(keys(appjet).join('\n'));
 80  *
 81  * @param {object} obj
 82  */
 83 function keys(obj) {
 84   var array = [];
 85   var o = obj;
 86   if (o == undefined) {
 87     o = this;
 88   }
 89   for(var k in o) {
 90     if (o.hasOwnProperty(k)) {
 91       array.push(k);
 92     }
 93   }
 94   return array;
 95 }
 96 
 97 /**
 98  * Comparator that returns -1, +1, or 0 depending on whether a < b, or a > b, or
 99  * neither, respectively.
100  * @param {object} a
101  * @param {object} b
102  * @return {number} -1, 0, or +1
103  */
104 function cmp(a,b) {
105   if (a < b) {
106     return -1;
107   }
108   if (a > b) {
109     return 1;
110   }
111   return 0;
112 }
113 
114 /**
115  * Removes leading and trailing whitespace from a string.
116  * @param {string} str
117  * @return {string} The trimmed string.
118  */
119 function trim(str) {
120   return str.replace(/^\s+|\s+$/g, "");
121 }
122 
123 //----------------------------------------------------------------
124 // string set
125 //----------------------------------------------------------------
126 
127 // TODO: unit-test the string set, or just switch it over to
128 // java.util.HashSet or something.
129 
130 /**
131  * Constructor for an object that holds a set of strings, with basic
132  * set operations.  This is better for keeping track of a set than
133  * using an associative arrays, because it avoids key collisions with
134  * javascript built-ins like 'toString', 'class', etc.
135  *
136  * @constructor
137  * @class An object that holds a set of strings, with basic set operations.
138  * @param {string} initialString1
139  * @param {string} initialString2
140  * @param {string} etc ...
141  *
142  */
143 StringSet = function(initialElement1, initialElement2, etc) {
144   this._obj = {};
145   for (var i = 0; i < arguments.length; i++) {
146     if (typeof(arguments[i]) != 'string') {
147       throw new Error('StringSet constructor must take only string arguments.');
148     } else {
149       this._obj['$$'+arguments[i]] = true;
150     }
151   }
152 };
153 
154 /** @ignore */
155 StringSet.prototype.key = function(x) {
156   return '$$'+x;
157 }
158 
159 /**
160  * Returns whether this set contains the given string.
161  *
162  * @param {string} x
163  * @return {boolean} Whether x exits in this set.
164  */
165 StringSet.prototype.contains = function(x) {
166   return (this._obj.hasOwnProperty(this.key(x)) &&
167 	  this._obj[this.key(x)] === true);
168 };
169 
170 /**
171  * Adds the given string to the set if it is not already in it.
172  * @param {string} x
173  */
174 StringSet.prototype.add = function(x) {
175   this._obj[this.key(x)] = true;
176 };
177 
178 /**
179  * Removes the given string from the set if it is contained in it.
180  * @param {string} x The string to remove from the set.
181  */
182 StringSet.prototype.remove = function(x) {
183   if (this._obj[this.key(x)]) {
184     delete this._obj[this.key(x)];
185   }
186 };
187 
188 /**
189  * Iterators over the strings in the set, calling the provided
190  * function on each element.
191  * @param {function} f The function to call on each string in the
192  * set.
193  */
194 StringSet.prototype.forEach = function(f) {
195   var self = this;
196   eachProperty(this._obj, function(name) {
197     var realName = name.substring(2);
198     if (self.contains(realName)) f(realName);
199   });
200 }
201 
202 //----------------------------------------------------------------
203 // wget/wpost
204 //----------------------------------------------------------------
205 
206 /**
207  * The error thrown by wget and wpost if there's a problem retrieveing the requested URL.
208  *
209  * @constructor
210  * @param {string} message A status message describing why the action failed.
211  * @param {HttpResponse} info Additional info, including headers and received data, if any.
212  * @class The error thrown by wget and wpost if there's a problem retrieveing the requested URL.
213  */
214 function HttpRequestError(message, info) {
215   this.message = message;
216   this.info = info;
217   this.name = "HttpRequestError";
218 }
219 HttpRequestError.prototype = new Error();
220 
221 /**
222  * @class Description of an object containing the properties of the HTTP response returned by <code>wget</code> and <code>wpost</code>
223  *
224  * @name HttpResponse
225  */
226 
227 /**
228  * The status code of the server's response, or an internal status code if connecting to the server failed.
229  * @name status
230  * @type number
231  * @memberOf HttpResponse
232  */
233 /**
234  * An explanation of the status code if it is an internal code (that is, less than 0)
235  * @name statusInfo
236  * @type string
237  * @memberOf HttpResponse
238  */
239 /**
240  * The data returned by the server, if any.
241  * @name data
242  * @type string
243  * @memberOf HttpResponse
244  */
245 /**
246  * The content type returned by the server, if any.
247  * @name contentType
248  * @type string
249  * @memberOf HttpResponse
250  */
251 /**
252  * An object containing property names and values corresponding to the headers returned by the server.
253  * @name headers
254  * @type object
255  * @memberOf HttpResponse
256  */
257 
258 function _paramObjectToParamArray(params, enc) {
259   var pa = [];
260   eachProperty(params, function(k, v) {
261     pa.push(enc ? encodeURIComponent(k.toString()) : k.toString());
262     pa.push(enc ? encodeURIComponent(v.toString()) : v.toString());
263   });
264   return pa;
265 }
266 
267 
268 /**
269  * Fetches the text of a URL and returns it as a string.
270  *
271  * @example
272 g = wget("google.com");
273 page.setMode("plain");
274 print(raw(g));
275  *
276  * @param {string} url The name of the url to retreive. If the
277  * transport is not specified, HTTP is assumed.
278  * @param {object} [params] Optional parameters to include with the
279  * GET, as a dictionary of {name: value} entries.
280  * @param {object} [options] Optional object with three optional
281  * properties:<ul>
282  * <li><strong>headers:</strong> HTTP request headers to send
283  * with the GET, as a dictionary of {name: value} entries.</li>
284  * <li><strong>followRedirects:</strong> A boolean indicating
285  * whether to follow redirect headers returned by the server.
286  * Defaults to <code>true</code>.</li>
287  * <li><strong>complete:</strong> If <code>true</code>, wget returns
288  * an object containing all information about the response, not just
289  * its contents.  Otherwise, wget throws a HttpRequestError if it
290  * encounters an error GETing.
291  * </ul>
292  * @return {string} The full text of the url's content.
293  * @return {HttpResponse} The "complete" response, returned if the "complete" parameter is <code>true</code>.
294  */
295 function wget(url, params, options) {
296   if (!url.match(/^\w+\:\/\//)) {
297     url = "http://" + url;
298   }
299   var pa = _paramObjectToParamArray(params, false);
300   var ha = _paramObjectToParamArray(options ? options.headers : undefined, false);
301   var followRedir = (options === true ? true : (options && options.followRedirects === false ? false : true));
302   var ret = appjet._native.simplehttpclient_get(url, pa, ha, followRedir);
303   if ((options === true) || (options && options.complete))
304     return ret;
305   if (ret.status >= 200 && ret.status < 300) {
306     return ret.data;
307   } else {
308     throw new HttpRequestError(ret.statusInfo || ret.status, ret);
309   }
310 }
311 
312 /**
313  * Simple way to POST data to a URL and get back the response.  Values of params will
314  * automatically be escaped.
315  *
316  * @example
317 result = wpost("example.com", {id: 25, value: "here is the post value"});
318  *
319  * @param {string} url The url to POST to.
320  * @param {object} [params] Optional parameters to include with the
321  * POST, as a dictionary of {name: value} entries.
322  * @param {object} [options] Optional object with three optional
323  * properties:<ul>
324  * <li><strong>headers:</strong> HTTP request headers to send
325  * with the POST, as a dictionary of {name: value} entries.</li>
326  * <li><strong>followRedirects:</strong> A boolean indicating
327  * whether to follow redirect headers returned by the server.</li>
328  * <li><strong>complete:</strong> If <code>true</code>, wpost returns
329  * an object containing all information about the response, not just
330  * its contents.  Otherwise, wpost throws a HttpRequestError if it
331  * encounters an error POSTing.
332  * </ul>
333  * @return {string} The full text returned from the server POSTed to.
334  * @return {HttpResponse} The complete response, returned if the "complete" parameter is <code>true</code>.
335  */
336 function wpost(url, params, options) {
337   if (!url.match(/^\w+\:\/\//)) {
338     url = "http://" + url;
339   }
340   var pa = _paramObjectToParamArray(params, true);
341   var ha = _paramObjectToParamArray(options ? options.headers : undefined, false);
342   var followRedir = (options === true ? true : (options && options.followRedirects === false ? false : true));
343   var ret = appjet._native.simplehttpclient_post(url, pa, ha, followRedir);
344   if ((options === true) || (options && options.complete))
345     return ret;
346   if (ret.status >= 200 && ret.status < 300) {
347     return ret.data;
348   } else {
349     throw new HttpRequestError(ret.statusInfo || ret.status, ret);
350   }
351 }
352 
353 /**
354  * Simple way to send an email to a single recipient. Emails will have a
355  * "from" address of <code>noreply@{appjet.appName}.{appjet.mainDomain}</code>.
356  *
357  * Sending is limited to 100 emails per developer account per day.  However,
358  * emails sent to the address on file for the app's owner are not counted
359  * toward this limit.
360  *
361  * @example
362 result = sendEmail("noone@example.com", "Test Subject",
363                    "Greetings!", {"Reply-To": "sender@example.com"});
364  *
365  * @param {string} toAddress The one email address to send a message to.
366  * @param {string} subject The message subject.
367  * @param {string} body The message body.
368  * @param {object} [headers] Optional headers to include in the
369  * message, as a dictionary of {name: value} entries.
370  */
371 function sendEmail(toAddress, subject, body, headers) {
372   var pa = _paramObjectToParamArray(headers, false);
373   var ret = appjet._native.email_sendEmail(toAddress, subject, body, pa);
374   if (ret != "")
375     throw new Error(ret);
376 }
377 
378