/**
* Catroid: An on-device visual programming system for Android devices
* Copyright (C) 2010-2013 The Catrobat Team
* (<http://developer.catrobat.org/credits>)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* An additional term exception under section 7 of the GNU Affero
* General Public License, version 3, is available at
* http://developer.catrobat.org/license_additional_term
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.catrobat.musicdroid.test.code;
import junit.framework.TestCase;
import org.catrobat.musicdroid.test.utils.Utils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
public class StringsTest extends TestCase {
private static final String[] LANGUAGES = { "English" }; //, "German", "Russian", "Romanian" };
private static final String[] LANGUAGE_SUFFIXES = { "" }; //, "-de", "-ru", "-ro" };
private static final String SOURCE_DIRECTORY = "../Musicdroid/src";
private static final String RESOURCES_DIRECTORY = "../Musicdroid/res";
private static final String ANDROID_MANIFEST = "../Musicdroid/AndroidManifest.xml";
private static final String XML_STRING_PREFIX = "@string/";
private List<File> getStringFiles() throws IOException {
List<File> stringFiles = new ArrayList<File>();
for (String languageSuffix : LANGUAGE_SUFFIXES) {
File stringFile = new File("../Musicdroid/res/values" + languageSuffix + "/strings.xml");
assertNotNull("File is null: " + stringFile.getCanonicalPath(), stringFile);
if (!stringFile.exists() || !stringFile.canRead()) {
fail("Could not read file " + stringFile.getCanonicalPath());
}
stringFiles.add(stringFile);
}
return stringFiles;
}
private NodeList getStrings(File file) throws SAXException, IOException, ParserConfigurationException {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
Document document = documentBuilder.parse(file);
document.getDocumentElement().normalize();
return document.getElementsByTagName("string");
}
private List<String> getAllStringNames() throws SAXException, IOException, ParserConfigurationException {
List<String> allStringNames = new ArrayList<String>();
List<File> stringFiles = getStringFiles();
for (int i = 0; i < LANGUAGES.length; i++) {
File stringFile = stringFiles.get(i);
NodeList strings = getStrings(stringFile);
for (int nodeIndex = 0; nodeIndex < strings.getLength(); nodeIndex++) {
Node node = strings.item(nodeIndex);
assertTrue("Node is not an element: " + node.toString(), node.getNodeType() == Node.ELEMENT_NODE);
Element element = (Element) node;
String elementName = element.getAttribute("name");
if (!allStringNames.contains(elementName)) {
allStringNames.add(elementName);
}
}
}
return allStringNames;
}
private Map<String, List<String>> getStringNamesPerLanguage() throws SAXException, IOException,
ParserConfigurationException {
Map<String, List<String>> languageStrings = new HashMap<String, List<String>>();
List<File> stringFiles = getStringFiles();
for (String language : LANGUAGES) {
List<String> stringNames = new ArrayList<String>();
languageStrings.put(language, stringNames);
}
for (int i = 0; i < LANGUAGES.length; i++) {
File stringFile = stringFiles.get(i);
List<String> languageStringNames = languageStrings.get(LANGUAGES[i]);
NodeList strings = getStrings(stringFile);
for (int nodeIndex = 0; nodeIndex < strings.getLength(); nodeIndex++) {
Node node = strings.item(nodeIndex);
assertTrue("Node is not an element: " + node.toString(), node.getNodeType() == Node.ELEMENT_NODE);
Element element = (Element) node;
String elementName = element.getAttribute("name");
languageStringNames.add(elementName);
}
}
return languageStrings;
}
public void testStringTranslations() throws IOException, ParserConfigurationException, SAXException {
boolean missingStrings = false;
StringBuilder errorMessage = new StringBuilder();
List<String> allStringNames = getAllStringNames(); // Using a List instead of a set to preserve order
Map<String, List<String>> languageStrings = getStringNamesPerLanguage();
for (String stringName : allStringNames) {
for (String lanugage : LANGUAGES) {
List<String> languageStringNames = languageStrings.get(lanugage);
if (!languageStringNames.contains(stringName)) {
missingStrings = true;
errorMessage.append("\nString with name " + stringName + " is missing in " + lanugage);
}
}
}
assertFalse("There are untranslated Strings:" + errorMessage.toString(), missingStrings);
}
public void testUnusedStrings() throws SAXException, IOException, ParserConfigurationException {
String errorMessage = "";
boolean unusedStringsFound = false;
String javaSourceCodeBuilder = "";
File directory = new File(SOURCE_DIRECTORY);
assertTrue("Couldn't find directory: " + SOURCE_DIRECTORY, directory.exists() && directory.isDirectory());
assertTrue("Couldn't read directory: " + SOURCE_DIRECTORY, directory.canRead());
List<File> filesToCheck = Utils.getFilesFromDirectoryByExtension(directory, ".java");
for (File file : filesToCheck) {
BufferedReader reader = new BufferedReader(new FileReader(file));
String currentLine = null;
while ((currentLine = reader.readLine()) != null) {
javaSourceCodeBuilder += currentLine + "\n";
}
reader.close();
}
String javaSourceCode = javaSourceCodeBuilder;
String xmlSourceCodeBuilder = "";
directory = new File(RESOURCES_DIRECTORY);
assertTrue("Couldn't find directory: " + RESOURCES_DIRECTORY, directory.exists() && directory.isDirectory());
assertTrue("Couldn't read directory: " + RESOURCES_DIRECTORY, directory.canRead());
filesToCheck = Utils.getFilesFromDirectoryByExtension(directory, ".xml");
filesToCheck.add(new File(ANDROID_MANIFEST));
for (File file : filesToCheck) {
if (!file.getName().equals("strings.xml")) {
BufferedReader reader = new BufferedReader(new FileReader(file));
String currentLine = null;
while ((currentLine = reader.readLine()) != null) {
xmlSourceCodeBuilder += currentLine + "\n";
}
reader.close();
}
}
String xmlSourceCode = xmlSourceCodeBuilder;
List<String> allStringNames = getAllStringNames(); // Using a List instead of a set to preserve order
Map<String, List<String>> languageStrings = getStringNamesPerLanguage();
for (String string : allStringNames) {
Pattern javaReferencePattern = Pattern.compile("R\\.string\\." + string + "[^\\w]");
Pattern xmlReferencePattern = Pattern.compile("@string/" + string + "[^\\w]");
if (!javaReferencePattern.matcher(javaSourceCode).find()
&& !xmlReferencePattern.matcher(xmlSourceCode).find()) {
unusedStringsFound = true;
errorMessage += "\nString with name " + string + " is unused (found in ";
for (String language : LANGUAGES) {
List<String> languageStringNames = languageStrings.get(language);
if (languageStringNames.contains(string)) {
errorMessage += language + ", ";
}
}
StringBuffer buffer = new StringBuffer(errorMessage);
errorMessage = buffer.replace(errorMessage.length() - 2, errorMessage.length(), ").").toString();
}
}
assertFalse("Unused string resources were found:" + errorMessage, unusedStringsFound);
}
private List<File> getLayoutXmlFiles() throws IOException {
List<File> layoutFiles = new ArrayList<File>();
File layoutDir = new File("../Musicdroid/res/layout/");
for (File file : layoutDir.listFiles()) {
if (file.getName().endsWith(".xml")) {
layoutFiles.add(file);
}
}
return layoutFiles;
}
private List<String> getUsedStringsFromLayoutFile(File layoutFile) throws IOException {
BufferedReader reader = new BufferedReader(new FileReader(layoutFile));
List<String> usedStrings = new ArrayList<String>();
String currentLine = null;
while ((currentLine = reader.readLine()) != null) {
String[] split = currentLine.split("\"");
// the number of " per line must be even
if (split.length <= 1) {
continue;
}
for (int i = 1; i < split.length; i += 2) {
if (split[i].startsWith(XML_STRING_PREFIX)) {
String stringToAdd = split[i].substring(XML_STRING_PREFIX.length());
usedStrings.add(stringToAdd + "|" + layoutFile.getName());
}
}
}
reader.close();
return usedStrings;
}
private List<String> getAllStringsUsedInLayoutXMLs() throws SAXException, IOException, ParserConfigurationException {
List<String> allStringNames = new ArrayList<String>();
List<File> layoutFiles = getLayoutXmlFiles();
for (File layoutFile : layoutFiles) {
List<String> usedStrings = getUsedStringsFromLayoutFile(layoutFile);
allStringNames.addAll(usedStrings);
}
return allStringNames;
}
public void testMissingStrings() throws SAXException, IOException, ParserConfigurationException {
String errorMessage = "";
boolean missingStringsFound = false;
String stringXmlSourceCodeBuilder = "";
File defaultResDirectory = new File("../Musicdroid/res/values/");
for (File defaultStringFile : defaultResDirectory.listFiles()) {
BufferedReader reader = new BufferedReader(new FileReader(defaultStringFile));
String currentLine = null;
while ((currentLine = reader.readLine()) != null) {
stringXmlSourceCodeBuilder += currentLine + "\n";
}
reader.close();
}
String stringXmlSourceCode = stringXmlSourceCodeBuilder;
List<String> allStringsUsedInLayoutFiles = getAllStringsUsedInLayoutXMLs();
Set<String> missingStrings = new HashSet<String>();
for (String stringPairUsedInXml : allStringsUsedInLayoutFiles) {
String[] split = stringPairUsedInXml.split("\\|");
String stringUsedInXml = split[0];
String layoutFileName = split[1];
if (!stringXmlSourceCode.contains(stringUsedInXml)) {
missingStringsFound = true;
missingStrings.add(stringUsedInXml + " used in the file: " + layoutFileName);
}
}
for (String missing : missingStrings) {
errorMessage += "\nString with name " + missing + " is missing in the default resource folder.";
}
assertFalse("Missing string resources were found:" + errorMessage, missingStringsFound);
}
}