/** * Copyright (C) 2012 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.web.analytics.blotter; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.lang.StringUtils; import org.joda.beans.Bean; import org.joda.beans.MetaBean; import org.joda.beans.MetaProperty; import com.google.common.collect.Lists; import com.opengamma.OpenGammaRuntimeException; import com.opengamma.util.ArgumentChecker; /** * TODO is there a generally useful way to have pluggable handlers to override default behaviour for specific properties? * or would that have to be done in the visitors? * could also handle it by property name instead of using the metaproperty */ /* package */ class BeanTraverser { /** Decorators that are applied to the visitor in the {@link #traverse} method. */ private final List<BeanVisitorDecorator> _decorators; /* package */ BeanTraverser() { _decorators = Collections.emptyList(); } /* package */ BeanTraverser(BeanVisitorDecorator... decorators) { _decorators = Arrays.asList(decorators); // first decorator in the list should be on the outside, need to reverse before wrapping Collections.reverse(_decorators); } /* package */ Object traverse(MetaBean metaBean, BeanVisitor<?> visitor) { BeanVisitor<?> decoratedVisitor = decorate(visitor); decoratedVisitor.visitMetaBean(metaBean); List<BeanTraversalFailure> failures = Lists.newArrayList(); for (MetaProperty<?> property : metaBean.metaPropertyIterable()) { Class<?> propertyType = property.propertyType(); try { if (Bean.class.isAssignableFrom(propertyType)) { decoratedVisitor.visitBeanProperty(property, this); } else if (Set.class.isAssignableFrom(propertyType)) { decoratedVisitor.visitSetProperty(property, this); } else if (List.class.isAssignableFrom(propertyType)) { decoratedVisitor.visitListProperty(property, this); } else if (Collection.class.isAssignableFrom(propertyType)) { decoratedVisitor.visitCollectionProperty(property, this); } else if (Map.class.isAssignableFrom(propertyType)) { decoratedVisitor.visitMapProperty(property, this); } else { decoratedVisitor.visitProperty(property, this); } } catch (Exception e) { failures.add(new BeanTraversalFailure(e, property)); } } if (failures.isEmpty()) { return decoratedVisitor.finish(); } else { throw new BeanTraversalException(metaBean, visitor, failures); } } private BeanVisitor<?> decorate(BeanVisitor<?> visitor) { BeanVisitor<?> decoratedVisitor = visitor; for (BeanVisitorDecorator decorator : _decorators) { decoratedVisitor = decorator.decorate(decoratedVisitor); } return decoratedVisitor; } } /** * Wraps a property and the exception that occurred when the traverser tried to visit the property. */ /* package */ class BeanTraversalFailure { /** The exception triggered by the attempted visit. */ private final Exception _exception; /** The visited property. */ private final MetaProperty<?> _property; /* package */ BeanTraversalFailure(Exception exception, MetaProperty<?> property) { ArgumentChecker.notNull(exception, "exception"); ArgumentChecker.notNull(property, "property"); _exception = exception; _property = property; } /* package */ Exception getException() { return _exception; } @Override public String toString() { String message = _exception.getMessage() == null ? null : "'" + _exception.getMessage() + "'"; return "[" + _property.toString() + ", " + message + "]"; } } /** * Exception thrown after a bean traversal that threw exceptions. All exceptions thrown during traversal are added * to this exception as {@link #addSuppressed suppressed} exceptions. */ /* package */ class BeanTraversalException extends OpenGammaRuntimeException { /** Serialization version. */ private static final long serialVersionUID = -3048022694152981946L; /* package */ BeanTraversalException(MetaBean metaBean, BeanVisitor<?> visitor, List<BeanTraversalFailure> failures) { super(buildMessage(metaBean, visitor, failures)); for (BeanTraversalFailure failure : failures) { addSuppressed(failure.getException()); } } private static String buildMessage(MetaBean metaBean, BeanVisitor<?> visitor, List<BeanTraversalFailure> failures) { ArgumentChecker.notNull(metaBean, "metaBean"); ArgumentChecker.notEmpty(failures, "failures"); ArgumentChecker.notNull(visitor, "visitor"); return "Bean traversal failed. " + "bean: " + metaBean + ", " + "visitor: " + visitor + ", " + "failures: [" + StringUtils.join(failures, ", ") + "]"; } }