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}