1 /* Licensed to the Apache Software Foundation (ASF) under one or more
  2  * contributor license agreements.  See the NOTICE file distributed with
  3  * this work for additional information regarding copyright ownership.
  4  * The ASF licenses this file to you under the Apache License, Version 2.0
  5  * (the "License"); you may not use this file except in compliance with
  6  * the License.  You may obtain a copy of the License at
  7  *
  8  *      http://www.apache.org/licenses/LICENSE-2.0
  9  *
 10  * Unless required by applicable law or agreed to in writing, software
 11  * distributed under the License is distributed on an "AS IS" BASIS,
 12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  * See the License for the specific language governing permissions and
 14  * limitations under the License.
 15  */
 16 /*
 17  theoretically we could save some code
 18  by
 19  defining the parent object as
 20  var parent = new Object();
 21  parent.prototype = new myfaces._impl.core._Runtime();
 22  extendClass(function () {
 23  }, parent , {
 24  But for now we are not doing it the little bit of saved
 25  space is not worth the loss of readability
 26  */
 27 /**
 28  * @memberOf myfaces._impl
 29  * @namespace
 30  * @name _util
 31  */
 32 /**
 33  * @class
 34  * @name _Lang
 35  * @memberOf myfaces._impl._util
 36  * @extends myfaces._impl.core._Runtime
 37  * @namespace
 38  * @description Object singleton for Language related methods, this object singleton
 39  * decorates the namespace myfaces._impl.core._Runtime and adds a bunch of new methods to
 40  * what _Runtime provided
 41  * */
 42 _MF_SINGLTN(_PFX_UTIL + "_Lang", Object, /** @lends myfaces._impl._util._Lang.prototype */ {
 43     _processedExceptions: {},
 44     _installedLocale: null,
 45     _RT: myfaces._impl.core._Runtime,
 46     /**
 47      * returns a given localized message upon a given key
 48      * basic java log like templating functionality is included
 49      *
 50      * @param {String} key the key for the message
 51      * @param {String} optional default message if none was found
 52      *
 53      * Additionally you can pass additional arguments, which are used
 54      * in the same way java log templates use the params
 55      *
 56      * @param key
 57      */
 58     getMessage: function(key, defaultMessage /*,vararg templateParams*/) {
 59         if (!this._installedLocale) {
 60             //we first try to install language and variant, if that one fails
 61             //we try to install the language only, and if that one fails
 62             //we install the base messages
 63             this.initLocale();
 64         }
 65         var msg = this._installedLocale[key] || defaultMessage || key + " - undefined message";
 66         for (var cnt = 2; cnt < arguments.length; cnt++) {
 67             msg = msg.replace(new RegExp(["\\{",cnt - 2,"\\}"].join(""), "g"), new String(arguments[cnt]));
 68         }
 69         return msg;
 70     },
 71     /**
 72      * (re)inits the currently installed
 73      * messages so that after loading the main scripts
 74      * a new locale can be installed optionally
 75      * to our i18n subsystem
 76      *
 77      * @param newLocale locale override
 78      */
 79     initLocale: function(newLocale) {
 80         if (newLocale) {
 81             this._installedLocale = new newLocale();
 82             return;
 83         }
 84         var language_Variant = this._RT.getLanguage(this._RT.getGlobalConfig("locale"));
 85         var langStr = language_Variant ? language_Variant.language : "";
 86         var variantStr = language_Variant ? [language_Variant.language,"_",language_Variant.variant || ""].join("") : "";
 87         var i18nRoot = myfaces._impl.i18n;
 88         var i18nHolder = i18nRoot["Messages_" + variantStr] ||
 89                 i18nRoot["Messages_" + langStr] ||
 90                 i18nRoot["Messages"];
 91         this._installedLocale = new i18nHolder();
 92     },
 93     assertType: function(probe, theType) {
 94         return this._RT.assertType(probe, theType);
 95     },
 96     exists: function(nms, theType) {
 97         return this._RT.exists(nms, theType);
 98     },
 99     isExceptionProcessed: function(e) {
100         return !! this._processedExceptions[e.toString()];
101     },
102     setExceptionProcessed: function(e) {
103         this._processedExceptions[e.toString()] = true;
104     },
105     clearExceptionProcessed: function() {
106         //ie again
107         for (var key in this._processedExceptions) {
108             this._processedExceptions[key] = null;
109         }
110         this._processedExceptions = {};
111     },
112     fetchNamespace : function(namespace) {
113         this._assertStr(namespace, "fetchNamespace", "namespace");
114         return this._RT.fetchNamespace(namespace);
115     },
116     reserveNamespace : function(namespace) {
117         this._assertStr(namespace, "reserveNamespace", "namespace");
118         return this._RT.reserveNamespace(namespace);
119     },
120     globalEval : function(code) {
121         this._assertStr(code, "globalEval", "code");
122         return  this._RT.globalEval(code);
123     },
124     /**
125      * determines the correct event depending
126      * on the browsers state
127      *
128      * @param evt incoming event object (note not all browsers
129      * have this)
130      *
131      * @return an event object no matter what is incoming
132      */
133     getEvent: function(evt) {
134         evt = (!evt) ? window.event || {} : evt;
135         return evt;
136     },
137     /**
138      * cross port from the dojo lib
139      * browser save event resolution
140      * @param evt the event object
141      * (with a fallback for ie events if none is present)
142      */
143     getEventTarget: function(evt) {
144         //ie6 and 7 fallback
145         evt = this.getEvent(evt);
146         /**
147          * evt source is defined in the jsf events
148          * seems like some component authors use our code
149          * so we add it here see also
150          * https://issues.apache.org/jira/browse/MYFACES-2458
151          * not entirely a bug but makes sense to add this
152          * behavior. I dont use it that way but nevertheless it
153          * does not break anything so why not
154          * */
155         var t = evt.srcElement || evt.target || evt.source || null;
156         while ((t) && (t.nodeType != 1)) {
157             t = t.parentNode;
158         }
159         return t;
160     },
161     /**
162      * consume event in a browser independend manner
163      * @param event the event which should not be propagated anymore
164      */
165     consumeEvent: function(event) {
166         //w3c model vs ie model again
167         event = event || window.event;
168         (event.stopPropagation) ? event.stopPropagation() : event.cancelBubble = true;
169     },
170     /**
171      * equalsIgnoreCase, case insensitive comparison of two strings
172      *
173      * @param source
174      * @param destination
175      */
176     equalsIgnoreCase: function(source, destination) {
177         //either both are not set or null
178         if (!source && !destination) {
179             return true;
180         }
181         //source or dest is set while the other is not
182         if (!source || !destination) return false;
183         //in any other case we do a strong string comparison
184         return source.toLowerCase() === destination.toLowerCase();
185     },
186     /**
187      * escapes a strings special chars (crossported from dojo 1.3+)
188      *
189      * @param str the string
190      *
191      * @param except a set of exceptions
192      */
193     escapeString: function(/*String*/str, /*String?*/except) {
194         //	summary:
195         //		Adds escape sequences for special characters in regular expressions
196         // except:
197         //		a String with special characters to be left unescaped
198         return str.replace(/([\.$?*|:{}\(\)\[\]\\\/\+^])/g, function(ch) {
199             if (except && except.indexOf(ch) != -1) {
200                 return ch;
201             }
202             return "\\" + ch;
203         }); // String
204     },
205     /**
206      * Save document.getElementById (this code was ported over from dojo)
207      * the idea is that either a string or domNode can be passed
208      * @param {Object} reference the reference which has to be byIded
209      */
210     byId : function(/*object*/ reference) {
211         if (!reference) {
212             throw Error(this.getMessage("ERR_REF_OR_ID", null, "_Lang.byId", "reference"));
213         }
214         return (this.isString(reference)) ? document.getElementById(reference) : reference;
215     },
216     /**
217      * Helper function to provide a trim with a given splitter regular expression
218      * @param {String} it the string to be trimmed
219      * @param {RegExp} splitter the splitter regular expressiion
220      *
221      * FIXME is this still used?
222      */
223     trimStringInternal : function(it, splitter) {
224         return this.strToArray(it, splitter).join(splitter);
225     },
226     /**
227      * String to array function performs a string to array transformation
228      * @param {String} it the string which has to be changed into an array
229      * @param {RegExp} splitter our splitter reglar expression
230      * @return an array of the splitted string
231      */
232     strToArray : function(/*string*/ it, /*regexp*/ splitter) {
233         //	summary:
234         //		Return true if it is a String
235         this._assertStr(it, "strToArray", "it");
236         if (!splitter) {
237             throw Error(this.getMessage("ERR_PARAM_STR_RE", null, "myfaces._impl._util._Lang.strToArray", "splitter"));
238         }
239         var retArr = it.split(splitter);
240         var len = retArr.length;
241         for (var cnt = 0; cnt < len; cnt++) {
242             retArr[cnt] = this.trim(retArr[cnt]);
243         }
244         return retArr;
245     },
246     _assertStr: function(it, functionName, paramName) {
247         if (!this.isString(it)) {
248             throw Error(this.getMessage("ERR_PARAM_STR", null, "myfaces._impl._util._Lang." + functionName, paramName));
249         }
250     },
251     /**
252      * hyperfast trim
253      * http://blog.stevenlevithan.com/archives/faster-trim-javascript
254      * crossported from dojo
255      */
256     trim : function(/*string*/ str) {
257         this._assertStr(str, "trim", "str");
258         str = str.replace(/^\s\s*/, '');
259         var ws = /\s/;
260         var i = str.length;
261         while (ws.test(str.charAt(--i))) {
262             //do nothing
263         }
264         return str.slice(0, i + 1);
265     },
266     /**
267      * Backported from dojo
268      * a failsafe string determination method
269      * (since in javascript String != "" typeof alone fails!)
270      * @param it {|Object|} the object to be checked for being a string
271      * @return true in case of being a string false otherwise
272      */
273     isString: function(/*anything*/ it) {
274         //	summary:
275         //		Return true if it is a String
276         return !!arguments.length && it != null && (typeof it == "string" || it instanceof String); // Boolean
277     },
278     /**
279      * hitch backported from dojo
280      * hitch allows to assign a function to a dedicated scope
281      * this is helpful in situations when function reassignments
282      * can happen
283      * (notably happens often in lazy xhr code)
284      *
285      * @param {Function} scope of the function to be executed in
286      * @param {Function} method to be executed, the method must be of type function
287      *
288      * @return whatever the executed method returns
289      */
290     hitch : function(scope, method) {
291         return !scope ? method : function() {
292             return method.apply(scope, arguments || []);
293         }; // Function
294     },
295     /**
296      * Helper function to merge two maps
297      * into one
298      * @param {Object} dest the destination map
299      * @param {Object} src the source map
300      * @param {boolean} overwrite if set to true the destination is overwritten if the keys exist in both maps
301      **/
302     mixMaps: function(dest, src, overwrite, blockFilter, whitelistFilter) {
303         if (!dest || !src) {
304             throw Error(this.getMessage("ERR_PARAM_MIXMAPS", null, "_Lang.mixMaps"));
305         }
306         var _undef = "undefined";
307         for (var key in src) {
308             if (blockFilter && blockFilter[key]) {
309                 continue;
310             }
311             if (whitelistFilter && !whitelistFilter[key]) {
312                 continue;
313             }
314             if (!overwrite) {
315                 /**
316                  *we use exists instead of booleans because we cannot rely
317                  *on all values being non boolean, we would need an elvis
318                  *operator in javascript to shorten this :-(
319                  */
320                 dest[key] = (_undef != typeof dest[key]) ? dest[key] : src[key];
321             } else {
322                 dest[key] = (_undef != typeof src[key]) ? src[key] : dest[key];
323             }
324         }
325         return dest;
326     },
327     /**
328      * checks if an array contains an element
329      * @param {Array} arr   array
330      * @param {String} str string to check for
331      */
332     contains : function(arr, str) {
333         if (!arr || !str) {
334             throw Error(this.getMessage("ERR_MUST_BE_PROVIDED", null, "_Lang.contains", "arr {array}", "str {string}"));
335         }
336         return this.arrIndexOf(arr, str) != -1;
337     },
338     arrToMap: function(arr, offset) {
339         var ret = new Array(arr.length);
340         var len = arr.length;
341         offset = (offset) ? offset : 0;
342         for (var cnt = 0; cnt < len; cnt++) {
343             ret[arr[cnt]] = cnt + offset;
344         }
345         return ret;
346     },
347     objToArray: function(obj, offset, pack) {
348         if (!obj) {
349             return null;
350         }
351         //since offset is numeric we cannot use the shortcut due to 0 being false
352         //special condition array delivered no offset no pack
353         if (obj instanceof Array && !offset && !pack)  return obj;
354         var finalOffset = ('undefined' != typeof offset || null != offset) ? offset : 0;
355         var finalPack = pack || [];
356         try {
357             return finalPack.concat(Array.prototype.slice.call(obj, finalOffset));
358         } catch (e) {
359             //ie8 (again as only browser) delivers for css 3 selectors a non convertible object
360             //we have to do it the hard way
361             //ie8 seems generally a little bit strange in its behavior some
362             //objects break the function is everything methodology of javascript
363             //and do not implement apply call, or are pseudo arrays which cannot
364             //be sliced
365             for (var cnt = finalOffset; cnt < obj.length; cnt++) {
366                 finalPack.push(obj[cnt]);
367             }
368             return finalPack;
369         }
370     },
371     /**
372      * foreach implementation utilizing the
373      * ECMAScript wherever possible
374      * with added functionality
375      *
376      * @param arr the array to filter
377      * @param func the closure to apply the function to, with the syntax defined by the ecmascript functionality
378      * function (element<,key, array>)
379      * <p />
380      * optional params
381      * <p />
382      * <ul>
383      *      <li>param startPos (optional) the starting position </li>
384      *      <li>param scope (optional) the scope to apply the closure to  </li>
385      * </ul>
386      */
387     arrForEach: function(arr, func /*startPos, scope*/) {
388         if (!arr || !arr.length) return;
389         var startPos = Number(arguments[2]) || 0;
390         var thisObj = arguments[3];
391         //check for an existing foreach mapping on array prototypes
392         //IE9 still does not pass array objects as result for dom ops
393         arr = this.objToArray(arr);
394         (startPos) ? arr.slice(startPos).forEach(func, thisObj) : arr.forEach(func, thisObj);
395     },
396     /**
397      * foreach implementation utilizing the
398      * ECMAScript wherever possible
399      * with added functionality
400      *
401      * @param arr the array to filter
402      * @param func the closure to apply the function to, with the syntax defined by the ecmascript functionality
403      * function (element<,key, array>)
404      * <p />
405      * additional params
406      * <ul>
407      *  <li> startPos (optional) the starting position</li>
408      *  <li> scope (optional) the scope to apply the closure to</li>
409      * </ul>
410      */
411     arrFilter: function(arr, func /*startPos, scope*/) {
412         if (!arr || !arr.length) return [];
413         arr = this.objToArray(arr);
414         return ((startPos) ? arr.slice(startPos).filter(func, thisObj) : arr.filter(func, thisObj));
415     },
416     /**
417      * adds a EcmaScript optimized indexOf to our mix,
418      * checks for the presence of an indexOf functionality
419      * and applies it, otherwise uses a fallback to the hold
420      * loop method to determine the index
421      *
422      * @param arr the array
423      * @param element the index to search for
424      */
425     arrIndexOf: function(arr, element /*fromIndex*/) {
426         if (!arr || !arr.length) return -1;
427         var pos = Number(arguments[2]) || 0;
428         arr = this.objToArray(arr);
429         return arr.indexOf(element, pos);
430     },
431     /**
432      * helper to automatically apply a delivered arguments map or array
433      * to its destination which has a field "_"<key> and a full field
434      *
435      * @param dest the destination object
436      * @param args the arguments array or map
437      * @param argNames the argument names to be transferred
438      */
439     applyArgs: function(dest, args, argNames) {
440         var UDEF = 'undefined';
441         if (argNames) {
442             for (var cnt = 0; cnt < args.length; cnt++) {
443                 //dest can be null or 0 hence no shortcut
444                 if (UDEF != typeof dest["_" + argNames[cnt]]) {
445                     dest["_" + argNames[cnt]] = args[cnt];
446                 }
447                 if (UDEF != typeof dest[ argNames[cnt]]) {
448                     dest[argNames[cnt]] = args[cnt];
449                 }
450             }
451         } else {
452             for (var key in args) {
453                 if (UDEF != typeof dest["_" + key]) {
454                     dest["_" + key] = args[key];
455                 }
456                 if (UDEF != typeof dest[key]) {
457                     dest[key] = args[key];
458                 }
459             }
460         }
461     },
462     /**
463      * creates a standardized error message which can be reused by the system
464      *
465      * @param sourceClass the source class issuing the exception
466      * @param func the function issuing the exception
467      * @param error the error object itself (optional)
468      */
469     createErrorMsg: function(sourceClass, func, error) {
470         var ret = [];
471         var keyValToStr = this.hitch(this, this.keyValToStr),
472                 getMsg = this.hitch(this, this.getMessage),
473                 pushRet = this.hitch(ret, ret.push);
474         pushRet(keyValToStr(getMsg("MSG_AFFECTED_CLASS"), sourceClass));
475         pushRet(keyValToStr(getMsg("MSG_AFFECTED_METHOD"), func));
476         /*we push the values into separate vars to improve the compression*/
477         var errName = error.name;
478         var errMsg = error.message;
479         var errDesc = error.description;
480         var errNum = error.number;
481         var errLineNo = error.lineNumber;
482         if (error) {
483             var _UDEF = "undefined";
484             pushRet(keyValToStr(getMsg("MSG_ERROR_NAME"), errName ? errName : _UDEF));
485             pushRet(keyValToStr(getMsg("MSG_ERROR_MESSAGE"), errMsg ? errMsg : _UDEF));
486             pushRet(keyValToStr(getMsg("MSG_ERROR_DESC"), errDesc ? errDesc : _UDEF));
487             pushRet(keyValToStr(getMsg("MSG_ERROR_NO"), _UDEF != typeof errNum ? errNum : _UDEF));
488             pushRet(keyValToStr(getMsg("MSG_ERROR_LINENO"), _UDEF != typeof errLineNo ? errLineNo : _UDEF));
489         }
490         return ret.join("");
491     },
492     /**
493      * transforms a key value pair into a string
494      * @param key the key
495      * @param val the value
496      * @param delimiter the delimiter
497      */
498     keyValToStr: function(key, val, delimiter) {
499         var ret = [];
500         pushRet = this.hitch(ret, ret.push);
501         pushRet(key);
502         pushRet(val);
503         delimiter = delimiter || "\n";
504 		pushRet(delimiter);
505         return ret.join("");
506     },
507     parseXML: function(txt) {
508         try {
509             var parser = new DOMParser();
510             return parser.parseFromString(txt, "text/xml");
511         } catch (e) {
512             //undefined internal parser error
513             return null;
514         }
515     },
516     serializeXML: function(xmlNode, escape) {
517         if (!escape) {
518             if (xmlNode.data) return xmlNode.data; //CDATA block has raw data
519             if (xmlNode.textContent) return xmlNode.textContent; //textNode has textContent
520         }
521         return (new XMLSerializer()).serializeToString(xmlNode);
522     },
523     serializeChilds: function(xmlNode) {
524         var buffer = [];
525         if (!xmlNode.childNodes) return "";
526         for (var cnt = 0; cnt < xmlNode.childNodes.length; cnt++) {
527             buffer.push(this.serializeXML(xmlNode.childNodes[cnt]));
528         }
529         return buffer.join("");
530     },
531     isXMLParseError: function(xmlContent) {
532         //TODO determine the ie specific part here
533         //no xml content
534         if (xmlContent == null) return true;
535         var findParseError = function(node) {
536             if (!node || !node.childNodes) return false;
537             for (var cnt = 0; cnt < node.childNodes.length; cnt++) {
538                 var childNode = node.childNodes[cnt];
539                 if (childNode.tagName && childNode.tagName == "parsererror") return true;
540             }
541             return false;
542         };
543         return !xmlContent ||
544                 (this.exists(xmlContent, "parseError.errorCode") && xmlContent.parseError.errorCode != 0) ||
545                 findParseError(xmlContent);
546     },
547     /**
548      * creates a neutral form data wrapper over an existing form Data element
549      * the wrapper delegates following methods, append
550      * and adds makeFinal as finalizing method which returns the final
551      * send representation of the element
552      *
553      * @param formData an array
554      */
555     createFormDataDecorator: function(formData) {
556         //we simulate the dom level 2 form element here
557         var _newCls = null;
558         var bufInstance = null;
559         if (!this.FormDataDecoratorArray) {
560             this.FormDataDecoratorArray = function (theFormData) {
561                 this._valBuf = theFormData;
562                 this._idx = {};
563             };
564             _newCls = this.FormDataDecoratorArray;
565             _newCls.prototype.append = function(key, val) {
566                 this._valBuf.push([encodeURIComponent(key), encodeURIComponent(val)].join("="));
567                 this._idx[key] = true;
568             };
569             _newCls.prototype.hasKey = function(key) {
570                 return !!this._idx[key];
571             };
572             _newCls.prototype.makeFinal = function() {
573                 return this._valBuf.join("&");
574             };
575         }
576         if (!this.FormDataDecoratorString) {
577             this.FormDataDecoratorString = function (theFormData) {
578                 this._preprocessedData = theFormData;
579                 this._valBuf = [];
580                 this._idx = {};
581             };
582             _newCls = this.FormDataDecoratorString;
583             _newCls.prototype.append = function(key, val) {
584                 this._valBuf.push([encodeURIComponent(key), encodeURIComponent(val)].join("="));
585                 this._idx[key] = true;
586             };
587             //for now we check only for keys which are added subsequently otherwise we do not perform any checks
588             _newCls.prototype.hasKey = function(key) {
589                 return !!this._idx[key];
590             };
591             _newCls.prototype.makeFinal = function() {
592                 if (this._preprocessedData != "") {
593                     return this._preprocessedData + "&" + this._valBuf.join("&")
594                 } else {
595                     return this._valBuf.join("&");
596                 }
597             };
598         }
599         if (!this.FormDataDecoratorOther) {
600             this.FormDataDecoratorOther = function (theFormData) {
601                 this._valBuf = theFormData;
602                 this._idx = {};
603             };
604             _newCls = this.FormDataDecoratorOther;
605             _newCls.prototype.append = function(key, val) {
606                 this._valBuf.append(key, val);
607                 this._idx[key] = true;
608             };
609             _newCls.prototype.hasKey = function(key) {
610                 return !!this._idx[key];
611             };
612             _newCls.prototype.makeFinal = function() {
613                 return this._valBuf;
614             };
615         }
616         if (formData instanceof Array) {
617             bufInstance = new this.FormDataDecoratorArray(formData);
618         } else if (this.isString(formData)) {
619             bufInstance = new this.FormDataDecoratorString(formData);
620         } else {
621             bufInstance = new this.FormDataDecoratorOther(formData);
622         }
623         return bufInstance;
624     },
625     /**
626      * define a property mechanism which is browser neutral
627      * we cannot use the existing setter and getter mechanisms
628      * for now because old browsers do not support them
629      * in the long run we probably can switch over
630      * or make a code split between legacy and new
631      *
632      * TODO find a way to map this mechanism to the standard
633      * props mechanism modern browsers have
634      *
635      * @param obj
636      * @param name
637      * @param value
638      */
639     attr: function(obj, name, value) {
640         var findAccessor = function(theObj, theName) {
641             return (theObj["_" + theName]) ? "_" + theName : ( (theObj[theName]) ? theName : null)
642         };
643         var applyAttr = function(theObj, theName, value, isFunc) {
644             if (value) {
645                 if (isFunc) {
646                     theObj[theName](value);
647                 } else {
648                     theObj[theName] = value;
649                 }
650                 return null;
651             }
652             return (isFunc) ? theObj[theName]() : theObj[theName];
653         };
654         try {
655             var finalAttr = findAccessor(obj, name);
656             //simple attibute no setter and getter overrides
657             if (finalAttr) {
658                 return applyAttr(obj, finalAttr, value);
659             }
660             //lets check for setter and getter overrides
661             var found = false;
662             var prefix = (value) ? "set" : "get";
663             finalAttr = [prefix,name.substr(0, 1).toUpperCase(),name.substr(1)].join("");
664             finalAttr = findAccessor(obj, finalAttr);
665             if (finalAttr) {
666                 return applyAttr(obj, finalAttr, value, true);
667             }
668             throw Error("property " + name + " not found");
669         } finally {
670             findAccessor = null;
671             applyAttr = null;
672         }
673     }
674 });
675