/* * � Copyright IBM Corp. 2011, 2014 * * 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: 19 May 2011 * GroupReuseTest.java */ package com.ibm.xsp.test.framework.registry; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; 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.FacesProject; import com.ibm.xsp.registry.FacesProperty; import com.ibm.xsp.registry.FacesSharableRegistry; import com.ibm.xsp.registry.RegistryUtil; import com.ibm.xsp.registry.parse.ParseUtil; 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.annotate.DefinitionTagsAnnotater; import com.ibm.xsp.test.framework.registry.annotate.DesignerExtensionSubsetAnnotater; import com.ibm.xsp.test.framework.registry.annotate.PropertiesHaveCategoriesTest; import com.ibm.xsp.test.framework.registry.annotate.PropertyTagsAnnotater; import com.ibm.xsp.test.framework.registry.annotate.SpellCheckTest; import com.ibm.xsp.test.framework.setup.SkipFileContent; /** * * @author Maire Kehoe (mkehoe@ie.ibm.com) */ public class GroupReuseTest extends AbstractXspTest { @Override public String getDescription() { // Note, see the Console view System.err text for the proposed <group>s // that might be used in the place of each <property> with JUnit test fail. return "that controls should not declare <property>s where there are existing <group>s containing those <property>s"; } public void testGroupReuse() throws Exception { FacesSharableRegistry reg = TestProject.createRegistryWithAnnotater(this, new PropertyTagsAnnotater(), new PropertyReuseFamilyAnnotater(), new PropertiesHaveCategoriesTest.PropertyCategoryAnnotater(), new SpellCheckTest.DescriptionDisplayNameAnnotater(), new DefinitionTagsAnnotater()); // The relevant xsp-config skips are: // <designer-extension> // <reuse-family>(propName)-(logical group)</reuse-family> // e.g.: // <reuse-family>title-accessibility</reuse-family> // <reuse-family>title-columnHeader</reuse-family> // and // <reuse-changes>(changes)</reuse-changes> // used with <reuse-family> where the property is the same as the definitive // property declaration, except that it differs somewhat Map<String, List<FacesGroupDefinition>> propNameToDefiningGroups = new HashMap<String, List<FacesGroupDefinition>>(); for (FacesDefinition def : reg.findDefs()) { if( def instanceof FacesGroupDefinition ){ FacesGroupDefinition group = (FacesGroupDefinition)def; for (String propName : group.getPropertyNames()) { List<FacesGroupDefinition> groupList = propNameToDefiningGroups.get(propName); if( null == groupList ){ groupList = new ArrayList<FacesGroupDefinition>(); propNameToDefiningGroups.put(propName, groupList); } groupList.add(group); } } } addGroupUsageExtension(reg); Map<String, List<FacesDefinition>> propNameToDefiningDefs = new HashMap<String, List<FacesDefinition>>(); for (FacesDefinition def : reg.findDefs()) { if( def instanceof FacesGroupDefinition ){ continue; } for (String inlinePropName : def.getDefinedInlinePropertyNames()) { List<FacesDefinition> defList = propNameToDefiningDefs.get(inlinePropName); if( null == defList ){ defList = new ArrayList<FacesDefinition>(); propNameToDefiningDefs.put(inlinePropName, defList); } defList.add(def); } } Map<String,SuggestedGroups> propNameToSuggestedGroups = getHardCodedSuggestedGroups(); String[] reuseFamilyIds = getLibReuseFamilys(reg, this); Map<String,DefinitiveDeclaration> reuseFamilyDeclarations = findDefinitiveDeclarations(reg, reuseFamilyIds); String fails = ""; boolean isCheckOtherDefs = isCheckOtherDefs(); boolean isCheckOtherGroups = isCheckOtherGroups(); // for the definitions in the library. for (FacesDefinition def : TestProject.getDefinitions(reg, this)) { boolean isControl = def instanceof FacesComponentDefinition; boolean isComplex = def instanceof FacesComplexDefinition; boolean isGroup = def instanceof FacesGroupDefinition; String defUsage = isControl? "control" : isComplex? "complex" : "other"; for (String inlinePropName : def.getDefinedInlinePropertyNames()) { FacesProperty prop = def.getProperty(inlinePropName); String reuseFamily = getReuseFamily(prop); if( null != reuseFamily ){ DefinitiveDeclaration original = reuseFamilyDeclarations.get(reuseFamily); if( def == original.def && prop == original.prop ){ // this is the first declaration of a reuse family continue; } String allChanges = getChanges(prop, original); String expectedReuseChanges = getReuseChanges(prop); if( null == expectedReuseChanges ){ expectedReuseChanges = ""; } if( ! StringUtil.equals(expectedReuseChanges, allChanges) ){ fails += def.getFile().getFilePath() + " " + ParseUtil.getTagRef(def) + " " + inlinePropName+ " (reuse-family=" +reuseFamily+ ") differs from the original by: " +allChanges+ " [expected reuse-changes: " +expectedReuseChanges+ "] [reuse-family original is in: " +ParseUtil.getTagRef(original.def)+"]\n"; } // Already has a reuse-family so shouldn't suggest reuse. continue; } SuggestedGroups hardCodedSuggestion = propNameToSuggestedGroups.get(inlinePropName); String[] suggestedGroupIds = (null == hardCodedSuggestion)? null : isControl? hardCodedSuggestion.suggestedComponentGroups : isComplex? hardCodedSuggestion.suggestedComplexGroups : null; if( null != suggestedGroupIds ){ String groups = XspTestUtil.concatStrings(suggestedGroupIds); String msg = def.getFile().getFilePath() + " " + ParseUtil.getTagRef(def) + " " + inlinePropName + " Should reuse <group-type-ref> for an existing " + defUsage + " group: " + groups + ""; System.err.println(msg + "\n existing " +defUsage+ " groups: " +groups); fails += msg + "\n"; continue; } String groups = isGroup? null : toGroupListString(propNameToDefiningGroups.get(inlinePropName), defUsage); if( isCheckOtherGroups && null != groups ){ String msg = def.getFile().getFilePath()+" "+ParseUtil.getTagRef(def)+" "+inlinePropName +" Maybe should reuse <group-type-ref> of an existing " +defUsage+ " group."; System.err.println(msg + "\n existing " +defUsage+ " groups: " +groups); fails += msg; fails += " Proposed " +defUsage+ " groups for reuse: " + groups + "\n"; continue; } if( isCheckOtherDefs && !isGroup){ List<FacesDefinition> defList = propNameToDefiningDefs.get(inlinePropName); String defs = toDefListString(defList, def, isControl); if( null != defs ){ if( isControl ){ String msg = def.getFile().getFilePath()+" "+ParseUtil.getTagRef(def)+ " "+ inlinePropName + " Declared <property> also present in other <component>s, " + "consider refactor a <group>."; fails += msg + "\n"; System.err.println(msg + "\n other components: "+ defs); } else if( isComplex ){ String msg = def.getFile().getFilePath()+" "+ParseUtil.getTagRef(def)+ " "+ inlinePropName + " Declared <property> also present in other <complex>s, " + "consider refactor a <group>."; fails += msg + "\n"; System.err.println(msg + "\n other complexes: "+ defs); } continue; } } // else, unique property, ok to declare inline } } fails = XspTestUtil.removeMultilineFailSkips(fails, SkipFileContent.concatSkips(getSkipFails(), this, "testGroupReuse")); if( fails.length() > 0 ){ fail(XspTestUtil.getMultilineFailMessage(fails)); } } /** * @param prop * @param original * @return */ protected String getChanges(FacesProperty prop, DefinitiveDeclaration original) { String functionalChanges = KnownPropertyRedefinitionTest.getChanges(prop, original.prop); if( "none".equals(functionalChanges) ){ functionalChanges = ""; } String categoryChanges = getCategoryChanges(prop, original.prop); String descrNameChanges = getDescrNameChanges(prop, original.prop); String allChanges = functionalChanges; if( categoryChanges.length() > 0 ){ allChanges += KnownPropertyRedefinitionTest.comma(allChanges)+categoryChanges; } if( descrNameChanges.length() > 0 ){ allChanges += KnownPropertyRedefinitionTest.comma(allChanges)+descrNameChanges; } return allChanges; } /** * @param prop * @param prop2 * @return */ private String getCategoryChanges(FacesProperty prop, FacesProperty originalProp) { String changes = ""; String propName = (String) prop.getExtension("category"); String originalPropName = (String) originalProp.getExtension("category"); if( !StringUtil.equals(propName, originalPropName) ){ changes += KnownPropertyRedefinitionTest.comma(changes)+"category="+propName; } return changes; } /** * @param prop * @param prop2 * @return */ private String getDescrNameChanges(FacesProperty prop, FacesProperty originalProp) { String changes = ""; String propName = (String) prop.getExtension("display-name"); String originalPropName = (String) originalProp.getExtension("display-name"); if( !StringUtil.equals(propName, originalPropName) ){ changes += "display-name"; } String propDescr = (String) prop.getExtension("description"); String originalPropDescr = (String) originalProp.getExtension("description"); if( ! StringUtil.equals(propDescr, originalPropDescr) ){ changes += KnownPropertyRedefinitionTest.comma(changes)+"description"; } return changes; } /** * @param reg * @param relevantReuseFamilyIds * @return */ private Map<String, DefinitiveDeclaration> findDefinitiveDeclarations( FacesSharableRegistry reg, String[] relevantReuseFamilyIds) { if( relevantReuseFamilyIds.length == 0){ return Collections.emptyMap(); } int notFoundCount = relevantReuseFamilyIds.length; Map<String, DefinitiveDeclaration> map = new HashMap<String, GroupReuseTest.DefinitiveDeclaration>(); outer: for (FacesProject proj : reg.getProjectList()) { for (FacesLibraryFragment file : proj.getFiles()) { for (FacesDefinition def : file.getDefs()) { for (FacesProperty prop : RegistryUtil.getProperties(def, def.getDefinedInlinePropertyNames()) ) { String reuseFamily = getReuseFamily(prop); if( null == reuseFamily ){ continue; } int index = Arrays.binarySearch(relevantReuseFamilyIds, reuseFamily); if( index >= 0 && ! map.containsKey(reuseFamily) ){ map.put(reuseFamily, new DefinitiveDeclaration( // reuseFamily, def, prop)); notFoundCount--; if( 0 == notFoundCount ){ break outer; } } } } } } return map; } /** * @param reg * @param groupReuseTest * @return */ private String[] getLibReuseFamilys(FacesSharableRegistry reg, AbstractXspTest test) { List<String> list = null; List<FacesDefinition> defs = TestProject.getLibDefinitions(reg, test); // want to find the 1st definition, instead of usually the last defined Collections.reverse(defs); for (FacesDefinition def : defs) { for (FacesProperty prop : RegistryUtil.getProperties(def, def.getDefinedInlinePropertyNames())) { String reuseFamily = getReuseFamily(prop); if( null != reuseFamily ){ if( null == list ){ list = new ArrayList<String>(); list.add(reuseFamily); }else{ if( !list.contains(reuseFamily) ){ list.add(reuseFamily); } } } } } if( null == list ){ return StringUtil.EMPTY_STRING_ARRAY; } Collections.sort(list); return StringUtil.toStringArray(list); } protected String[] getSkipFails(){ return StringUtil.EMPTY_STRING_ARRAY; } protected Map<String,SuggestedGroups> getHardCodedSuggestedGroups(){ return new HashMap<String, GroupReuseTest.SuggestedGroups>(); } protected void addAll(Map<String, SuggestedGroups> existingSuggestions, SuggestedGroups[] extra){ for (SuggestedGroups suggestion : extra) { SuggestedGroups existing = existingSuggestions.get(suggestion.forPropertyName); if( null != existing ){ // merge suggestion with extra suggestion = new SuggestedGroups(suggestion.forPropertyName, nonEmpty(XspTestUtil.concat(existing.suggestedComponentGroups, suggestion.suggestedComponentGroups)), nonEmpty(XspTestUtil.concat(existing.suggestedComplexGroups, suggestion.suggestedComplexGroups))); } existingSuggestions.put(suggestion.forPropertyName, suggestion); } } private String[] nonEmpty(String[] arr){ // convert String[0] to null if( null != arr && arr.length == 0){ return null; } return arr; } protected SuggestedGroups controlGroups(String forPropertyName, String... suggestedComponentGroups){ return new SuggestedGroups(forPropertyName, /*suggestedComponentGroups*/suggestedComponentGroups, /*suggestedComplexGroups*/null); } protected static class SuggestedGroups{ public final String forPropertyName; public final String[] suggestedComponentGroups; public final String[] suggestedComplexGroups; /** * @param forPropertyName * @param preventAnySuggestion * @param useSuggestedComponentGroups * @param suggestedComponentGroups * @param useSuggestedComplexGroups * @param suggestedComplexGroups */ public SuggestedGroups(String forPropertyName, String[] suggestedComponentGroups, String[] suggestedComplexGroups) { super(); this.forPropertyName = forPropertyName; this.suggestedComponentGroups = suggestedComponentGroups; this.suggestedComplexGroups = suggestedComplexGroups; } } /** * Available to override in subclasses, defaults to true. * @return */ protected boolean isCheckOtherDefs() { return true; } /** * Available to override in subclasses, defaults to true. * @return */ protected boolean isCheckOtherGroups() { return true; } private void addGroupUsageExtension(FacesSharableRegistry reg) { for (FacesDefinition def : reg.findDefs()) { String usage; if( def instanceof FacesComponentDefinition){ usage = "control"; }else if( def instanceof FacesComplexDefinition ){ usage = "complex"; }else{ continue; } for (String groupTypeRef : def.getGroupTypeRefs()) { addGroupUsageExtension(reg, groupTypeRef, usage); } } } private void addGroupUsageExtension(FacesSharableRegistry reg, String groupTypeRef, String usage) { FacesGroupDefinition group = (FacesGroupDefinition) reg.findDef(groupTypeRef); if( null == group ){ return; } String existingUsage = (String) group.getExtension("usage"); if( null != existingUsage ){ return; } group.setExtension("usage", usage); for (String innerGroupTypeRef : group.getGroupTypeRefs()) { addGroupUsageExtension(reg, innerGroupTypeRef, usage); } } private String toDefListString(List<? extends FacesDefinition> list, FacesDefinition except, boolean isControl) { StringBuilder result = new StringBuilder(); for (FacesDefinition def : list) { if( def == except ){ continue; } if( isControl ){ if( !(def instanceof FacesComponentDefinition) ){ continue; } }else{ if( !(def instanceof FacesComplexDefinition) ){ continue; } } result.append(ParseUtil.getTagRef(def)).append(' '); } if( result.length() == 0 ){ return null; } result.deleteCharAt(result.length() - 1); return result.toString(); } private String toGroupListString(List<FacesGroupDefinition> list, String matchUsage) { if( null == list ){ return null; } StringBuilder result = new StringBuilder(); for (FacesGroupDefinition group : list) { if( !matchUsage.equals(group.getExtension("usage")) ){ continue; } result.append(ParseUtil.getTagRef(group)).append(' '); } if( result.length() == 0 ){ return null; } result.deleteCharAt(result.length() - 1); return result.toString(); } private class PropertyReuseFamilyAnnotater extends DesignerExtensionSubsetAnnotater{ @Override protected boolean isApplicableExtensibleNode(FacesExtensibleNode parsed) { return parsed instanceof FacesProperty; } @Override protected String[] createExtNameArr() { return new String[]{ "reuse-family", "reuse-changes", }; } @Override protected Object parseValue(String extensionName, String value) { // if( "reuse-family".equals(extensionName) || "reuse-family-decl".equals(extensionName) || ... ){ return value; // } // return value; } } public static String getReuseFamily(FacesProperty prop){ return (String) prop.getExtension("reuse-family"); } public static String getReuseChanges(FacesProperty prop){ return (String) prop.getExtension("reuse-changes"); } private static class DefinitiveDeclaration{ // public String reuseFamily; public FacesDefinition def; public FacesProperty prop; public DefinitiveDeclaration( // String reuseFamily, FacesDefinition def, FacesProperty prop) { super(); // this.reuseFamily = reuseFamily; this.def = def; this.prop = prop; } } }