org.brushingbits.jnap.common.bean.visitor.BeanPropertyVisitor.java Source code

Java tutorial

Introduction

Here is the source code for org.brushingbits.jnap.common.bean.visitor.BeanPropertyVisitor.java

Source

/*
 * BeanPropertyVisitor.java created on 2010-07-01
 *
 * Created by Brushing Bits Labs
 * http://www.brushingbits.org
 *
 * Licensed 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 org.brushingbits.jnap.common.bean.visitor;

import java.beans.PropertyDescriptor;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.brushingbits.jnap.common.util.StringMatcher;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.util.Assert;

/**
 * @author Daniel Rochetti
 * @since 1.0
 */
public abstract class BeanPropertyVisitor {

    private static Log logger = LogFactory.getLog(BeanPropertyVisitor.class);

    protected static enum State {
        STILL, VISITING_ARRAY, VISITING_ARRAY_ITEM, VISITING_COLLECTION, VISITING_COLLECTION_ITEM, VISITING_MAP, VISITING_MAP_KEY, VISITING_MAP_VALUE, VISITING_BEAN_PROPERTY, VISITING_BEAN
    };

    protected BeanPropertyFilter propertyFilter;
    protected PropertyTypeResolver propertyTypeResolver;

    protected VisitorContext context;
    protected boolean preventCircularVisiting;
    protected List<Class<?>> standardTypes;

    /**
     * 
     * @param propertyFilter
     */
    public BeanPropertyVisitor(BeanPropertyFilter propertyFilter, PropertyTypeResolver propertyTypeResolver) {
        // validating arguments
        Assert.notNull(propertyFilter);
        Assert.notNull(propertyTypeResolver);

        this.propertyFilter = propertyFilter;
        this.context = new VisitorContext();
        this.preventCircularVisiting = true;

        // init type resolver (fallback to default if necessary)
        try {
            propertyTypeResolver.init();
            this.propertyTypeResolver = propertyTypeResolver;
        } catch (Exception e) {
            logger.warn("", e);
            this.propertyTypeResolver = new DefaultTypeResolver();
        }

        // standard types (default)
        this.standardTypes = new ArrayList<Class<?>>();
        this.standardTypes.add(String.class);
        this.standardTypes.add(Number.class);
        this.standardTypes.add(Boolean.class);
        this.standardTypes.add(Date.class);
        this.standardTypes.add(Calendar.class);
        this.standardTypes.add(URI.class);
        this.standardTypes.add(URL.class);
    }

    /**
     * 
     * @param propertyFilter
     */
    public BeanPropertyVisitor(BeanPropertyFilter propertyFilter) {
        this(propertyFilter, new HibernateTypeResolver());
    }

    /**
     * 
     */
    public BeanPropertyVisitor() {
        this(BeanPropertyFilter.getDefault());
    }

    protected abstract void visitBean(Object bean, Class<?> type);

    protected abstract void visitStandardProperty(Object property, Class<?> type);

    protected abstract void visitArray(Object[] array, Class<?> type);

    protected abstract void visitCollection(Collection<?> collection, Class<?> type);

    protected abstract void visitMap(Map<?, ?> map, Class<?> type);

    /**
     * 
     * @param source
     */
    public void visit(Object source) {
        logger.warn("Source object is null, there is nothing to visit!");
        if (source != null) {
            debug("Visiting object...");
            final String currentPath = getCurrentPath();
            debug("...current path is: " + currentPath);
            final Class<?> type = this.propertyTypeResolver.resolve(source, this.propertyFilter);
            debug("...current type is: " + type.getName());

            final boolean excluded = type == null || shouldExcludeProperty(currentPath) || shouldExcludeType(type)
                    || shouldExcludeAssignableType(type);
            debug("");
            final int currentLevel = context.getCurrentLevel();
            final boolean included = shouldIncludeProperty(currentPath) || currentLevel == -1;
            debug("");
            final boolean validDepth = propertyFilter.getDepth() == -1 || currentLevel <= propertyFilter.getDepth();
            debug("the current depth is " + currentLevel + ", max depth is " + propertyFilter.getDepth());
            if (included || (!excluded && validDepth)) {
                if (isStandardType(type)) {
                    handleStandardProperty(source, type);
                } else if (type.isArray()) {
                    handleArray((Object[]) source, type);
                } else if (Collection.class.isAssignableFrom(type)) {
                    handleCollection((Collection<?>) source, type);
                } else if (Map.class.isAssignableFrom(type)) {
                    handleMap((Map<?, ?>) source, type);
                } else {
                    // so, we assume it's a nested bean (let's go 'down' another level)
                    if (!context.wasAlreadyVisited(source) || !this.preventCircularVisiting) {
                        handleBean(source, type);
                    }
                }
            }
        }
    }

