/* * Copyright 2013 MicaByte Systems * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package com.micabytes.util; import android.content.Context; import android.support.annotation.Nullable; import com.micabytes.GameApplication; import com.micabytes.R; import org.jetbrains.annotations.NonNls; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.AbstractMap; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.regex.Pattern; /** TODO: Deprecate most of this functionality. */ /** * StringHandler is a wrapper around the standard Android getString functionality. It is primarily * used for formatting magic on strings using the GameObjectInterface. */ @SuppressWarnings({"UtilityClass", "unused", "NonConstantStringShouldBeStringBuffer"}) public final class StringHandler { private static final String TAG = StringHandler.class.getName(); private static final int NOT_FOUND = -1; @NonNls public static final char AT = '@'; public static final char HASH_MARK = '#'; public static final char PERCENT = '%'; public static final String NULL = "null"; public static final char PLUS = '+'; public static final char UNDERSCORE = '_'; @NonNls public static final char WHITESPACE = ' '; @NonNls public static final char SQUARE_BRACE_LEFT = '['; public static final char SQUARE_BRACE_RIGHT = ']'; public static final char BRACKET_LEFT = '('; public static final char BRACKET_RIGHT = ')'; @NonNls public static final String DOT = "."; @NonNls public static final String WAVY_BRACE_LEFT = "{"; @NonNls public static final String WAVY_BRACE_RIGHT = "}"; @NonNls public static final String LINE_SEPARATOR = "line.separator"; @SuppressWarnings("AccessOfSystemProperties") @NonNls public static final String EOL = System.getProperty(LINE_SEPARATOR); @NonNls public static final String SLASH = "/"; @SuppressWarnings("HardcodedLineSeparator") @NonNls public static final String SLASH_N = "\\n"; @NonNls public static final String NUMBER_STRING = "%d"; private static final Pattern AND_SPLITTER = Pattern.compile("[&]"); private static final Pattern GEQ_SPLITTER = Pattern.compile("[>=]+"); private static final Pattern LEQ_SPLITTER = Pattern.compile("[<=]+"); private static final Pattern GT_SPLITTER = Pattern.compile("[>]+"); private static final Pattern LT_SPLITTER = Pattern.compile("[<]+"); private static final Pattern EQ_SPLITTER = Pattern.compile("[=]+"); private static final Pattern DOT_SPLITTER = Pattern.compile("[.]"); public static final Pattern UNDERSCORE_SPLITTER = Pattern.compile("[_]"); private StringHandler() { // NOOP } public static String get(int id) { Context c = GameApplication.getInstance(); return c.getString(id); } @SuppressWarnings("OverloadedVarargsMethod") public static String get(int id, Object... args) { Context c = GameApplication.getInstance(); return c.getString(id, args); } public static String list(List<String> list) { Context c = GameApplication.getInstance(); if (list.isEmpty()) return ""; if (list.size() == 1) return list.get(0); if (list.size() == 2) return list.get(0) + WHITESPACE + c.getString(R.string.stringhandler_and1) + WHITESPACE + list.get(1); StringBuilder ret = new StringBuilder(); for (int i = 0; i < (list.size() - 1); i++) { ret.append(list.get(i)); if (i < (list.size() - 2)) { ret.append(c.getString(R.string.stringhandler_comma)); ret.append(WHITESPACE); } else { ret.append(c.getString(R.string.stringhandler_and2)); ret.append(WHITESPACE); } } ret.append(list.get(list.size() - 1)); return ret.toString(); } /** * This is the workhorse function of the class. It takes a string and strips out the formatting * code, replacing it with appropriate text from the variables. <p/> Formatting code: <p/> * [/TEXT0/TEXT1/TEXT2/.../] Randomly selects one text fragment to display. <p/> [# VARIABLE / * TEXT0 / TEXT1 / TEXT2 #] Selects a text fragment based on the variable. A value of 0 selects * the first variable, 1 selects the second, and any other value selects the third. This is useful * to handle text where you are not sure of the plural (e.g, [# $number /no cannon/1 cannon/many * cannons#] <p/> $VARIABLE The appropriate variable is selected; integer, doubles and strings are * substituted directly into the text; for BaseObject variables, the appropriate text is retrieved * using the getString methods. Dot notation is used (e.g., $MyObject.MyString - MyString is * passed to the getString function). * * @param text The text to be formatted * @param variables A hash map containing variables * @return String with all of the scripting code replaced appropriately */ @SuppressWarnings({"MethodWithMoreThanThreeNegations", "OverlyComplexMethod", "ConstantConditions"}) public static String format(String text, @Nullable HashMap<String, Object> variables) { String ret = resolveLineBreaks(text); // Markup Link Notation int start = ret.indexOf(SQUARE_BRACE_LEFT); int end; while (start != NOT_FOUND) { end = ret.indexOf(SQUARE_BRACE_RIGHT, start); if (end == NOT_FOUND) start = NOT_FOUND; else { String opt = ret.substring(start + 1, end); @NonNls String condition = null; if (ret.charAt(end + 1) == BRACKET_LEFT) { int i = ret.indexOf(BRACKET_RIGHT, end); condition = ret.substring(end + 2, i).trim(); if (i != NOT_FOUND) end = i; } String replace = ret.substring(start, end + 1); String[] tokens = opt.split("[|]"); if (tokens.length == 1) ret = ret.replace(replace, tokens[RandomHandler.random(tokens.length)]); else if ("?".equals(condition)) { ret = ret.replace(replace, tokens[RandomHandler.random(tokens.length)]); } else if (condition != null){ condition = condition.replace("?", ""); int nInt = evaluate(condition, variables); if (nInt > (tokens.length - 1)) nInt = tokens.length - 1; if (nInt < 0) nInt = 0; ret = ret.replace(replace, tokens[nInt]); } start = ret.indexOf(SQUARE_BRACE_LEFT); } } // Game variable substitution if (variables != null) { int startV = ret.indexOf(WAVY_BRACE_LEFT); while (startV != NOT_FOUND) { end = ret.indexOf(WAVY_BRACE_RIGHT, startV); if (end == NOT_FOUND) startV = NOT_FOUND; else { String variable = ret.substring(startV + 1, end); String stringToReplace = ret.substring(startV, end + 1); String replaceWith = getStringValue(variable, variables); ret = ret.replace(stringToReplace, replaceWith); startV = ret.indexOf(WAVY_BRACE_LEFT); } } } return ret; } private static String resolveLineBreaks(String text) { //noinspection AccessOfSystemProperties return text.replace(SLASH_N, System.getProperty(LINE_SEPARATOR)); } private static String getStringValue(String key, AbstractMap<String, Object> variables) { if (variables == null) { return GameConstants.ERROR; } String str = key.trim().toLowerCase(Locale.US); if (str.isEmpty()) return GameConstants.ERROR; String[] tokens = DOT_SPLITTER.split(str, 2); if (tokens.length > 2) { GameLog.e(TAG, "Failed to getStringValue variable value for object " + str); return GameConstants.ERROR; } Object obj = variables.get(tokens[0]); if (obj == null) { return GameConstants.ERROR; } if (obj instanceof String) return (String) obj; if (tokens.length == 1) return obj.toString(); GameObjectInterface sObj = (GameObjectInterface) obj; //Object res = sObj.getAttribute(tokens[1]); //if (res instanceof String) return (String) res; //return res.toString(); return ""; } @SuppressWarnings("StaticMethodOnlyUsedInOneClass") public static String randomSplit(String str, String divisor) { if (str.contains(divisor)) { String[] tokens = str.split(SQUARE_BRACE_LEFT + divisor + SQUARE_BRACE_RIGHT); if (tokens.length > 0) { return tokens[RandomHandler.random(tokens.length)]; } } return str; } @SuppressWarnings("StringContatenationInLoop") public static String listString(ArrayList<String> list) { Context c = GameApplication.getInstance(); @NonNls String ret = ""; if (list.isEmpty()) return ret; if (list.size() == 1) { ret = list.get(0); return ret; } if (list.size() == 2) { ret = list.get(0) + c.getString(R.string.stringhandler_and1) + list.get(1); return ret; } for (int i = 0; i < (list.size() - 1); i++) { ret += list.get(i); ret += i < (list.size() - 2) ? c.getString(R.string.stringhandler_comma) : c.getString(R.string.stringhandler_and2); } ret += list.get(list.size() - 1); return ret; } public static String signedString(int i) { if (i < 0) return Integer.toString(i); return PLUS + Integer.toString(i); } @SuppressWarnings("OverloadedVarargsMethod") public static String get(int id, HashMap<String, Object> variables, Object... args) { Context c = GameApplication.getInstance(); return format(c.getString(id, args), variables); } public static String getString(InputStream is) { BufferedReader reader = null; StringBuilder sb = new StringBuilder(); try { //noinspection IOResourceOpenedButNotSafelyClosed,resource reader = new BufferedReader(new InputStreamReader(is, GameConstants.UTF_8)); String line; //noinspection NestedAssignment while ((line = reader.readLine()) != null) { sb.append(line).append(EOL); } } catch (IOException e) { GameLog.logException(e); } finally { try { if (reader != null) { reader.close(); } } catch (IOException e) { GameLog.logException(e); } try { is.close(); } catch (IOException e) { GameLog.logException(e); } } return sb.toString(); } @SuppressWarnings("CollectionDeclaredAsConcreteClass") public static int evaluate(String test, HashMap<String, Object> variables) { String[] tokens = AND_SPLITTER.split(test); if (tokens.length == 1) return evaluateStatement(test, variables); boolean ret = true; for (String s : tokens) { if (evaluateStatement(s, variables) <= 0) ret = false; } return ret ? 1 : 0; } @SuppressWarnings({"MethodWithMultipleReturnPoints", "OverlyComplexMethod", "FeatureEnvy"}) private static int evaluateStatement(String str, AbstractMap<String, Object> variables) { String[] tokens; // Random Value // >= if (str.contains(">=")) { tokens = GEQ_SPLITTER.split(str); if (tokens.length == 2) { String val1 = tokens[0].trim().toLowerCase(Locale.US); String val2 = tokens[1].trim().toLowerCase(Locale.US); return (getVariableValue(val1, variables) >= getVariableValue(val2, variables)) ? 1 : 0; } GameLog.e(TAG, "Could not parse statement fragment GEQ:" + str); return 0; } // >= if (str.contains("<=")) { tokens = LEQ_SPLITTER.split(str); if (tokens.length == 2) { String val1 = tokens[0].trim().toLowerCase(Locale.US); String val2 = tokens[1].trim().toLowerCase(Locale.US); return (getVariableValue(val1, variables) <= getVariableValue(val2, variables)) ? 1 : 0; } GameLog.e(TAG, "Could not parse statement fragment LEQ:" + str); return 0; } // > if (str.contains(">")) { tokens = GT_SPLITTER.split(str); if (tokens.length == 2) { String val1 = tokens[0].trim().toLowerCase(Locale.US); String val2 = tokens[1].trim().toLowerCase(Locale.US); return (getVariableValue(val1, variables) > getVariableValue(val2, variables)) ? 1 : 0; } GameLog.e(TAG, "Could not parse statement fragment GT:" + str); return 0; } // < if (str.contains("<")) { tokens = LT_SPLITTER.split(str); if (tokens.length == 2) { String val1 = tokens[0].trim().toLowerCase(Locale.US); String val2 = tokens[1].trim().toLowerCase(Locale.US); return (getVariableValue(val1, variables) < getVariableValue(val2, variables)) ? 1 : 0; } GameLog.e(TAG, "Could not parse statement fragment LT:" + str); return 0; } // Set Last, as it will otherwise take precedence over all the others. // = if (str.contains("=")) { tokens = EQ_SPLITTER.split(str); if (tokens.length == 2) { String val1 = tokens[0].trim().toLowerCase(Locale.US); String val2 = tokens[1].trim().toLowerCase(Locale.US); return (getVariableValue(val1, variables) == getVariableValue(val2, variables)) ? 1 : 0; } GameLog.e(TAG, "Could not parse statement fragment " + str); return 0; } // Retrieve return getVariableValue(str, variables); } @SuppressWarnings({"MethodWithMultipleReturnPoints", "OverlyComplexMethod"}) private static int getVariableValue(String key, AbstractMap<String, Object> variables) { String str = key.trim().toLowerCase(Locale.US); if (str.isEmpty()) return 0; try { return Integer.parseInt(str); } catch (NumberFormatException ignored) { // NOOP } String[] tokens = DOT_SPLITTER.split(str, 2); if (tokens.length > 2) { GameLog.e(TAG, "Failed to getVariableValue for object " + str); return 0; } if (variables == null) return 0; Object obj = variables.get(tokens[0]); if (obj == null) { return 0; } if (obj instanceof Boolean) { return (Boolean) obj ? 1 : 0; } if (obj instanceof Integer) return (Integer) obj; if (obj instanceof Double) return ((Double) obj).intValue(); if (obj instanceof String) return 1; GameObjectInterface gObj = (GameObjectInterface) obj; /*Object attribute = gObj.getAttribute(tokens[1]); if (attribute instanceof Boolean) { return (Boolean) attribute ? 1 : 0; } if (attribute instanceof Integer) return (Integer) attribute; if (attribute instanceof Double) return ((Double) attribute).intValue(); if (attribute instanceof String) return 1; */ return 0; } }