1   /**
2    * Copyright (c) 2000-2010 Liferay, Inc. All rights reserved.
3    *
4    * The contents of this file are subject to the terms of the Liferay Enterprise
5    * Subscription License ("License"). You may not use this file except in
6    * compliance with the License. You can obtain a copy of the License by
7    * contacting Liferay, Inc. See the License for the specific language governing
8    * permissions and limitations under the License, including but not limited to
9    * distribution rights of the Software.
10   *
11   *
12   * 
13   */
14  
15  package com.liferay.portal.service.impl;
16  
17  import com.liferay.portal.SystemException;
18  import com.liferay.portal.kernel.image.SpriteProcessorUtil;
19  import com.liferay.portal.kernel.log.Log;
20  import com.liferay.portal.kernel.log.LogFactoryUtil;
21  import com.liferay.portal.kernel.plugin.PluginPackage;
22  import com.liferay.portal.kernel.plugin.Version;
23  import com.liferay.portal.kernel.servlet.ServletContextUtil;
24  import com.liferay.portal.kernel.util.GetterUtil;
25  import com.liferay.portal.kernel.util.ListUtil;
26  import com.liferay.portal.kernel.util.ReleaseInfo;
27  import com.liferay.portal.kernel.util.ServerDetector;
28  import com.liferay.portal.kernel.util.StringPool;
29  import com.liferay.portal.kernel.util.StringUtil;
30  import com.liferay.portal.kernel.util.Validator;
31  import com.liferay.portal.kernel.xml.Document;
32  import com.liferay.portal.kernel.xml.Element;
33  import com.liferay.portal.kernel.xml.SAXReaderUtil;
34  import com.liferay.portal.model.ColorScheme;
35  import com.liferay.portal.model.PluginSetting;
36  import com.liferay.portal.model.PortletConstants;
37  import com.liferay.portal.model.Theme;
38  import com.liferay.portal.model.impl.ColorSchemeImpl;
39  import com.liferay.portal.model.impl.ThemeImpl;
40  import com.liferay.portal.plugin.PluginUtil;
41  import com.liferay.portal.service.base.ThemeLocalServiceBaseImpl;
42  import com.liferay.portal.theme.ThemeCompanyId;
43  import com.liferay.portal.theme.ThemeCompanyLimit;
44  import com.liferay.portal.theme.ThemeGroupId;
45  import com.liferay.portal.theme.ThemeGroupLimit;
46  import com.liferay.portal.util.PortalUtil;
47  import com.liferay.util.ContextReplace;
48  
49  import java.io.File;
50  
51  import java.util.ArrayList;
52  import java.util.HashSet;
53  import java.util.Iterator;
54  import java.util.List;
55  import java.util.Map;
56  import java.util.Properties;
57  import java.util.Set;
58  import java.util.concurrent.ConcurrentHashMap;
59  
60  import javax.servlet.ServletContext;
61  
62  /**
63   * <a href="ThemeLocalServiceImpl.java.html"><b><i>View Source</i></b></a>
64   *
65   * @author Brian Wing Shun Chan
66   * @author Jorge Ferrer
67   */
68  public class ThemeLocalServiceImpl extends ThemeLocalServiceBaseImpl {
69  
70      public ColorScheme getColorScheme(
71          long companyId, String themeId, String colorSchemeId,
72          boolean wapTheme) {
73  
74          colorSchemeId = GetterUtil.getString(colorSchemeId);
75  
76          Theme theme = getTheme(companyId, themeId, wapTheme);
77  
78          Map<String, ColorScheme> colorSchemesMap = theme.getColorSchemesMap();
79  
80          ColorScheme colorScheme = colorSchemesMap.get(colorSchemeId);
81  
82          if (colorScheme == null) {
83              List<ColorScheme> colorSchemes = theme.getColorSchemes();
84  
85              if (colorSchemes.size() > 0) {
86                  for (int i = (colorSchemes.size() - 1); i >= 0; i--) {
87                      colorScheme = colorSchemes.get(i);
88  
89                      if (colorScheme.isDefaultCs()) {
90                          break;
91                      }
92                  }
93              }
94          }
95  
96          if (colorScheme == null) {
97              if (wapTheme) {
98                  colorSchemeId = ColorSchemeImpl.getDefaultWapColorSchemeId();
99              }
100             else {
101                 colorSchemeId =
102                     ColorSchemeImpl.getDefaultRegularColorSchemeId();
103             }
104         }
105 
106         if (colorScheme == null) {
107             colorScheme = ColorSchemeImpl.getNullColorScheme();
108         }
109 
110         return colorScheme;
111     }
112 
113     public Theme getTheme(long companyId, String themeId, boolean wapTheme) {
114         themeId = GetterUtil.getString(themeId);
115 
116         Theme theme = _getThemes(companyId).get(themeId);
117 
118         if (theme == null) {
119             if (_log.isWarnEnabled()) {
120                 _log.warn(
121                     "No theme found for specified theme id " + themeId +
122                         ". Returning the default theme.");
123             }
124 
125             if (wapTheme) {
126                 themeId = ThemeImpl.getDefaultWapThemeId();
127             }
128             else {
129                 themeId = ThemeImpl.getDefaultRegularThemeId();
130             }
131 
132             theme = _themes.get(themeId);
133         }
134 
135         if (theme == null) {
136             if (_themes.isEmpty()) {
137                 if (_log.isDebugEnabled()) {
138                     _log.debug("No themes are installed");
139                 }
140 
141                 return null;
142             }
143 
144             _log.error(
145                 "No theme found for default theme id " + themeId +
146                     ". Returning a random theme.");
147 
148             Iterator<Map.Entry<String, Theme>> itr =
149                 _themes.entrySet().iterator();
150 
151             while (itr.hasNext()) {
152                 Map.Entry<String, Theme> entry = itr.next();
153 
154                 theme = entry.getValue();
155             }
156         }
157 
158         return theme;
159     }
160 
161     public List<Theme> getThemes(long companyId) {
162         List<Theme> themes = ListUtil.fromCollection(
163             _getThemes(companyId).values());
164 
165         return ListUtil.sort(themes);
166     }
167 
168     public List<Theme> getThemes(
169             long companyId, long groupId, long userId, boolean wapTheme)
170         throws SystemException {
171 
172         List<Theme> themes = getThemes(companyId);
173 
174         themes = (List<Theme>)PluginUtil.restrictPlugins(
175             themes, companyId, userId);
176 
177         Iterator<Theme> itr = themes.iterator();
178 
179         while (itr.hasNext()) {
180             Theme theme = itr.next();
181 
182             if ((!theme.isGroupAvailable(groupId)) ||
183                 (theme.isWapTheme() != wapTheme)) {
184 
185                 itr.remove();
186             }
187         }
188 
189         return themes;
190     }
191 
192     public List<Theme> getWARThemes() {
193         List<Theme> themes = ListUtil.fromCollection(_themes.values());
194 
195         Iterator<Theme> itr = themes.iterator();
196 
197         while (itr.hasNext()) {
198             Theme theme = itr.next();
199 
200             if (!theme.isWARFile()) {
201                 itr.remove();
202             }
203         }
204 
205         return themes;
206     }
207 
208     public List<String> init(
209         ServletContext servletContext, String themesPath,
210         boolean loadFromServletContext, String[] xmls,
211         PluginPackage pluginPackage) {
212 
213         return init(
214             null, servletContext, themesPath, loadFromServletContext, xmls,
215             pluginPackage);
216     }
217 
218     public List<String> init(
219         String servletContextName, ServletContext servletContext,
220         String themesPath, boolean loadFromServletContext, String[] xmls,
221         PluginPackage pluginPackage) {
222 
223         List<String> themeIds = new ArrayList<String>();
224 
225         try {
226             for (int i = 0; i < xmls.length; i++) {
227                 Set<String> themes = _readThemes(
228                     servletContextName, servletContext, themesPath,
229                     loadFromServletContext, xmls[i], pluginPackage);
230 
231                 Iterator<String> itr = themes.iterator();
232 
233                 while (itr.hasNext()) {
234                     String themeId = itr.next();
235 
236                     if (!themeIds.contains(themeId)) {
237                         themeIds.add(themeId);
238                     }
239                 }
240             }
241         }
242         catch (Exception e) {
243             e.printStackTrace();
244         }
245 
246         _themesPool.clear();
247 
248         return themeIds;
249     }
250 
251     public void uninstallThemes(List<String> themeIds) {
252         for (int i = 0; i < themeIds.size(); i++) {
253             String themeId = themeIds.get(i);
254 
255             _themes.remove(themeId);
256 
257             layoutTemplateLocalService.uninstallLayoutTemplates(themeId);
258         }
259 
260         _themesPool.clear();
261     }
262 
263     private List<ThemeCompanyId> _getCompanyLimitExcludes(Element el) {
264         List<ThemeCompanyId> includes = new ArrayList<ThemeCompanyId>();
265 
266         if (el != null) {
267             List<Element> companyIds = el.elements("company-id");
268 
269             for (int i = 0; i < companyIds.size(); i++) {
270                 Element companyIdEl = companyIds.get(i);
271 
272                 String name = companyIdEl.attributeValue("name");
273                 String pattern = companyIdEl.attributeValue("pattern");
274 
275                 ThemeCompanyId themeCompanyId = null;
276 
277                 if (Validator.isNotNull(name)) {
278                     themeCompanyId = new ThemeCompanyId(name, false);
279                 }
280                 else if (Validator.isNotNull(pattern)) {
281                     themeCompanyId = new ThemeCompanyId(pattern, true);
282                 }
283 
284                 if (themeCompanyId != null) {
285                     includes.add(themeCompanyId);
286                 }
287             }
288         }
289 
290         return includes;
291     }
292 
293     private List<ThemeCompanyId> _getCompanyLimitIncludes(Element el) {
294         return _getCompanyLimitExcludes(el);
295     }
296 
297     private List<ThemeGroupId> _getGroupLimitExcludes(Element el) {
298         List<ThemeGroupId> includes = new ArrayList<ThemeGroupId>();
299 
300         if (el != null) {
301             List<Element> groupIds = el.elements("group-id");
302 
303             for (int i = 0; i < groupIds.size(); i++) {
304                 Element groupIdEl = groupIds.get(i);
305 
306                 String name = groupIdEl.attributeValue("name");
307                 String pattern = groupIdEl.attributeValue("pattern");
308 
309                 ThemeGroupId themeGroupId = null;
310 
311                 if (Validator.isNotNull(name)) {
312                     themeGroupId = new ThemeGroupId(name, false);
313                 }
314                 else if (Validator.isNotNull(pattern)) {
315                     themeGroupId = new ThemeGroupId(pattern, true);
316                 }
317 
318                 if (themeGroupId != null) {
319                     includes.add(themeGroupId);
320                 }
321             }
322         }
323 
324         return includes;
325     }
326 
327     private List<ThemeGroupId> _getGroupLimitIncludes(Element el) {
328         return _getGroupLimitExcludes(el);
329     }
330 
331     private Map<String, Theme> _getThemes(long companyId) {
332         Map<String, Theme> themes = _themesPool.get(companyId);
333 
334         if (themes == null) {
335             themes = new ConcurrentHashMap<String, Theme>();
336 
337             Iterator<Map.Entry<String, Theme>> itr =
338                 _themes.entrySet().iterator();
339 
340             while (itr.hasNext()) {
341                 Map.Entry<String, Theme> entry = itr.next();
342 
343                 String themeId = entry.getKey();
344                 Theme theme = entry.getValue();
345 
346                 if (theme.isCompanyAvailable(companyId)) {
347                     themes.put(themeId, theme);
348                 }
349             }
350 
351             _themesPool.put(companyId, themes);
352         }
353 
354         return themes;
355     }
356 
357     private Version _getVersion(String version) {
358         if (version.equals("${current-version}")) {
359             version = ReleaseInfo.getVersion();
360         }
361 
362         return Version.getInstance(version);
363     }
364 
365     private void _readColorSchemes(
366         Element theme, Map<String, ColorScheme> colorSchemes,
367         ContextReplace themeContextReplace) {
368 
369         Iterator<Element> itr = theme.elements("color-scheme").iterator();
370 
371         while (itr.hasNext()) {
372             Element colorScheme = itr.next();
373 
374             ContextReplace colorSchemeContextReplace =
375                 (ContextReplace)themeContextReplace.clone();
376 
377             String id = colorScheme.attributeValue("id");
378 
379             colorSchemeContextReplace.addValue("color-scheme-id", id);
380 
381             ColorScheme colorSchemeModel = colorSchemes.get(id);
382 
383             if (colorSchemeModel == null) {
384                 colorSchemeModel = new ColorSchemeImpl(id);
385             }
386 
387             String name = GetterUtil.getString(
388                 colorScheme.attributeValue("name"), colorSchemeModel.getName());
389 
390             name = colorSchemeContextReplace.replace(name);
391 
392             boolean defaultCs = GetterUtil.getBoolean(
393                 colorScheme.elementText("default-cs"),
394                 colorSchemeModel.isDefaultCs());
395 
396             String cssClass = GetterUtil.getString(
397                 colorScheme.elementText("css-class"),
398                 colorSchemeModel.getCssClass());
399 
400             cssClass = colorSchemeContextReplace.replace(cssClass);
401 
402             colorSchemeContextReplace.addValue("css-class", cssClass);
403 
404             String colorSchemeImagesPath = GetterUtil.getString(
405                 colorScheme.elementText("color-scheme-images-path"),
406                 colorSchemeModel.getColorSchemeImagesPath());
407 
408             colorSchemeImagesPath = colorSchemeContextReplace.replace(
409                 colorSchemeImagesPath);
410 
411             colorSchemeContextReplace.addValue(
412                 "color-scheme-images-path", colorSchemeImagesPath);
413 
414             colorSchemeModel.setName(name);
415             colorSchemeModel.setDefaultCs(defaultCs);
416             colorSchemeModel.setCssClass(cssClass);
417             colorSchemeModel.setColorSchemeImagesPath(colorSchemeImagesPath);
418 
419             colorSchemes.put(id, colorSchemeModel);
420         }
421     }
422 
423     private Set<String> _readThemes(
424             String servletContextName, ServletContext servletContext,
425             String themesPath, boolean loadFromServletContext, String xml,
426             PluginPackage pluginPackage)
427         throws Exception {
428 
429         Set<String> themeIds = new HashSet<String>();
430 
431         if (xml == null) {
432             return themeIds;
433         }
434 
435         Document doc = SAXReaderUtil.read(xml, true);
436 
437         Element root = doc.getRootElement();
438 
439         Version portalVersion = _getVersion(ReleaseInfo.getVersion());
440 
441         boolean compatible = false;
442 
443         Element compatibilityEl = root.element("compatibility");
444 
445         if (compatibilityEl != null) {
446             Iterator<Element> itr = compatibilityEl.elements(
447                 "version").iterator();
448 
449             while (itr.hasNext()) {
450                 Element versionEl = itr.next();
451 
452                 Version version = _getVersion(versionEl.getTextTrim());
453 
454                 if (version.includes(portalVersion)) {
455                     compatible = true;
456 
457                     break;
458                 }
459             }
460         }
461 
462         if (!compatible) {
463             _log.error(
464                 "Themes in this WAR are not compatible with " +
465                     ReleaseInfo.getServerInfo());
466 
467             return themeIds;
468         }
469 
470         ThemeCompanyLimit companyLimit = null;
471 
472         Element companyLimitEl = root.element("company-limit");
473 
474         if (companyLimitEl != null) {
475             companyLimit = new ThemeCompanyLimit();
476 
477             Element companyIncludesEl =
478                 companyLimitEl.element("company-includes");
479 
480             if (companyIncludesEl != null) {
481                 companyLimit.setIncludes(
482                     _getCompanyLimitIncludes(companyIncludesEl));
483             }
484 
485             Element companyExcludesEl =
486                 companyLimitEl.element("company-excludes");
487 
488             if (companyExcludesEl != null) {
489                 companyLimit.setExcludes(
490                     _getCompanyLimitExcludes(companyExcludesEl));
491             }
492         }
493 
494         ThemeGroupLimit groupLimit = null;
495 
496         Element groupLimitEl = root.element("group-limit");
497 
498         if (groupLimitEl != null) {
499             groupLimit = new ThemeGroupLimit();
500 
501             Element groupIncludesEl = groupLimitEl.element("group-includes");
502 
503             if (groupIncludesEl != null) {
504                 groupLimit.setIncludes(_getGroupLimitIncludes(groupIncludesEl));
505             }
506 
507             Element groupExcludesEl =
508                 groupLimitEl.element("group-excludes");
509 
510             if (groupExcludesEl != null) {
511                 groupLimit.setExcludes(_getGroupLimitExcludes(groupExcludesEl));
512             }
513         }
514 
515         long timestamp = ServletContextUtil.getLastModified(servletContext);
516 
517         Iterator<Element> itr1 = root.elements("theme").iterator();
518 
519         while (itr1.hasNext()) {
520             Element theme = itr1.next();
521 
522             ContextReplace themeContextReplace = new ContextReplace();
523 
524             themeContextReplace.addValue("themes-path", themesPath);
525 
526             String themeId = theme.attributeValue("id");
527 
528             if (servletContextName != null) {
529                 themeId =
530                     themeId + PortletConstants.WAR_SEPARATOR +
531                         servletContextName;
532             }
533 
534             themeId = PortalUtil.getJsSafePortletId(themeId);
535 
536             themeContextReplace.addValue("theme-id", themeId);
537 
538             themeIds.add(themeId);
539 
540             Theme themeModel = _themes.get(themeId);
541 
542             if (themeModel == null) {
543                 themeModel = new ThemeImpl(themeId);
544 
545                 _themes.put(themeId, themeModel);
546             }
547 
548             themeModel.setTimestamp(timestamp);
549 
550             PluginSetting pluginSetting =
551                 pluginSettingLocalService.getDefaultPluginSetting();
552 
553             themeModel.setPluginPackage(pluginPackage);
554             themeModel.setDefaultPluginSetting(pluginSetting);
555 
556             themeModel.setThemeCompanyLimit(companyLimit);
557             themeModel.setThemeGroupLimit(groupLimit);
558 
559             if (servletContextName != null) {
560                 themeModel.setServletContextName(servletContextName);
561             }
562 
563             themeModel.setLoadFromServletContext(loadFromServletContext);
564 
565             String name = GetterUtil.getString(
566                 theme.attributeValue("name"), themeModel.getName());
567 
568             String rootPath = GetterUtil.getString(
569                 theme.elementText("root-path"), themeModel.getRootPath());
570 
571             rootPath = themeContextReplace.replace(rootPath);
572 
573             themeContextReplace.addValue("root-path", rootPath);
574 
575             String templatesPath = GetterUtil.getString(
576                 theme.elementText("templates-path"),
577                 themeModel.getTemplatesPath());
578 
579             templatesPath = themeContextReplace.replace(templatesPath);
580             templatesPath = StringUtil.safePath(templatesPath);
581 
582             themeContextReplace.addValue("templates-path", templatesPath);
583 
584             String cssPath = GetterUtil.getString(
585                 theme.elementText("css-path"), themeModel.getCssPath());
586 
587             cssPath = themeContextReplace.replace(cssPath);
588             cssPath = StringUtil.safePath(cssPath);
589 
590             themeContextReplace.addValue("css-path", cssPath);
591 
592             String imagesPath = GetterUtil.getString(
593                 theme.elementText("images-path"),
594                 themeModel.getImagesPath());
595 
596             imagesPath = themeContextReplace.replace(imagesPath);
597             imagesPath = StringUtil.safePath(imagesPath);
598 
599             themeContextReplace.addValue("images-path", imagesPath);
600 
601             String javaScriptPath = GetterUtil.getString(
602                 theme.elementText("javascript-path"),
603                 themeModel.getJavaScriptPath());
604 
605             javaScriptPath = themeContextReplace.replace(javaScriptPath);
606             javaScriptPath = StringUtil.safePath(javaScriptPath);
607 
608             themeContextReplace.addValue("javascript-path", javaScriptPath);
609 
610             String virtualPath = GetterUtil.getString(
611                 theme.elementText("virtual-path"), themeModel.getVirtualPath());
612 
613             String templateExtension = GetterUtil.getString(
614                 theme.elementText("template-extension"),
615                 themeModel.getTemplateExtension());
616 
617             themeModel.setName(name);
618             themeModel.setRootPath(rootPath);
619             themeModel.setTemplatesPath(templatesPath);
620             themeModel.setCssPath(cssPath);
621             themeModel.setImagesPath(imagesPath);
622             themeModel.setJavaScriptPath(javaScriptPath);
623             themeModel.setVirtualPath(virtualPath);
624             themeModel.setTemplateExtension(templateExtension);
625 
626             Element settingsEl = theme.element("settings");
627 
628             if (settingsEl != null) {
629                 Iterator<Element> itr2 = settingsEl.elements(
630                     "setting").iterator();
631 
632                 while (itr2.hasNext()) {
633                     Element settingEl = itr2.next();
634 
635                     String key = settingEl.attributeValue("key");
636                     String value = settingEl.attributeValue("value");
637 
638                     themeModel.setSetting(key, value);
639                 }
640             }
641 
642             themeModel.setWapTheme(GetterUtil.getBoolean(
643                 theme.elementText("wap-theme"), themeModel.isWapTheme()));
644 
645             Element rolesEl = theme.element("roles");
646 
647             if (rolesEl != null) {
648                 Iterator<Element> itr2 = rolesEl.elements(
649                     "role-name").iterator();
650 
651                 while (itr2.hasNext()) {
652                     Element roleNameEl = itr2.next();
653 
654                     pluginSetting.addRole(roleNameEl.getText());
655                 }
656             }
657 
658             _readColorSchemes(
659                 theme, themeModel.getColorSchemesMap(), themeContextReplace);
660             _readColorSchemes(
661                 theme, themeModel.getColorSchemesMap(), themeContextReplace);
662 
663             Element layoutTemplatesEl = theme.element("layout-templates");
664 
665             if (layoutTemplatesEl != null) {
666                 Element standardEl = layoutTemplatesEl.element("standard");
667 
668                 if (standardEl != null) {
669                     layoutTemplateLocalService.readLayoutTemplate(
670                         servletContextName, servletContext, null,
671                         standardEl, true, themeId, pluginPackage);
672                 }
673 
674                 Element customEl = layoutTemplatesEl.element("custom");
675 
676                 if (customEl != null) {
677                     layoutTemplateLocalService.readLayoutTemplate(
678                         servletContextName, servletContext, null,
679                         customEl, false, themeId, pluginPackage);
680                 }
681             }
682 
683             if (!themeModel.isWapTheme()) {
684                 _setSpriteImages(servletContext, themeModel, imagesPath);
685             }
686         }
687 
688         return themeIds;
689     }
690 
691     private void _setSpriteImages(
692             ServletContext servletContext, Theme theme, String resourcePath)
693         throws Exception {
694 
695         Set<String> resourcePaths = servletContext.getResourcePaths(
696             resourcePath);
697 
698         if (resourcePaths == null) {
699             return;
700         }
701 
702         List<File> images = new ArrayList<File>(resourcePaths.size());
703 
704         for (String curResourcePath : resourcePaths) {
705             if (curResourcePath.endsWith(StringPool.SLASH)) {
706                 _setSpriteImages(servletContext, theme, curResourcePath);
707             }
708             else if (curResourcePath.endsWith(".png")) {
709                 String realPath = ServletContextUtil.getRealPath(
710                     servletContext, curResourcePath);
711 
712                 if (realPath != null) {
713                     images.add(new File(realPath));
714                 }
715                 else {
716                     if (ServerDetector.isTomcat()) {
717                         if (_log.isInfoEnabled()) {
718                             _log.info(ServletContextUtil.LOG_INFO_SPRITES);
719                         }
720                     }
721                     else {
722                         _log.error(
723                             "Real path for " + curResourcePath + " is null");
724                     }
725                 }
726             }
727         }
728 
729         String spriteFileName = ".sprite.png";
730         String spritePropertiesFileName = ".sprite.properties";
731         String spritePropertiesRootPath = ServletContextUtil.getRealPath(
732             servletContext, theme.getImagesPath());
733 
734         Properties spriteProperties = SpriteProcessorUtil.generate(
735             images, spriteFileName, spritePropertiesFileName,
736             spritePropertiesRootPath, 16, 16, 10240);
737 
738         if (spriteProperties == null) {
739             return;
740         }
741 
742         spriteFileName =
743             resourcePath.substring(
744                 theme.getImagesPath().length(), resourcePath.length()) +
745             spriteFileName;
746 
747         theme.setSpriteImages(spriteFileName, spriteProperties);
748     }
749 
750     private static Log _log = LogFactoryUtil.getLog(
751         ThemeLocalServiceImpl.class);
752 
753     private static Map<String, Theme> _themes =
754         new ConcurrentHashMap<String, Theme>();
755     private static Map<Long, Map<String, Theme>> _themesPool =
756         new ConcurrentHashMap<Long, Map<String, Theme>>();
757 
758 }