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.services.ComponentConstructor; 037 import org.apache.tapestry.spec.ComponentSpecification; 038 import org.apache.tapestry.spec.IComponentSpecification; 039 import org.apache.tapestry.util.DescribedLocation; 040 041 /** 042 * A utility class that is used to instantiate abstract Tapestry pages and components. It creates, 043 * at runtime, a subclass where all abstract properties are filled in (each property complete with 044 * an instance variable, an accessor method and a mutator method). This isn't quite the same as how 045 * the class is enhanced at runtime (though it does use a subset of the same 046 * {@link org.apache.tapestry.enhance.EnhancementWorker code}), but is sufficient to unit test the 047 * class, especially listener methods. 048 * <p> 049 * One part of the enhancement is that the 050 * {@link org.apache.tapestry.IComponent#getSpecification() specification} and 051 * {@link org.apache.tapestry.IComponent#getMessages() messages} properties of the page or 052 * component class are converted into read/write properties that can be set via reflection 053 * (including {@link #newInstance(Class, Map)}. 054 * 055 * @author Howard Lewis Ship 056 * @since 4.0 057 */ 058 public class Creator 059 { 060 /** 061 * Keyed on Class, value is an {@link ComponentConstructor}. 062 */ 063 private final Map _constructors = new HashMap(); 064 065 private final ClassFactory _classFactory = new ClassFactoryImpl(); 066 067 private final ClassResolver _classResolver = new DefaultClassResolver(); 068 069 private final List _workers = new ArrayList(); 070 071 private final Resource _creatorResource = new ClasspathResource(_classResolver, 072 "/CreatorLocation"); 073 074 private final Location _creatorLocation = new DescribedLocation(_creatorResource, 075 "Creator Location"); 076 077 { 078 // Overrride AbstractComponent's implementations of 079 // these two properties (making them read/write). 080 081 _workers.add(new CreatePropertyWorker("messages", _creatorLocation)); 082 _workers.add(new CreatePropertyWorker("specification", _creatorLocation)); 083 084 // Implement any abstract properties. 085 // Note that we don't bother setting the errorLog property 086 // so failures may turn into NPEs. 087 088 _workers.add(new AbstractPropertyWorker()); 089 } 090 091 private ComponentConstructor createComponentConstructor(Class inputClass) 092 { 093 if (inputClass.isInterface() || inputClass.isPrimitive() || inputClass.isArray()) 094 throw new IllegalArgumentException(ScriptMessages.wrongTypeForEnhancement(inputClass)); 095 096 EnhancementOperationImpl op = new EnhancementOperationImpl(_classResolver, 097 new ComponentSpecification(), inputClass, _classFactory, null); 098 099 IComponentSpecification spec = new ComponentSpecification(); 100 spec.setLocation(_creatorLocation); 101 102 Iterator i = _workers.iterator(); 103 while (i.hasNext()) 104 { 105 EnhancementWorker worker = (EnhancementWorker) i.next(); 106 107 worker.performEnhancement(op, spec); 108 } 109 110 return op.getConstructor(); 111 } 112 113 private ComponentConstructor getComponentConstructor(Class inputClass) 114 { 115 ComponentConstructor result = (ComponentConstructor) _constructors.get(inputClass); 116 117 if (result == null) 118 { 119 result = createComponentConstructor(inputClass); 120 121 _constructors.put(inputClass, result); 122 } 123 124 return result; 125 } 126 127 /** 128 * Given a particular abstract class; will create an instance of that class. A subclass is 129 * created with all abstract properties filled in with ordinary implementations. 130 */ 131 public Object newInstance(Class abstractClass) 132 { 133 ComponentConstructor constructor = getComponentConstructor(abstractClass); 134 135 try 136 { 137 return constructor.newInstance(); 138 } 139 catch (Exception ex) 140 { 141 throw new ApplicationRuntimeException(ScriptMessages.unableToInstantiate( 142 abstractClass, 143 ex)); 144 } 145 } 146 147 /** 148 * Creates a new instance of a given class, and then initializes properties of the instance. The 149 * map contains string keys that are property names, and object values. 150 */ 151 public Object newInstance(Class abstractClass, Map properties) 152 { 153 Object result = newInstance(abstractClass); 154 155 if (properties != null) 156 { 157 Iterator i = properties.entrySet().iterator(); 158 159 while (i.hasNext()) 160 { 161 Map.Entry e = (Map.Entry) i.next(); 162 163 String propertyName = (String) e.getKey(); 164 165 PropertyUtils.write(result, propertyName, e.getValue()); 166 } 167 } 168 169 return result; 170 } 171 172 /** 173 * A convienience (useful in test code) for invoking {@link #newInstance(Class, Map)}. The Map 174 * is constructed from the properties array, which consists of alternating keys and values. 175 */ 176 177 public Object newInstance(Class abstractClass, Object[] properties) 178 { 179 Map propertyMap = Tapestry.convertArrayToMap(properties); 180 181 return newInstance(abstractClass, propertyMap); 182 } 183 }