001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.utils; 014 015import static org.apache.juneau.internal.FileUtils.*; 016 017import java.io.*; 018import java.util.*; 019import java.util.ResourceBundle.*; 020 021/** 022 * Utility class for finding resources for a class. 023 * 024 * <p> 025 * Same as {@link Class#getResourceAsStream(String)} except looks for resources with localized file names. 026 * 027 * <p> 028 * If the <code>locale</code> is specified, then we look for resources whose name matches that locale. 029 * For example, if looking for the resource <js>"MyResource.txt"</js> for the Japanese locale, we will look for 030 * files in the following order: 031 * <ol> 032 * <li><js>"MyResource_ja_JP.txt"</js> 033 * <li><js>"MyResource_ja.txt"</js> 034 * <li><js>"MyResource.txt"</js> 035 * </ol> 036 */ 037public class ClasspathResourceFinderSimple implements ClasspathResourceFinder { 038 039 /** 040 * Reusable instance. 041 */ 042 public static final ClasspathResourceFinderSimple INSTANCE = new ClasspathResourceFinderSimple(); 043 044 private static final ResourceBundle.Control RB_CONTROL = ResourceBundle.Control.getControl(Control.FORMAT_DEFAULT); 045 private static final List<Locale> ROOT_LOCALE = Arrays.asList(Locale.ROOT); 046 047 048 @Override /* ClasspathResourceFinder */ 049 public InputStream findResource(Class<?> baseClass, String name, Locale locale) throws IOException { 050 return findClasspathResource(baseClass, name, locale); 051 } 052 053 /** 054 * Workhorse method for retrieving a resource from the classpath. 055 * 056 * <p> 057 * This method can be overridden by subclasses to provide customized handling of resource retrieval from the classpath. 058 * 059 * @param baseClass The base class providing the classloader. 060 * @param name The resource name. 061 * @param locale 062 * The resource locale. 063 * <br>If <jk>null</jk>, won't look for localized file names. 064 * @return The resource stream, or <jk>null</jk> if it couldn't be found. 065 * @throws IOException 066 */ 067 protected InputStream findClasspathResource(Class<?> baseClass, String name, Locale locale) throws IOException { 068 if (locale == null) 069 return baseClass.getResourceAsStream(name); 070 for (String n : getCandidateFileNames(name, locale)) { 071 InputStream is = baseClass.getResourceAsStream(n); 072 if (is != null) 073 return is; 074 } 075 return null; 076 } 077 078 /** 079 * Returns the candidate file names for the specified file name in the specified locale. 080 * 081 * <p> 082 * For example, if looking for the <js>"MyResource.txt"</js> file in the Japanese locale, the iterator will return 083 * names in the following order: 084 * <ol> 085 * <li><js>"MyResource_ja_JP.txt"</js> 086 * <li><js>"MyResource_ja.txt"</js> 087 * <li><js>"MyResource.txt"</js> 088 * </ol> 089 * 090 * <p> 091 * If the locale is <jk>null</jk>, then it will only return <js>"MyResource.txt"</js>. 092 * 093 * @param fileName The name of the file to get candidate file names on. 094 * @param l 095 * The locale. 096 * <br>If <jk>null</jk>, won't look for localized file names. 097 * @return An iterator of file names to look at. 098 */ 099 protected static Iterable<String> getCandidateFileNames(final String fileName, final Locale l) { 100 return new Iterable<String>() { 101 @Override 102 public Iterator<String> iterator() { 103 return new Iterator<String>() { 104 final Iterator<Locale> locales = getCandidateLocales(l).iterator(); 105 String baseName, ext; 106 107 @Override 108 public boolean hasNext() { 109 return locales.hasNext(); 110 } 111 112 @Override 113 public String next() { 114 Locale l2 = locales.next(); 115 if (l2.toString().isEmpty()) 116 return fileName; 117 if (baseName == null) 118 baseName = getBaseName(fileName); 119 if (ext == null) 120 ext = getExtension(fileName); 121 return baseName + "_" + l2.toString() + (ext.isEmpty() ? "" : ('.' + ext)); 122 } 123 @Override 124 public void remove() { 125 throw new UnsupportedOperationException(); 126 } 127 }; 128 } 129 }; 130 } 131 132 /** 133 * Returns the candidate locales for the specified locale. 134 * 135 * <p> 136 * For example, if <code>locale</code> is <js>"ja_JP"</js>, then this method will return: 137 * <ol> 138 * <li><js>"ja_JP"</js> 139 * <li><js>"ja"</js> 140 * <li><js>""</js> 141 * </ol> 142 * 143 * @param locale The locale to get the list of candidate locales for. 144 * @return The list of candidate locales. 145 */ 146 static final List<Locale> getCandidateLocales(Locale locale) { 147 if (locale == null) 148 return ROOT_LOCALE; 149 return RB_CONTROL.getCandidateLocales("", locale); 150 } 151}