package com.github.javafaker.service; import java.io.InputStream; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.logging.Logger; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.WordUtils; import org.yaml.snakeyaml.Yaml; @SuppressWarnings({ "rawtypes", "unchecked" }) public class FakeValuesService { private static final char[] METHOD_NAME_DELIMITERS = { '_' }; private static final Logger logger = Logger.getLogger(FakeValuesService.class.getName()); private final Map<String, Object> fakeValuesMap; private final RandomService randomService; public FakeValuesService(final Locale locale, final RandomService randomService) { logger.info("Using locale " + locale); String languageCode = locale.getLanguage(); final InputStream stream = findStream(languageCode + ".yml"); if (stream == null) { throw new LocaleDoesNotExistException(String.format("%s could not be found, does not have a corresponding yaml file", locale)); } Map valuesMap = (Map) new Yaml().load(stream); valuesMap = (Map) valuesMap.get(languageCode); fakeValuesMap = (Map<String, Object>) valuesMap.get("faker"); this.randomService = randomService; } private InputStream findStream(final String filename) { InputStream streamOnClass = getClass().getResourceAsStream(filename); if (streamOnClass != null) { return streamOnClass; } return getClass().getClassLoader().getResourceAsStream(filename); } /** * Fetch a random value from an array item specified by the key */ public Object fetch(final String key) { List valuesArray = (List) fetchObject(key); return valuesArray.get(nextInt(valuesArray.size())); } /** * Same as {@link #fetch(String)} except this casts the result into a String. */ public String fetchString(final String key) { return (String) fetch(key); } /** * Return the object selected by the key from yaml file. * * @param key * key contains path to an object. Path segment is separated by dot. E.g. name.first_name */ public Object fetchObject(final String key) { String[] path = key.split("\\."); Object currentValue = fakeValuesMap; for (String pathSection : path) { currentValue = ((Map<String, Object>) currentValue).get(pathSection); } return currentValue; } /** * A property that is composed of other properties. * * It firstly fetches the formatKey using {@link #fetch(String)}. It will proceed to convert the returned properties from the * {@link #fetch(String)} method to a methodName and invoke this method against the object passed in. Finally, concatenation occurs with * the return values of the methods using the joiner parameter as a separator. * */ public String composite(final String formatKey, final String joiner, final Object objectToInvokeMethodOn) { List<String> format = (List<String>) fetch(formatKey); String[] parts = new String[format.size()]; for (int i = 0; i < parts.length; i++) { // remove leading colon String methodName = format.get(i).substring(1); // convert to camel case methodName = WordUtils.capitalizeFully(methodName, METHOD_NAME_DELIMITERS).replaceAll("_", ""); methodName = methodName.substring(0, 1).toLowerCase() + methodName.substring(1); try { parts[i] = (String) objectToInvokeMethodOn.getClass().getMethod(methodName, (Class[]) null).invoke(objectToInvokeMethodOn); } catch (Exception e) { throw new RuntimeException(e); } } return StringUtils.join(parts, joiner); } /** * Returns a string with the '#' characters in the parameter replaced with random digits between 0-9 inclusive. * * For example, the string "ABC##EFG" could be replaced with a string like "ABC99EFG". * */ public String numerify(final String numberString) { StringBuffer sb = new StringBuffer(); for (int i = 0; i < numberString.length(); i++) { if (numberString.charAt(i) == '#') { sb.append(nextInt(10)); } else { sb.append(numberString.charAt(i)); } } return sb.toString(); } /** * Applies both a {@link #numerify(String)} and a {@link #letterify(String)} over the incoming string. * */ public String bothify(final String string) { return letterify(numerify(string)); } /** * Returns a string with the '?' characters in the parameter replaced with random alphabetic characters. * * For example, the string "12??34" could be replaced with a string like "12AB34". * */ public String letterify(final String letterString) { StringBuffer sb = new StringBuffer(); for (int i = 0; i < letterString.length(); i++) { if (letterString.charAt(i) == '?') { sb.append((char) (97 + nextInt(26))); // a-z } else { sb.append(letterString.charAt(i)); } } return sb.toString(); } private int nextInt(final int n) { return randomService.nextInt(n); } }