/*
* � Copyright IBM Corp. 2013
*
* 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.
*/
/*
* Author: Maire Kehoe (mkehoe@ie.ibm.com)
* Date: 18 Jul 2011
* TranslatableStringsTest.java
*/
package com.ibm.xsp.test.framework.registry.annotate;
import com.ibm.commons.util.StringUtil;
import com.ibm.xsp.registry.FacesDefinition;
import com.ibm.xsp.registry.FacesExtensibleNode;
import com.ibm.xsp.registry.FacesGroupDefinition;
import com.ibm.xsp.registry.FacesProperty;
import com.ibm.xsp.registry.FacesSharableRegistry;
import com.ibm.xsp.registry.RegistryUtil;
import com.ibm.xsp.test.framework.AbstractXspTest;
import com.ibm.xsp.test.framework.TestProject;
import com.ibm.xsp.test.framework.XspTestUtil;
import com.ibm.xsp.test.framework.registry.XspRegistryTestUtil;
import com.ibm.xsp.test.framework.registry.annotate.SpellCheckTest.DescriptionDisplayNameAnnotater;
import com.ibm.xsp.test.framework.setup.SkipFileContent;
/**
*
* @author Maire Kehoe (mkehoe@ie.ibm.com)
*/
public class TranslatableStringsTest extends AbstractXspTest {
@Override
public String getDescription() {
return "that <description> and <display-name>s are present";
}
private static final String[] s_allowNonCapitalCase = new String[]{
// Note, Bob Perron sent on a link describing the capitalization
// rules in English: http://grammartips.homestead.com/caps.html
// These are allowed as lower case, except where they are the
// first or last word.
// articles:
"the",
"a",
"an",
// conjunctions:
"for",
"and",
"nor",
"but",
"or",
"so",
// prepositions:
"on",
"of",
"in",
"for",
"at",
"by",
"with",
"from",
"as",
// particle:
"to",
};
private static final String[] s_allowCamelCased = new String[]{
"JavaScript",
"OneUI", // Lotus OneUI: http://www-12.lotus.com/ldd/doc/oneuidoc/docpublic/index.htm
"iNotes", // Lotus iNotes: http://www-01.ibm.com/software/lotus/products/inotes/
"iCal", // The iCal data format: http://en.wikipedia.org/wiki/ICalendar http://tools.ietf.org/html/rfc5545
};
public void testTranslatableStrings() throws Exception {
FacesSharableRegistry reg = TestProject.createRegistryWithAnnotater(
this, new DescriptionDisplayNameAnnotater());
String fails = "";
String[] allowNonCapitalCase = getAllowNonCapitalCase();
String[] allowCamelCased = getAllowCamelCased();
for (FacesDefinition def : TestProject.getLibDefinitions(reg, this)) {
fails += checkPresent(def, null);
fails += checkLabelReferences(def, null);
fails += checkDescrLength(def, null);
fails += checkNewlines(def, null);
fails += checkDisplayNameCapitalized(def, null, allowNonCapitalCase, allowCamelCased);
for (FacesProperty prop : RegistryUtil.getProperties(def, def.getDefinedInlinePropertyNames()) ) {
fails += checkPresent(prop, def);
fails += checkLabelReferences(prop, def);
fails += checkDescrLength(prop, def);
fails += checkNewlines(prop, def);
fails += checkDisplayNameCapitalized(prop, def, allowNonCapitalCase, allowCamelCased);
}
}
fails = XspTestUtil.removeMultilineFailSkips(fails,
SkipFileContent.concatSkips(getSkipFails(), this, "testTranslatableStrings"));
if( fails.length() > 0 ){
fail(XspTestUtil.getMultilineFailMessage(fails));
}
}
/**
* These are camelCased propert names (e.g. JavaScript, iNotes),
* which should always be written as provided here,
* that is, the rules about never using camel-case and
* capitalizing words do not apply.
* @return
*/
protected String[] getAllowCamelCased() {
return s_allowCamelCased;
}
/**
* @return
*/
protected String[] getAllowNonCapitalCase() {
return s_allowNonCapitalCase;
}
protected String[] getSkipFails() {
return StringUtil.EMPTY_STRING_ARRAY;
}
/**
* @param def
* @return
*/
private String checkPresent(FacesExtensibleNode def, FacesDefinition surroundingTag) {
if( def instanceof FacesGroupDefinition ){
// <group>s don't need description/display-name
return "";
}
String description = (String) def.getExtension("description");
String displayName = (String) def.getExtension("display-name");
if( null == description && null == displayName ){
return tagRef(def, surroundingTag)+" description and display-name missing\n";
}
if( null == description ){
return tagRef(def, surroundingTag)+" description missing. " +
"display-name=" + displayName+ "\n";
}
if( null == displayName ){
return tagRef(def, surroundingTag) + " display-name missing. "
+ "description=" + description + "\n";
}
return "";
}
private String checkLabelReferences(FacesExtensibleNode def, FacesDefinition surroundingTag){
String fails = "";
fails += checkLabelReferences(def, surroundingTag, "description");
fails += checkLabelReferences(def, surroundingTag, "display-name");
return fails;
}
private String checkLabelReferences(FacesExtensibleNode def, FacesDefinition surroundingTag, String toCheck){
String message = (String) def.getExtension(toCheck);
if( null == message ){
// will already have failed checkPresent
return "";
}
message = message.trim();
if( message.length() == 0 ){
// will already have failed checkPresent
return "";
}
if( '%' == message.charAt(0) || '%' == message.charAt(message.length()-1)){
if( message.startsWith("%/") ){
// See Label reference mechanism,
// %/ indicates referencing the description or display-name of a different property
// http://www-10.lotus.com/ldd/ddwiki.nsf/dx/XPages_configuration_file_format#label-reference-mechanism
return tagRef(def, surroundingTag) + " " + toCheck
+ " has an unresolved label reference: "
+ message + "\n";
}else{
// % indicates referencing a key in the corresponding _locale.properties file.
return tagRef(def, surroundingTag) + " " + toCheck
+ " has an unresolved properties file key: "
+ message + "\n";
}
}
return "";
}
private String checkDescrLength(FacesExtensibleNode def, FacesDefinition surroundingTag) {
String description = (String) def.getExtension("description");
if( null == description ){
// will already have failed checkPresent
return "";
}
String displayName = (String) def.getExtension("display-name");
int nameLen = (null == displayName? 0 : displayName.length()) ;
if( description.length() < nameLen + 5 ){
// TODO skipping "Triggered" descriptions as they're often short
if( description.startsWith("Triggered") ){
return "";
}
return tagRef(def, surroundingTag)
+ " description probably too short: "
+ description.replaceAll("\n", "\\\\n") + "\n";
}
return "";
}
private String checkDisplayNameCapitalized(FacesExtensibleNode def,
FacesDefinition surroundingTag, String[] allowNonCapitalCase, String[] allowCamelCased) {
String displayName = (String) def.getExtension("display-name");
if( null == displayName ){
// will already have failed checkPresent
return "";
}
String fails = "";
String[] words = displayName.split("[ /()+-]");
int i = 0;
for (String word : words) {
if( word.length() > 0 ){
char firstChar = word.charAt(0);
if( Character.isLowerCase(firstChar) ){
// the hard-coded list of camelCased words (like iNotes), are not required
// to capitalize the first letter.
boolean isWordAllowCamelCase = (-1 != XspTestUtil.indexOf(allowCamelCased, word));
if( !isWordAllowCamelCase ){
// These allowNonCapitalCase words allowed as lower case,
// except where they are the first or last word.
boolean isFirstOrLastWord = (i == 0) || (i == words.length - 1);
boolean requireCapitalize = isFirstOrLastWord
|| (-1 == XspTestUtil.indexOf(allowNonCapitalCase, word));
if( requireCapitalize ){
fails += tagRef(def, surroundingTag)
+ " display-name word not capitalized (" + word + ") in: "
+ displayName.replaceAll("\n", "\\\\n") + "\n";
}
}// end !isWordAllowCamelCase
}
if( isHasSubsequentUpperCaseChar(word) && !isAllUpperCase(word) ){
if( -1 == XspTestUtil.indexOf(allowCamelCased, word) ){
fails += tagRef(def, surroundingTag)
+ " display-name has camelCased word (" + word + ") in: "
+ displayName.replaceAll("\n", "\\\\n") + "\n";
}
}
}
i++;
}
return fails;
}
/**
* @param word
* @return
*/
private boolean isHasSubsequentUpperCaseChar(String word) {
int startAt = 1;
for (int i = startAt; i < word.length(); i++) {
char nthChar = word.charAt(i);
if( Character.isUpperCase(nthChar) ){
return true;
}
}
return false;
}
/**
* @param word
* @return
*/
private boolean isAllUpperCase(String word) {
for (int i = 0; i < word.length(); i++) {
char nthChar = word.charAt(i);
if( Character.isLowerCase(nthChar) ){
return false;
}
}
return true;
}
private String checkNewlines(FacesExtensibleNode def, FacesDefinition surroundingTag) {
String fails = "";
fails += checkMessageNewlines(def, surroundingTag, "description");
fails += checkMessageNewlines(def, surroundingTag, "display-name");
return fails;
}
private String checkMessageNewlines(FacesExtensibleNode def, FacesDefinition surroundingTag, String toCheck) {
String message = (String) def.getExtension(toCheck);
if( null == message ){
// will already have failed checkPresent
return "";
}
if( -1 != message.indexOf('\n') || -1 != message.indexOf('\t')){
return tagRef(def, surroundingTag) + " " + toCheck
+ " contains newlines or tabs: "
+ message.replaceAll("\n", "\\\\n").replaceAll("\t", "\\\\t") + "\n";
}
return "";
}
private String tagRef(FacesExtensibleNode node, FacesDefinition surroundingTag) {
if( node instanceof FacesDefinition ){
FacesDefinition def = (FacesDefinition) node;
String filePath = def.getFile().getFilePath();
return filePath+" "+XspRegistryTestUtil.descr(def);
}
if( node instanceof FacesProperty ){
String filePath = surroundingTag.getFile().getFilePath();
return filePath+" "+XspRegistryTestUtil.descr(surroundingTag, (FacesProperty)node);
}
throw new RuntimeException("Unhandled type "+node.getClass().getName());
// return null;
}
}