com.github.wicket.autowire.AutoWire.java Source code

Java tutorial

Introduction

Here is the source code for com.github.wicket.autowire.AutoWire.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with this
 * work for additional information regarding copyright ownership. The ASF
 * licenses this file to You under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
 * or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package com.github.wicket.autowire;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;

import org.apache.wicket.Application;
import org.apache.wicket.Component;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.application.IComponentInitializationListener;
import org.apache.wicket.application.IComponentInstantiationListener;
import org.apache.wicket.markup.*;
import org.apache.wicket.markup.html.TransparentWebMarkupContainer;
import org.apache.wicket.markup.html.border.Border;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.resolver.WicketContainerResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static java.util.Map.Entry;

public final class AutoWire implements IComponentInitializationListener, IComponentInstantiationListener {

    private static final Logger log = LoggerFactory.getLogger(Component.class);

    private AutoWire() {

    }

    private static final ComponentCache cache = new ComponentCache();

    public static void install(final Application application) {
        final AutoWire instance = new AutoWire();
        application.getComponentInitializationListeners().add(instance);
        application.getComponentInstantiationListeners().add(instance);
    }

    @Override
    public void onInstantiation(final Component component) {
        Value value = cache.get(component.getClass());
        if (value == null) {
            if (log.isTraceEnabled()) {
                log.trace("MISS");
            }

            synchronized (AutoWire.class) {
                value = cache.get(component.getClass());
                if (value == null) {
                    List<Action> actions = getInstantiationActions(component);

                    value = new Value(actions);
                    cache.put(component.getClass(), value);
                }
            }
        }
        value.performInstantiationActions(component);
    }

    private List<Action> getInstantiationActions(Component component) {
        List<Action> actions = new ArrayList<Action>();

        if (isAutoWiringPossible(component)) {
            Set<String> done = new HashSet<String>();
            Class<?> clazz = component.getClass();
            // iterate over class hierarchy
            while (Component.class.isAssignableFrom(clazz)) {
                if (log.isTraceEnabled()) {
                    log.trace("looking for fields in class " + clazz);
                }
                // iterate over declared field
                for (final Field field : clazz.getDeclaredFields()) {
                    if (field.isAnnotationPresent(AutoComponent.class)) {
                        AutoComponent ann = field.getAnnotation(AutoComponent.class);
                        if (ann.inject()) {
                            final String id = ann.id().isEmpty() ? field.getName() : ann.id();
                            // fields in super classes are ignored, if they are in subclasses too
                            if (!done.contains(id)) {
                                done.add(id);
                                Component value = getValue(component, field);
                                if (value == null) {
                                    actions.add(new AssignInstanceAction(field, id));
                                } else {
                                    if (log.isTraceEnabled()) {
                                        log.trace(
                                                "Field " + field.getName() + " is already initialized. skipping.");
                                    }
                                }
                            }
                        }
                    }
                }
                clazz = clazz.getSuperclass();
            }
        }

        if (log.isTraceEnabled()) {
            log.trace("Actions: " + actions);
        }

        return actions;
    }

    private static Component getValue(Component component, Field field) {
        boolean accessible = field.isAccessible();
        field.setAccessible(true);
        try {
            Component value = (Component) field.get(component);
            field.setAccessible(accessible);
            return value;
        } catch (IllegalAccessException e) {
            return null;
        }
    }

    @Override
    public void onInitialize(final Component component) {
        if (isAutoWiringPossible(component)) {
            try {
                Value value = cache.get(component.getClass());
                value.performInitializeActions(component);
            } catch (final MarkupNotFoundException e) {
                return;
            }
        }
    }

    private boolean isAutoWiringPossible(final Component component) {
        return component instanceof MarkupContainer && !(component instanceof TransparentWebMarkupContainer);
    }

    private static Component buildComponent(Component component, final String id, Node child) {
        Class<?> clazz = component.getClass();
        while (Component.class.isAssignableFrom(clazz)) {
            try {
                Field field = null;
                Component value = null;
                // look for annotated field
                for (Field iter : clazz.getDeclaredFields()) {
                    if (iter.isAnnotationPresent(AutoComponent.class)) {
                        value = getValue(component, iter);
                        if (value != null && value.getId().equals(id)) {
                            child.field = iter;
                            break;
                        } else {
                            value = null;
                        }
                    }
                }
                if (value != null) {
                    return value;
                }
            } catch (final SecurityException e) {
                throw new RuntimeException(e);
            } catch (final IllegalArgumentException e) {
                throw new RuntimeException(e);
            }
            clazz = clazz.getSuperclass();
        }

        return null;
    }

