001 // Copyright 2006, 2007, 2008, 2009, 2010, 2011 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.tapestry5.internal.transform; 016 017 import org.apache.tapestry5.ComponentResources; 018 import org.apache.tapestry5.annotations.Component; 019 import org.apache.tapestry5.annotations.MixinClasses; 020 import org.apache.tapestry5.annotations.Mixins; 021 import org.apache.tapestry5.internal.InternalConstants; 022 import org.apache.tapestry5.internal.KeyValue; 023 import org.apache.tapestry5.internal.TapestryInternalUtils; 024 import org.apache.tapestry5.ioc.Location; 025 import org.apache.tapestry5.ioc.Orderable; 026 import org.apache.tapestry5.ioc.internal.services.StringLocation; 027 import org.apache.tapestry5.ioc.internal.util.CollectionFactory; 028 import org.apache.tapestry5.ioc.internal.util.InternalUtils; 029 import org.apache.tapestry5.ioc.internal.util.TapestryException; 030 import org.apache.tapestry5.model.ComponentModel; 031 import org.apache.tapestry5.model.MutableComponentModel; 032 import org.apache.tapestry5.model.MutableEmbeddedComponentModel; 033 import org.apache.tapestry5.plastic.*; 034 import org.apache.tapestry5.services.ComponentClassResolver; 035 import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2; 036 import org.apache.tapestry5.services.transform.TransformationSupport; 037 038 import java.util.List; 039 040 /** 041 * Finds fields with the {@link org.apache.tapestry5.annotations.Component} annotation and updates 042 * the model. Also 043 * checks for the {@link Mixins} and {@link MixinClasses} annotations and uses them to update the {@link ComponentModel} 044 * . 045 */ 046 public class ComponentWorker implements ComponentClassTransformWorker2 047 { 048 private final ComponentClassResolver resolver; 049 050 public ComponentWorker(ComponentClassResolver resolver) 051 { 052 this.resolver = resolver; 053 } 054 055 public void transform(PlasticClass plasticClass, TransformationSupport support, MutableComponentModel model) 056 { 057 for (PlasticField field : plasticClass.getFieldsWithAnnotation(Component.class)) 058 { 059 transformField(plasticClass, model, field); 060 } 061 } 062 063 private void transformField(PlasticClass transformation, MutableComponentModel model, PlasticField field) 064 { 065 Component annotation = field.getAnnotation(Component.class); 066 067 field.claim(annotation); 068 069 String annotationId = annotation.id(); 070 071 String fieldName = field.getName(); 072 073 String id = InternalUtils.isNonBlank(annotationId) ? annotationId : InternalUtils.stripMemberName(fieldName); 074 075 String type = field.getTypeName(); 076 077 Location location = new StringLocation(String.format("%s.%s", transformation.getClassName(), fieldName), 0); 078 079 MutableEmbeddedComponentModel embedded = model.addEmbeddedComponent(id, annotation.type(), type, annotation 080 .inheritInformalParameters(), location); 081 082 addParameters(embedded, annotation.parameters()); 083 084 updateModelWithPublishedParameters(embedded, annotation); 085 086 convertAccessToField(field, id); 087 088 addMixinClasses(field, embedded); 089 addMixinTypes(field, embedded); 090 } 091 092 private void convertAccessToField(PlasticField field, String id) 093 { 094 String fieldName = field.getName(); 095 096 ComputedValue<FieldConduit<Object>> computedConduit = createProviderForEmbeddedComponentConduit(fieldName, id); 097 098 field.setComputedConduit(computedConduit); 099 } 100 101 private ComputedValue<FieldConduit<Object>> createProviderForEmbeddedComponentConduit(final String fieldName, 102 final String id) 103 { 104 return new ComputedValue<FieldConduit<Object>>() 105 { 106 public FieldConduit<Object> get(InstanceContext context) 107 { 108 final ComponentResources resources = context.get(ComponentResources.class); 109 110 return new ReadOnlyComponentFieldConduit(resources, fieldName) 111 { 112 public Object get(Object instance, InstanceContext context) 113 { 114 return resources.getEmbeddedComponent(id); 115 } 116 }; 117 } 118 }; 119 } 120 121 private void updateModelWithPublishedParameters(MutableEmbeddedComponentModel embedded, Component annotation) 122 { 123 String names = annotation.publishParameters(); 124 125 if (InternalUtils.isNonBlank(names)) 126 { 127 List<String> published = CollectionFactory.newList(TapestryInternalUtils.splitAtCommas(names)); 128 embedded.setPublishedParameters(published); 129 } 130 131 } 132 133 private void addMixinClasses(PlasticField field, MutableEmbeddedComponentModel model) 134 { 135 MixinClasses annotation = field.getAnnotation(MixinClasses.class); 136 137 if (annotation == null) 138 return; 139 140 boolean orderEmpty = annotation.order().length == 0; 141 142 if (!orderEmpty && annotation.order().length != annotation.value().length) 143 throw new TapestryException(TransformMessages.badMixinConstraintLength(annotation, field.getName()), model, 144 null); 145 146 for (int i = 0; i < annotation.value().length; i++) 147 { 148 String[] constraints = orderEmpty ? InternalConstants.EMPTY_STRING_ARRAY : TapestryInternalUtils 149 .splitMixinConstraints(annotation.order()[i]); 150 151 model.addMixin(annotation.value()[i].getName(), constraints); 152 } 153 } 154 155 private void addMixinTypes(PlasticField field, MutableEmbeddedComponentModel model) 156 { 157 Mixins annotation = field.getAnnotation(Mixins.class); 158 159 if (annotation == null) 160 return; 161 162 for (String typeName : annotation.value()) 163 { 164 Orderable<String> typeAndOrder = TapestryInternalUtils.mixinTypeAndOrder(typeName); 165 String mixinClassName = resolver.resolveMixinTypeToClassName(typeAndOrder.getTarget()); 166 model.addMixin(mixinClassName, typeAndOrder.getConstraints()); 167 } 168 } 169 170 private void addParameters(MutableEmbeddedComponentModel embedded, String[] parameters) 171 { 172 for (String parameter : parameters) 173 { 174 KeyValue kv = TapestryInternalUtils.parseKeyValue(parameter); 175 176 embedded.addParameter(kv.getKey(), kv.getValue()); 177 } 178 } 179 }