/** * */ package net.frontlinesms.plugins; import java.io.IOException; import java.io.InputStream; import java.util.Collections; import java.util.Locale; import java.util.Map; import org.apache.log4j.Logger; import net.frontlinesms.FrontlineUtils; import net.frontlinesms.ui.UiGeneratorController; import net.frontlinesms.ui.i18n.InternationalisationUtils; /** * Base implementation of the {@link PluginController} annotation. * * Implementers of this class *must* carry the {@link PluginControllerProperties} annotation. * * This class includes default implementation of the text resource loading methods. These attempt to load text resources * in the following way: * TODO properly document how this is done from the methods {@link #getDefaultTextResource()} and {@link #getTextResource(Locale)}. * * @author Alex */ public abstract class BasePluginController implements PluginController { //> STATIC CONSTANTS //> INSTANCE PROPERTIES /** Logging object for this class */ protected final Logger log = FrontlineUtils.getLogger(this.getClass()); /** Lazy-initialized singleton Thinlet Tab component for this instance of this plugin. */ private Object thinletTab; /** * The instance of {@link UiGeneratorController} used to {@link #initThinletTab(UiGeneratorController)} * the current value of {@link #thinletTab}. This is used to check whether the {@link UiGeneratorController} passed to * {@link #getTab(UiGeneratorController)} is the same instance that was used to create the tab in the first place. If the * {@link UiGeneratorController} has changed, the tab will be discarded and this value reset. */ private UiGeneratorController tabUiController; //> CONSTRUCTORS //> ACCESSORS /** @see net.frontlinesms.plugins.PluginController#getTab(net.frontlinesms.ui.UiGeneratorController) */ public synchronized Object getTab(UiGeneratorController uiController) { // N.B. we are deliberately checking the references of the UiGeneratorController here, rather // than .equals() as we just want to know if it is the same instance. if(this.thinletTab == null || this.tabUiController!=uiController) { this.tabUiController = uiController; this.thinletTab = this.initThinletTab(uiController); } return this.thinletTab; } /** * Initialise the Thinlet tab component for this plugin instance. This method should ONLY initialise * the visible UI. The tab itself can be discarded and re-initialised at any time by the * {@link BasePluginController}. Notably this will happen when the display language of the UI is * changed. * @param uiController {@link UiGeneratorController} instance that will be the parent of this tab. * @return a new instance of the thinlet tab component for this plugin. */ protected abstract Object initThinletTab(UiGeneratorController uiController); /** * @see PluginController#getName(Locale locale) * This actually loads the whole property file for this plugin and takes the plugin name. * Try to avoid calling this function frequently. **/ public String getName(Locale locale) { assert(this.getClass().isAnnotationPresent(PluginControllerProperties.class)): "Implementers of this class *must* implement the PluginControllerProperties annotation and specify the i18nKey attribute."; String pluginName = getTextResource(locale).get(this.getClass().getAnnotation(PluginControllerProperties.class).i18nKey()); if (pluginName == null) { return this.getClass().getAnnotation(PluginControllerProperties.class).name(); } else { return pluginName; } } /** * Override if the plugin needs settings */ public PluginSettingsController getSettingsController(UiGeneratorController uiController) { return null; } /** @see net.frontlinesms.plugins.PluginController#getDefaultTextResource() */ public Map<String, String> getDefaultTextResource() { Map<String, String> defaultTextResource = getTextResource(); if(defaultTextResource != null) return defaultTextResource; else return Collections.emptyMap(); } /** @see net.frontlinesms.plugins.PluginController#getTextResource(java.util.Locale) */ public Map<String, String> getTextResource(Locale locale) { String variant = locale.getVariant(); String country = locale.getCountry(); String language = locale.getLanguage(); if(variant != null && variant.length() > 0) { Map<String, String> textResource = getTextResource(language, country, variant); if(textResource != null) return textResource; } if(country != null && country.length() > 0) { Map<String, String> textResource = getTextResource(language, country); if(textResource != null) return textResource; } if(language != null && language.length() > 0) { Map<String, String> textResource = getTextResource(language); if(textResource != null) return textResource; } return Collections.emptyMap(); } //> INSTANCE HELPER METHODS /** * Gets a text resource file from the classpath. * @param nameExtensions extensions added to the end of the standard filename. These will be separated from the base name and each other by underscores. * @return The text resource, or <code>null</code> if the resource could not be found. */ private final Map<String, String> getTextResource(String... nameExtensions) { String resourceFilePath = getTextResourcePath(nameExtensions); // Attempt to load the text resource using relative path with local classloader InputStream textResourceInputStream = this.getClass().getResourceAsStream(resourceFilePath); if(textResourceInputStream == null) { // Resource could not be found, so return null return null; } else { try { return InternationalisationUtils.loadTextResources(resourceFilePath, textResourceInputStream); } catch (IOException ex) { log.info("There was a problem loading language bundle from " + resourceFilePath, ex); return null; } } } /** * Gets the path for a text resource bundle. * @param nameExtensions extensions added to the end of the standard filename. These will be separated from the base name and each other by underscores. * @return classpath location of the text resource bundle */ private String getTextResourcePath(String... nameExtensions) { String resourceFilePath = getTextResourceFilename(nameExtensions) + ".properties"; return resourceFilePath; } /** * Gets the filename for the text resource bundle. * @param nameExtensions extensions added to the end of the standard filename. These will be separated from the base name and each other by underscores. * @return File name for the text resource bundle */ private String getTextResourceFilename(String ... nameExtensions) { // Construct the name of the .properties file String fileName = this.getClass().getSimpleName(); // Add suffix "Text" to file name, BEFORE the language & country suffixes fileName += "Text"; // Append any required extensions to the filename for(String extension : nameExtensions) { if(extension != null) { fileName += "_" + extension; } } return fileName; } /** * Gets the main icon of the Plugin * @return */ public String getIcon(Class<? extends BasePluginController> clazz) { if(clazz.isAnnotationPresent(PluginControllerProperties.class)) { PluginControllerProperties properties = clazz.getAnnotation(PluginControllerProperties.class); return properties.iconPath(); } else { return '/' + clazz.getPackage().getName().replace('.', '/') + '/' + clazz.getSimpleName() + ".png"; } } //> STATIC FACTORIES //> STATIC HELPER METHODS }