package checkers.propkey; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; import java.util.Collections; import java.util.HashSet; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.ResourceBundle; import java.util.Set; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.SupportedOptions; import checkers.basetype.BaseTypeChecker; import checkers.propkey.quals.PropertyKey; import checkers.quals.TypeQualifiers; import checkers.quals.Unqualified; /** * A type-checker that checks that only valid keys are used to access property files * and resource bundles. * Subclasses can specialize this class for the different uses of property files, * for examples see the i18n and compilermsgs checkers. * * Currently, the checker supports two methods to check: * * <ol> * <li value="1">Property files: * A common method for localization using a property file, mapping the * keys to values. * Programmers pass the property file locations via * {@code propfiles} option (e.g. {@code -Apropfiles=/path/to/messages.properties}), * separating multiple files by a colon ":". * </li> * * <li value="2">{@link ResourceBundle}: * Programmers pass the {@code baseName} name of the bundle via * {@code bundlename} (e.g. {@code -Abundlename=MyResource}. The checker uses * the resource associated with the default {@link Locale} in the compilation * system. * </li> * * </ol> * */ // Subclasses need something similar to this: @TypeQualifiers( {PropertyKey.class, Unqualified.class} ) // Subclasses need exactly this: @SupportedOptions( {"propfiles", "bundlenames"} ) public class PropertyKeyChecker extends BaseTypeChecker { private Set<String> lookupKeys; @Override public void init(ProcessingEnvironment env) { super.init(env); this.lookupKeys = Collections.unmodifiableSet(buildLookupKeys(env.getOptions())); } /** * Returns a set of the valid keys that can be used. */ public Set<String> getLookupKeys() { return this.lookupKeys; } private Set<String> buildLookupKeys(Map<String, String> options) { Set<String> result = new HashSet<String>(); if (options.containsKey("propfiles")) { result.addAll( keysOfPropertyFiles(env.getOptions().get("propfiles")) ); } if (options.containsKey("bundlenames")) { result.addAll( keysOfResourceBundle(env.getOptions().get("bundlenames")) ); } return result; } private Set<String> keysOfPropertyFiles(String names) { String[] namesArr = names.split(":"); if (namesArr == null) { System.err.println("Couldn't parse the properties files: <" + names + ">"); return Collections.emptySet(); } Set<String> result = new HashSet<String>(); for (String name : namesArr) { try { Properties prop = new Properties(); InputStream in = null; ClassLoader cl = this.getClass().getClassLoader(); if (cl == null) { // the class loader is null if the system class loader was // used cl = ClassLoader.getSystemClassLoader(); } in = cl.getResourceAsStream(name); if (in == null) { // if the classloader didn't manage to load the file, try // whether a FileInputStream works. For absolute paths this // might help. try { in = new FileInputStream(name); } catch (FileNotFoundException e) { // ignore } } if (in == null) { System.err.println("Couldn't find the properties file: " + name); // report(Result.failure("propertykeychecker.filenotfound", // name), null); // return Collections.emptySet(); continue; } prop.load(in); result.addAll(prop.stringPropertyNames()); } catch (Exception e) { // TODO: is there a nicer way to report messages, that are not // connected to an AST node? // One cannot use report, because it needs a node. System.err.println("Exception in PropertyKeyChecker.keysOfPropertyFile: " + e); e.printStackTrace(); } } return result; } private Set<String> keysOfResourceBundle(String bundleNames) { String[] namesArr = bundleNames.split(":"); if (namesArr == null) { System.err.println("Couldn't parse the resource bundles: <" + bundleNames + ">"); return Collections.emptySet(); } Set<String> result = new HashSet<String>(); for (String bundleName : namesArr) { ResourceBundle bundle = ResourceBundle.getBundle(bundleName); if (bundle == null) { System.err.println("Couldn't find the resource bundle: <" + bundleName + "> for locale <" + Locale.getDefault() + ">"); continue; } result.addAll(bundle.keySet()); } return result; } }