001package ca.uhn.fhir.i18n; 002 003import ca.uhn.fhir.context.ConfigurationException; 004 005import java.text.MessageFormat; 006import java.util.*; 007import java.util.concurrent.ConcurrentHashMap; 008 009import static org.apache.commons.lang3.StringUtils.isNotBlank; 010import static org.apache.commons.lang3.StringUtils.trim; 011 012/* 013 * #%L 014 * HAPI FHIR - Core Library 015 * %% 016 * Copyright (C) 2014 - 2019 University Health Network 017 * %% 018 * Licensed under the Apache License, Version 2.0 (the "License"); 019 * you may not use this file except in compliance with the License. 020 * You may obtain a copy of the License at 021 * 022 * http://www.apache.org/licenses/LICENSE-2.0 023 * 024 * Unless required by applicable law or agreed to in writing, software 025 * distributed under the License is distributed on an "AS IS" BASIS, 026 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 027 * See the License for the specific language governing permissions and 028 * limitations under the License. 029 * #L% 030 */ 031 032/** 033 * This feature is not yet in its final state and should be considered an internal part of HAPI for now - use with caution 034 */ 035public class HapiLocalizer { 036 037 @SuppressWarnings("WeakerAccess") 038 public static final String UNKNOWN_I18N_KEY_MESSAGE = "!MESSAGE!"; 039 private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(HapiLocalizer.class); 040 private static boolean ourFailOnMissingMessage; 041 private final Map<String, MessageFormat> myKeyToMessageFormat = new ConcurrentHashMap<>(); 042 private List<ResourceBundle> myBundle = new ArrayList<>(); 043 private String[] myBundleNames; 044 045 public HapiLocalizer() { 046 this(HapiLocalizer.class.getPackage().getName() + ".hapi-messages"); 047 } 048 049 public HapiLocalizer(String... theBundleNames) { 050 myBundleNames = theBundleNames; 051 init(); 052 } 053 054 public Set<String> getAllKeys() { 055 HashSet<String> retVal = new HashSet<>(); 056 for (ResourceBundle nextBundle : myBundle) { 057 Enumeration<String> keysEnum = nextBundle.getKeys(); 058 while (keysEnum.hasMoreElements()) { 059 retVal.add(keysEnum.nextElement()); 060 } 061 } 062 return retVal; 063 } 064 065 /** 066 * @return Returns the raw message format string for the given key, or returns {@link #UNKNOWN_I18N_KEY_MESSAGE} if not found 067 */ 068 @SuppressWarnings("WeakerAccess") 069 public String getFormatString(String theQualifiedKey) { 070 String formatString = null; 071 for (ResourceBundle nextBundle : myBundle) { 072 if (nextBundle.containsKey(theQualifiedKey)) { 073 formatString = nextBundle.getString(theQualifiedKey); 074 formatString = trim(formatString); 075 } 076 if (isNotBlank(formatString)) { 077 break; 078 } 079 } 080 081 if (formatString == null) { 082 ourLog.warn("Unknown localization key: {}", theQualifiedKey); 083 if (ourFailOnMissingMessage) { 084 throw new ConfigurationException("Unknown localization key: " + theQualifiedKey); 085 } 086 formatString = UNKNOWN_I18N_KEY_MESSAGE; 087 } 088 return formatString; 089 } 090 091 public String getMessage(Class<?> theType, String theKey, Object... theParameters) { 092 return getMessage(toKey(theType, theKey), theParameters); 093 } 094 095 public String getMessage(String theQualifiedKey, Object... theParameters) { 096 if (theParameters != null && theParameters.length > 0) { 097 MessageFormat format = myKeyToMessageFormat.get(theQualifiedKey); 098 if (format != null) { 099 return format.format(theParameters); 100 } 101 102 String formatString = getFormatString(theQualifiedKey); 103 104 format = newMessageFormat(formatString); 105 myKeyToMessageFormat.put(theQualifiedKey, format); 106 return format.format(theParameters); 107 } 108 return getFormatString(theQualifiedKey); 109 } 110 111 MessageFormat newMessageFormat(String theFormatString) { 112 StringBuilder pattern = new StringBuilder(theFormatString.trim()); 113 114 115 for (int i = 0; i < (pattern.length()-1); i++) { 116 if (pattern.charAt(i) == '{') { 117 char nextChar = pattern.charAt(i+1); 118 if (nextChar >= '0' && nextChar <= '9') { 119 continue; 120 } 121 122 pattern.replace(i, i+1, "'{'"); 123 int closeBraceIndex = pattern.indexOf("}", i); 124 if (closeBraceIndex > 0) { 125 i = closeBraceIndex; 126 pattern.replace(i, i+1, "'}'"); 127 } 128 } 129 } 130 131 return new MessageFormat(pattern.toString()); 132 } 133 134 protected void init() { 135 for (String nextName : myBundleNames) { 136 myBundle.add(ResourceBundle.getBundle(nextName)); 137 } 138 } 139 140 /** 141 * This <b>global setting</b> causes the localizer to fail if any attempts 142 * are made to retrieve a key that does not exist. This method is primarily for 143 * unit tests. 144 */ 145 public static void setOurFailOnMissingMessage(boolean ourFailOnMissingMessage) { 146 HapiLocalizer.ourFailOnMissingMessage = ourFailOnMissingMessage; 147 } 148 149 public static String toKey(Class<?> theType, String theKey) { 150 return theType.getName() + '.' + theKey; 151 } 152 153}