    // set value on duplicated field of parent classes too!
    private static void setValue(Component instance, final Component component, Field field)
            throws IllegalAccessException {
        Class<?> clazz = field.getDeclaringClass();
        while (Component.class.isAssignableFrom(clazz)) {
            for (Field f : clazz.getDeclaredFields()) {
                if (f.getName().equals(field.getName())) {
                    boolean accessible = f.isAccessible();
                    f.setAccessible(true);
                    f.set(component, instance);
                    f.setAccessible(accessible);
                }
            }
            clazz = clazz.getSuperclass();
        }
    }

    private Component getInstance(final Class<?> componentClass, final Component enclosing, final String id)
            throws NoSuchMethodException, InstantiationException, IllegalAccessException, IllegalArgumentException,
            InvocationTargetException {
        if (componentClass.getEnclosingClass() == null || Modifier.isStatic(componentClass.getModifiers())) {
            // -- Static inner class or normal class
            final Constructor<?> constructor = componentClass.getDeclaredConstructor(String.class);
            constructor.setAccessible(true);
            return (Component) constructor.newInstance(id);
        } else {
            if (enclosing != null && componentClass.getEnclosingClass().isAssignableFrom(enclosing.getClass())) {
                final Constructor<?> constructor = componentClass
                        .getDeclaredConstructor(componentClass.getEnclosingClass(), String.class);
                constructor.setAccessible(true);
                return (Component) constructor.newInstance(enclosing, id);
            }
            throw new RuntimeException(
                    "Unable to initialize inner class " + componentClass.getClass().getSimpleName() + " with id "
                            + id + ". Enclosing class is not in the component hierarchy.");
        }
    }

    private static class Value {

        private List<Action> instantiationActions;
        private Map<String, Node> cache = new ConcurrentHashMap<String, Node>();

        public Value(List<Action> instantiationActions) {
            this.instantiationActions = instantiationActions;
        }

        public void performInstantiationActions(Component component) {
            for (Action action : instantiationActions) {
                action.perform(component);
            }
        }

        public void performInitializeActions(Component component) {
            final IMarkupFragment markup = ((MarkupContainer) component).getMarkup(null);

            if (markup == null) {
                return;
            }

            String key = markup.toString(false);
            Node node = cache.get(key);
            if (node == null) {
                if (log.isTraceEnabled()) {
                    log.trace("MARKUP MISS");
                }
                synchronized (AutoWire.class) {
                    node = cache.get(key);
                    if (node == null) {
                        node = getNode(component, markup);
                        cache.put(key, node);
                    }
                }
            }

            node.lastUsed = System.currentTimeMillis();
            node.initialize(component);

            cleanup();
        }

        // avoid memory leaks if makup is changing often.
        private void cleanup() {
            long treshold = System.currentTimeMillis() - 8 * 24 * 60 * 60 * 1000;
            if (cache.size() > 30) {
                for (Iterator<Entry<String, Node>> iterator = cache.entrySet().iterator(); iterator.hasNext();) {
                    Entry<String, Node> next = iterator.next();
                    if (next.getValue().lastUsed < treshold) {
                        iterator.remove();
                    }
                }
            }
        }

