Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / src / core / src / class / ClassManager.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * @author Jacky Nguyen <jacky@sencha.com>
17  * @docauthor Jacky Nguyen <jacky@sencha.com>
18  * @class Ext.ClassManager
19  *
20  * Ext.ClassManager manages all classes and handles mapping from string class name to
21  * actual class objects throughout the whole framework. It is not generally accessed directly, rather through
22  * these convenient shorthands:
23  *
24  * - {@link Ext#define Ext.define}
25  * - {@link Ext#create Ext.create}
26  * - {@link Ext#widget Ext.widget}
27  * - {@link Ext#getClass Ext.getClass}
28  * - {@link Ext#getClassName Ext.getClassName}
29  *
30  * # Basic syntax:
31  *
32  *     Ext.define(className, properties);
33  *
34  * in which `properties` is an object represent a collection of properties that apply to the class. See
35  * {@link Ext.ClassManager#create} for more detailed instructions.
36  *
37  *     Ext.define('Person', {
38  *          name: 'Unknown',
39  *
40  *          constructor: function(name) {
41  *              if (name) {
42  *                  this.name = name;
43  *              }
44  *
45  *              return this;
46  *          },
47  *
48  *          eat: function(foodType) {
49  *              alert("I'm eating: " + foodType);
50  *
51  *              return this;
52  *          }
53  *     });
54  *
55  *     var aaron = new Person("Aaron");
56  *     aaron.eat("Sandwich"); // alert("I'm eating: Sandwich");
57  *
58  * Ext.Class has a powerful set of extensible {@link Ext.Class#registerPreprocessor pre-processors} which takes care of
59  * everything related to class creation, including but not limited to inheritance, mixins, configuration, statics, etc.
60  *
61  * # Inheritance:
62  *
63  *     Ext.define('Developer', {
64  *          extend: 'Person',
65  *
66  *          constructor: function(name, isGeek) {
67  *              this.isGeek = isGeek;
68  *
69  *              // Apply a method from the parent class' prototype
70  *              this.callParent([name]);
71  *
72  *              return this;
73  *
74  *          },
75  *
76  *          code: function(language) {
77  *              alert("I'm coding in: " + language);
78  *
79  *              this.eat("Bugs");
80  *
81  *              return this;
82  *          }
83  *     });
84  *
85  *     var jacky = new Developer("Jacky", true);
86  *     jacky.code("JavaScript"); // alert("I'm coding in: JavaScript");
87  *                               // alert("I'm eating: Bugs");
88  *
89  * See {@link Ext.Base#callParent} for more details on calling superclass' methods
90  *
91  * # Mixins:
92  *
93  *     Ext.define('CanPlayGuitar', {
94  *          playGuitar: function() {
95  *             alert("F#...G...D...A");
96  *          }
97  *     });
98  *
99  *     Ext.define('CanComposeSongs', {
100  *          composeSongs: function() { ... }
101  *     });
102  *
103  *     Ext.define('CanSing', {
104  *          sing: function() {
105  *              alert("I'm on the highway to hell...")
106  *          }
107  *     });
108  *
109  *     Ext.define('Musician', {
110  *          extend: 'Person',
111  *
112  *          mixins: {
113  *              canPlayGuitar: 'CanPlayGuitar',
114  *              canComposeSongs: 'CanComposeSongs',
115  *              canSing: 'CanSing'
116  *          }
117  *     })
118  *
119  *     Ext.define('CoolPerson', {
120  *          extend: 'Person',
121  *
122  *          mixins: {
123  *              canPlayGuitar: 'CanPlayGuitar',
124  *              canSing: 'CanSing'
125  *          },
126  *
127  *          sing: function() {
128  *              alert("Ahem....");
129  *
130  *              this.mixins.canSing.sing.call(this);
131  *
132  *              alert("[Playing guitar at the same time...]");
133  *
134  *              this.playGuitar();
135  *          }
136  *     });
137  *
138  *     var me = new CoolPerson("Jacky");
139  *
140  *     me.sing(); // alert("Ahem...");
141  *                // alert("I'm on the highway to hell...");
142  *                // alert("[Playing guitar at the same time...]");
143  *                // alert("F#...G...D...A");
144  *
145  * # Config:
146  *
147  *     Ext.define('SmartPhone', {
148  *          config: {
149  *              hasTouchScreen: false,
150  *              operatingSystem: 'Other',
151  *              price: 500
152  *          },
153  *
154  *          isExpensive: false,
155  *
156  *          constructor: function(config) {
157  *              this.initConfig(config);
158  *
159  *              return this;
160  *          },
161  *
162  *          applyPrice: function(price) {
163  *              this.isExpensive = (price > 500);
164  *
165  *              return price;
166  *          },
167  *
168  *          applyOperatingSystem: function(operatingSystem) {
169  *              if (!(/^(iOS|Android|BlackBerry)$/i).test(operatingSystem)) {
170  *                  return 'Other';
171  *              }
172  *
173  *              return operatingSystem;
174  *          }
175  *     });
176  *
177  *     var iPhone = new SmartPhone({
178  *          hasTouchScreen: true,
179  *          operatingSystem: 'iOS'
180  *     });
181  *
182  *     iPhone.getPrice(); // 500;
183  *     iPhone.getOperatingSystem(); // 'iOS'
184  *     iPhone.getHasTouchScreen(); // true;
185  *     iPhone.hasTouchScreen(); // true
186  *
187  *     iPhone.isExpensive; // false;
188  *     iPhone.setPrice(600);
189  *     iPhone.getPrice(); // 600
190  *     iPhone.isExpensive; // true;
191  *
192  *     iPhone.setOperatingSystem('AlienOS');
193  *     iPhone.getOperatingSystem(); // 'Other'
194  *
195  * # Statics:
196  *
197  *     Ext.define('Computer', {
198  *          statics: {
199  *              factory: function(brand) {
200  *                 // 'this' in static methods refer to the class itself
201  *                  return new this(brand);
202  *              }
203  *          },
204  *
205  *          constructor: function() { ... }
206  *     });
207  *
208  *     var dellComputer = Computer.factory('Dell');
209  *
210  * Also see {@link Ext.Base#statics} and {@link Ext.Base#self} for more details on accessing
211  * static properties within class methods
212  *
213  * @singleton
214  */
215 (function(Class, alias) {
216
217     var slice = Array.prototype.slice;
218
219     var Manager = Ext.ClassManager = {
220
221         /**
222          * @property {Object} classes
223          * All classes which were defined through the ClassManager. Keys are the
224          * name of the classes and the values are references to the classes.
225          * @private
226          */
227         classes: {},
228
229         /**
230          * @private
231          */
232         existCache: {},
233
234         /**
235          * @private
236          */
237         namespaceRewrites: [{
238             from: 'Ext.',
239             to: Ext
240         }],
241
242         /**
243          * @private
244          */
245         maps: {
246             alternateToName: {},
247             aliasToName: {},
248             nameToAliases: {}
249         },
250
251         /** @private */
252         enableNamespaceParseCache: true,
253
254         /** @private */
255         namespaceParseCache: {},
256
257         /** @private */
258         instantiators: [],
259
260         //<debug>
261         /** @private */
262         instantiationCounts: {},
263         //</debug>
264
265         /**
266          * Checks if a class has already been created.
267          *
268          * @param {String} className
269          * @return {Boolean} exist
270          */
271         isCreated: function(className) {
272             var i, ln, part, root, parts;
273
274             //<debug error>
275             if (typeof className !== 'string' || className.length < 1) {
276                 Ext.Error.raise({
277                     sourceClass: "Ext.ClassManager",
278                     sourceMethod: "exist",
279                     msg: "Invalid classname, must be a string and must not be empty"
280                 });
281             }
282             //</debug>
283
284             if (this.classes.hasOwnProperty(className) || this.existCache.hasOwnProperty(className)) {
285                 return true;
286             }
287
288             root = Ext.global;
289             parts = this.parseNamespace(className);
290
291             for (i = 0, ln = parts.length; i < ln; i++) {
292                 part = parts[i];
293
294                 if (typeof part !== 'string') {
295                     root = part;
296                 } else {
297                     if (!root || !root[part]) {
298                         return false;
299                     }
300
301                     root = root[part];
302                 }
303             }
304
305             Ext.Loader.historyPush(className);
306
307             this.existCache[className] = true;
308
309             return true;
310         },
311
312         /**
313          * Supports namespace rewriting
314          * @private
315          */
316         parseNamespace: function(namespace) {
317             //<debug error>
318             if (typeof namespace !== 'string') {
319                 Ext.Error.raise({
320                     sourceClass: "Ext.ClassManager",
321                     sourceMethod: "parseNamespace",
322                     msg: "Invalid namespace, must be a string"
323                 });
324             }
325             //</debug>
326
327             var cache = this.namespaceParseCache;
328
329             if (this.enableNamespaceParseCache) {
330                 if (cache.hasOwnProperty(namespace)) {
331                     return cache[namespace];
332                 }
333             }
334
335             var parts = [],
336                 rewrites = this.namespaceRewrites,
337                 rewrite, from, to, i, ln, root = Ext.global;
338
339             for (i = 0, ln = rewrites.length; i < ln; i++) {
340                 rewrite = rewrites[i];
341                 from = rewrite.from;
342                 to = rewrite.to;
343
344                 if (namespace === from || namespace.substring(0, from.length) === from) {
345                     namespace = namespace.substring(from.length);
346
347                     if (typeof to !== 'string') {
348                         root = to;
349                     } else {
350                         parts = parts.concat(to.split('.'));
351                     }
352
353                     break;
354                 }
355             }
356
357             parts.push(root);
358
359             parts = parts.concat(namespace.split('.'));
360
361             if (this.enableNamespaceParseCache) {
362                 cache[namespace] = parts;
363             }
364
365             return parts;
366         },
367
368         /**
369          * Creates a namespace and assign the `value` to the created object
370          *
371          *     Ext.ClassManager.setNamespace('MyCompany.pkg.Example', someObject);
372          *
373          *     alert(MyCompany.pkg.Example === someObject); // alerts true
374          *
375          * @param {String} name
376          * @param {Object} value
377          */
378         setNamespace: function(name, value) {
379             var root = Ext.global,
380                 parts = this.parseNamespace(name),
381                 ln = parts.length - 1,
382                 leaf = parts[ln],
383                 i, part;
384
385             for (i = 0; i < ln; i++) {
386                 part = parts[i];
387
388                 if (typeof part !== 'string') {
389                     root = part;
390                 } else {
391                     if (!root[part]) {
392                         root[part] = {};
393                     }
394
395                     root = root[part];
396                 }
397             }
398
399             root[leaf] = value;
400
401             return root[leaf];
402         },
403
404         /**
405          * The new Ext.ns, supports namespace rewriting
406          * @private
407          */
408         createNamespaces: function() {
409             var root = Ext.global,
410                 parts, part, i, j, ln, subLn;
411
412             for (i = 0, ln = arguments.length; i < ln; i++) {
413                 parts = this.parseNamespace(arguments[i]);
414
415                 for (j = 0, subLn = parts.length; j < subLn; j++) {
416                     part = parts[j];
417
418                     if (typeof part !== 'string') {
419                         root = part;
420                     } else {
421                         if (!root[part]) {
422                             root[part] = {};
423                         }
424
425                         root = root[part];
426                     }
427                 }
428             }
429
430             return root;
431         },
432
433         /**
434          * Sets a name reference to a class.
435          *
436          * @param {String} name
437          * @param {Object} value
438          * @return {Ext.ClassManager} this
439          */
440         set: function(name, value) {
441             var targetName = this.getName(value);
442
443             this.classes[name] = this.setNamespace(name, value);
444
445             if (targetName && targetName !== name) {
446                 this.maps.alternateToName[name] = targetName;
447             }
448
449             return this;
450         },
451
452         /**
453          * Retrieve a class by its name.
454          *
455          * @param {String} name
456          * @return {Ext.Class} class
457          */
458         get: function(name) {
459             if (this.classes.hasOwnProperty(name)) {
460                 return this.classes[name];
461             }
462
463             var root = Ext.global,
464                 parts = this.parseNamespace(name),
465                 part, i, ln;
466
467             for (i = 0, ln = parts.length; i < ln; i++) {
468                 part = parts[i];
469
470                 if (typeof part !== 'string') {
471                     root = part;
472                 } else {
473                     if (!root || !root[part]) {
474                         return null;
475                     }
476
477                     root = root[part];
478                 }
479             }
480
481             return root;
482         },
483
484         /**
485          * Register the alias for a class.
486          *
487          * @param {Ext.Class/String} cls a reference to a class or a className
488          * @param {String} alias Alias to use when referring to this class
489          */
490         setAlias: function(cls, alias) {
491             var aliasToNameMap = this.maps.aliasToName,
492                 nameToAliasesMap = this.maps.nameToAliases,
493                 className;
494
495             if (typeof cls === 'string') {
496                 className = cls;
497             } else {
498                 className = this.getName(cls);
499             }
500
501             if (alias && aliasToNameMap[alias] !== className) {
502                 //<debug info>
503                 if (aliasToNameMap.hasOwnProperty(alias) && Ext.isDefined(Ext.global.console)) {
504                     Ext.global.console.log("[Ext.ClassManager] Overriding existing alias: '" + alias + "' " +
505                         "of: '" + aliasToNameMap[alias] + "' with: '" + className + "'. Be sure it's intentional.");
506                 }
507                 //</debug>
508
509                 aliasToNameMap[alias] = className;
510             }
511
512             if (!nameToAliasesMap[className]) {
513                 nameToAliasesMap[className] = [];
514             }
515
516             if (alias) {
517                 Ext.Array.include(nameToAliasesMap[className], alias);
518             }
519
520             return this;
521         },
522
523         /**
524          * Get a reference to the class by its alias.
525          *
526          * @param {String} alias
527          * @return {Ext.Class} class
528          */
529         getByAlias: function(alias) {
530             return this.get(this.getNameByAlias(alias));
531         },
532
533         /**
534          * Get the name of a class by its alias.
535          *
536          * @param {String} alias
537          * @return {String} className
538          */
539         getNameByAlias: function(alias) {
540             return this.maps.aliasToName[alias] || '';
541         },
542
543         /**
544          * Get the name of a class by its alternate name.
545          *
546          * @param {String} alternate
547          * @return {String} className
548          */
549         getNameByAlternate: function(alternate) {
550             return this.maps.alternateToName[alternate] || '';
551         },
552
553         /**
554          * Get the aliases of a class by the class name
555          *
556          * @param {String} name
557          * @return {String[]} aliases
558          */
559         getAliasesByName: function(name) {
560             return this.maps.nameToAliases[name] || [];
561         },
562
563         /**
564          * Get the name of the class by its reference or its instance.
565          *
566          *     Ext.ClassManager.getName(Ext.Action); // returns "Ext.Action"
567          *
568          * {@link Ext#getClassName Ext.getClassName} is alias for {@link Ext.ClassManager#getName Ext.ClassManager.getName}.
569          *
570          * @param {Ext.Class/Object} object
571          * @return {String} className
572          */
573         getName: function(object) {
574             return object && object.$className || '';
575         },
576
577         /**
578          * Get the class of the provided object; returns null if it's not an instance
579          * of any class created with Ext.define.
580          *
581          *     var component = new Ext.Component();
582          *
583          *     Ext.ClassManager.getClass(component); // returns Ext.Component
584          *
585          * {@link Ext#getClass Ext.getClass} is alias for {@link Ext.ClassManager#getClass Ext.ClassManager.getClass}.
586          *
587          * @param {Object} object
588          * @return {Ext.Class} class
589          */
590         getClass: function(object) {
591             return object && object.self || null;
592         },
593
594         /**
595          * Defines a class.
596          *
597          * {@link Ext#define Ext.define} and {@link Ext.ClassManager#create Ext.ClassManager.create} are almost aliases
598          * of each other, with the only exception that Ext.define allows definition of {@link Ext.Class#override overrides}.
599          * To avoid trouble, always use Ext.define.
600          *
601          *     Ext.define('My.awesome.Class', {
602          *         someProperty: 'something',
603          *         someMethod: function() { ... }
604          *         ...
605          *
606          *     }, function() {
607          *         alert('Created!');
608          *         alert(this === My.awesome.Class); // alerts true
609          *
610          *         var myInstance = new this();
611          *     });
612          *
613          * @param {String} className The class name to create in string dot-namespaced format, for example:
614          * `My.very.awesome.Class`, `FeedViewer.plugin.CoolPager`. It is highly recommended to follow this simple convention:
615          *
616          * - The root and the class name are 'CamelCased'
617          * - Everything else is lower-cased
618          *
619          * @param {Object} data The key-value pairs of properties to apply to this class. Property names can be of any valid
620          * strings, except those in the reserved list below:
621          *
622          * - {@link Ext.Base#self self}
623          * - {@link Ext.Class#alias alias}
624          * - {@link Ext.Class#alternateClassName alternateClassName}
625          * - {@link Ext.Class#config config}
626          * - {@link Ext.Class#extend extend}
627          * - {@link Ext.Class#inheritableStatics inheritableStatics}
628          * - {@link Ext.Class#mixins mixins}
629          * - {@link Ext.Class#override override} (only when using {@link Ext#define Ext.define})
630          * - {@link Ext.Class#requires requires}
631          * - {@link Ext.Class#singleton singleton}
632          * - {@link Ext.Class#statics statics}
633          * - {@link Ext.Class#uses uses}
634          *
635          * @param {Function} [createdFn] callback to execute after the class is created, the execution scope of which
636          * (`this`) will be the newly created class itself.
637          *
638          * @return {Ext.Base}
639          */
640         create: function(className, data, createdFn) {
641             var manager = this;
642
643             //<debug error>
644             if (typeof className !== 'string') {
645                 Ext.Error.raise({
646                     sourceClass: "Ext",
647                     sourceMethod: "define",
648                     msg: "Invalid class name '" + className + "' specified, must be a non-empty string"
649                 });
650             }
651             //</debug>
652
653             data.$className = className;
654
655             return new Class(data, function() {
656                 var postprocessorStack = data.postprocessors || manager.defaultPostprocessors,
657                     registeredPostprocessors = manager.postprocessors,
658                     index = 0,
659                     postprocessors = [],
660                     postprocessor, process, i, ln;
661
662                 delete data.postprocessors;
663
664                 for (i = 0, ln = postprocessorStack.length; i < ln; i++) {
665                     postprocessor = postprocessorStack[i];
666
667                     if (typeof postprocessor === 'string') {
668                         postprocessor = registeredPostprocessors[postprocessor];
669
670                         if (!postprocessor.always) {
671                             if (data[postprocessor.name] !== undefined) {
672                                 postprocessors.push(postprocessor.fn);
673                             }
674                         }
675                         else {
676                             postprocessors.push(postprocessor.fn);
677                         }
678                     }
679                     else {
680                         postprocessors.push(postprocessor);
681                     }
682                 }
683
684                 process = function(clsName, cls, clsData) {
685                     postprocessor = postprocessors[index++];
686
687                     if (!postprocessor) {
688                         manager.set(className, cls);
689
690                         Ext.Loader.historyPush(className);
691
692                         if (createdFn) {
693                             createdFn.call(cls, cls);
694                         }
695
696                         return;
697                     }
698
699                     if (postprocessor.call(this, clsName, cls, clsData, process) !== false) {
700                         process.apply(this, arguments);
701                     }
702                 };
703
704                 process.call(manager, className, this, data);
705             });
706         },
707
708         /**
709          * Instantiate a class by its alias.
710          *
711          * If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will
712          * attempt to load the class via synchronous loading.
713          *
714          *     var window = Ext.ClassManager.instantiateByAlias('widget.window', { width: 600, height: 800, ... });
715          *
716          * {@link Ext#createByAlias Ext.createByAlias} is alias for {@link Ext.ClassManager#instantiateByAlias Ext.ClassManager.instantiateByAlias}.
717          *
718          * @param {String} alias
719          * @param {Object...} args Additional arguments after the alias will be passed to the
720          * class constructor.
721          * @return {Object} instance
722          */
723         instantiateByAlias: function() {
724             var alias = arguments[0],
725                 args = slice.call(arguments),
726                 className = this.getNameByAlias(alias);
727
728             if (!className) {
729                 className = this.maps.aliasToName[alias];
730
731                 //<debug error>
732                 if (!className) {
733                     Ext.Error.raise({
734                         sourceClass: "Ext",
735                         sourceMethod: "createByAlias",
736                         msg: "Cannot create an instance of unrecognized alias: " + alias
737                     });
738                 }
739                 //</debug>
740
741                 //<debug warn>
742                 if (Ext.global.console) {
743                     Ext.global.console.warn("[Ext.Loader] Synchronously loading '" + className + "'; consider adding " +
744                          "Ext.require('" + alias + "') above Ext.onReady");
745                 }
746                 //</debug>
747
748                 Ext.syncRequire(className);
749             }
750
751             args[0] = className;
752
753             return this.instantiate.apply(this, args);
754         },
755
756         /**
757          * Instantiate a class by either full name, alias or alternate name.
758          *
759          * If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will
760          * attempt to load the class via synchronous loading.
761          *
762          * For example, all these three lines return the same result:
763          *
764          *     // alias
765          *     var window = Ext.ClassManager.instantiate('widget.window', { width: 600, height: 800, ... });
766          *
767          *     // alternate name
768          *     var window = Ext.ClassManager.instantiate('Ext.Window', { width: 600, height: 800, ... });
769          *
770          *     // full class name
771          *     var window = Ext.ClassManager.instantiate('Ext.window.Window', { width: 600, height: 800, ... });
772          *
773          * {@link Ext#create Ext.create} is alias for {@link Ext.ClassManager#instantiate Ext.ClassManager.instantiate}.
774          *
775          * @param {String} name
776          * @param {Object...} args Additional arguments after the name will be passed to the class' constructor.
777          * @return {Object} instance
778          */
779         instantiate: function() {
780             var name = arguments[0],
781                 args = slice.call(arguments, 1),
782                 alias = name,
783                 possibleName, cls;
784
785             if (typeof name !== 'function') {
786                 //<debug error>
787                 if ((typeof name !== 'string' || name.length < 1)) {
788                     Ext.Error.raise({
789                         sourceClass: "Ext",
790                         sourceMethod: "create",
791                         msg: "Invalid class name or alias '" + name + "' specified, must be a non-empty string"
792                     });
793                 }
794                 //</debug>
795
796                 cls = this.get(name);
797             }
798             else {
799                 cls = name;
800             }
801
802             // No record of this class name, it's possibly an alias, so look it up
803             if (!cls) {
804                 possibleName = this.getNameByAlias(name);
805
806                 if (possibleName) {
807                     name = possibleName;
808
809                     cls = this.get(name);
810                 }
811             }
812
813             // Still no record of this class name, it's possibly an alternate name, so look it up
814             if (!cls) {
815                 possibleName = this.getNameByAlternate(name);
816
817                 if (possibleName) {
818                     name = possibleName;
819
820                     cls = this.get(name);
821                 }
822             }
823
824             // Still not existing at this point, try to load it via synchronous mode as the last resort
825             if (!cls) {
826                 //<debug warn>
827                 if (Ext.global.console) {
828                     Ext.global.console.warn("[Ext.Loader] Synchronously loading '" + name + "'; consider adding " +
829                          "Ext.require('" + ((possibleName) ? alias : name) + "') above Ext.onReady");
830                 }
831                 //</debug>
832
833                 Ext.syncRequire(name);
834
835                 cls = this.get(name);
836             }
837
838             //<debug error>
839             if (!cls) {
840                 Ext.Error.raise({
841                     sourceClass: "Ext",
842                     sourceMethod: "create",
843                     msg: "Cannot create an instance of unrecognized class name / alias: " + alias
844                 });
845             }
846
847             if (typeof cls !== 'function') {
848                 Ext.Error.raise({
849                     sourceClass: "Ext",
850                     sourceMethod: "create",
851                     msg: "'" + name + "' is a singleton and cannot be instantiated"
852                 });
853             }
854             //</debug>
855
856             //<debug>
857             if (!this.instantiationCounts[name]) {
858                 this.instantiationCounts[name] = 0;
859             }
860
861             this.instantiationCounts[name]++;
862             //</debug>
863
864             return this.getInstantiator(args.length)(cls, args);
865         },
866
867         /**
868          * @private
869          * @param name
870          * @param args
871          */
872         dynInstantiate: function(name, args) {
873             args = Ext.Array.from(args, true);
874             args.unshift(name);
875
876             return this.instantiate.apply(this, args);
877         },
878
879         /**
880          * @private
881          * @param length
882          */
883         getInstantiator: function(length) {
884             if (!this.instantiators[length]) {
885                 var i = length,
886                     args = [];
887
888                 for (i = 0; i < length; i++) {
889                     args.push('a['+i+']');
890                 }
891
892                 this.instantiators[length] = new Function('c', 'a', 'return new c('+args.join(',')+')');
893             }
894
895             return this.instantiators[length];
896         },
897
898         /**
899          * @private
900          */
901         postprocessors: {},
902
903         /**
904          * @private
905          */
906         defaultPostprocessors: [],
907
908         /**
909          * Register a post-processor function.
910          *
911          * @param {String} name
912          * @param {Function} postprocessor
913          */
914         registerPostprocessor: function(name, fn, always) {
915             this.postprocessors[name] = {
916                 name: name,
917                 always: always ||  false,
918                 fn: fn
919             };
920
921             return this;
922         },
923
924         /**
925          * Set the default post processors array stack which are applied to every class.
926          *
927          * @param {String/String[]} The name of a registered post processor or an array of registered names.
928          * @return {Ext.ClassManager} this
929          */
930         setDefaultPostprocessors: function(postprocessors) {
931             this.defaultPostprocessors = Ext.Array.from(postprocessors);
932
933             return this;
934         },
935
936         /**
937          * Insert this post-processor at a specific position in the stack, optionally relative to
938          * any existing post-processor
939          *
940          * @param {String} name The post-processor name. Note that it needs to be registered with
941          * {@link Ext.ClassManager#registerPostprocessor} before this
942          * @param {String} offset The insertion position. Four possible values are:
943          * 'first', 'last', or: 'before', 'after' (relative to the name provided in the third argument)
944          * @param {String} relativeName
945          * @return {Ext.ClassManager} this
946          */
947         setDefaultPostprocessorPosition: function(name, offset, relativeName) {
948             var defaultPostprocessors = this.defaultPostprocessors,
949                 index;
950
951             if (typeof offset === 'string') {
952                 if (offset === 'first') {
953                     defaultPostprocessors.unshift(name);
954
955                     return this;
956                 }
957                 else if (offset === 'last') {
958                     defaultPostprocessors.push(name);
959
960                     return this;
961                 }
962
963                 offset = (offset === 'after') ? 1 : -1;
964             }
965
966             index = Ext.Array.indexOf(defaultPostprocessors, relativeName);
967
968             if (index !== -1) {
969                 Ext.Array.splice(defaultPostprocessors, Math.max(0, index + offset), 0, name);
970             }
971
972             return this;
973         },
974
975         /**
976          * Converts a string expression to an array of matching class names. An expression can either refers to class aliases
977          * or class names. Expressions support wildcards:
978          *
979          *     // returns ['Ext.window.Window']
980          *     var window = Ext.ClassManager.getNamesByExpression('widget.window');
981          *
982          *     // returns ['widget.panel', 'widget.window', ...]
983          *     var allWidgets = Ext.ClassManager.getNamesByExpression('widget.*');
984          *
985          *     // returns ['Ext.data.Store', 'Ext.data.ArrayProxy', ...]
986          *     var allData = Ext.ClassManager.getNamesByExpression('Ext.data.*');
987          *
988          * @param {String} expression
989          * @return {String[]} classNames
990          */
991         getNamesByExpression: function(expression) {
992             var nameToAliasesMap = this.maps.nameToAliases,
993                 names = [],
994                 name, alias, aliases, possibleName, regex, i, ln;
995
996             //<debug error>
997             if (typeof expression !== 'string' || expression.length < 1) {
998                 Ext.Error.raise({
999                     sourceClass: "Ext.ClassManager",
1000                     sourceMethod: "getNamesByExpression",
1001                     msg: "Expression " + expression + " is invalid, must be a non-empty string"
1002                 });
1003             }
1004             //</debug>
1005
1006             if (expression.indexOf('*') !== -1) {
1007                 expression = expression.replace(/\*/g, '(.*?)');
1008                 regex = new RegExp('^' + expression + '$');
1009
1010                 for (name in nameToAliasesMap) {
1011                     if (nameToAliasesMap.hasOwnProperty(name)) {
1012                         aliases = nameToAliasesMap[name];
1013
1014                         if (name.search(regex) !== -1) {
1015                             names.push(name);
1016                         }
1017                         else {
1018                             for (i = 0, ln = aliases.length; i < ln; i++) {
1019                                 alias = aliases[i];
1020
1021                                 if (alias.search(regex) !== -1) {
1022                                     names.push(name);
1023                                     break;
1024                                 }
1025                             }
1026                         }
1027                     }
1028                 }
1029
1030             } else {
1031                 possibleName = this.getNameByAlias(expression);
1032
1033                 if (possibleName) {
1034                     names.push(possibleName);
1035                 } else {
1036                     possibleName = this.getNameByAlternate(expression);
1037
1038                     if (possibleName) {
1039                         names.push(possibleName);
1040                     } else {
1041                         names.push(expression);
1042                     }
1043                 }
1044             }
1045
1046             return names;
1047         }
1048     };
1049
1050     var defaultPostprocessors = Manager.defaultPostprocessors;
1051     //<feature classSystem.alias>
1052
1053     /**
1054      * @cfg {String[]} alias
1055      * @member Ext.Class
1056      * List of short aliases for class names.  Most useful for defining xtypes for widgets:
1057      *
1058      *     Ext.define('MyApp.CoolPanel', {
1059      *         extend: 'Ext.panel.Panel',
1060      *         alias: ['widget.coolpanel'],
1061      *         title: 'Yeah!'
1062      *     });
1063      *
1064      *     // Using Ext.create
1065      *     Ext.widget('widget.coolpanel');
1066      *     // Using the shorthand for widgets and in xtypes
1067      *     Ext.widget('panel', {
1068      *         items: [
1069      *             {xtype: 'coolpanel', html: 'Foo'},
1070      *             {xtype: 'coolpanel', html: 'Bar'}
1071      *         ]
1072      *     });
1073      */
1074     Manager.registerPostprocessor('alias', function(name, cls, data) {
1075         var aliases = data.alias,
1076             i, ln;
1077
1078         delete data.alias;
1079
1080         for (i = 0, ln = aliases.length; i < ln; i++) {
1081             alias = aliases[i];
1082
1083             this.setAlias(cls, alias);
1084         }
1085     });
1086
1087     /**
1088      * @cfg {Boolean} singleton
1089      * @member Ext.Class
1090      * When set to true, the class will be instantiated as singleton.  For example:
1091      *
1092      *     Ext.define('Logger', {
1093      *         singleton: true,
1094      *         log: function(msg) {
1095      *             console.log(msg);
1096      *         }
1097      *     });
1098      *
1099      *     Logger.log('Hello');
1100      */
1101     Manager.registerPostprocessor('singleton', function(name, cls, data, fn) {
1102         fn.call(this, name, new cls(), data);
1103         return false;
1104     });
1105
1106     /**
1107      * @cfg {String/String[]} alternateClassName
1108      * @member Ext.Class
1109      * Defines alternate names for this class.  For example:
1110      *
1111      *     Ext.define('Developer', {
1112      *         alternateClassName: ['Coder', 'Hacker'],
1113      *         code: function(msg) {
1114      *             alert('Typing... ' + msg);
1115      *         }
1116      *     });
1117      *
1118      *     var joe = Ext.create('Developer');
1119      *     joe.code('stackoverflow');
1120      *
1121      *     var rms = Ext.create('Hacker');
1122      *     rms.code('hack hack');
1123      */
1124     Manager.registerPostprocessor('alternateClassName', function(name, cls, data) {
1125         var alternates = data.alternateClassName,
1126             i, ln, alternate;
1127
1128         if (!(alternates instanceof Array)) {
1129             alternates = [alternates];
1130         }
1131
1132         for (i = 0, ln = alternates.length; i < ln; i++) {
1133             alternate = alternates[i];
1134
1135             //<debug error>
1136             if (typeof alternate !== 'string') {
1137                 Ext.Error.raise({
1138                     sourceClass: "Ext",
1139                     sourceMethod: "define",
1140                     msg: "Invalid alternate of: '" + alternate + "' for class: '" + name + "'; must be a valid string"
1141                 });
1142             }
1143             //</debug>
1144
1145             this.set(alternate, cls);
1146         }
1147     });
1148
1149     Manager.setDefaultPostprocessors(['alias', 'singleton', 'alternateClassName']);
1150
1151     Ext.apply(Ext, {
1152         /**
1153          * @method
1154          * @member Ext
1155          * @alias Ext.ClassManager#instantiate
1156          */
1157         create: alias(Manager, 'instantiate'),
1158
1159         /**
1160          * @private
1161          * API to be stablized
1162          *
1163          * @param {Object} item
1164          * @param {String} namespace
1165          */
1166         factory: function(item, namespace) {
1167             if (item instanceof Array) {
1168                 var i, ln;
1169
1170                 for (i = 0, ln = item.length; i < ln; i++) {
1171                     item[i] = Ext.factory(item[i], namespace);
1172                 }
1173
1174                 return item;
1175             }
1176
1177             var isString = (typeof item === 'string');
1178
1179             if (isString || (item instanceof Object && item.constructor === Object)) {
1180                 var name, config = {};
1181
1182                 if (isString) {
1183                     name = item;
1184                 }
1185                 else {
1186                     name = item.className;
1187                     config = item;
1188                     delete config.className;
1189                 }
1190
1191                 if (namespace !== undefined && name.indexOf(namespace) === -1) {
1192                     name = namespace + '.' + Ext.String.capitalize(name);
1193                 }
1194
1195                 return Ext.create(name, config);
1196             }
1197
1198             if (typeof item === 'function') {
1199                 return Ext.create(item);
1200             }
1201
1202             return item;
1203         },
1204
1205         /**
1206          * Convenient shorthand to create a widget by its xtype, also see {@link Ext.ClassManager#instantiateByAlias}
1207          *
1208          *     var button = Ext.widget('button'); // Equivalent to Ext.create('widget.button')
1209          *     var panel = Ext.widget('panel'); // Equivalent to Ext.create('widget.panel')
1210          *
1211          * @method
1212          * @member Ext
1213          * @param {String} name  xtype of the widget to create.
1214          * @param {Object...} args  arguments for the widget constructor.
1215          * @return {Object} widget instance
1216          */
1217         widget: function(name) {
1218             var args = slice.call(arguments);
1219             args[0] = 'widget.' + name;
1220
1221             return Manager.instantiateByAlias.apply(Manager, args);
1222         },
1223
1224         /**
1225          * @method
1226          * @member Ext
1227          * @alias Ext.ClassManager#instantiateByAlias
1228          */
1229         createByAlias: alias(Manager, 'instantiateByAlias'),
1230
1231         /**
1232          * @cfg {String} override
1233          * @member Ext.Class
1234          * 
1235          * Defines an override applied to a class. Note that **overrides can only be created using
1236          * {@link Ext#define}.** {@link Ext.ClassManager#create} only creates classes.
1237          * 
1238          * To define an override, include the override property. The content of an override is
1239          * aggregated with the specified class in order to extend or modify that class. This can be
1240          * as simple as setting default property values or it can extend and/or replace methods.
1241          * This can also extend the statics of the class.
1242          *
1243          * One use for an override is to break a large class into manageable pieces.
1244          *
1245          *      // File: /src/app/Panel.js
1246          *
1247          *      Ext.define('My.app.Panel', {
1248          *          extend: 'Ext.panel.Panel',
1249          *          requires: [
1250          *              'My.app.PanelPart2',
1251          *              'My.app.PanelPart3'
1252          *          ]
1253          *
1254          *          constructor: function (config) {
1255          *              this.callSuper(arguments); // calls Ext.panel.Panel's constructor
1256          *              //...
1257          *          },
1258          *
1259          *          statics: {
1260          *              method: function () {
1261          *                  return 'abc';
1262          *              }
1263          *          }
1264          *      });
1265          *
1266          *      // File: /src/app/PanelPart2.js
1267          *      Ext.define('My.app.PanelPart2', {
1268          *          override: 'My.app.Panel',
1269          *
1270          *          constructor: function (config) {
1271          *              this.callSuper(arguments); // calls My.app.Panel's constructor
1272          *              //...
1273          *          }
1274          *      });
1275          *
1276          * Another use of overrides is to provide optional parts of classes that can be
1277          * independently required. In this case, the class may even be unaware of the
1278          * override altogether.
1279          *
1280          *      Ext.define('My.ux.CoolTip', {
1281          *          override: 'Ext.tip.ToolTip',
1282          *
1283          *          constructor: function (config) {
1284          *              this.callSuper(arguments); // calls Ext.tip.ToolTip's constructor
1285          *              //...
1286          *          }
1287          *      });
1288          *
1289          * The above override can now be required as normal.
1290          *
1291          *      Ext.define('My.app.App', {
1292          *          requires: [
1293          *              'My.ux.CoolTip'
1294          *          ]
1295          *      });
1296          *
1297          * Overrides can also contain statics:
1298          *
1299          *      Ext.define('My.app.BarMod', {
1300          *          override: 'Ext.foo.Bar',
1301          *
1302          *          statics: {
1303          *              method: function (x) {
1304          *                  return this.callSuper([x * 2]); // call Ext.foo.Bar.method
1305          *              }
1306          *          }
1307          *      });
1308          *
1309          * IMPORTANT: An override is only included in a build if the class it overrides is
1310          * required. Otherwise, the override, like the target class, is not included.
1311          */
1312         
1313         /**
1314          * @method
1315          *
1316          * @member Ext
1317          * @alias Ext.ClassManager#create
1318          */
1319         define: function (className, data, createdFn) {
1320             if (!data.override) {
1321                 return Manager.create.apply(Manager, arguments);
1322             }
1323
1324             var requires = data.requires,
1325                 uses = data.uses,
1326                 overrideName = className;
1327
1328             className = data.override;
1329
1330             // hoist any 'requires' or 'uses' from the body onto the faux class:
1331             data = Ext.apply({}, data);
1332             delete data.requires;
1333             delete data.uses;
1334             delete data.override;
1335
1336             // make sure className is in the requires list:
1337             if (typeof requires == 'string') {
1338                 requires = [ className, requires ];
1339             } else if (requires) {
1340                 requires = requires.slice(0);
1341                 requires.unshift(className);
1342             } else {
1343                 requires = [ className ];
1344             }
1345
1346 // TODO - we need to rework this to allow the override to not require the target class
1347 //  and rather 'wait' for it in such a way that if the target class is not in the build,
1348 //  neither are any of its overrides.
1349 //
1350 //  Also, this should process the overrides for a class ASAP (ideally before any derived
1351 //  classes) if the target class 'requires' the overrides. Without some special handling, the
1352 //  overrides so required will be processed before the class and have to be bufferred even
1353 //  in a build.
1354 //
1355 // TODO - we should probably support the "config" processor on an override (to config new
1356 //  functionaliy like Aria) and maybe inheritableStatics (although static is now supported
1357 //  by callSuper). If inheritableStatics causes those statics to be included on derived class
1358 //  constructors, that probably means "no" to this since an override can come after other
1359 //  classes extend the target.
1360             return Manager.create(overrideName, {
1361                     requires: requires,
1362                     uses: uses,
1363                     isPartial: true,
1364                     constructor: function () {
1365                         //<debug error>
1366                         throw new Error("Cannot create override '" + overrideName + "'");
1367                         //</debug>
1368                     }
1369                 }, function () {
1370                     var cls = Manager.get(className);
1371                     if (cls.override) { // if (normal class)
1372                         cls.override(data);
1373                     } else { // else (singleton)
1374                         cls.self.override(data);
1375                     }
1376
1377                     if (createdFn) {
1378                         // called once the override is applied and with the context of the
1379                         // overridden class (the override itself is a meaningless, name-only
1380                         // thing).
1381                         createdFn.call(cls);
1382                     }
1383                 });
1384         },
1385
1386         /**
1387          * @method
1388          * @member Ext
1389          * @alias Ext.ClassManager#getName
1390          */
1391         getClassName: alias(Manager, 'getName'),
1392
1393         /**
1394          * Returns the displayName property or className or object.
1395          * When all else fails, returns "Anonymous".
1396          * @param {Object} object
1397          * @return {String}
1398          */
1399         getDisplayName: function(object) {
1400             if (object.displayName) {
1401                 return object.displayName;
1402             }
1403
1404             if (object.$name && object.$class) {
1405                 return Ext.getClassName(object.$class) + '#' + object.$name;
1406             }
1407
1408             if (object.$className) {
1409                 return object.$className;
1410             }
1411
1412             return 'Anonymous';
1413         },
1414
1415         /**
1416          * @method
1417          * @member Ext
1418          * @alias Ext.ClassManager#getClass
1419          */
1420         getClass: alias(Manager, 'getClass'),
1421
1422         /**
1423          * Creates namespaces to be used for scoping variables and classes so that they are not global.
1424          * Specifying the last node of a namespace implicitly creates all other nodes. Usage:
1425          *
1426          *     Ext.namespace('Company', 'Company.data');
1427          *
1428          *     // equivalent and preferable to the above syntax
1429          *     Ext.namespace('Company.data');
1430          *
1431          *     Company.Widget = function() { ... };
1432          *
1433          *     Company.data.CustomStore = function(config) { ... };
1434          *
1435          * @method
1436          * @member Ext
1437          * @param {String} namespace1
1438          * @param {String} namespace2
1439          * @param {String} etc
1440          * @return {Object} The namespace object. (If multiple arguments are passed, this will be the last namespace created)
1441          */
1442         namespace: alias(Manager, 'createNamespaces')
1443     });
1444
1445     /**
1446      * Old name for {@link Ext#widget}.
1447      * @deprecated 4.0.0 Use {@link Ext#widget} instead.
1448      * @method
1449      * @member Ext
1450      * @alias Ext#widget
1451      */
1452     Ext.createWidget = Ext.widget;
1453
1454     /**
1455      * Convenient alias for {@link Ext#namespace Ext.namespace}
1456      * @method
1457      * @member Ext
1458      * @alias Ext#namespace
1459      */
1460     Ext.ns = Ext.namespace;
1461
1462     Class.registerPreprocessor('className', function(cls, data) {
1463         if (data.$className) {
1464             cls.$className = data.$className;
1465             //<debug>
1466             cls.displayName = cls.$className;
1467             //</debug>
1468         }
1469     }, true);
1470
1471     Class.setDefaultPreprocessorPosition('className', 'first');
1472
1473     Class.registerPreprocessor('xtype', function(cls, data) {
1474         var xtypes = Ext.Array.from(data.xtype),
1475             widgetPrefix = 'widget.',
1476             aliases = Ext.Array.from(data.alias),
1477             i, ln, xtype;
1478
1479         data.xtype = xtypes[0];
1480         data.xtypes = xtypes;
1481
1482         aliases = data.alias = Ext.Array.from(data.alias);
1483
1484         for (i = 0,ln = xtypes.length; i < ln; i++) {
1485             xtype = xtypes[i];
1486
1487             //<debug error>
1488             if (typeof xtype != 'string' || xtype.length < 1) {
1489                 throw new Error("[Ext.define] Invalid xtype of: '" + xtype + "' for class: '" + name + "'; must be a valid non-empty string");
1490             }
1491             //</debug>
1492
1493             aliases.push(widgetPrefix + xtype);
1494         }
1495
1496         data.alias = aliases;
1497     });
1498
1499     Class.setDefaultPreprocessorPosition('xtype', 'last');
1500
1501     Class.registerPreprocessor('alias', function(cls, data) {
1502         var aliases = Ext.Array.from(data.alias),
1503             xtypes = Ext.Array.from(data.xtypes),
1504             widgetPrefix = 'widget.',
1505             widgetPrefixLength = widgetPrefix.length,
1506             i, ln, alias, xtype;
1507
1508         for (i = 0, ln = aliases.length; i < ln; i++) {
1509             alias = aliases[i];
1510
1511             //<debug error>
1512             if (typeof alias != 'string') {
1513                 throw new Error("[Ext.define] Invalid alias of: '" + alias + "' for class: '" + name + "'; must be a valid string");
1514             }
1515             //</debug>
1516
1517             if (alias.substring(0, widgetPrefixLength) === widgetPrefix) {
1518                 xtype = alias.substring(widgetPrefixLength);
1519                 Ext.Array.include(xtypes, xtype);
1520
1521                 if (!cls.xtype) {
1522                     cls.xtype = data.xtype = xtype;
1523                 }
1524             }
1525         }
1526
1527         data.alias = aliases;
1528         data.xtypes = xtypes;
1529     });
1530
1531     Class.setDefaultPreprocessorPosition('alias', 'last');
1532
1533 })(Ext.Class, Ext.Function.alias);
1534