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 - 2018 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 = new MessageFormat(formatString.trim()); 105 myKeyToMessageFormat.put(theQualifiedKey, format); 106 return format.format(theParameters); 107 } 108 return getFormatString(theQualifiedKey); 109 } 110 111 protected void init() { 112 for (String nextName : myBundleNames) { 113 myBundle.add(ResourceBundle.getBundle(nextName)); 114 } 115 } 116 117 /** 118 * This <b>global setting</b> causes the localizer to fail if any attempts 119 * are made to retrieve a key that does not exist. This method is primarily for 120 * unit tests. 121 */ 122 public static void setOurFailOnMissingMessage(boolean ourFailOnMissingMessage) { 123 HapiLocalizer.ourFailOnMissingMessage = ourFailOnMissingMessage; 124 } 125 126 public static String toKey(Class<?> theType, String theKey) { 127 return theType.getName() + '.' + theKey; 128 } 129 130}