package org.ovirt.engine.ui.common.utils; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.MissingResourceException; import java.util.Set; import org.ovirt.engine.ui.frontend.utils.BaseContextPathData; import org.ovirt.engine.ui.uicommonweb.DynamicMessages; import com.google.gwt.i18n.client.Dictionary; import com.google.gwt.i18n.client.LocaleInfo; import com.google.gwt.regexp.shared.MatchResult; import com.google.gwt.regexp.shared.RegExp; import com.google.gwt.safehtml.shared.UriUtils; /** * Contains dynamic messages available to the application. * <p> * This class defines all supported message keys as well as corresponding value accessor methods. Subclasses should * register sensible fallback values for supported message keys. */ public class BaseDynamicMessages implements DynamicMessages { /** * This class defines keys used to look up messages from the {@code Dictionary}. */ public enum DynamicMessageKey { APPLICATION_TITLE("application_title"), //$NON-NLS-1$ VERSION_ABOUT("version_about"), //$NON-NLS-1$ COPY_RIGHT_NOTICE("copy_right_notice"), //$NON-NLS-1$ GUIDE_URL("guide_url"), //$NON-NLS-1$ EXTENDED_GUIDE_URL("extended_guide_url"), //$NON-NLS-1$ GUIDE_LINK_LABEL("guide_link_label"), //$NON-NLS-1$ CLIENT_RESOURCES("client_resources"), //$NON-NLS-1$ CONSOLE_CLIENT_RESOURCES("console_client_resources"), //$NON-NLS-1$ CONSOLE_CLIENT_RESOURCES_URL("console_client_resources_url"), //$NON-NLS-1$ VENDOR_URL("vendor_url"), //$NON-NLS-1$ DOC("doc"), //$NON-NLS-1$ FENCING_OPTIONS("fencing_options"), //$NON-NLS-1$ FENCING_OPTIONS_URL("fencing_options_url"); //$NON-NLS-1$ private final String value; DynamicMessageKey(String value) { this.value = value; } public String getValue() { return value; } } /** * The name under which the dictionary will appear in the host page. */ private static final String MESSAGES_DICTIONARY_NAME = "messages"; //$NON-NLS-1$ /** * The pattern used to locate place holders within messages. */ private static final RegExp PLACE_HOLDER_PATTERN = RegExp.compile("\\{(\\d+)\\}", "g"); //$NON-NLS-1$ //$NON-NLS-2$ /** * The {@code Dictionary} that contains the messages from the host page. */ private final Dictionary dictionary; /** * The {@code Map} containing the fallback values in case the message is not found in the dictionary. */ private final Map<DynamicMessageKey, String> fallbackValues = new HashMap<>(); public BaseDynamicMessages() { this(Dictionary.getDictionary(MESSAGES_DICTIONARY_NAME)); } BaseDynamicMessages(final Dictionary dictionary) { this.dictionary = dictionary; } /** * Adds the fallback for a particular key to the fallback map. * * @param key * The key for the fallback. * @param value * The fallback message. */ protected void addFallback(final DynamicMessageKey key, final String value) { fallbackValues.put(key, value); } /** * Returns the string value associated with the given key. * <p> * If the {@code Dictionary} doesn't contain the value associated with given key, then return the fallback value. If * the fallback value isn't defined for given key, then return empty string. * * @param key * The key * @return The message, either from the {@code Dictionary} or the fallback value. */ protected String getString(final DynamicMessageKey key) { try { if (dictionary != null) { return dictionary.get(key.getValue()); } } catch (MissingResourceException mre) { // Do nothing, the key doesn't exist. } String fallback = fallbackValues.get(key); if (fallback == null) { // Use empty string for missing fallback value. fallback = ""; //$NON-NLS-1$ } return fallback; } /** * Formats the message associated with the given key using the passed in parameters. * <p> * The message body must conform to the following standard: * <ol> * <li>The place holders must follow the following format regex \{\d\}, for instance {0}</li> * <li>The place holder sequence must start at 0 and be continuous so {0}, {1}, {2} is valid but {0}, {2} is not</li> * <li>One can have the same place holder more than once, so {0}, {0} valid</li> * <li>The order is not important, so {2}, {0}, {1} is valid</li> * </ol> * One can pass more parameters than place holders in the message body, any extra parameters will simply be ignored. * * @param key * The key to use to lookup the message body. * @param args * Zero or more arguments to replace in the message body. * @return The formatted string. * @throws IllegalArgumentException * if the message body does not conform to the above standard, or if there are less arguments than place * holders in the message body. */ protected String formatString(final DynamicMessageKey key, final String... args) { String message = getString(key); if (args != null) { List<Integer> placeHolderList = getPlaceHolderList(message); if (placeHolderList.size() > args.length) { throw new IllegalArgumentException("Number of place holders does " //$NON-NLS-1$ + "not match number of arguments"); //$NON-NLS-1$ } for (int i = 0; i < args.length; i++) { message = message.replaceAll("\\{" + i + "\\}", args[i]); //$NON-NLS-1$ //$NON-NLS-2$ } } return message; } /** * Parse the message body and return a list of integers, one for each place holder index in the body. * <p> * For instance, if the message is 'One {0} over the {1} nest {0}' then the list will be [0,1]. Duplicates are * turned into a single element in the resulting list. * * @param message * The message body to parse. * @return A list of integers matching the indexes of the place holders within the message body. */ protected List<Integer> getPlaceHolderList(final String message) { MatchResult matcher; Set<Integer> matchedPlaceHolders = new HashSet<>(); for (matcher = PLACE_HOLDER_PATTERN.exec(message); matcher != null; matcher = PLACE_HOLDER_PATTERN.exec(message)) { matchedPlaceHolders.add(Integer.valueOf(matcher.getGroup(1))); } List<Integer> result = new ArrayList<>(matchedPlaceHolders); Collections.sort(result); for (int i = 0; i < result.size(); i++) { if (i != result.get(i)) { throw new IllegalArgumentException("Invalid place holder index found"); //$NON-NLS-1$ } } return result; } /** * Convenience method to get the current locale as a string. * @return The current locale as a String. */ protected String getCurrentLocaleAsString() { return LocaleInfo.getCurrentLocale().getLocaleName(); } @Override public final String applicationTitle() { return getString(DynamicMessageKey.APPLICATION_TITLE); } @Override public final String ovirtVersionAbout(final String version) { return formatString(DynamicMessageKey.VERSION_ABOUT, version); } @Override public final String copyRightNotice() { return getString(DynamicMessageKey.COPY_RIGHT_NOTICE); } @Override public final String guideUrl() { return formatString(DynamicMessageKey.GUIDE_URL, getCurrentLocaleAsString()); } @Override public final String guideLinkLabel() { return getString(DynamicMessageKey.GUIDE_LINK_LABEL); } @Override public final String consoleClientResources() { return getString(DynamicMessageKey.CONSOLE_CLIENT_RESOURCES); } @Override public final String clientResources() { return getString(DynamicMessageKey.CLIENT_RESOURCES); } @Override public final String consoleClientResourcesUrl() { String url = getString(DynamicMessageKey.CONSOLE_CLIENT_RESOURCES_URL); boolean isAbsolute = UriUtils.extractScheme(url) != null; return isAbsolute ? url : "/" + BaseContextPathData.getRelativePath() + url; //$NON-NLS-1$ } @Override public final String vendorUrl() { return getString(DynamicMessageKey.VENDOR_URL); } @Override public final String applicationDocTitle() { return getString(DynamicMessageKey.DOC); } @Override public final String fencingOptions() { return getString(DynamicMessageKey.FENCING_OPTIONS); } @Override public final String fencingOptionsUrl() { String url = getString(DynamicMessageKey.FENCING_OPTIONS_URL); boolean isAbsolute = UriUtils.extractScheme(url) != null; return isAbsolute ? url : "/" + BaseContextPathData.getRelativePath() + url; //$NON-NLS-1$ } }