    protected void handleBean(Object source, Class<?> type) {
        context.nextLevel();
        visitBean(source, type);
        BeanWrapper sourceBean = PropertyAccessorFactory.forBeanPropertyAccess(source);
        PropertyDescriptor[] beanProperties = BeanUtils.getPropertyDescriptors(type);
        for (PropertyDescriptor propertyDescriptor : beanProperties) {
            String name = propertyDescriptor.getName();
            context.pushPath(name);
            if (sourceBean.isReadableProperty(name)) {
                Object value = sourceBean.getPropertyValue(name);
                visit(value);
            }
            context.popPath();
        }
        context.prevLevel();
    }

    protected void handleStandardProperty(Object property, Class<?> type) {
        visitStandardProperty(property, type);
    }

    protected void handleArray(Object[] array, Class<?> type) {
        context.setCurrentState(State.VISITING_ARRAY);
        visitArray(array, type);
        for (Object item : array) {
            context.setCurrentState(State.VISITING_ARRAY_ITEM);
            visit(item);
        }
    }

    protected void handleCollection(Collection<?> collection, Class<?> type) {
        context.setCurrentState(State.VISITING_COLLECTION);
        visitCollection(collection, type);
        for (Object item : collection) {
            context.setCurrentState(State.VISITING_COLLECTION_ITEM);
            visit(item);
        }
    }

    protected void handleMap(Map<?, ?> map, Class<?> type) {
        context.setCurrentState(State.VISITING_MAP);
        visitMap(map, type);
        for (Object key : map.keySet()) {
            context.setCurrentState(State.VISITING_MAP_KEY);
            visit(key);
            context.setCurrentState(State.VISITING_MAP_VALUE);
            visit(map.get(key));
        }
    }

    protected boolean isStandardType(Class<?> type) {
        boolean standard = type.isPrimitive() || type.isEnum();
        if (!standard) {
            for (Class<?> standardType : this.standardTypes) {
                standard = standardType.isAssignableFrom(type);
                if (standard) {
                    break;
                }
            }
        }
        return standard;
    }

    /**
     * 
     * @param type
     */
    public void registerStandardTypes(Class<?>... type) {
        this.standardTypes.addAll(Arrays.asList(type));
    }

    /**
     * <code>Mutator</code> ("setter") method for property <code>standardTypes</code>.
     */
    public void setStandardTypes(List<Class<?>> standardTypes) {
        this.standardTypes = standardTypes;
    }

    protected String getCurrentPath() {
        StringBuilder path = new StringBuilder();
        for (Iterator<String> iter = context.getCurrentPath().descendingIterator(); iter.hasNext();) {
            String pathElement = iter.next();
            path.append(pathElement);
            if (iter.hasNext()) {
                path.append(".");
            }
        }
        return path.toString();
    }

    protected boolean shouldExcludeProperty(String path) {
        return StringMatcher.match(path, propertyFilter.getExcludeProperties().toArray(new String[] {}));
    }

    protected boolean shouldExcludeType(Class<?> type) {
        return StringMatcher.match(type.getName(), propertyFilter.getExcludeTypes().toArray(new String[] {}));
    }

    protected boolean shouldExcludeAssignableType(Class<?> type) {
        boolean exclude = false;
        List<Class<?>> excludeAssignableTypes = propertyFilter.getExcludeAssignableTypes();
        for (Class<?> excludeType : excludeAssignableTypes) {
            if (excludeType.isAssignableFrom(type)) {
                exclude = true;
                break;
            }
        }
        return exclude;
    }

    protected boolean shouldIncludeProperty(String path) {
        return StringMatcher.match(path, propertyFilter.getIncludeProperties().toArray(new String[] {}));
    }

    /**
     * Local logger debug message.
     * @param msg Debug message.
     */
    protected void debug(String msg) {
        if (logger.isDebugEnabled()) {
            logger.debug(msg);
        }
    }

    /**
     * 
     * @author Daniel Rochetti
     * @since 1.0
     */
    protected class VisitorContext {

        private int currentLevel = -1;
        private State currentState = State.STILL;
        private Deque<String> currentPath;
        private List<Object> alreadyVisited;

        public int getCurrentLevel() {
            return currentLevel;
        }

        public void nextLevel() {
            this.currentLevel += 1;
        }

        public void prevLevel() {
            this.currentLevel -= 1;
        }

        public Deque<String> getCurrentPath() {
            return currentPath;
        }

        public String getCurrentPathRepresentation() {
            StringBuilder path = new StringBuilder();
            for (Iterator<String> iter = currentPath.descendingIterator(); iter.hasNext();) {
                path.append(iter.next());
                if (iter.hasNext()) {
                    path.append(".");
                }
            }
            return path.toString();
        }

        public String getCurrentPropertyName() {
            return currentPath.getFirst();
        }

        public void pushPath(String path) {
            this.currentPath.addFirst(path);
        }

        public void popPath() {
            this.currentPath.removeFirst();
        }

        public void setAsVisited(Object bean) {
            this.alreadyVisited.add(bean);
        }

        public boolean wasAlreadyVisited(Object bean) {
            return this.alreadyVisited.contains(bean);
        }

        public BeanPropertyVisitor.State getCurrentState() {
            return currentState;
        }

        public void setCurrentState(BeanPropertyVisitor.State currentState) {
            this.currentState = currentState;
        }

    }

}