/*
* � 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.
*/
package com.ibm.xsp.test.framework.registry;
import java.util.ArrayList;
import java.util.List;
import javax.faces.FactoryFinder;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.render.RenderKit;
import javax.faces.render.RenderKitFactory;
import javax.faces.render.Renderer;
import com.ibm.commons.util.StringUtil;
import com.ibm.xsp.registry.FacesComponentDefinition;
import com.ibm.xsp.registry.FacesSharableRegistry;
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.setup.SkipFileContent;
/**
* @author Stephen Renwick
* @author Maire Kehoe (mkehoe@ie.ibm.com)
*/
public class ComponentRendererTest extends AbstractXspTest {
@Override
public String getDescription() {
return "that most components have a renderer-type and component-family";
}
public void testComponentRenderers() throws Exception{
List<Class<?>> renderTypesDiffer = getRenderTypesDifferSkips();
List<Class<?>> rendererTypesNull = getRendererTypesNullSkips();
List<Class<?>> rendererTypeConstNull = getRendererTypeConstNullSkips();
FacesSharableRegistry reg = TestProject.createRegistryWithAnnotater(this,
new DefinitionTagsAnnotater());
FacesContext context = TestProject.createFacesContext(this);
// a control tree is required because the renderKitId is read from the viewRoot,
// so without it can't test context.getRenderKit().getRenderer
TestProject.loadEmptyPage(this, context);
// the per-renderKit expected values
RenderKitCheckInfo[] kitInfos = getRenderKitInfos(context);
StringBuffer fails = new StringBuffer();
// Iterate through all components and check render details
for (FacesComponentDefinition compDef : TestProject.getLibComponents(reg, this)) {
// create the component instance
UIComponent component = instanciateComponent(compDef);
if (component == null) {
// will often be null if !compDef.isTag
continue;
}
// check the component configuration
if (!StringUtil.equals(component.getFamily(), compDef.getComponentFamily())) {
addFailComponentFamilyWrong(fails, component, compDef);
}
if( !StringUtil.equals(component.getRendererType(), compDef.getRendererType()) ){
if( !allowsNonEqualRendererType(renderTypesDiffer, component) ){
addFailRendererTypeWrong(fails, compDef, component);
}
}
String rendererTypeConstant = rendererTypeConstant(component);
if( null != rendererTypeConstant && !StringUtil.equals(component.getRendererType(), rendererTypeConstant) ){
addFailRendererConstantWrong(fails, component, compDef, rendererTypeConstant);
}
if (!compDef.isTag()) {
continue;
}
if ( null == component.getRendererType() ) {
if( ! allowsNullRendererType(rendererTypesNull, component) ){
addFailRendererTypeNull(fails, component, compDef);
}
// no rendererType => not rendered
// so not test the renderers
}
if( null == rendererTypeConstant ){
if( ! allowsNullRendererTypeConst(rendererTypeConstNull, component) ){
addFailRendererTypeConstantNull(fails, component, compDef);
}
}
if( null == component.getFamily() ){
addFailComponentFamilyNull(fails, component, compDef);
}
if( null != component.getFamily() && null != component.getRendererType() ){
String componentFamily = component.getFamily();
String rendererType = component.getRendererType();
Renderer renderer = context.getRenderKit().getRenderer(componentFamily, rendererType);
boolean hasRenderer = (null != renderer);
boolean expectRenderer = true;
if( DefinitionTagsAnnotater.isTagged(compDef, "no-faces-config-renderer") ){
expectRenderer = false;
}
if( hasRenderer != expectRenderer ){
if( !hasRenderer ){
String message = compDef.getFile().getFilePath()+" "+ getCompId(compDef)
+ " No runtime renderer found.\n";
System.err.print(ComponentRendererTest.class.getName()
+ ".testComponentRenderers() : " + message);
fails.append( message);
}else{
String message = compDef.getFile().getFilePath()+" "+ getCompId(compDef)
+ " Unexpected runtime renderer found, when "
+ "<tags>" +"no-faces-config-renderer"+"< is present\n";
System.err.print(ComponentRendererTest.class.getName()
+ ".testComponentRenderers() : " + message);
fails.append( message);
}
}
}
}
checkAllSkipsUsed(fails, rendererTypesNull, renderTypesDiffer, kitInfos, rendererTypeConstNull);
fails = new StringBuffer(XspTestUtil.removeMultilineFailSkips(fails.toString(),
SkipFileContent.concatSkips(getSkipFails(), this, "testComponentRenderers")));
if (fails.length() > 0) {
fail(XspTestUtil.getMultilineFailMessage(fails.toString()));
}
}
protected String[] getSkipFails(){
return StringUtil.EMPTY_STRING_ARRAY;
}
/**
* The list of component class names where isRendered returns true but
* there's no HTML renderer for the component-family and renderer-type
* combination. This list should be as short as possible.
* @return
*/
protected List<Class<?>> getHtmlNoRendererSkips() {
List<Class<?>> skips = new ArrayList<Class<?>>();
return skips;
}
protected List<Class<?>> getRenderTypesDifferSkips() {
List<Class<?>> skips = new ArrayList<Class<?>>();
return skips;
}
/**
* Note, components without a renderer-type cannot use
* renderer-specific properties.
* @return
*/
protected List<Class<?>> getRendererTypesNullSkips() {
List<Class<?>> skips = new ArrayList<Class<?>>();
return skips;
}
protected List<Class<?>> getRendererTypeConstNullSkips() {
List<Class<?>> skips = new ArrayList<Class<?>>();
return skips;
}
public List<FacesComponentDefinition> getNonLocalCompDefs(
FacesSharableRegistry compReg) {
List<FacesComponentDefinition> comps = new ArrayList<FacesComponentDefinition>();
for (FacesSharableRegistry depend : compReg.getDepends()) {
comps.addAll( depend.findComponentLocalDefs() );
}
return comps;
}
private String rendererTypeConstant(UIComponent component) {
return XspTestUtil.getStringConstant(component.getClass(), "RENDERER_TYPE", /*declared*/false);
}
private void checkAllSkipsUsed(StringBuffer fails, List<Class<?>> rendererTypesNull, List<Class<?>> renderTypesDiffer, RenderKitCheckInfo[] kitInfos, List<Class<?>> rendererTypeConstNull) {
checkSkipArrayEmpty(fails, renderTypesDiffer, "rendererType mismatch");
checkSkipArrayEmpty(fails, rendererTypesNull, "null rendererType");
checkSkipArrayEmpty(fails, rendererTypeConstNull, "RENDERER_TYPE null or not present");
for (int i = 0; i < kitInfos.length; i++) {
RenderKitCheckInfo info = kitInfos[i];
List<Class<?>> skips = info.getSkippedComponents();
String expected = "skip for kit:" +info.getRenderKitAlias();
checkSkipArrayEmpty(fails, skips, expected);
}
}
private void checkSkipArrayEmpty(StringBuffer fails, List<Class<?>> skipArray, String expected) {
for (Class<?> skippedComponentClass : skipArray) {
if( null != skippedComponentClass ){
fails.append("skip not used, component:"
+ skippedComponentClass.getName() + " expected "
+ expected + " \n");
}
}
}
private void addFailRendererConstantWrong(StringBuffer fails, UIComponent component,
FacesComponentDefinition compDef, String rendererTypeConstant) {
String message = compDef.getFile().getFilePath()+" "+ getCompId(compDef)
+ " Mismatch RENDERER_TYPE != getRendererType() for "
+ XspTestUtil.getShortClass(component) + ". Expected "
+ component.getRendererType() + ", was "
+ rendererTypeConstant + "(RENDERER_TYPE)\n";
System.err.print(ComponentRendererTest.class.getName()
+ ".testComponentRenderers() : " + message);
fails.append(message);
}
private void addFailRendererTypeWrong(StringBuffer fails, FacesComponentDefinition compDef, UIComponent component) {
String message = compDef.getFile().getFilePath()+" "+ getCompId(compDef)
+ " Mismatch xsp-config<renderer-type> != getRendererType() for "
+ XspTestUtil.getShortClass(component) + ". Expected "
+ component.getRendererType() + ", was >"
+ compDef.getRendererType()+ "</renderer-type>\n";
System.err.print(ComponentRendererTest.class.getName()
+ ".testComponentRenderers() : " + message);
fails.append( message);
}
private void addFailComponentFamilyWrong(StringBuffer fails, UIComponent component, FacesComponentDefinition compDef) {
String message = compDef.getFile().getFilePath()+" "+ getCompId(compDef)
+ " Mismatch <component-family> != getFamily() for "
+ XspTestUtil.getShortClass(component) + ". Expected "
+ component.getFamily() + ", was >"
+ compDef.getComponentFamily() + "</component-family>\n";
System.err.print(ComponentRendererTest.class.getName()
+ ".testComponentRenderers() : " + message);
fails.append( message);
}
private void addFailRendererTypeNull(StringBuffer fails, UIComponent component, FacesComponentDefinition compDef) {
String message = compDef.getFile().getFilePath()+" "+ getCompId(compDef)
+ " Problem getRendererType() was null for "
+ XspTestUtil.getShortClass(component) + " (should be set in constructor)\n";
System.err.print(ComponentRendererTest.class.getName()
+ ".testComponentRenderers() : " + message);
fails.append( message);
}
private void addFailComponentFamilyNull(StringBuffer fails,
UIComponent component, FacesComponentDefinition compDef) {
String message = compDef.getFile().getFilePath()+" "+ getCompId(compDef)
+ " Problem getFamily() was null for "
+ XspTestUtil.getShortClass(component) + " (getFamily() method should be overridden)\n";
System.err.print(ComponentRendererTest.class.getName()
+ ".testComponentRenderers() : " + message);
fails.append( message);
}
private void addFailRendererTypeConstantNull(StringBuffer fails,
UIComponent component, FacesComponentDefinition compDef) {
String message = compDef.getFile().getFilePath()+" "+ getCompId(compDef)
+ " Problem RENDERER_TYPE was null for "
+ XspTestUtil.getShortClass(component)
+ " It is required since 8.5.3 "
+ "to allow the rendererType to be set in a theme file.\n";
System.err.print(ComponentRendererTest.class.getName()
+ ".testComponentRenderers() : " + message);
fails.append(message);
}
private RenderKitCheckInfo[] getRenderKitInfos(FacesContext context) {
// get from subclass
RenderKitCheckInfo[] kitInfos = createRenderKitInfos();
// validate RenderKitInfos RenderKits exist
RenderKitFactory factory = (RenderKitFactory) FactoryFinder
.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
for (int i = 0; i < kitInfos.length; i++) {
RenderKit kit = kitInfos[i].findRenderKit(context, factory);
if (null == kit) {
assertNotNull("No faces-config containing the renderKit "+ kitInfos[i].getRenderKitId(), /*kit*/null);
}
}
return kitInfos;
}
protected static class RenderKitCheckInfo {
private String _renderKitId;
private String _renderKitAlias;
private List<Class<?>> _skippedComps;
private RenderKit _renderKit;
public RenderKitCheckInfo(String renderKitId, String renderKitAlias, List<Class<?>> skippedComps) {
super();
_renderKitId = renderKitId;
_renderKitAlias = renderKitAlias;
_skippedComps = skippedComps;
}
/**
* Return a RenderKit, lazily created.
*/
public RenderKit findRenderKit(FacesContext context, RenderKitFactory factory) {
_renderKit = factory.getRenderKit(context, getRenderKitId());
return _renderKit;
}
public RenderKit getRenderKit() {
return _renderKit;
}
public List<Class<?>> getSkippedComponents() {
return _skippedComps;
}
public String getRenderKitAlias() {
return _renderKitAlias;
}
public String getRenderKitId() {
return _renderKitId;
}
}
/**
* Return the renderKit infos, where none are null.
*
* @return
*/
protected RenderKitCheckInfo[] createRenderKitInfos() {
RenderKitCheckInfo[] result = new RenderKitCheckInfo[1];
result[0] = new RenderKitCheckInfo(RenderKitFactory.HTML_BASIC_RENDER_KIT, "html", getHtmlNoRendererSkips());
return result;
}
private String getCompId(FacesComponentDefinition component) {
return XspRegistryTestUtil.descr(component);
}
private boolean allowsNonEqualRendererType(List<Class<?>> renderTypesDiffer, UIComponent component) {
return isStillInSkipList(renderTypesDiffer, component);
}
private boolean allowsNullRendererType(List<Class<?>> rendererTypesNull, UIComponent component) {
return isStillInSkipList(rendererTypesNull, component);
}
private boolean allowsNullRendererTypeConst(List<Class<?>> rendererTypesNull, UIComponent component) {
return isStillInSkipList(rendererTypesNull, component);
}
private boolean isStillInSkipList(List<Class<?>> skips, UIComponent component) {
int i = 0;
for (Class<?> skip : skips) {
if ( null != skip && skip.equals(component.getClass()) ) {
skips.set(i, null);
return true;
}
i++;
}
return false;
}
private UIComponent instanciateComponent(FacesComponentDefinition compDef){
Class<?> clazz = compDef.getJavaClass();
Exception ex;
try{
return (UIComponent) clazz.newInstance();
} catch (InstantiationException e) {
ex = e;
} catch (IllegalAccessException e) {
ex = e;
}
if (compDef.isTag()) {
System.err.println(ComponentRendererTest.class.getName()
+ ".testComponentRenderers() : "
+ "Problem creating an instance of " + clazz
+ ". Defined in "
+ compDef.getFile().getFilePath());
ex.printStackTrace();
fail("Problem creating an instance for the tag. Class is "
+ clazz);
}
return null;
}
}