    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}