1 /* 2 * The contents of this file are subject to the terms of the Common Development and 3 * Distribution License (the License). You may not use this file except in compliance with the 4 * License. 5 * 6 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 7 * specific language governing permission and limitations under the License. 8 * 9 * When distributing Covered Software, include this CDDL Header Notice in each file and include 10 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 11 * Header, with the fields enclosed by brackets [] replaced by your own identifying 12 * information: "Portions copyright [year] [name of copyright owner]". 13 * 14 * Copyright 2016 ForgeRock AS. 15 */ 16 package org.forgerock.util.i18n; 17 18 import java.net.URI; 19 import java.net.URISyntaxException; 20 import java.util.MissingResourceException; 21 import java.util.Objects; 22 23 /** 24 * Represents a String which could be localizable. If it is localizable it needs to be in the following format, 25 * {@code i18n:bundle#key} which is a URI where: 26 * <ul> 27 * <li>{@code i18n:} is the scheme specifying that the string is localizable</li> 28 * <li>{@code bundle} is the path of the bundle in the classpath (optional, if missing, 29 * a {@link Class} has to be provided and will be used as resource bundle name)</li> 30 * <li>{@code key}, the fragment, is the key of the translated string</li> 31 * </ul> 32 * This class attempts to make the i18n work for an OSGi environment, by encapsulating the name of a resource bundle, 33 * the key in the resource bundle to use, and the {@code ClassLoader} in which the resource bundle can be found, in the 34 * assumption that when it comes to serializing this object, the calling code (e.g. HttpFrameworkServlet and the 35 * Grizzly HandlerAdapter) is in a different classloader and so will not have direct access to the resource bundle. 36 * <p> 37 * A default value {@code LocalizableString} can be provided so that if the key is not found in the bundle, another 38 * value can be specified, which could be either another bundle reference, or a plain value. 39 * </p> 40 */ 41 public class LocalizableString { 42 43 /** 44 * A constant used to indicate a string should be translated. 45 */ 46 public static final String TRANSLATION_KEY_PREFIX = "i18n:"; 47 48 private final ClassLoader loader; 49 private final String value; 50 private final URI resource; 51 private final LocalizableString defaultValue; 52 53 /** 54 * String only constructor for non-localizable {@code String} values. 55 * @param value a string 56 */ 57 public LocalizableString(String value) { 58 this(value, (ClassLoader) null); 59 } 60 61 /** 62 * Constructor for potentially localizable {@code String}. 63 * If resource bundle name not provided in the {@code value}, then the provided {@code type} name will be 64 * used instead. 65 * @param value the String ({@literal i18n:#key.name} is accepted here) 66 * @param type class used to support relative resource bundle lookup (must not be {@code null}) 67 */ 68 public LocalizableString(String value, Class<?> type) { 69 // Integrates class name as the resource name in the value 70 this(value.replace(":#", ":" + type.getName().replace(".", "/") + "#"), type.getClassLoader()); 71 } 72 73 /** 74 * Constructor for potentially localizable {@code String}. 75 * @param value the String 76 * @param loader the {@code ClassLoader} where the string definition should be obtained 77 */ 78 public LocalizableString(String value, ClassLoader loader) { 79 this(value, loader, null); 80 } 81 82 /** 83 * Constructor for potentially localizable {@code String}. If a default value is not specified, if the {@code key} 84 * is a valid URI, its fragment will be used, and otherwise the whole {@code key} value will be used. 85 * @param key the localizable key 86 * @param loader the {@code ClassLoader} where the string definition should be obtained 87 * @param defaultValue the default value to use if not localizable. 88 */ 89 public LocalizableString(String key, ClassLoader loader, LocalizableString defaultValue) { 90 this.loader = loader; 91 this.value = key; 92 this.defaultValue = defaultValue; 93 94 URI resource = null; 95 if (value != null && value.startsWith(TRANSLATION_KEY_PREFIX) && loader != null) { 96 try { 97 resource = new URI(value); 98 } catch (URISyntaxException e) { 99 // empty - use original value 100 } 101 } 102 this.resource = resource; 103 } 104 105 /** 106 * Returns the contained string, translated if applicable. 107 * @param locales The preferred locales for the translation. 108 * @return the translated string 109 */ 110 public String toTranslatedString(PreferredLocales locales) { 111 if (resource == null) { 112 return this.value; 113 } else { 114 try { 115 return locales.getBundleInPreferredLocale(resource.getSchemeSpecificPart(), loader) 116 .getString(resource.getFragment()); 117 } catch (MissingResourceException e) { 118 // the bundle wasn't found, so we use the default value, or return the fragment 119 return defaultValue == null ? resource.getFragment() : defaultValue.toTranslatedString(locales); 120 } 121 } 122 } 123 124 /** 125 * The default toString method. No translation is applied. 126 * @return the untranslated string value 127 */ 128 public String toString() { 129 return defaultValue == null ? value : "[" + value + "], default [" + defaultValue + "]"; 130 } 131 132 /** 133 * The default equals operation. 134 * @param o another object 135 * @return true if the other object is equal 136 */ 137 @Override 138 public boolean equals(Object o) { 139 if (this == o) { 140 return true; 141 } 142 if (o == null || getClass() != o.getClass()) { 143 return false; 144 } 145 146 LocalizableString that = (LocalizableString) o; 147 148 return value.equals(that.value) && Objects.equals(defaultValue, that.defaultValue); 149 150 } 151 152 /** 153 * Default hashcode implementation. 154 * @return the hashcode of the string 155 */ 156 @Override 157 public int hashCode() { 158 return Objects.hash(value, defaultValue); 159 } 160 }