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 015package org.apache.tapestry5.internal.transform; 016 017import org.apache.tapestry5.ComponentResources; 018import org.apache.tapestry5.annotations.Component; 019import org.apache.tapestry5.annotations.MixinClasses; 020import org.apache.tapestry5.annotations.Mixins; 021import org.apache.tapestry5.commons.Location; 022import org.apache.tapestry5.commons.internal.services.StringLocation; 023import org.apache.tapestry5.commons.internal.util.TapestryException; 024import org.apache.tapestry5.commons.util.CollectionFactory; 025import org.apache.tapestry5.commons.util.CommonsUtils; 026import org.apache.tapestry5.internal.KeyValue; 027import org.apache.tapestry5.internal.TapestryInternalUtils; 028import org.apache.tapestry5.ioc.Orderable; 029import org.apache.tapestry5.ioc.internal.util.InternalUtils; 030import org.apache.tapestry5.model.ComponentModel; 031import org.apache.tapestry5.model.MutableComponentModel; 032import org.apache.tapestry5.model.MutableEmbeddedComponentModel; 033import org.apache.tapestry5.plastic.*; 034import org.apache.tapestry5.services.ComponentClassResolver; 035import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2; 036import org.apache.tapestry5.services.transform.TransformationSupport; 037 038import 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 */ 046public 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(String.format("%d mixins defined via @MixinClasses on field '%s', but %d ordering constraints \\\n" + 144 " specified (expected 0 or %1$d).", annotation.value().length, field.getName(), annotation.order().length), model, 145 null); 146 147 for (int i = 0; i < annotation.value().length; i++) 148 { 149 String[] constraints = orderEmpty ? CommonsUtils.EMPTY_STRING_ARRAY : TapestryInternalUtils 150 .splitMixinConstraints(annotation.order()[i]); 151 152 model.addMixin(annotation.value()[i].getName(), constraints); 153 } 154 } 155 156 private void addMixinTypes(PlasticField field, MutableEmbeddedComponentModel model) 157 { 158 Mixins annotation = field.getAnnotation(Mixins.class); 159 160 if (annotation == null) 161 return; 162 163 for (String typeName : annotation.value()) 164 { 165 Orderable<String> typeAndOrder = TapestryInternalUtils.mixinTypeAndOrder(typeName); 166 String mixinClassName = resolver.resolveMixinTypeToClassName(typeAndOrder.getTarget()); 167 model.addMixin(mixinClassName, typeAndOrder.getConstraints()); 168 } 169 } 170 171 private void addParameters(MutableEmbeddedComponentModel embedded, String[] parameters) 172 { 173 for (String parameter : parameters) 174 { 175 KeyValue kv = TapestryInternalUtils.parseKeyValue(parameter); 176 177 embedded.addParameter(kv.getKey(), kv.getValue()); 178 } 179 } 180}