001: /* ZkFns.java
002:
003: {{IS_NOTE
004: Purpose:
005:
006: Description:
007:
008: History:
009: Tue Jun 7 11:09:48 2005, Created by tomyeh
010: }}IS_NOTE
011:
012: Copyright (C) 2005 Potix Corporation. All Rights Reserved.
013:
014: {{IS_RIGHT
015: This program is distributed under GPL Version 2.0 in the hope that
016: it will be useful, but WITHOUT ANY WARRANTY.
017: }}IS_RIGHT
018: */
019: package org.zkoss.zk.fn;
020:
021: import java.util.LinkedHashSet;
022: import java.util.Set;
023: import java.util.Map;
024: import java.util.Iterator;
025: import java.util.List;
026: import java.util.LinkedList;
027: import java.util.Collection;
028: import java.util.ArrayList;
029: import java.util.Set;
030: import java.util.Locale;
031: import java.util.Calendar;
032: import java.text.SimpleDateFormat;
033: import java.text.DecimalFormatSymbols;
034: import java.io.Writer;
035: import java.io.IOException;
036:
037: import javax.servlet.ServletRequest;
038: import javax.servlet.ServletContext;
039: import javax.servlet.http.HttpServletRequest;
040: import javax.servlet.http.HttpServletResponse;
041:
042: import org.zkoss.lang.Strings;
043: import org.zkoss.lang.Objects;
044: import org.zkoss.util.CacheMap;
045: import org.zkoss.util.Locales;
046: import org.zkoss.util.logging.Log;
047: import org.zkoss.web.fn.ServletFns;
048: import org.zkoss.web.servlet.JavaScript;
049: import org.zkoss.web.servlet.StyleSheet;
050:
051: import org.zkoss.zk.ui.Component;
052: import org.zkoss.zk.ui.WebApp;
053: import org.zkoss.zk.ui.Desktop;
054: import org.zkoss.zk.ui.Page;
055: import org.zkoss.zk.ui.Execution;
056: import org.zkoss.zk.ui.Executions;
057: import org.zkoss.zk.ui.UiException;
058: import org.zkoss.zk.ui.util.Configuration;
059: import org.zkoss.zk.ui.util.ThemeProvider;
060: import org.zkoss.zk.ui.sys.PageCtrl;
061: import org.zkoss.zk.ui.sys.ExecutionsCtrl;
062: import org.zkoss.zk.ui.metainfo.LanguageDefinition;
063: import org.zkoss.zk.ui.http.WebManager;
064: import org.zkoss.zk.ui.http.ExecutionImpl;
065: import org.zkoss.zk.ui.impl.Attributes;
066: import org.zkoss.zk.au.AuResponse;
067:
068: /**
069: * Utilities for using EL.
070: *
071: * @author tomyeh
072: */
073: public class ZkFns {
074: private static final Log log = Log.lookup(ZkFns.class);
075:
076: /** Denotes whether style sheets are generated for this request. */
077: private static final String ATTR_LANG_CSS_GENED = "javax.zkoss.zk.lang.css.generated";
078: //Naming with javax to be able to shared among portlets
079: /** Denotes whether JavaScripts are generated for this request. */
080: private static final String ATTR_LANG_JS_GENED = "javax.zkoss.zk.lang.js.generated";
081:
082: //Naming with javax to be able to shared among portlets
083:
084: protected ZkFns() {
085: }
086:
087: /** Redraw the specified component into the specified out.
088: *
089: * @param comp the component. If null, nothing happens
090: * @param out the output. If null, the current output
091: * will be used.
092: */
093: public static final void redraw(Component comp, Writer out)
094: throws IOException {
095: if (comp == null)
096: return; //nothing to do
097: if (out == null)
098: out = getCurrentOut();
099: try {
100: comp.redraw(out);
101: } catch (Throwable ex) {
102: //Commons-el sometime eat exception, so show more info to debug
103: log.realCauseBriefly("Failed to redraw " + comp, ex);
104: if (ex instanceof IOException)
105: throw (IOException) ex;
106: throw UiException.Aide.wrap(ex);
107: }
108: }
109:
110: /** Returns the current writer to generate the output.
111: * @since 3.0.0
112: */
113: public static final Writer getCurrentOut() throws IOException {
114: return ServletFns.getCurrentOut();
115: }
116:
117: /** Returns JavaScript for handling the specified response.
118: */
119: public static final String outResponseJavaScripts(
120: Collection responses) {
121: if (responses == null || responses.isEmpty())
122: return "";
123:
124: final StringBuffer sb = new StringBuffer(256)
125: .append("zk.addInit(function(){\n");
126: for (Iterator it = responses.iterator(); it.hasNext();) {
127: final AuResponse response = (AuResponse) it.next();
128: sb.append("zk.process('").append(response.getCommand())
129: .append("',");
130:
131: final String[] data = response.getData();
132: final int datanum = data != null ? data.length : 0;
133: sb.append(datanum);
134: for (int j = 0; j < datanum; ++j) {
135: sb.append(",\"");
136: if (data[j] != null)
137: sb.append(Strings.escape(data[j], "\"\\\n\r"));
138: sb.append('"');
139: }
140: sb.append(");\n");
141: }
142: return sb.append("});").toString();
143: }
144:
145: /** Returns HTML tags to include all JavaScript files and codes that are
146: * required when loading a ZUML page.
147: *
148: * <p>Note: it assumes {@link Executions#getCurrent} is available.
149: *
150: * <p>FUTURE CONSIDERATION: we might generate the inclusion on demand
151: * instead of all at once.
152: */
153: public static final String outLangJavaScripts(String action) {
154: final ServletRequest request = ServletFns.getCurrentRequest();
155: if (WebManager.getRequestLocal(request, ATTR_LANG_JS_GENED) != null)
156: return ""; //nothing to generate
157: WebManager.setRequestLocal(request, ATTR_LANG_JS_GENED,
158: Boolean.TRUE);
159:
160: if (action == null)
161: throw new IllegalArgumentException("null");
162:
163: final Desktop desktop = Executions.getCurrent().getDesktop();
164: final WebApp wapp = desktop.getWebApp();
165: final Configuration config = wapp.getConfiguration();
166: final String deviceType = desktop.getDeviceType();
167:
168: final StringBuffer sb = new StringBuffer(1536);
169:
170: final Set jses = new LinkedHashSet(37);
171: for (Iterator it = LanguageDefinition.getByDeviceType(
172: deviceType).iterator(); it.hasNext();)
173: jses.addAll(((LanguageDefinition) it.next())
174: .getJavaScripts());
175: for (Iterator it = jses.iterator(); it.hasNext();)
176: append(sb, (JavaScript) it.next());
177:
178: sb.append("\n<script type=\"text/javascript\">\n").append(
179: "zk_ver='").append(wapp.getVersion()).append(
180: "';\nzk_action=\"").append(action).append(
181: "\";\nzk_procto=").append(
182: config.getProcessingPromptDelay()).append(
183: ";\nzk_tipto=").append(config.getTooltipDelay())
184: .append(";\nzk_resndto=").append(
185: config.getResendDelay()).append(";\n");
186:
187: if (!config.isDisableBehindModalEnabled())
188: sb.append("zk.ndbModal=true;\n");
189:
190: if (config.isKeepDesktopAcrossVisits()
191: || request.getAttribute(Attributes.NO_CACHE) == null)
192: sb.append("zk.keepDesktop=true;\n");
193:
194: if (config.getPerformanceMeter() != null)
195: sb.append("zk.pfmeter=true;\n");
196:
197: sb.append("zk.eru={");
198: final int[] cers = config.getClientErrorReloadCodes();
199: boolean first = true;
200: for (int j = 0; j < cers.length; ++j) {
201: final String uri = config.getClientErrorReload(cers[j]);
202: if (uri != null) {
203: if (first)
204: first = false;
205: else
206: sb.append(',');
207:
208: sb.append("e").append(cers[j]).append(":'").append(
209: Strings.escape(uri, "'\\")).append('\'');
210: }
211: }
212: sb.append("};\n");
213:
214: for (Iterator it = LanguageDefinition.getByDeviceType(
215: deviceType).iterator(); it.hasNext();) {
216: final LanguageDefinition langdef = (LanguageDefinition) it
217: .next();
218:
219: //Generate module versions
220: final Set mods = langdef.getJavaScriptModules().entrySet();
221: if (!mods.isEmpty()) {
222: for (Iterator e = mods.iterator(); e.hasNext();) {
223: final Map.Entry me = (Map.Entry) e.next();
224: sb.append("\nzk.mods[\"").append(me.getKey())
225: .append("\"]=\"").append(me.getValue())
226: .append("\";");
227: }
228: }
229: }
230:
231: sb.append("\n</script>\n");
232: final String uamsg = desktop.getDevice()
233: .getUnavailableMessage();
234: if (uamsg != null)
235: sb.append("<noscript>\n").append(uamsg).append(
236: "\n</noscript>\n");
237:
238: return sb.toString();
239: }
240:
241: private static void append(StringBuffer sb, JavaScript js) {
242: sb.append("\n<script type=\"text/javascript\"");
243: if (js.getSrc() != null) {
244: String url;
245: try {
246: url = ServletFns.encodeURL(js.getSrc());
247: } catch (javax.servlet.ServletException ex) {
248: throw new UiException(ex);
249: }
250:
251: //Note: Jetty might encode jessionid into URL, which
252: //Dojo cannot handle, so we have to remove it
253: int j = url.lastIndexOf(';');
254: if (j > 0 && url.indexOf('.', j + 1) < 0
255: && url.indexOf('/', j + 1) < 0)
256: url = url.substring(0, j);
257:
258: sb.append(" src=\"").append(url).append('"');
259: final String charset = js.getCharset();
260: if (charset != null)
261: sb.append(" charset=\"").append(charset).append('"');
262: sb.append('>');
263: } else {
264: sb.append(">\n").append(js.getContent());
265: }
266: sb.append("\n</script>");
267: }
268:
269: /** Returns HTML tags to include all style sheets that are
270: * defined in all languages.
271: *
272: * <p>Note: it assumes {@link Executions#getCurrent} is available.
273: *
274: * <p>In addition to style sheets defined in lang.xml and lang-addon.xml,
275: * it also include:
276: * <ol>
277: * <li>The style sheet specified in the theme-uri parameter.</li>
278: * </ol>
279: *
280: * <p>FUTURE CONSIDERATION: we might generate the inclusion on demand
281: * instead of all at once.
282: */
283: public static final String outLangStyleSheets() {
284: final ServletRequest request = ServletFns.getCurrentRequest();
285: if (WebManager.getRequestLocal(request, ATTR_LANG_CSS_GENED) != null)
286: return ""; //nothing to generate
287: WebManager.setRequestLocal(request, ATTR_LANG_CSS_GENED,
288: Boolean.TRUE);
289:
290: final StringBuffer sb = new StringBuffer(512);
291: final Execution exec = Executions.getCurrent();
292: for (Iterator it = getStyleSheets(exec).iterator(); it
293: .hasNext();)
294: append(sb, (StyleSheet) it.next(), exec, null);
295:
296: return sb.toString();
297: }
298:
299: /** Returns HTML tags to include style sheets of the specified device
300: * of the current application.
301: *
302: * <p>Unlike {@link #outLangStyleSheets}, it uses the current
303: * servlet context
304: * to look for the style sheets. Thus, this method can be used even
305: * if the current execution is not available ({@link Executions#getCurrent}
306: * can be null).
307: *
308: * <p>In summary:<br/>
309: * {@link #outLangStyleSheets} is used to design the component
310: * templates, while {@link #outDeviceStyleSheets} is used by DSP/JSP
311: * that does nothing with ZUML pages (i.e., not part of an execution).
312: *
313: * @param deviceType the device type, such as ajax.
314: * It can not be null.
315: * @since 3.0.2
316: */
317: public static final String outDeviceStyleSheets(String deviceType) {
318: final ServletRequest request = ServletFns.getCurrentRequest();
319: if (WebManager.getRequestLocal(request, ATTR_LANG_CSS_GENED) != null)
320: return ""; //nothing to generate
321: if (deviceType == null)
322: throw new IllegalArgumentException();
323: WebManager.setRequestLocal(request, ATTR_LANG_CSS_GENED,
324: Boolean.TRUE);
325:
326: final StringBuffer sb = new StringBuffer(256);
327:
328: final ServletContext svlctx = ServletFns
329: .getCurrentServletContext();
330: final Configuration config = WebManager.getWebManager(svlctx)
331: .getWebApp().getConfiguration();
332:
333: Execution exec = ExecutionsCtrl.getCurrent();
334: final boolean fake = exec == null;
335: if (fake)//it shall be null, but, just in case,
336: ExecutionsCtrl.setCurrent(exec = new ExecutionImpl(svlctx,
337: (HttpServletRequest) request,
338: (HttpServletResponse) ServletFns
339: .getCurrentResponse(), null, null));
340: try {
341: final List ss = getStyleSheets0(exec, config, deviceType);
342: for (Iterator it = ss.iterator(); it.hasNext();)
343: append(sb, (StyleSheet) it.next(), exec, null);
344: return sb.toString();
345: } finally {
346: if (fake)
347: ExecutionsCtrl.setCurrent(null);
348: }
349: }
350:
351: /** Returns a list of {@link StyleSheet} that shall be generated
352: * to the client for the specified execution.
353: */
354: public static final List getStyleSheets(Execution exec) {
355: //Process all languages
356: final Desktop desktop = exec.getDesktop();
357: return getStyleSheets0(exec, desktop.getWebApp()
358: .getConfiguration(), desktop.getDeviceType());
359: }
360:
361: private static final List getStyleSheets0(Execution exec,
362: Configuration config, String deviceType) {
363: final Set disabled = config.getDisabledThemeURIs();
364: final List sses = new LinkedList(); //a list of StyleSheet
365: for (Iterator it = LanguageDefinition.getByDeviceType(
366: deviceType).iterator(); it.hasNext();) {
367: final LanguageDefinition langdef = (LanguageDefinition) it
368: .next();
369: for (Iterator e = langdef.getStyleSheets().iterator(); e
370: .hasNext();) {
371: final StyleSheet ss = (StyleSheet) e.next();
372: if (!disabled.contains(ss.getHref()))
373: sses.add(ss);
374: }
375: }
376:
377: //Process configuration
378: final ThemeProvider themeProvider = config.getThemeProvider();
379: if (themeProvider != null) {
380: final List org = new LinkedList();
381: for (Iterator it = sses.iterator(); it.hasNext();) {
382: final StyleSheet ss = (StyleSheet) it.next();
383: org.add(ss.getHref()); //we don't support getContent
384: }
385:
386: final String[] hrefs = config.getThemeURIs();
387: for (int j = 0; j < hrefs.length; ++j)
388: org.add(hrefs[j]);
389:
390: sses.clear();
391: final Collection res = themeProvider
392: .getThemeURIs(exec, org);
393: if (res != null) {
394: for (Iterator it = res.iterator(); it.hasNext();)
395: sses.add(new StyleSheet((String) it.next(),
396: "text/css"));
397: }
398: } else {
399: final String[] hrefs = config.getThemeURIs();
400: for (int j = 0; j < hrefs.length; ++j)
401: sses.add(new StyleSheet(hrefs[j], "text/css"));
402: }
403: return sses;
404: }
405:
406: private static void append(StringBuffer sb, StyleSheet ss,
407: Execution exec, Page page) {
408: String href = ss.getHref();
409: if (href != null) {
410: try {
411: if (exec != null)
412: href = (String) exec.evaluate(page, href,
413: String.class);
414:
415: if (href != null && href.length() > 0)
416: sb.append("\n<link rel=\"stylesheet\" type=\"")
417: .append(ss.getType()).append("\" href=\"")
418: .append(ServletFns.encodeURL(href)).append(
419: "\"/>");
420: } catch (javax.servlet.ServletException ex) {
421: throw new UiException(ex);
422: }
423: } else {
424: sb.append("\n<style");
425: if (ss.getType() != null)
426: sb.append(" type=\"").append(ss.getType()).append('"');
427: sb.append(">\n").append(ss.getContent()).append(
428: "\n</style>");
429: }
430: }
431:
432: /** Converts the specified URI to absolute if necessary.
433: * Refer to {@link Execution#toAbsoluteURI}.
434: *
435: * @param skipInclude whether not to convert to an absolute URI if
436: * the current page is included by another page.
437: * When use the include directive, skipInclude shall be true.
438: */
439: public static String toAbsoluteURI(String uri, boolean skipInclude) {
440: return Executions.getCurrent().toAbsoluteURI(uri, skipInclude);
441: }
442:
443: /** Converts the specified URI to absolute if not included by another page.
444: * It is a shortcut of {@link #toAbsoluteURI(String, boolean)} with skipInclude
445: * is true.
446: */
447: public static String toAbsoluteURI(String uri) {
448: return toAbsoluteURI(uri, true);
449: //we preserve this method for backward compatibility (since some developers
450: //might have old version core.dsp.tld
451: }
452:
453: /** Returns the content that will be placed inside the header element
454: * of the specified page.
455: * For HTML, the header element is the HEAD element.
456: */
457: public static final String outHeaders(Page page) {
458: return ((PageCtrl) page).getHeaders();
459: }
460:
461: /** Returns the content that will be generated
462: * as the attributes of the root element of the specified page.
463: * For HTML, the root element is the HTML element.
464: */
465: public static final String outRootAttributes(Page page) {
466: return ((PageCtrl) page).getRootAttributes();
467: }
468:
469: /** Returns the content type (never null).
470: * @since 3.0.0
471: */
472: public static final String outContentType(Page page) {
473: final String contentType = ((PageCtrl) page).getContentType();
474: return contentType != null ? contentType : page.getDesktop()
475: .getDevice().getContentType();
476: }
477:
478: /** Returns the doc type, or null if not available.
479: * It is null or <!DOCTYPE ...>.
480: * @since 3.0.0
481: */
482: public static final String outDocType(Page page) {
483: final String docType = ((PageCtrl) page).getDocType();
484: return trimAndLF(docType != null ? docType : page.getDesktop()
485: .getDevice().getDocType());
486: }
487:
488: /** Trims and appends a linefeed if necessary.
489: */
490: private static final String trimAndLF(String s) {
491: if (s != null) {
492: s = s.trim();
493: final int len = s.length();
494: if (len > 0 && s.charAt(len - 1) != '\n')
495: s += '\n';
496: }
497: return s;
498: }
499:
500: /** Returns the first line to be generated to the output,
501: * or null if no special first line.
502: */
503: public static final String outFirstLine(Page page) {
504: return trimAndLF(((PageCtrl) page).getFirstLine());
505: }
506:
507: /** Generates Locale-dependent strings in JavaScript syntax.
508: */
509: public final static String outLocaleJavaScript() {
510: final Locale locale = Locales.getCurrent();
511: return outNumberJavaScript(locale) + outDateJavaScript(locale);
512: }
513:
514: /** Output number relevant texts.
515: */
516: private final static String outNumberJavaScript(Locale locale) {
517: final DecimalFormatSymbols symbols = new DecimalFormatSymbols(
518: locale);
519: final StringBuffer sb = new StringBuffer(128);
520: appendAssignJavaScript(sb, "zk.GROUPING", symbols
521: .getGroupingSeparator());
522: appendAssignJavaScript(sb, "zk.DECIMAL", symbols
523: .getDecimalSeparator());
524: appendAssignJavaScript(sb, "zk.PERCENT", symbols.getPercent());
525: appendAssignJavaScript(sb, "zk.MINUS", symbols.getMinusSign());
526: return sb.toString();
527: }
528:
529: private final static void appendAssignJavaScript(StringBuffer sb,
530: String nm, char val) {
531: final char quot = val == '"' ? '\'' : '"';
532: sb.append(nm).append('=').append(quot).append(val).append(quot)
533: .append(";\n");
534: }
535:
536: /** Output date/calendar relevant labels.
537: */
538: private final static String outDateJavaScript(Locale locale) {
539: synchronized (_datejs) {
540: final String djs = (String) _datejs.get(locale);
541: if (djs != null)
542: return djs;
543: }
544:
545: String djs = getDateJavaScript(locale);
546: synchronized (_datejs) { //OK to race
547: //To minimize memory use, reuse the string if they are the same
548: //which is common
549: for (Iterator it = _datejs.values().iterator(); it
550: .hasNext();) {
551: final String val = (String) it.next();
552: if (val.equals(djs))
553: djs = val;
554: }
555: _datejs.put(locale, djs);
556: }
557: return djs;
558: }
559:
560: private final static String getDateJavaScript(Locale locale) {
561: final StringBuffer sb = new StringBuffer(512);
562:
563: final Calendar cal = Calendar.getInstance(locale);
564: cal.clear();
565:
566: final int firstDayOfWeek = cal.getFirstDayOfWeek();
567: sb.append("zk.DOW_1ST=").append(
568: firstDayOfWeek - Calendar.SUNDAY).append(";\n");
569:
570: final boolean zhlang = locale.getLanguage().equals("zh");
571: SimpleDateFormat df = new SimpleDateFormat("E", locale);
572: final String[] sdow = new String[7], s2dow = new String[7];
573: for (int j = firstDayOfWeek, k = 0; k < 7; ++k) {
574: cal.set(Calendar.DAY_OF_WEEK, j);
575: sdow[k] = df.format(cal.getTime());
576: if (++j > Calendar.SATURDAY)
577: j = Calendar.SUNDAY;
578:
579: if (zhlang) {
580: s2dow[k] = sdow[k].length() >= 3 ? sdow[k].substring(2)
581: : sdow[k];
582: } else {
583: final int len = sdow[k].length();
584: final char cc = sdow[k].charAt(len - 1);
585: s2dow[k] = cc == '.' || cc == ',' ? sdow[k].substring(
586: 0, len - 1) : sdow[k];
587: }
588: }
589:
590: df = new SimpleDateFormat("EEEE", locale);
591: final String[] fdow = new String[7];
592: for (int j = firstDayOfWeek, k = 0; k < 7; ++k) {
593: cal.set(Calendar.DAY_OF_WEEK, j);
594: fdow[k] = df.format(cal.getTime());
595: if (++j > Calendar.SATURDAY)
596: j = Calendar.SUNDAY;
597: }
598:
599: df = new SimpleDateFormat("MMM", locale);
600: final String[] smon = new String[12], s2mon = new String[12];
601: for (int j = 0; j < 12; ++j) {
602: cal.set(Calendar.MONTH, j);
603: smon[j] = df.format(cal.getTime());
604:
605: if (zhlang) {
606: s2mon[j] = smon[0].length() >= 2 ? //remove the last char
607: smon[j].substring(0, smon[j].length() - 1)
608: : smon[j];
609: } else {
610: final int len = smon[j].length();
611: final char cc = smon[j].charAt(len - 1);
612: s2mon[j] = cc == '.' || cc == ',' ? smon[j].substring(
613: 0, len - 1) : smon[j];
614: }
615: }
616:
617: df = new SimpleDateFormat("MMMM", locale);
618: final String[] fmon = new String[12];
619: for (int j = 0; j < 12; ++j) {
620: cal.set(Calendar.MONTH, j);
621: fmon[j] = df.format(cal.getTime());
622: }
623:
624: appendJavaScriptArray(sb, "SDOW", sdow);
625: if (Objects.equals(s2dow, sdow))
626: sb.append("zk.S2DOW=zk.SDOW;\n");
627: else
628: appendJavaScriptArray(sb, "S2DOW", s2dow);
629: if (Objects.equals(fdow, sdow))
630: sb.append("zk.FDOW=zk.SDOW;\n");
631: else
632: appendJavaScriptArray(sb, "FDOW", fdow);
633:
634: appendJavaScriptArray(sb, "SMON", smon);
635: if (Objects.equals(s2mon, smon))
636: sb.append("zk.S2MON=zk.SMON;\n");
637: else
638: appendJavaScriptArray(sb, "S2MON", s2mon);
639: if (Objects.equals(fmon, smon))
640: sb.append("zk.FMON=zk.SMON;\n");
641: else
642: appendJavaScriptArray(sb, "FMON", fmon);
643:
644: //AM/PM available since ZK 3.0
645: df = new SimpleDateFormat("a", locale);
646: cal.set(Calendar.HOUR_OF_DAY, 3);
647: final String[] ampm = new String[2];
648: ampm[0] = df.format(cal.getTime());
649: cal.set(Calendar.HOUR_OF_DAY, 15);
650: ampm[1] = df.format(cal.getTime());
651: appendJavaScriptArray(sb, "APM", ampm);
652:
653: return sb.toString();
654: }
655:
656: private static final void appendJavaScriptArray(StringBuffer sb,
657: String varnm, String[] vals) {
658: sb.append("zk.").append(varnm).append("=[");
659: for (int j = 0;;) {
660: sb.append('"').append(Strings.escape(vals[j], "\\\""))
661: .append('"');
662: if (++j >= vals.length)
663: break;
664: else
665: sb.append(',');
666: }
667: sb.append("];\n");
668: }
669:
670: private static final CacheMap _datejs;
671: static {
672: _datejs = new CacheMap(8);
673: _datejs.setLifetime(24 * 60 * 60 * 1000);
674: }
675: }
|