        private Node getNode(Component component, IMarkupFragment markup) {

            final MarkupStream stream = new MarkupStream(markup);

            final Stack<AtomicReference<Component>> stack = new Stack<AtomicReference<Component>>();
            stack.push(new AtomicReference<Component>(component));

            Node node = new Node();

            // detect borders.
            boolean addToBorder = false;

            if (log.isTraceEnabled()) {
                log.trace("Performing auto wiring for component " + component);
            }

            // no associated markup: component tag is part of the markup
            MarkupElement containerTag = null;
            // current criteria is fragile! find better way to check if component tag of component is part its markup.
            if (skipFirstComponentTag(component, stream)) {
                if (log.isTraceEnabled()) {
                    log.trace("Skipped component tag " + stream.get());
                }
                containerTag = stream.get();
                stream.next();
            }

            while (stream.skipUntil(ComponentTag.class)) {
                final ComponentTag tag = stream.getTag();

                if (log.isTraceEnabled()) {
                    log.trace("Processing tag " + tag);
                }

                // track border tags
                if (tag instanceof WicketTag) {
                    if (((WicketTag) tag).isBorderTag() && tag.isOpen()) {
                        addToBorder = true;
                    } else if (((WicketTag) tag).isBodyTag() && tag.isOpen()) {
                        addToBorder = false;
                    } else if (((WicketTag) tag).isBodyTag() && tag.isClose()) {
                        addToBorder = true;
                    } else if (((WicketTag) tag).isBorderTag() && tag.isClose()) {
                        addToBorder = false;
                    }
                }

                if (log.isTraceEnabled()) {
                    log.trace("addToBorder? " + addToBorder);
                }

                // maintain bread crumbs and build components
                if (isComponentTag(tag)) {
                    if (tag.isOpen() || tag.isOpenClose()) {
                        final Component container = stack.peek().get();
                        final Component cmp;
                        final Node child = new Node();

                        if (log.isTraceEnabled()) {
                            log.trace("Current parent component is " + container);
                        }
                        if (container == null) {
                            cmp = null;
                        } else {
                            cmp = buildComponent(component, tag.getId(), child);
                        }

                        if (log.isTraceEnabled()) {
                            log.trace("Resolved component is " + cmp + ". Adding to parent now.");
                        }

                        if (cmp != null) {
                            if (container instanceof MarkupContainer) {
                                if (addToBorder && container instanceof Border) {
                                    child.border = true;
                                } else {
                                    child.border = false;
                                }
                                child.id = cmp.getId();
                                node.add(child);
                            } else if (container == null) {
                                throw new RuntimeException(
                                        "component " + tag.getId() + " was auto wired, but its parent not!");
                            } else {
                                throw new RuntimeException("only containers may contain child elements. type of "
                                        + container + " is not a container!");
                            }
                        }
                        // push even if cmp is null, to track if parent is auto-wired
                        if (tag.isOpen() && !tag.hasNoCloseTag()) {
                            if (log.isTraceEnabled()) {
                                log.trace("Tag has a body. Adding to stack now.");
                            }
                            stack.push(new AtomicReference<Component>(cmp));
                            if (cmp != null) {
                                node = child;
                            }
                            if (log.isTraceEnabled()) {
                                log.trace("Current stack: " + stack);
                            }
                        }
                    } else if (tag.isClose() && !tag.getOpenTag().isAutoComponentTag()) {
                        // the container tag is part of the inherited markup. do not pop stack on container tag close.
                        if (containerTag == null || !tag.closes(containerTag)) {
                            if (log.isTraceEnabled()) {
                                log.trace("Tag is closing. Pop the stack now.");
                            }
                            if (stack.pop().get() != null) {
                                node = node.parent;
                            }
                            if (log.isTraceEnabled()) {
                                log.trace("Current stack: " + stack);
                            }
                        }
                    }
                }
                if (log.isTraceEnabled()) {
                    log.trace("--- Tag done. ---");
                }
                stream.next();
            }
            if (stack.size() != 1) {
                throw new RuntimeException("Stack must only contain one element " + stack);
            }

            return node;
        }

        private boolean skipFirstComponentTag(Component component, MarkupStream stream) {
            if (stream.get() instanceof ComponentTag
                    && ((ComponentTag) stream.get()).getId().equals(component.getId())) {
                return true;
            } else if (component instanceof ListItem) {
                return true;
            } else {
                return false;
            }
        }

        private boolean isComponentTag(ComponentTag tag) {
            return !(tag instanceof WicketTag) && !tag.isAutoComponentTag()
                    || tag.getName().equals(WicketContainerResolver.CONTAINER);
        }

    }

    private static class ComponentCache extends ConcurrentHashMap<Class<? extends Component>, Value> {

    }

    private static class Node {

        Node parent = null;
        Field field = null;
        List<Node> childNodes = new ArrayList<Node>();
        boolean border = false;
        public String id = null;
        long lastUsed = System.currentTimeMillis();

        public void add(Node child) {
            child.parent = this;
            childNodes.add(child);
        }

        @Override
        public String toString() {
            return "Node{" + "field=" + ((field != null) ? field.getName() : null) + ", childNodes=" + childNodes
                    + ", border=" + border + ", id='" + id + '\'' + '}';
        }

        public void initialize(Component component) {
            initialize(component, component);
        }

        private void initialize(Component root, Component parent) {
            for (Node child : childNodes) {
                Component value = getValue(root, child.field);
                if (child.border) {
                    ((Border) parent).addToBorder(value);
                } else {
                    ((MarkupContainer) parent).add(value);
                }
                if (!child.childNodes.isEmpty()) {
                    child.initialize(root, value);
                }
            }
        }

    }

    private interface Action {
        void perform(Component component);
    }

    private class AssignInstanceAction implements Action {

        private final Field field;
        private final String id;

        public AssignInstanceAction(Field field, String id) {
            this.field = field;
            this.id = id;
        }

        @Override
        public String toString() {
            return "Assign instance with id " + id + " to field " + field.getName();
        }

        @Override
        public void perform(Component component) {
            try {
                Component instance = getInstance(field.getType(), component, id);
                setValue(instance, component, field);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }
}