1 /*
  2  * CalendarFactory.js - Constructs new instances of the right subclass of Calendar
  3  *
  4  * Copyright © 2015, 2018, JEDLSoft
  5  *
  6  * Licensed under the Apache License, Version 2.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at
  9  *
 10  *     http://www.apache.org/licenses/LICENSE-2.0
 11  *
 12  * Unless required by applicable law or agreed to in writing, software
 13  * distributed under the License is distributed on an "AS IS" BASIS,
 14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 15  *
 16  * See the License for the specific language governing permissions and
 17  * limitations under the License.
 18  */
 19 
 20 var ilib = require("../index.js");
 21 var Locale = require("./Locale.js");
 22 var LocaleInfo = require("./LocaleInfo.js");
 23 var Calendar = require("./Calendar.js");
 24 
 25 /**
 26  * Factory method to create a new instance of a calendar subclass.<p>
 27  *
 28  * The options parameter can be an object that contains the following
 29  * properties:
 30  *
 31  * <ul>
 32  * <li><i>type</i> - specify the type of the calendar desired. The
 33  * list of valid values changes depending on which calendars are
 34  * defined. When assembling your iliball.js, include those calendars
 35  * you wish to use in your program or web page, and they will register
 36  * themselves with this factory method. The "official", "gregorian",
 37  * and "julian" calendars are all included by default, as they are the
 38  * standard calendars for much of the world.
 39  * <li><i>locale</i> - some calendars vary depending on the locale.
 40  * For example, the "official" calendar transitions from a Julian-style
 41  * calendar to a Gregorian-style calendar on a different date for
 42  * each country, as the governments of those countries decided to
 43  * adopt the Gregorian calendar at different times.
 44  *
 45  * <li><i>onLoad</i> - a callback function to call when the calendar object is fully
 46  * loaded. When the onLoad option is given, the calendar factory will attempt to
 47  * load any missing locale data using the ilib loader callback.
 48  * When the constructor is done (even if the data is already preassembled), the
 49  * onLoad function is called with the current instance as a parameter, so this
 50  * callback can be used with preassembled or dynamic loading or a mix of the two.
 51  *
 52  * <li><i>sync</i> - tell whether to load any missing locale data synchronously or
 53  * asynchronously. If this option is given as "false", then the "onLoad"
 54  * callback must be given, as the instance returned from this constructor will
 55  * not be usable for a while.
 56  *
 57  * <li><i>loadParams</i> - an object containing parameters to pass to the
 58  * loader callback function when locale data is missing. The parameters are not
 59  * interpretted or modified in any way. They are simply passed along. The object
 60  * may contain any property/value pairs as long as the calling code is in
 61  * agreement with the loader callback function as to what those parameters mean.
 62  * </ul>
 63  *
 64  * If a locale is specified, but no type, then the calendar that is default for
 65  * the locale will be instantiated and returned. If neither the type nor
 66  * the locale are specified, then the calendar for the default locale will
 67  * be used.
 68  *
 69  * @static
 70  * @param {Object=} options options controlling the construction of this instance, or
 71  * undefined to use the default options
 72  * @return {Calendar} an instance of a calendar object of the appropriate type
 73  */
 74 var CalendarFactory = function (options) {
 75     var locale,
 76         type,
 77         sync = true,
 78         instance;
 79 
 80     if (options) {
 81         if (options.locale) {
 82             locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale;
 83         }
 84 
 85         type = options.type || options.calendar;
 86 
 87         if (typeof(options.sync) === 'boolean') {
 88             sync = options.sync;
 89         }
 90     }
 91 
 92     if (!locale) {
 93         locale = new Locale();    // default locale
 94     }
 95 
 96     if (!type) {
 97         new LocaleInfo(locale, {
 98             sync: sync,
 99             loadParams: options && options.loadParams,
100             onLoad: function(info) {
101                 type = info.getCalendar();
102 
103                 instance = CalendarFactory._init(type, options);
104             }
105         });
106     } else {
107         instance = CalendarFactory._init(type, options);
108     }
109 
110     return instance;
111 };
112 
113 /**
114  * Map calendar names to classes to initialize in the dynamic code model.
115  * TODO: Need to figure out some way that this doesn't have to be updated by hand.
116  * @private
117  */
118 CalendarFactory._dynMap = {
119     "coptic":       "Coptic",
120     "ethiopic":     "Ethiopic",
121     "gregorian":    "Gregorian",
122     "han":          "Han",
123     "hebrew":       "Hebrew",
124     "islamic":      "Islamic",
125     "julian":       "Julian",
126     "persian":      "Persian",
127     "persian-algo": "PersianAlgo",
128     "thaisolar":    "ThaiSolar"
129 };
130 
131 function circumventWebPack(x) {
132     return "./" + x + "Cal.js";
133 }
134 
135 /**
136  * Dynamically load the code for a calendar and calendar class if necessary.
137  * @protected
138  */
139 CalendarFactory._dynLoadCalendar = function (name, fnc) {
140     if (!Calendar._constructors[name]) {
141         var entry = CalendarFactory._dynMap[name];
142         if (entry) {
143             // eslint-disable-next-line
144             Calendar._constructors[name] = require(fnc(entry));
145         }
146     }
147     return Calendar._constructors[name];
148 };
149 
150 /** @private */
151 CalendarFactory._init = function(type, options) {
152     var cons;
153 
154     if (ilib.isDynCode()) {
155         CalendarFactory._dynLoadCalendar(type, circumventWebPack);
156     }
157 
158     cons = Calendar._constructors[type];
159 
160     // pass the same options through to the constructor so the subclass
161     // has the ability to do something with if it needs to
162     if (!cons && typeof(options.onLoad) === "function") {
163         options.onLoad(undefined);
164     }
165     return cons && new cons(options);
166 };
167 
168 /**
169  * Return an array of known calendar types that the factory method can instantiate.
170  *
171  * @return {Array.<string>} an array of calendar types
172  */
173 CalendarFactory.getCalendars = function () {
174     var arr = [],
175         c;
176 
177     if (ilib.isDynCode()) {
178         for (c in CalendarFactory._dynMap) {
179             CalendarFactory._dynLoadCalendar(c, circumventWebPack);
180         }
181     }
182 
183     for (c in Calendar._constructors) {
184         if (c && Calendar._constructors[c]) {
185             arr.push(c); // code like a pirate
186         }
187     }
188 
189     return arr;
190 };
191 
192 module.exports = CalendarFactory;
193