/*
* � Copyright IBM Corp. 2011, 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: 31 Mar 2011
* NamingConventionTest.java
*/
package com.ibm.xsp.test.framework.registry;
import java.util.ArrayList;
import java.util.Collections;
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 org.w3c.dom.Element;
import com.ibm.commons.util.StringUtil;
import com.ibm.xsp.registry.FacesComplexDefinition;
import com.ibm.xsp.registry.FacesComponentDefinition;
import com.ibm.xsp.registry.FacesDefinition;
import com.ibm.xsp.registry.FacesExtensibleNode;
import com.ibm.xsp.registry.FacesGroupDefinition;
import com.ibm.xsp.registry.FacesLibraryFragment;
import com.ibm.xsp.registry.FacesSharableRegistry;
import com.ibm.xsp.registry.RegistryUtil;
import com.ibm.xsp.registry.parse.ElementUtil;
import com.ibm.xsp.registry.parse.RegistryAnnotater;
import com.ibm.xsp.registry.parse.RegistryAnnotaterInfo;
import com.ibm.xsp.test.framework.AbstractXspTest;
import com.ibm.xsp.test.framework.ConfigUtil;
import com.ibm.xsp.test.framework.TestProject;
import com.ibm.xsp.test.framework.XspTestUtil;
import com.ibm.xsp.test.framework.setup.SkipFileContent;
/**
*
* @author Maire Kehoe (mkehoe@ie.ibm.com)
*/
public class NamingConventionTest extends AbstractXspTest {
protected static int SEV_FATAL = 1;
protected static int SEV_ERROR = 2;
protected static int SEV_WARNING = 3;
protected static int SEV_INFO = 4;
/**
* A low severity (high sev number) above the severity cutoff.
*/
protected static int SEV_IGNORE = 11;
/**
* Not the severity of any rule, only used as a cutoff
* for {@link #getRuleSeverityLevelCutoff()};
*/
protected static int SEV_ANYTHING = 10;
/**
*
* @author Maire Kehoe (mkehoe@ie.ibm.com)
*/
private final class NamingMetaDataAnnotater implements RegistryAnnotater {
public void annotate(RegistryAnnotaterInfo info,
FacesExtensibleNode node, Element elem) {
if (node instanceof FacesLibraryFragment) {
FacesLibraryFragment file = (FacesLibraryFragment) node;
// <faces-config-extension>
// <designer-extension>
// <control-subpackage-name>dojo.form</control-subpackage-name>
// </designer-extension>
// </faces-config-extension>
String controlSubpackageName = null;
outOfBothForLoops:
for (Element extensionContainer : ElementUtil.getChildren(elem, "faces-config-extension")) {
for (Element extensionBlock : ElementUtil.getChildren(extensionContainer, "designer-extension")) {
controlSubpackageName = ElementUtil.extractValue(extensionBlock, "control-subpackage-name");
if( null != controlSubpackageName ){
break outOfBothForLoops;
}
}
}
if( null != controlSubpackageName ){
file.setExtension("control-subpackage-name", controlSubpackageName);
}
}
}
}
private static String getControlSubpackageName(FacesLibraryFragment file){
return (String) file.getExtension("control-subpackage-name");
}
// To mark a skip as used, increase the Object[] length to 4.
private static final int skipUsedLength = 4;
@Override
public String getDescription() {
// note this only looks at the xsp-config contents, other tests
// will check that the actual control / complex-type constants/values
// match the xsp-config values.
return "that the controls in the library match the naming conventions";
}
private String[] _expectedPrefixes = new String[]{
// [0] package-name prefix
null, //"com.ibm.xsp",
// [1] abstract component package-name suffix: usually ".component"
".component",
// [2] tag component package-name suffix: usually ".component" or "component.xp."
// can end with ".+" or ".+", like ".component.+"
".component.xp",
// [3] abstract component short java-class prefix: usually "UI"
"UI",
// [4] tag component short java-class prefix: usually "Xsp"
"Xsp",
// [5] abstract component short java-class suffix: possibly "Ex" or "Ex2"
"",
// [6] tag component short java-class suffix: possibly "Ex" or "Ex2"
"",
// [7] abstract component-type short-name prefix: usually "UI"
"UI",
// [8] tag component-type short-name prefix: usually ""
"",
};
private Pattern camelCaseHeadDown;
private Pattern camelCaseHeadUp;
private Map<String, Integer> ruleSeverityOverrides;
// @Override
// protected String[][] getExtraConfig() {
// String[][] extraConfig = super.getExtraConfig();
// extraConfig = XspTestUtil.concat(extraConfig, new String[][]{
// // set these 2 configs to test all libs.
// {"target.library", null},
// {"target.all","true"},
// });
// return extraConfig;
// }
public void testNamingConventions() throws Exception {
// TODO Naming conventions for Complex-types
FacesSharableRegistry reg = TestProject.createRegistryWithAnnotater(this, new NamingMetaDataAnnotater());
String[] expectedPrefixes = getExpectedPrefixes();
String expectedPackagePrefix = expectedPrefixes[0];
if( null == expectedPackagePrefix ){
expectedPackagePrefix = ConfigUtil.getNamingConventionPackagePrefix(this);
if( StringUtil.isEmpty(expectedPackagePrefix) ){
throw new RuntimeException("Prefix not found in config.properties, "
+"like: NamingConvention.package.prefix=com.example.foo");
}
}
String expectedAbstractComponentPackageSuffix = expectedPrefixes[1];
String expectedTagComponentPackageSuffix = expectedPrefixes[2];
String expectedAbstractComponentShortClassPrefix = expectedPrefixes[3];
String expectedTagComponentShortClassPrefix = expectedPrefixes[4];
String expectedAbstractComponentShortClassSuffix = expectedPrefixes[5];
String expectedTagComponentShortClassSuffix = expectedPrefixes[6];
String expectedAbstractComponentShortTypePrefix = expectedPrefixes[7];
String expectedTagComponentShortTypePrefix = expectedPrefixes[8];
List<Object[]> ruleSeverityOverridesArr = getRuleSeverityOverrides();
ruleSeverityOverrides = toRuleSeverityOverrideMap(ruleSeverityOverridesArr);
List<Object[]> unusualTagness = getUnusualTagnessSkips();
List<Object[]> nonObviousTagName = getNonObviousTagNameSkips();
List<String> badRendererShortNamePrefixes = getBadRendererShortNamePrefixes();
List<ComplexTypeExpectedNaming> complexTypeExpectedNamings = parse(reg, getComplexTypeExpectedNamings());
List<ComplexTypeExpectedNaming> detectedComplexTypeBases = detectOtherComplexTypeBases(reg, TestProject.getLibComplexDefs(reg, this), complexTypeExpectedNamings);
int severityCutoff = getRuleSeverityLevelCutoff();
String fails = "";
boolean isRequireControlSubpackageName = isRequireControlSubpackageName();
Set<String> filePathsCheckedForControlSubPackage = isRequireControlSubpackageName? new HashSet<String>() : null;
List<FacesDefinition> defs = TestProject.getLibDefinitions(reg, this);
defs = filterDefinitions(defs);
for (FacesDefinition someDefType : defs ) {
if( someDefType instanceof FacesComponentDefinition ){
FacesComponentDefinition def = (FacesComponentDefinition) someDefType;
String packageName = def.getJavaClass().getPackage().getName();
boolean isUseTagNaming = def.isTag();
Object[] tagnessSkip = findUnusualTagnessSkip(unusualTagness, def.getJavaClass());
if( null != tagnessSkip ){
if( !(isUseTagNaming == ((Boolean)tagnessSkip[1]).booleanValue() ) ){
fails += path(def)+" Bad unusualTagness skip: isTag(" +isUseTagNaming+") != expectedIsTag(" +tagnessSkip[1]+")\n";
}
isUseTagNaming = (Boolean)tagnessSkip[2];
}
String expectedControlSubpackageName = getControlSubpackageName(def.getFile());
boolean isControlSubpackageInConfig = null != expectedControlSubpackageName;
if( isRequireControlSubpackageName && !isControlSubpackageInConfig){
String filePath = def.getFile().getFilePath();
if( ! filePathsCheckedForControlSubPackage.contains(filePath) ){
filePathsCheckedForControlSubPackage.add(filePath);
String[] parsedClass = parseToPrefixSubpackageAndShortName(expectedPackagePrefix, def.getJavaClass().getName());
String inferredSubpackage = parsedClass[1];
if( inferredSubpackage.equals("component") ){
inferredSubpackage = "";
}else if( inferredSubpackage.startsWith("component.") ){
inferredSubpackage = inferredSubpackage.substring("component.".length());
}
expectedControlSubpackageName = inferredSubpackage;
def.getFile().setExtension("control-subpackage-name", inferredSubpackage);
fails += def.getFile().getFilePath()
+ " Missing required <control-subpackage-name> "
+ "in config file, inferring subpackage [" +inferredSubpackage+ "]\n";
}
}
String expectedComponentPackageSuffix = isUseTagNaming? expectedTagComponentPackageSuffix : expectedAbstractComponentPackageSuffix;
if( ! packageName.startsWith(expectedPackagePrefix) ){
if( severityCutoff >= sev("1", SEV_ERROR) ){
fails += path(def)+"[Rule1] Bad package name "+def.getJavaClass().getName()+" does not begin with "+expectedPackagePrefix+"\n";
}
}else{
String actualSuffix = packageName.substring(expectedPackagePrefix.length());
if( isControlSubpackageInConfig ){
boolean isSuffixAllowFuzzyMatch = expectedComponentPackageSuffix.endsWith(".*") || expectedComponentPackageSuffix.endsWith(".+");
if (isSuffixAllowFuzzyMatch) {
// remove the fuzzy suffix, as this config file gives a control-subpackage-name
// remove trailing ".+" or ".*"
expectedComponentPackageSuffix = expectedComponentPackageSuffix.substring(0, expectedComponentPackageSuffix.length()-2);
}
expectedComponentPackageSuffix = expectedComponentPackageSuffix+"."+expectedControlSubpackageName;
if( !expectedComponentPackageSuffix.equals(actualSuffix) ){
String expectedPackageName = expectedPackagePrefix + expectedComponentPackageSuffix;
if( severityCutoff >= sev("2a", SEV_WARNING) ){
fails += path(def)+"[Rule2a] Bad component-class package name "+def.getJavaClass().getName()+" does not match "+expectedPackageName+"\n";
}
}
}else{
if( !isFuzzyMatch(expectedComponentPackageSuffix, actualSuffix) ){
String expectedPackageName = expectedPackagePrefix + expectedComponentPackageSuffix;
if( severityCutoff >= sev("2c", SEV_WARNING) ){
fails += path(def)+"[Rule2c] Bad component-class package name "+def.getJavaClass().getName()+" not in the form "+expectedPackageName+"\n";
}
}
}
}
String shortClassName = def.getJavaClass().getSimpleName();
String expectedShortClassPrefix = isUseTagNaming? expectedTagComponentShortClassPrefix : expectedAbstractComponentShortClassPrefix;
if( ! shortClassName.startsWith(expectedShortClassPrefix) ){
if( severityCutoff >= sev("3", SEV_ERROR) ){
fails += path(def)+"[Rule3] Bad component-class short name "+shortClassName+" does not begin with "+expectedShortClassPrefix+"\n";
}
}
String expectedShortClassSuffix = isUseTagNaming? expectedTagComponentShortClassSuffix : expectedAbstractComponentShortClassSuffix;
if( ! shortClassName.endsWith(expectedShortClassSuffix) ){
if( severityCutoff >= sev("4", SEV_WARNING) ){
fails += path(def)+"[Rule4] Bad component-class short name "+shortClassName+" does not end with "+expectedShortClassSuffix+"\n";
}
}
String shortNameFromClass = shortClassName;
if( expectedShortClassPrefix.length() > 0 && shortNameFromClass.startsWith(expectedShortClassPrefix) ){
shortNameFromClass = shortNameFromClass.substring(expectedShortClassPrefix.length());
}
if( expectedShortClassSuffix.length() > 0 && shortNameFromClass.endsWith(expectedShortClassSuffix) ){
int end = shortNameFromClass.length() - expectedShortClassSuffix.length();
shortNameFromClass = shortNameFromClass.substring(0, end);
}
String componentType = def.getComponentType();
String[] componentTypeParsed = parseToPrefixSubpackageAndShortName(expectedPackagePrefix, componentType);
String componentTypePrefixPackage = componentTypeParsed[0];
String componentTypeSubpackage = componentTypeParsed[1];
String componentTypeShortName = componentTypeParsed[2];
if( ! StringUtil.equals(componentTypePrefixPackage, expectedPackagePrefix) ){
if( severityCutoff >= sev("5a", SEV_ERROR) ){
fails += path(def) + "[Rule5a] Bad component-type " + componentType + " does not begin with common-prefix " + expectedPackagePrefix + ", has prefix-package \"" + componentTypePrefixPackage + "\"\n";
}
}
if( !StringUtil.isEmpty(expectedComponentPackageSuffix) ){
if( componentTypeSubpackage.startsWith(expectedComponentPackageSuffix+".") ){
// component-type contains .component.
if( severityCutoff >= sev("6a", SEV_WARNING) ){
fails += path(def)+"[Rule6a] Bad component-type "+componentType+" should not have the class-only subpackage "+("."+expectedComponentPackageSuffix+".")+"\n";
}
}
}
// expectedControlSubpackageName is "xp" or "dojo.form"
if( !StringUtil.equals(componentTypeSubpackage, expectedControlSubpackageName) ){
// fail.
if( StringUtil.isEmpty(expectedControlSubpackageName) ){
// expect no subpackage
if( severityCutoff >= sev("6b", SEV_WARNING) ){
fails += path(def) + "[Rule6b] Bad component-type " + componentType + " has unexpected subpackage " + componentTypeSubpackage + "\n";
}
}else if( componentTypeSubpackage.startsWith(expectedControlSubpackageName+".") ){
// starts with xp.something
String extraSubpackage = componentTypeSubpackage
.substring(expectedControlSubpackageName
.length() + 1);
if( severityCutoff >= sev("6e", SEV_WARNING) ){
fails += path(def) + "[Rule6e] Bad component-type " + componentType + " has extra subpackage " + extraSubpackage + "\n";
}
}else{
// starts with foo, not xp
if( severityCutoff >= sev("6d", SEV_ERROR) ){
fails += path(def) + "[Rule6d] Bad component-type " + componentType + ", expect subpackage [" + expectedControlSubpackageName + "], was [" + componentTypeSubpackage + "]\n";
}
}
}
// expectedTagComponentShortTypePrefix be UI or "" (is "" for the XPages runtime)
String expectedShortTypePrefix = isUseTagNaming? expectedTagComponentShortTypePrefix : expectedAbstractComponentShortTypePrefix;
if( !StringUtil.isEmpty(expectedShortTypePrefix) ){
if( !componentTypeShortName.startsWith(expectedShortTypePrefix) ){
if( severityCutoff >= sev("6f", SEV_WARNING) ){
fails += path(def)+"[Rule6f] Bad component-type "+componentType+" missing short type prefix \""+expectedShortTypePrefix+"\"\n";
}
}
}
if( !StringUtil.isEmpty(componentTypeShortName) && !Character.isUpperCase(componentTypeShortName.charAt(0)) ){
if( severityCutoff >= sev("6h", SEV_ERROR) ){
fails += path(def)+"[Rule6h] Bad component-type "+componentType+" short name not Capitalized "+componentTypeShortName+"\n";
}
}
if (!StringUtil.isEmpty(expectedShortClassPrefix)
&& !StringUtil.equals(expectedShortClassPrefix,
expectedShortTypePrefix)) {
if( componentTypeShortName.startsWith(expectedShortClassPrefix) ){
if( severityCutoff >= sev("7a", SEV_ERROR) ){
fails += path(def) + "[Rule7a] component-type short name "
+ "should not have class short name prefix "
+ expectedShortClassPrefix + " : "
+ "component-type " + componentType + "["
+ componentTypeShortName + "]\n";
}
}
}
String shortNameFromTypeWithoutPrefix = componentTypeShortName;
if( shortNameFromTypeWithoutPrefix.startsWith(expectedShortTypePrefix) ){
shortNameFromTypeWithoutPrefix = shortNameFromTypeWithoutPrefix.substring(expectedShortTypePrefix.length());
}
if( shortNameFromTypeWithoutPrefix.startsWith(expectedShortClassPrefix) ){
shortNameFromTypeWithoutPrefix = shortNameFromTypeWithoutPrefix.substring(expectedShortClassPrefix.length());
}
if( !shortNameFromClass.equals(shortNameFromTypeWithoutPrefix) ){
if( severityCutoff >= sev("7b", SEV_WARNING) ){
fails += path(def)+"[Rule7b] Class and type short names do not match: "
+"component-class " +def.getJavaClass().getName()
+"[" +shortNameFromClass+"] "
+"!= "
+"component-type "+componentType
+"[" +shortNameFromTypeWithoutPrefix+"]\n";
}
}
String tagName = def.isTag()? def.getTagName() : null;
String shortNameFromTagName = null;
if( null != tagName ){
Pattern regExp = compile("[a-z][a-zA-Z]*");
if( ! regExp.matcher(tagName).matches() ){
if( severityCutoff >= sev("8", SEV_ERROR) ){
fails += path(def)+"[Rule8] Bad tag-name "+tagName+", expected camelCase, does not match "+regExp.pattern()+"\n";
}
}
shortNameFromTagName = Character.toUpperCase(tagName.charAt(0))+tagName.substring(1);
boolean expectShortClassMatchTagName = true;
Object[] tagNameSkip = findNonObviousTagNameSkip(nonObviousTagName, def.getJavaClass());
if( null != tagNameSkip ){
String expectedTagName = (String) tagNameSkip[1];
if( !expectedTagName.equals(tagName) ){
fails += path(def)+" Bad nonObviousTagName skip: "
+ "actualTagName("+tagName+") "
+ "!= expectedTagName(" +expectedTagName+")\n";
}
if( shortNameFromClass.equals(shortNameFromTagName) ){
fails += path(def) + " Bad nonObviousTagName skip: "
+ "expect unmatched class and tag-name short names, but actually match - "
+ "component-class " + def.getJavaClass().getName()
+ "[" + shortNameFromClass + "], "
+ "tag-name " + tagName
+ "[" + shortNameFromTagName + "]\n";
}
}
if( null == tagNameSkip && !shortNameFromClass.equals(shortNameFromTagName) ){
if( expectShortClassMatchTagName ){
if( severityCutoff >= sev("9", SEV_INFO) ){
fails += path(def)
+ "[Rule9] Class and tag-name short names do not match: "
+ "component-class "
+ def.getJavaClass().getName() + "["
+ shortNameFromClass + "] " + "!= "
+ "tag-name " + tagName + "["
+ shortNameFromTagName + "]\n";
}
}
}
}// end ( null != tagName )
String componentFamily = def.getComponentFamily();
if( null != componentFamily && !isInheritedComponentFamily(def) ){
String[] componentFamilyParsed = parseToPrefixSubpackageAndShortName(expectedPackagePrefix, componentFamily);
String componentFamilyPrefixPackage = componentFamilyParsed[0];
// String componentFamilySubpackage = componentFamilyParsed[1];
// String componentFamilyShortName = componentFamilyParsed[2];
String expectedFamilyPrefix = expectedPackagePrefix+".";
if( ! StringUtil.equals(componentFamilyPrefixPackage, expectedPackagePrefix) ){
if( severityCutoff >= sev("10a", SEV_ERROR) ){
fails += path(def)+"[Rule10a] Bad component-family "+componentFamily+" does not have the common-prefix "+expectedFamilyPrefix+"\n";
}
}
if( ! componentFamily.equals(componentType) ){
if( severityCutoff >= sev("10b", SEV_WARNING) ){
fails += path(def)+"[Rule10b] Bad (non-inherited) component-family "+componentFamily+" does not match component-type (" +componentType+")\n";
}
}
String shortNameFromFamily = componentFamily.substring(componentFamily.lastIndexOf('.')+1);
// String expectedShortNamePrefix = isUseTagNaming? expectedTagComponentShortTypePrefix : expectedAbstractComponentShortTypePrefix;
// if( shortNameFromFamily.startsWith(expectedShortNamePrefix) ){
// shortNameFromFamily = shortNameFromFamily.substring(shortNameFromFamily.length());
// }
if( !shortNameFromClass.equals(shortNameFromFamily) ){
if( severityCutoff >= sev("11", SEV_WARNING) ){
fails += path(def)+"[Rule11] Class and (non-inherited)family short names do not match: "
+"component-family "+componentFamily
+"[" +shortNameFromFamily+"] "
+"!= "
+"component-class " +def.getJavaClass().getName()
+"[" +shortNameFromClass+"] "
+"\n";
}
}
}
String rendererType = def.getRendererType();
if( null != rendererType && !isInheritedRendererType(def) ){
String[] rendererTypeParsed = parseToPrefixSubpackageAndShortName(expectedPackagePrefix, rendererType);
String rendererTypePrefixPackage = rendererTypeParsed[0];
String rendererTypeSubpackage = rendererTypeParsed[1];
String rendererTypeShortName = rendererTypeParsed[2];
if( ! StringUtil.equals(rendererTypePrefixPackage, expectedPackagePrefix) ){
if( severityCutoff >= sev("12a", SEV_ERROR) ){
fails += path(def)+"[Rule12a] Bad renderer-type "+rendererType+" does not have the common-prefix "+expectedPackagePrefix+"\n";
}
}
// renderer-type short names must match the regular expression "[A-Z][a-zA-Z]*"
Pattern regExp = compile("[A-Z][a-zA-Z]*");
if( ! regExp.matcher(rendererTypeShortName).matches() ){
if( severityCutoff >= sev("12c", SEV_ERROR) ){
fails += path(def)+"[Rule12c] Bad renderer-type "+rendererType+", expected CamelCase short name, " +rendererTypeShortName+" does not match "+regExp.pattern()+"\n";
}
}
if( rendererTypeShortName.endsWith("Renderer") ){
if( severityCutoff >= sev("12d", SEV_ERROR) ){
fails += path(def)+"[Rule12d] Bad renderer-type "+rendererType+", should not end with Renderer\n";
}
}
for (String badPrefix : badRendererShortNamePrefixes) {
// prevent OneUI prefix.
if( rendererTypeShortName.startsWith(badPrefix) ){
if( severityCutoff >= sev("12e", SEV_WARNING) ){
fails += path(def)+"[Rule12e] Bad renderer-type "+rendererType+", should not begin with " +badPrefix +"\n";
}
continue;
}
}
if( ! StringUtil.isEmpty(rendererTypeSubpackage) ){
if( rendererTypeSubpackage.startsWith("renderkit")){
// renderer-type contains .renderkit.
if( severityCutoff >= sev("12f", SEV_WARNING) ){
fails += path(def)+"[Rule12f] Bad renderer-type "+rendererType+" should not have the class-only subpackage renderkit\n";
}
}
}
// expect subpackages
// if( !StringUtil.isEmpty(rendererTypeSubpackage) ){
// fails += path(def)+"[Rule12b] Bad renderer-type "+rendererType+" has extra '.' separator(s), not in the expected format: "+expectedPackagePrefix+".ControlName\n";
// }
// expectedControlSubpackageName is "xp" or "dojo.form"
if( !StringUtil.equals(rendererTypeSubpackage, expectedControlSubpackageName) ){
// fail.
if( StringUtil.isEmpty(expectedControlSubpackageName) ){
// expect no subpackage
if( severityCutoff >= sev("12g", SEV_WARNING) ){
fails += path(def) + "[Rule12g] Bad renderer-type " + rendererType + " has unexpected subpackage " + rendererTypeSubpackage + "\n";
}
}else if( rendererTypeSubpackage.startsWith(expectedControlSubpackageName+".") ){
// starts with xp.something
String extraSubpackage = rendererTypeSubpackage
.substring(expectedControlSubpackageName
.length() + 1);
if( severityCutoff >= sev("12h", SEV_WARNING) ){
fails += path(def) + "[Rule12h] Bad renderer-type " + rendererType + " has extra subpackage " + extraSubpackage + "\n";
}
}else{
// starts with foo, not xp
if( severityCutoff >= sev("12i", SEV_ERROR) ){
fails += path(def) + "[Rule12i] Bad renderer-type " + rendererType + ", expect subpackage [" + expectedControlSubpackageName + "], was [" + rendererTypeSubpackage + "]\n";
}
}
}
// note, not checking the renderer-type against the class short-name
// as the renderer-types are often a new invention.
}
}
else if( someDefType instanceof FacesComplexDefinition ){
FacesComplexDefinition def = (FacesComplexDefinition) someDefType;
ComplexTypeExpectedNaming naming = findMatch(reg, complexTypeExpectedNamings, def);
if( null == naming ){
naming = findMatch(reg, detectedComplexTypeBases, def);
if( null != naming ){
// multiple rules depend on the detected naming subpackage.
// Only report the problem if some of those rules
// are severer than the cutoff
boolean reportSubpackageProblems = false
|| (severityCutoff >= sev("13b", SEV_WARNING))
|| (severityCutoff >= sev("13c", SEV_WARNING))
|| (severityCutoff >= sev("13e", SEV_WARNING))
|| false;
// need to update the JUnit test, as this detected base
// should be in the complexTypeExpectedNamings list
if( naming.origin == ComplexTypeExpectedNaming.DETECTED_IN_LIBRARY ){
if( naming.baseComplexDef == def ){
if( reportSubpackageProblems ){
fails += path(def)+ " Need to update JUnit test, "
+"this baseComplexType not in getComplexTypeExpectedNamings(), "
+"using detected subpackage "+naming.subpackage+" \n";
}
}else{
// will add the fail when iterating past that def.
}
}else{ // DETECTED_IN_DEPENDS
if( reportSubpackageProblems ){
fails += path(def)+ " Need to update JUnit test, "
+ "this complex-type depends an undeclared baseComplexType ("
+ path(naming.baseComplexDef)
+ ") not in getComplexTypeExpectedNamings(), "
+ "using detected subpackage "+naming.subpackage+" \n";
}
}
}
}
String complexClassName = def.getJavaClass().getName();
String[] complexClassParsed = parseToPrefixSubpackageAndShortName(expectedPackagePrefix, complexClassName);
String complexClassPrefixPackage = complexClassParsed[0];
String complexClassSubpackage = complexClassParsed[1];
String complexClassShortName = complexClassParsed[2];
if( !StringUtil.equals(complexClassPrefixPackage, expectedPackagePrefix) ){
if( severityCutoff >= sev("13a", SEV_ERROR) ){
fails += path(def)+"[Rule13a] Bad complex-class package name "+def.getJavaClass().getName()+" does not begin with "+expectedPackagePrefix+"\n";
}
}
if( null != naming ){
String expectedSubpackage = naming.subpackage;
if( StringUtil.equals(complexClassSubpackage, expectedSubpackage) ){
// all is good
}else if( null != complexClassSubpackage && complexClassSubpackage.startsWith(expectedSubpackage+".") ){
// extra subpackage, instead of .resource is .resource.something
if( severityCutoff >= sev("13b", SEV_WARNING) ){
fails += path(def) + "[Rule13b] Bad complex-class package name " + def.getJavaClass().getName()
+ " has extra subpackage, expected [" +expectedSubpackage
+ "] but was [" +complexClassSubpackage+ "]\n";
}
}else{
// !=
if( severityCutoff >= sev("13c", SEV_WARNING) ){
fails += path(def) + "[Rule13c] Bad complex-class package name " + def.getJavaClass().getName()
+ ", expect subpackage [" + expectedSubpackage + "], was [" + complexClassSubpackage + "]\n";
}
}
}
// complex-class short names must match the regular expression "[A-Z][a-zA-Z]*"
Pattern regExp = compile("[A-Z][a-zA-Z]*");
if( ! regExp.matcher(complexClassShortName).matches() ){
if( severityCutoff >= sev("13d", SEV_ERROR) ){
fails += path(def)+"[Rule13d] Bad complex-class "+complexClassName+", expected CamelCase short name, " +complexClassShortName+" does not match "+regExp.pattern()+"\n";
}
}
if( null != naming ){
String expectedNameSuffix = naming.nameSuffix;
if( !complexClassShortName.endsWith(expectedNameSuffix) ){
if( severityCutoff >= sev("13e", SEV_WARNING) ){
fails += path(def)+"[Rule13e] Bad complex-class "+complexClassName+", short name [" +complexClassShortName+"] does not have suffix ["+expectedNameSuffix+"]\n";
}
}
}
String complexId = def.getComplexId();
if( ! StringUtil.equals(complexId, complexClassName) ){
if( severityCutoff >= sev("14a", SEV_WARNING) ){
fails += path(def)+"[Rule14a] Bad complex-id "+complexId+", does not match complex-class [" +complexClassName+"]\n";
}
}
if( (severityCutoff >= sev("14b", SEV_IGNORE)) || (severityCutoff >= sev("14c", SEV_IGNORE)) ){
// There's an alternative to Rule14a, where instead you use Rule14b and Rule14c,
// which are not normally tested (use getRuleSeverityOverrides to enable those and disable Rule14a).
String classPackage = def.getJavaClass().getPackage().getName();
if( def.isTag() ){
String expectedTagComplexId = classPackage+"."+def.getTagName();
if( ! StringUtil.equals(complexId, expectedTagComplexId) ){
if( severityCutoff >= sev("14b", SEV_IGNORE) ){
fails += path(def)+"[Rule14b] Bad tag complex-id "+complexId+", does not match expected [" +expectedTagComplexId+"]\n";
}
}
}else{ // !def.isTag
String classShortName = complexClassName.substring(complexClassName.lastIndexOf('.')+1);
String lowerCamelCaseShortName = Character.toLowerCase(classShortName.charAt(0))+classShortName.substring(1);
String expectedAbstractComplexId = lowerCamelCaseShortName+"Interface";
if( ! StringUtil.equals(complexId, expectedAbstractComplexId) ){
if( severityCutoff >= sev("14c", SEV_IGNORE) ){
fails += path(def)+"[Rule14c] Bad abstract complex-id "+complexId+", does not match expected [" +expectedAbstractComplexId+"]\n";
}
}
}
}
}
else if( someDefType instanceof FacesGroupDefinition ){
FacesGroupDefinition def = (FacesGroupDefinition) someDefType;
String groupType = def.getGroupType();
String[] complexClassParsed = parseToPrefixSubpackageAndShortName(expectedPackagePrefix, groupType);
String groupTypePrefixPackage = complexClassParsed[0];
String groupTypeSubpackage = complexClassParsed[1];
String groupTypeShortName = complexClassParsed[2];
if( "group".equals(groupTypeShortName) ){
if( severityCutoff >= sev("15c", SEV_WARNING) ){
fails += path(def) + "[Rule15c] Bad group-type short name [group] in " + groupType
+ ", expect package name like " +expectedPackagePrefix+".group.*"
+", with the group purpose as the short name.\n";
}
groupTypeSubpackage += ".group";
groupTypeShortName = "";
}
if( !StringUtil.equals(groupTypePrefixPackage, expectedPackagePrefix) ){
if( severityCutoff >= sev("15a", SEV_WARNING) ){
fails += path(def)+"[Rule15a] Bad group-type package name "+groupType+" does not begin with "+expectedPackagePrefix+"\n";
}
}
String expectedSubpackage = "group";
if( StringUtil.equals(groupTypeSubpackage, expectedSubpackage) ){
// all is good
}else if( null != groupTypeSubpackage && groupTypeSubpackage.startsWith(expectedSubpackage+".") ){
// extra subpackage, instead of .resource is .resource.something
if( severityCutoff >= sev("15b", SEV_WARNING) ){
fails += path(def) + "[Rule15b] Bad group-type package name " + groupType
+ " has extra subpackage, expected [" +expectedSubpackage
+ "] but was [" +groupTypeSubpackage+ "]\n";
}
}else{
// !=
if( severityCutoff >= sev("15c", SEV_WARNING) ){
fails += path(def) + "[Rule15c] Bad group-type package name " + groupType
+ ", expect subpackage [" + expectedSubpackage + "], was [" + groupTypeSubpackage + "]\n";
}
}
}
fails += moreRules(someDefType, severityCutoff, expectedPrefixes);
}
fails = XspTestUtil.removeMultilineFailSkips(fails,
SkipFileContent.concatSkips(getSkips(), this, "testNamingConventions"));
for (Object[] skip : unusualTagness) {
if( !isSkipUsed(skip) ){
fails += "Unused unusualTagness skip: " +((Class<?>)skip[0]).getName()
+" actualIsTag=" +skip[1]+" actualNamedAsTag="+skip[2]+"\n";
}
}
for (Object[] skip : nonObviousTagName) {
if( !isSkipUsed(skip) ){
fails += "Unused nonObviousTagName skip: "
+ ((Class<?>) skip[0]).getName()
+ " tagName="+ skip[1] + "\n";
}
}
if( fails.length() > 0 ){
fail( XspTestUtil.getMultilineFailMessage(fails));
}
}
/**
* Available to override in subclasses.
*/
protected String moreRules(FacesDefinition someDefType, int severityCutoff,
String[] expectedPrefixes) {
return "";
}
/**
* @param ruleSeverityOverridesArr
* @return
*/
private Map<String, Integer> toRuleSeverityOverrideMap(List<Object[]> ruleSeverityOverridesArr) {
if( ruleSeverityOverridesArr.isEmpty() ){
return Collections.emptyMap();
}
Map<String, Integer> overrides = new HashMap<String, Integer>();
for (Object[] override : ruleSeverityOverridesArr) {
String rule = (String) override[0];
Integer newSeverity = (Integer) override[1];
overrides.put(rule, newSeverity);
}
return overrides;
}
/**
* @param complexTypeExpectedNamings
* @param def
* @return
*/
private ComplexTypeExpectedNaming findMatch(FacesSharableRegistry reg,
List<ComplexTypeExpectedNaming> complexTypeExpectedNamings,
FacesComplexDefinition def) {
for (ComplexTypeExpectedNaming naming : complexTypeExpectedNamings) {
if( naming.isMatch(reg, def) ){
return naming;
}
}
return null;
}
/**
* @param reg
* @param libComplexDefs
* @param complexTypeExpectedNamings
* @return
*/
private List<ComplexTypeExpectedNaming> detectOtherComplexTypeBases(
FacesSharableRegistry reg, List<FacesComplexDefinition> libComplexDefs,
List<ComplexTypeExpectedNaming> complexTypeExpectedNamings) {
if( libComplexDefs.isEmpty() ){
return new ArrayList<ComplexTypeExpectedNaming>();
}
List<FacesComplexDefinition> knownSoFar = new ArrayList<FacesComplexDefinition>();
for (ComplexTypeExpectedNaming naming : complexTypeExpectedNamings) {
knownSoFar.add( naming.baseComplexDef );
}
List<ComplexTypeExpectedNaming> detected = new ArrayList<ComplexTypeExpectedNaming>();
for (FacesComplexDefinition def : libComplexDefs) {
if( def.getParent() == null || "com.ibm.xsp.BaseComplexType".equals(def.getParent().getId()) ){
continue;
}
boolean isKnownBase = false;
FacesComplexDefinition unknownBase = null;
for (FacesComplexDefinition ancestor = RegistryUtil.getComplexParent(def);
ancestor != null;
ancestor = RegistryUtil.getComplexParent(ancestor) ) {
if( knownSoFar.contains(ancestor) ){
isKnownBase = true;
break;
}
if (null == ancestor.getParent()
|| "com.ibm.xsp.BaseComplexType".equals(ancestor.getParent().getId()) ) {
unknownBase = ancestor;
break;
}
}
if( ! isKnownBase ){
boolean isBaseFromLibrary = libComplexDefs.contains(unknownBase);
int origin = isBaseFromLibrary? ComplexTypeExpectedNaming.DETECTED_IN_LIBRARY : ComplexTypeExpectedNaming.DETECTED_IN_DEPENDS;
detected.add(new ComplexTypeExpectedNaming(reg, unknownBase, origin));
knownSoFar.add(unknownBase);
}
}
return detected;
}
private Pattern compile(String regExp){
String camelCaseHeadDownRegExp = "[a-z][a-zA-Z]*";
if( camelCaseHeadDownRegExp.equals(regExp) ){
if( null == camelCaseHeadDown ){
camelCaseHeadDown = Pattern.compile(camelCaseHeadDownRegExp);
}
return camelCaseHeadDown;
}
String camelCaseHeadUpRegExp = "[A-Z][a-zA-Z]*";
if( camelCaseHeadUpRegExp.equals(regExp) ){
if( null == camelCaseHeadUp ){
camelCaseHeadUp = Pattern.compile(camelCaseHeadUpRegExp);
}
return camelCaseHeadUp;
}
throw new RuntimeException("Need to update junit test to handle different regular expressions, this one is not handled: "+regExp);
}
protected List<String> getBadRendererShortNamePrefixes(){
return new ArrayList<String>();
}
/**
* {}
* @return
*/
protected List<Object[]> getComplexTypeExpectedNamings(){
return new ArrayList<Object[]>();
}
private List<ComplexTypeExpectedNaming> parse(FacesSharableRegistry reg, List<Object[]> list){
List<ComplexTypeExpectedNaming> output = new ArrayList<ComplexTypeExpectedNaming>();
for (Object[] item : list) {
String subpackage = (String) item[1];
String nameSuffix = (String) item[2];
if( item[0] instanceof String){
String baseComplexId = (String) item[0];
output.add(new ComplexTypeExpectedNaming(reg, baseComplexId, subpackage, nameSuffix));
}else{
Class<?> baseComplexClass = (Class<?>) item[0];
output.add(new ComplexTypeExpectedNaming(reg, baseComplexClass, subpackage, nameSuffix));
}
}
return output;
}
private static class ComplexTypeExpectedNaming{
public static final int DETECTED_IN_LIBRARY = 1;
public static final int DETECTED_IN_DEPENDS = 2;
public static final int SPECIFIED_IN_SUBCLASS = 3;
public final FacesComplexDefinition baseComplexDef;
public final Class<?> baseComplexClass;
public final String subpackage;
public final String nameSuffix;
public final int origin;
public List<FacesDefinition> substitutableDefs;
public ComplexTypeExpectedNaming(FacesSharableRegistry reg,
Class<?> baseComplexClass, String subpackage, String nameSuffix) {
super();
this.baseComplexDef = RegistryUtil.getFirstComplexDefinition(reg, baseComplexClass);
if( null == baseComplexDef ){
throw new IllegalArgumentException("No complex-type for class: "+baseComplexClass);
}
this.baseComplexClass = baseComplexClass;
this.subpackage = subpackage;
this.origin = SPECIFIED_IN_SUBCLASS;
this.nameSuffix = nameSuffix;
}
public ComplexTypeExpectedNaming(FacesSharableRegistry reg, String baseComplexId, String subpackage, String nameSuffix){
super();
this.baseComplexDef = (FacesComplexDefinition) reg.findDef(baseComplexId);
if( null == baseComplexDef ){
throw new IllegalArgumentException("No complex-type for complex-id: "+baseComplexId);
}
this.baseComplexClass = baseComplexDef.getJavaClass();
this.subpackage = subpackage;
this.origin = SPECIFIED_IN_SUBCLASS;
this.nameSuffix = nameSuffix;
}
/**
* @param reg
* @param unknownBase
*/
public ComplexTypeExpectedNaming(FacesSharableRegistry reg,
FacesComplexDefinition baseComplexDef, int origin) {
super();
this.baseComplexDef = baseComplexDef;
this.baseComplexClass = baseComplexDef.getJavaClass();
String fullPackage = baseComplexClass.getPackage().getName();
this.subpackage = fullPackage.substring(fullPackage.lastIndexOf('.')+1);
this.origin = origin;
String simpleName = baseComplexClass.getSimpleName();
if( simpleName.startsWith("I") && Character.isUpperCase(simpleName.charAt(1)) ){
// ISomething, remove interface I prefix
simpleName = simpleName.substring(1);
}
if( simpleName.startsWith("Abstract") ){
simpleName = simpleName.substring("Abstract".length());
}
if( simpleName.endsWith("Impl") ){
simpleName = simpleName.substring(0, simpleName.length() - "Impl".length());
}
this.nameSuffix = simpleName;
}
public boolean isMatch(FacesSharableRegistry reg, FacesComplexDefinition def){
if( baseComplexClass.isAssignableFrom(def.getJavaClass()) ){
return true;
}
if( null == substitutableDefs){
substitutableDefs = RegistryUtil.getSubstitutableDefinitions(baseComplexDef, reg);
}
return substitutableDefs.contains(def);
}
}
/**
* @param expectedPackagePrefix
* @param name
* @return
*/
private String[] parseToPrefixSubpackageAndShortName(
String expectedPackagePrefix, String name) {
final String actualPackagePrefix;
final String subpackage;
final String shortName;
int lastDotIndex = name.lastIndexOf('.');
if( -1 == lastDotIndex ){
return new String[]{"","",name};
}
shortName = name.substring(lastDotIndex+1);
String fullPackage = name.substring(0, lastDotIndex);
int subpackageSeparatorDotIndex;
if( fullPackage.startsWith(expectedPackagePrefix+".") ){
// package begins with com.ibm.xsp.
subpackageSeparatorDotIndex = expectedPackagePrefix.length();
}else if( fullPackage.equals(expectedPackagePrefix) ){
// package is com.ibm.xsp.
subpackageSeparatorDotIndex = expectedPackagePrefix.length();
}else if( fullPackage.length() == 0 ){
// package is ""
subpackageSeparatorDotIndex = 0;
}else if( fullPackage.startsWith(expectedPackagePrefix) ){
// begins with com.ibm.xspsomething, instead of com.ibm.xsp.something
// subpackage = xspsomething
int previousDot = fullPackage.lastIndexOf('.',expectedPackagePrefix.length());
subpackageSeparatorDotIndex = -1 == previousDot? fullPackage.length() : previousDot;
}else{
// does not begin with com.ibm.xsp
String truncatedExpectedPackagePrefix;
if( expectedPackagePrefix.indexOf('.') == -1){
truncatedExpectedPackagePrefix = expectedPackagePrefix;
}else{
truncatedExpectedPackagePrefix = expectedPackagePrefix.substring(0, expectedPackagePrefix.lastIndexOf('.'));
}
if( name.startsWith(truncatedExpectedPackagePrefix)){
// starts with "com.ibm"
if( '.' == name.charAt(truncatedExpectedPackagePrefix.length()) ){
subpackageSeparatorDotIndex = truncatedExpectedPackagePrefix.length();
}else{
// starts with com.ibmsomething
int previousDot = name.lastIndexOf('.',truncatedExpectedPackagePrefix.length());
subpackageSeparatorDotIndex = (-1 == previousDot)? fullPackage.length() : previousDot;
}
}else{
// does not begin with com.ibm.xsp nor com.ibm
subpackageSeparatorDotIndex = fullPackage.length();
}
}
actualPackagePrefix = name.substring(0, subpackageSeparatorDotIndex);
if( subpackageSeparatorDotIndex<fullPackage.length() ){
subpackage = fullPackage.substring(subpackageSeparatorDotIndex+1);
}else{
subpackage = "";
}
return new String[]{actualPackagePrefix, subpackage, shortName};
}
protected boolean isRequireControlSubpackageName(){
return true;
}
private boolean isInheritedComponentFamily(FacesComponentDefinition def) {
FacesComponentDefinition parent = (FacesComponentDefinition) def.getParent();
if( null == parent ){
return false;
}
return StringUtil.equals(def.getComponentFamily(), parent.getComponentFamily());
}
/**
* @param def
* @return
*/
private boolean isInheritedRendererType(FacesComponentDefinition def) {
String rendererType = def.getRendererType();
// iterate through ancestors
for (FacesComponentDefinition a = (FacesComponentDefinition)def.getParent();
a != null;
a = (FacesComponentDefinition)a.getParent()) {
if( StringUtil.equals( rendererType, a.getRendererType()) ){
return true;
}
}
return false;
}
private boolean isFuzzyMatch(String expected, String actual) {
if( expected.equals(actual) ){
return true;
}
boolean fuzzyEnd;
boolean allowEmptyEnd = expected.endsWith(".*");
fuzzyEnd = allowEmptyEnd || expected.endsWith(".+");
if( fuzzyEnd ){
expected = expected.substring(0, expected.length() - 2);
}
int indexInActual = actual.indexOf(expected);
if (-1 == indexInActual) {
// required suffix absent.
if( expected.length() == 0 && (!fuzzyEnd || allowEmptyEnd) ){
// (!fuzzyStart || allowEmptyStart) &&
return true;
}
return false;
}
if( indexInActual > 0 ){
return false;
}
boolean endMismatch = false;
int afterSuffixIndex = indexInActual + expected.length();
String afterSuffix = afterSuffixIndex < actual.length()? actual.substring(afterSuffixIndex) : "";
if( afterSuffix.length() == 0 ){
endMismatch = fuzzyEnd || ! allowEmptyEnd;
}else if( afterSuffix.length() > 0){
endMismatch = !fuzzyEnd;
}
if( endMismatch ){
return false;
}
return true;
}
/**
* Can be used in the subclass to prevent testing certain controls.
* @param defs
* @return
*/
protected List<FacesDefinition> filterDefinitions(
List<FacesDefinition> defs) {
return defs;
}
/**
* @return
*/
protected String[] getExpectedPrefixes() {
return _expectedPrefixes;
}
/**
* Entries are:
* <pre>
* new Object[]{ defClass, String expectedTagName },
* </pre>
* @return
*/
protected List<Object[]> getNonObviousTagNameSkips() {
return new ArrayList<Object[]>();
}
/**
* Entries are:
* <pre>
* new Object[]{ defClass, boolean expectedIsTag, boolean expectedNamedAsTag},
* </pre>
* @return
*/
protected List<Object[]> getUnusualTagnessSkips() {
return new ArrayList<Object[]>();
}
/**
* Entries are the String skip text, without the trailing \n
* @return
*/
protected String[] getSkips(){
return StringUtil.EMPTY_STRING_ARRAY;
}
/**
* Entries are:
* <pre>
* new Object[]{ ruleString, newSeverityInteger},
* new Object[]{ "12a", SEV_WARNING},
* </pre>
* @return
*/
protected List<Object[]> getRuleSeverityOverrides(){
return new ArrayList<Object[]>();
}
protected int getRuleSeverityLevelCutoff(){
return SEV_ANYTHING;
}
/**
* Available to call in subclass overrides of {@link #moreRules(FacesDefinition, int, String[])};
* @param def
* @return
*/
protected int sev(String ruleName, int defaultSeverity){
Integer overrideSeverity = ruleSeverityOverrides.get(ruleName);
if( null != overrideSeverity ){
return overrideSeverity.intValue();
}
return defaultSeverity;
}
private Object[] findNonObviousTagNameSkip(List<Object[]> skipList,
Class<?> javaClass) {
int i = 0;
for (Object[] skip : skipList) {
if( javaClass.equals(skip[0]) ){
if( skip.length == skipUsedLength ){
throw new RuntimeException("Using nonObviousTagName skip more than once: "+javaClass);
}
markSkipAsUsed(skipList, i, skip);
return skip;
}
i++;
}
return null;
}
private Object[] findUnusualTagnessSkip(List<Object[]> skipList,
Class<?> javaClass) {
int i = 0;
for (Object[] skip : skipList) {
if( javaClass.equals(skip[0]) ){
if( skip.length == skipUsedLength ){
throw new RuntimeException("Using unusualTagness skip more than once: "+javaClass);
}
markSkipAsUsed(skipList, i, skip);
return skip;
}
i++;
}
return null;
}
private void markSkipAsUsed(List<Object[]> skipList, int skipIndex, Object[] skip) {
skip = XspTestUtil.concat(skip, new Object[skipUsedLength - skip.length]);
skip[skipUsedLength-1] = true;
skipList.set(skipIndex, skip);
}
private boolean isSkipUsed(Object[] skip) {
if( skip.length < skipUsedLength )
return false;
return true;
}
/**
* Available to call in subclass overrides of {@link #moreRules(FacesDefinition, int, String[])};
* @param def
* @return
*/
protected String path(FacesDefinition def) {
return def.getFile().getFilePath()+"/"+XspRegistryTestUtil.descr(def)+" ";
}
}