/* * Copyright 2006 the original author or authors. * * 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.springmodules.xt.model.specifications.composite; import java.lang.reflect.Method; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import org.springmodules.xt.model.notification.Message; import org.springmodules.xt.model.notification.Notification; import org.springmodules.xt.model.specifications.Specification; import org.springmodules.xt.model.specifications.composite.operator.BinaryOperator; import org.springmodules.xt.model.specifications.composite.operator.OperatorFactory; import org.springmodules.xt.model.specifications.AbstractSpecification; import org.springmodules.xt.model.specifications.support.InverseSpecification; import org.springmodules.xt.model.specifications.support.SpecificationGenericAdapter; import org.springmodules.xt.model.specifications.support.SpecificationDescriptionException; import org.springmodules.xt.model.specifications.support.SpecificationNotComposedException; import org.apache.commons.lang.builder.EqualsBuilder; import org.apache.commons.lang.builder.HashCodeBuilder; /** * {@link CompositeSpecification} implementation.<br> * You can use whatever specification class you implement, you have one only constraint: your specification method * (i.e. "isSatisfiedBy" or "validate" or so) <b>must not</b> be overloaded. * * @author Sergio Bossa */ public class CompositeSpecificationImpl<S, O> extends AbstractSpecification<O> implements CompositeSpecification<S, O> { private Class<S> specificationClass; private Method specificationMethod; private OperatorFactory operatorFactory = OperatorFactory.getInstance(); private List<Specification> specificationsList = new LinkedList<Specification>(); private List<BinaryOperator> operatorsList = new LinkedList<BinaryOperator>(); /** * Composite specification constructor: here you have to provide the <i>description</i> of the actual specification class * you want to compose.<br> * * @param specificationClass The class of the actual specification to compose. * @param specificationMethod The actual specification method to call for evaluation. */ public CompositeSpecificationImpl(Class<S> specificationClass, String specificationMethod) { this.specificationClass = specificationClass; try { Method[] methods = this.specificationClass.getDeclaredMethods(); for (Method m : methods) { if (m.getName().equals(specificationMethod)) { this.specificationMethod = m; break; } } if (this.specificationMethod == null) { throw new SpecificationDescriptionException("Wrong specification method: " + specificationMethod); } } catch(SecurityException ex) { throw new SpecificationDescriptionException("Wrong specification method: " + specificationMethod); } } /** * Start composing the specification.<br> * This is the first method to be called for composition. * * @param specification The actual specification to compose. */ public CompositeSpecification compose(S specification) { this.specificationsList.clear(); this.operatorsList.clear(); this.specificationsList.add(this.adaptSpecification(specification)); return this; } /** * Apply the logical <code>and</code> operator to this composite specification and the supplied one. * @param specification The supplied specification to compose. */ public CompositeSpecification and(S specification) { if (this.specificationsList.isEmpty()) { throw new SpecificationNotComposedException("You have to compose your specification first."); } this.operatorsList.add(this.operatorFactory.getAndOperator()); this.specificationsList.add(this.adaptSpecification(specification)); return this; } /** * Apply the logical <code>or</code> operator to this composite specification and the supplied one. * @param specification The supplied specification to compose. */ public CompositeSpecification or(S specification) { if (this.specificationsList.isEmpty()) { throw new SpecificationNotComposedException("You have to compose your specification first."); } this.operatorsList.add(this.operatorFactory.getOrOperator()); this.specificationsList.add(this.adaptSpecification(specification)); return this; } /** * Apply the logical <code>and</code> operator to this composite specification and another, supplied, composite specificaton. * @param specification The other composite specification. */ public CompositeSpecification and(CompositeSpecification<S, O> specification) { if (!specification.equals(this)) { throw new IllegalArgumentException("You cannot compose specifications with different descriptions."); } if (this.specificationsList.isEmpty()) { throw new SpecificationNotComposedException("You have to compose your specification first."); } this.operatorsList.add(this.operatorFactory.getAndOperator()); this.specificationsList.add(specification); return this; } /** * Apply the logical <code>or</code> operator to this composite specification and another, supplied, composite specificaton. * @param specification The other composite specification. */ public CompositeSpecification or(CompositeSpecification<S, O> specification) { if (!specification.equals(this)) { throw new IllegalArgumentException("You cannot compose specifications with different descriptions."); } if (this.specificationsList.isEmpty()) { throw new SpecificationNotComposedException("You have to compose your specification first."); } this.operatorsList.add(this.operatorFactory.getOrOperator()); this.specificationsList.add(specification); return this; } /** * Apply the logical, negated, <code>and</code> operator to this composite specification and the supplied one. * @param specification The supplied specification to compose. */ public CompositeSpecification andNot(S specification) { if (this.specificationsList.isEmpty()) { throw new SpecificationNotComposedException("You have to compose your specification first."); } this.operatorsList.add(this.operatorFactory.getAndOperator()); this.specificationsList.add(new InverseSpecification(this.adaptSpecification(specification))); return this; } /** * Apply the logical, negated, <code>or</code> operator to this composite specification and the supplied one. * @param specification The actual specification to compose. */ public CompositeSpecification orNot(S specification) { if (this.specificationsList.isEmpty()) { throw new SpecificationNotComposedException("You have to compose your specification first."); } this.operatorsList.add(this.operatorFactory.getOrOperator()); this.specificationsList.add(new InverseSpecification(this.adaptSpecification(specification))); return this; } /** * Apply the logical, negated, <code>and</code> operator to this composite specification and another, supplied, composite specificaton. * @param specification The other composite specification. */ public CompositeSpecification andNot(CompositeSpecification<S, O> specification) { if (!specification.equals(this)) { throw new IllegalArgumentException("You cannot compose specifications with different descriptions."); } if (this.specificationsList.isEmpty()) { throw new SpecificationNotComposedException("You have to compose your specification first."); } this.operatorsList.add(this.operatorFactory.getAndOperator()); this.specificationsList.add(new InverseSpecification(specification)); return this; } /** * Apply the logical, negated, <code>or</code> operator to this composite specification and another, supplied, composite specificaton. * @param specification The other composite specification. */ public CompositeSpecification orNot(CompositeSpecification<S, O> specification) { if (!specification.equals(this)) { throw new IllegalArgumentException("You cannot compose specifications with different descriptions."); } if (this.specificationsList.isEmpty()) { throw new SpecificationNotComposedException("You have to compose your specification first."); } this.operatorsList.add(this.operatorFactory.getOrOperator()); this.specificationsList.add(new InverseSpecification(specification)); return this; } public CompositeSpecification withMessage(Message message, boolean whenSatisfied) { Specification lastSpecification = this.specificationsList.get(this.specificationsList.size() - 1); lastSpecification.addMessage(message, whenSatisfied); return this; } public boolean equals(Object obj) { if (obj == null) return false; if (! (obj instanceof CompositeSpecificationImpl)) { return false; } else { CompositeSpecificationImpl otherSpec = (CompositeSpecificationImpl) obj; EqualsBuilder builder = new EqualsBuilder(); return builder.append(this.specificationClass, otherSpec.specificationClass) .append(this.specificationMethod, otherSpec.specificationMethod) .isEquals(); } } public int hashCode() { HashCodeBuilder builder = new HashCodeBuilder(); return builder.append(this.specificationClass) .append(this.specificationMethod) .toHashCode(); } protected boolean internalEvaluate(O object, Notification notification) { if (this.specificationsList.isEmpty()) { throw new SpecificationNotComposedException("You have to compose your specification first."); } Iterator<Specification> specificationsIt = this.specificationsList.iterator(); boolean result = specificationsIt.next().evaluate(object, notification); for (BinaryOperator op : operatorsList) { result = op.evaluate(specificationsIt.next(), result, object, notification); } return result; } private Specification adaptSpecification(S specification) { return new SpecificationGenericAdapter(specification, this.specificationMethod); } }