001// Copyright 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 java.util.Collections; 018import java.util.Comparator; 019import java.util.LinkedList; 020import java.util.List; 021import java.util.Set; 022 023import org.apache.tapestry5.EventConstants; 024import org.apache.tapestry5.annotations.PageActivationContext; 025import org.apache.tapestry5.ioc.internal.util.CollectionFactory; 026import org.apache.tapestry5.ioc.internal.util.InternalUtils; 027import org.apache.tapestry5.model.MutableComponentModel; 028import org.apache.tapestry5.plastic.FieldHandle; 029import org.apache.tapestry5.plastic.PlasticClass; 030import org.apache.tapestry5.plastic.PlasticField; 031import org.apache.tapestry5.runtime.Component; 032import org.apache.tapestry5.runtime.ComponentEvent; 033import org.apache.tapestry5.services.ComponentEventHandler; 034import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2; 035import org.apache.tapestry5.services.transform.TransformationSupport; 036 037/** 038 * Provides the page activation context handlers. 039 * 040 * @see org.apache.tapestry5.annotations.PageActivationContext 041 */ 042public class PageActivationContextWorker implements ComponentClassTransformWorker2 043{ 044 private static final Comparator<PlasticField> INDEX_COMPARATOR = new Comparator<PlasticField>() 045 { 046 public int compare(PlasticField field1, PlasticField field2) { 047 int index1 = field1.getAnnotation(PageActivationContext.class).index(); 048 int index2 = field2.getAnnotation(PageActivationContext.class).index(); 049 050 int compare = index1 < index2 ? -1 : (index1 > index2 ? 1 : 0); 051 if (compare == 0) 052 { 053 compare = field1.getName().compareTo(field2.getName()); 054 } 055 return compare; 056 } 057 }; 058 059 public void transform(PlasticClass plasticClass, TransformationSupport support, MutableComponentModel model) 060 { 061 List<PlasticField> fields = plasticClass.getFieldsWithAnnotation(PageActivationContext.class); 062 063 if (!fields.isEmpty()) 064 { 065 transformFields(support, fields); 066 } 067 } 068 069 private void transformFields(TransformationSupport support, List<PlasticField> fields) 070 { 071 List<PlasticField> sortedFields = CollectionFactory.newList(fields); 072 Collections.sort(sortedFields, INDEX_COMPARATOR); 073 validateSortedFields(sortedFields); 074 075 PlasticField firstField = sortedFields.get(0); 076 PageActivationContext firstAnnotation = firstField.getAnnotation(PageActivationContext.class); 077 078 // these arrays reduce memory usage and allow the PlasticField instances to be garbage collected 079 FieldHandle[] handles = new FieldHandle[sortedFields.size()]; 080 String[] typeNames = new String[sortedFields.size()]; 081 082 int i = 0; 083 for (PlasticField field : sortedFields) { 084 handles[i] = field.getHandle(); 085 typeNames[i] = field.getTypeName(); 086 ++i; 087 } 088 089 if (firstAnnotation.activate()) 090 { 091 support.addEventHandler(EventConstants.ACTIVATE, 1, 092 "PageActivationContextWorker activate event handler", createActivationHandler(handles, typeNames)); 093 } 094 095 if (firstAnnotation.passivate()) 096 { 097 support.addEventHandler(EventConstants.PASSIVATE, 0, 098 "PageActivationContextWorker passivate event handler", createPassivateHandler(handles)); 099 } 100 101 // We don't claim the field, and other workers may even replace it with a FieldConduit. 102 } 103 104 private void validateSortedFields(List<PlasticField> sortedFields) { 105 List<Integer> expectedIndexes = CollectionFactory.newList(); 106 List<Integer> actualIndexes = CollectionFactory.newList(); 107 Set<Boolean> activates = CollectionFactory.newSet(); 108 Set<Boolean> passivates = CollectionFactory.newSet(); 109 110 for (int i = 0; i < sortedFields.size(); ++i) { 111 PlasticField field = sortedFields.get(i); 112 PageActivationContext annotation = field.getAnnotation(PageActivationContext.class); 113 expectedIndexes.add(i); 114 actualIndexes.add(annotation.index()); 115 activates.add(annotation.activate()); 116 passivates.add(annotation.passivate()); 117 } 118 119 List<String> errors = CollectionFactory.newList(); 120 if (!expectedIndexes.equals(actualIndexes)) { 121 errors.add(String.format("Index values must start at 0 and increment by 1 (expected [%s], found [%s])", 122 InternalUtils.join(expectedIndexes), InternalUtils.join(actualIndexes))); 123 } 124 if (activates.size() > 1) { 125 errors.add("Illegal values for 'activate' (all fields must have the same value)"); 126 } 127 if (passivates.size() > 1) { 128 errors.add("Illegal values for 'passivate' (all fields must have the same value)"); 129 } 130 if (!errors.isEmpty()) { 131 throw new RuntimeException(String.format("Invalid values for @PageActivationContext: %s", InternalUtils.join(errors))); 132 } 133 } 134 135 private static ComponentEventHandler createActivationHandler(final FieldHandle[] handles, final String[] fieldTypes) 136 { 137 return new ComponentEventHandler() 138 { 139 public void handleEvent(Component instance, ComponentEvent event) 140 { 141 int count = Math.min(handles.length, event.getEventContext().getCount()); 142 for (int i = 0; i < count; ++i) 143 { 144 String fieldType = fieldTypes[i]; 145 FieldHandle handle = handles[i]; 146 Object value = event.coerceContext(i, fieldType); 147 handle.set(instance, value); 148 } 149 } 150 }; 151 } 152 153 private static ComponentEventHandler createPassivateHandler(final FieldHandle[] handles) 154 { 155 return new ComponentEventHandler() 156 { 157 public void handleEvent(Component instance, ComponentEvent event) 158 { 159 Object result; 160 if (handles.length == 1) { 161 // simple / common case for a single @PageActivationContext 162 result = handles[0].get(instance); 163 } else { 164 LinkedList<Object> list = CollectionFactory.newLinkedList(); 165 166 // iterate backwards 167 for (int i = handles.length - 1; i > -1; i--) { 168 FieldHandle handle = handles[i]; 169 Object value = handle.get(instance); 170 171 // ignore trailing nulls 172 if (value != null || !list.isEmpty()) { 173 list.addFirst(value); 174 } 175 } 176 result = list.isEmpty() ? null : list; 177 } 178 179 event.storeResult(result); 180 } 181 }; 182 } 183}