001 // Copyright 2004, 2005 The Apache Software Foundation 002 // 003 // Licensed under the Apache License, Version 2.0 (the "License"); 004 // you may not use this file except in compliance with the License. 005 // You may obtain a copy of the License at 006 // 007 // http://www.apache.org/licenses/LICENSE-2.0 008 // 009 // Unless required by applicable law or agreed to in writing, software 010 // distributed under the License is distributed on an "AS IS" BASIS, 011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012 // See the License for the specific language governing permissions and 013 // limitations under the License. 014 015 package org.apache.tapestry.test; 016 017 import java.util.ArrayList; 018 import java.util.HashMap; 019 import java.util.Iterator; 020 import java.util.List; 021 import java.util.Map; 022 023 import org.apache.hivemind.ApplicationRuntimeException; 024 import org.apache.hivemind.ClassResolver; 025 import org.apache.hivemind.Location; 026 import org.apache.hivemind.Resource; 027 import org.apache.hivemind.impl.DefaultClassResolver; 028 import org.apache.hivemind.service.ClassFactory; 029 import org.apache.hivemind.service.impl.ClassFactoryImpl; 030 import org.apache.hivemind.util.ClasspathResource; 031 import org.apache.hivemind.util.PropertyUtils; 032 import org.apache.tapestry.Tapestry; 033 import org.apache.tapestry.enhance.AbstractPropertyWorker; 034 import org.apache.tapestry.enhance.EnhancementOperationImpl; 035 import org.apache.tapestry.enhance.EnhancementWorker; 036 import org.apache.tapestry.enhance.InjectRenderWorker; 037 import org.apache.tapestry.services.ComponentConstructor; 038 import org.apache.tapestry.services.ComponentRenderWorker; 039 import org.apache.tapestry.spec.ComponentSpecification; 040 import org.apache.tapestry.spec.IComponentSpecification; 041 import org.apache.tapestry.util.DescribedLocation; 042 043 /** 044 * A utility class that is used to instantiate abstract Tapestry pages and components. It creates, 045 * at runtime, a subclass where all abstract properties are filled in (each property complete with 046 * an instance variable, an accessor method and a mutator method). This isn't quite the same as how 047 * the class is enhanced at runtime (though it does use a subset of the same 048 * {@link org.apache.tapestry.enhance.EnhancementWorker code}), but is sufficient to unit test the 049 * class, especially listener methods. 050 * <p> 051 * One part of the enhancement is that the 052 * {@link org.apache.tapestry.IComponent#getSpecification() specification} and 053 * {@link org.apache.tapestry.IComponent#getMessages() messages} properties of the page or 054 * component class are converted into read/write properties that can be set via reflection 055 * (including {@link #newInstance(Class, Map)}. 056 * 057 * @author Howard Lewis Ship 058 * @since 4.0 059 */ 060 public class Creator 061 { 062 /** 063 * Keyed on Class, value is an {@link ComponentConstructor}. 064 */ 065 private final Map _constructors = new HashMap(); 066 067 private final ClassFactory _classFactory = new ClassFactoryImpl(); 068 069 private final ClassResolver _classResolver = new DefaultClassResolver(); 070 071 private final List _workers = new ArrayList(); 072 073 private final Resource _creatorResource = new ClasspathResource(_classResolver, 074 "/CreatorLocation"); 075 076 private final Location _creatorLocation = new DescribedLocation(_creatorResource, 077 "Creator Location"); 078 079 private final ComponentRenderWorker _renderWorker = new MockComponentRenderWorker(); 080 081 private final InjectRenderWorker _injectRender = new InjectRenderWorker(); 082 083 { 084 085 _injectRender.setRenderWorker(_renderWorker); 086 087 // Overrride AbstractComponent's implementations of 088 // these two properties (making them read/write). 089 090 _workers.add(new CreatePropertyWorker("messages", _creatorLocation)); 091 _workers.add(new CreatePropertyWorker("specification", _creatorLocation)); 092 _workers.add(_injectRender); 093 094 // Implement any abstract properties. 095 // Note that we don't bother setting the errorLog property 096 // so failures may turn into NPEs. 097 098 _workers.add(new AbstractPropertyWorker()); 099 } 100 101 private ComponentConstructor createComponentConstructor(Class inputClass) 102 { 103 if (inputClass.isInterface() || inputClass.isPrimitive() || inputClass.isArray()) 104 throw new IllegalArgumentException(ScriptMessages.wrongTypeForEnhancement(inputClass)); 105 106 EnhancementOperationImpl op = new EnhancementOperationImpl(_classResolver, 107 new ComponentSpecification(), inputClass, _classFactory, null); 108 109 IComponentSpecification spec = new ComponentSpecification(); 110 spec.setLocation(_creatorLocation); 111 112 Iterator i = _workers.iterator(); 113 while (i.hasNext()) 114 { 115 EnhancementWorker worker = (EnhancementWorker) i.next(); 116 117 worker.performEnhancement(op, spec); 118 } 119 120 return op.getConstructor(); 121 } 122 123 private ComponentConstructor getComponentConstructor(Class inputClass) 124 { 125 ComponentConstructor result = (ComponentConstructor) _constructors.get(inputClass); 126 127 if (result == null) 128 { 129 result = createComponentConstructor(inputClass); 130 131 _constructors.put(inputClass, result); 132 } 133 134 return result; 135 } 136 137 /** 138 * Given a particular abstract class; will create an instance of that class. A subclass is 139 * created with all abstract properties filled in with ordinary implementations. 140 */ 141 public Object newInstance(Class abstractClass) 142 { 143 ComponentConstructor constructor = getComponentConstructor(abstractClass); 144 145 try 146 { 147 return constructor.newInstance(); 148 } 149 catch (Exception ex) 150 { 151 throw new ApplicationRuntimeException(ScriptMessages.unableToInstantiate( 152 abstractClass, 153 ex)); 154 } 155 } 156 157 /** 158 * Creates a new instance of a given class, and then initializes properties of the instance. The 159 * map contains string keys that are property names, and object values. 160 */ 161 public Object newInstance(Class abstractClass, Map properties) 162 { 163 Object result = newInstance(abstractClass); 164 165 if (properties != null) 166 { 167 Iterator i = properties.entrySet().iterator(); 168 169 while (i.hasNext()) 170 { 171 Map.Entry e = (Map.Entry) i.next(); 172 173 String propertyName = (String) e.getKey(); 174 175 PropertyUtils.write(result, propertyName, e.getValue()); 176 } 177 } 178 179 return result; 180 } 181 182 /** 183 * A convienience (useful in test code) for invoking {@link #newInstance(Class, Map)}. The Map 184 * is constructed from the properties array, which consists of alternating keys and values. 185 */ 186 187 public Object newInstance(Class abstractClass, Object[] properties) 188 { 189 Map propertyMap = Tapestry.convertArrayToMap(properties); 190 191 return newInstance(abstractClass, propertyMap); 192 } 193 }