| /* |
| * Copyright 2008 Niclas Hedhman. All rights Reserved. |
| * |
| * 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.qi4j.api.constraint; |
| |
| import java.lang.annotation.Annotation; |
| import java.lang.reflect.Member; |
| import java.text.MessageFormat; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Locale; |
| import java.util.MissingResourceException; |
| import java.util.ResourceBundle; |
| import org.qi4j.api.Qi4j; |
| import org.qi4j.api.composite.Composite; |
| import org.qi4j.functional.Function; |
| import org.qi4j.functional.Iterables; |
| |
| /** |
| * This Exception is thrown when there is one or more Constraint Violations in a method |
| * call. |
| * <p> |
| * The Constraint Violations are aggregated per method, and this exception will contain those |
| * violations, together with the Composite instance it happened on as well as the Method that |
| * was invoked. The Exception also has support for localized messages of these violations. |
| * </p> |
| */ |
| public class ConstraintViolationException |
| extends IllegalArgumentException |
| { |
| private static final long serialVersionUID = 1L; |
| |
| private final Collection<ConstraintViolation> constraintViolations; |
| private String methodName; |
| private String mixinTypeName; |
| private String instanceToString; |
| private Iterable<Class<?>> instanceTypes; |
| |
| public ConstraintViolationException( Composite instance, Member method, |
| Collection<ConstraintViolation> constraintViolations |
| ) |
| { |
| this( instance.toString(), Qi4j.FUNCTION_DESCRIPTOR_FOR.map( instance ).types(), method, constraintViolations ); |
| } |
| |
| public ConstraintViolationException( String instanceToString, |
| Iterable<Class<?>> instanceTypes, |
| Member method, |
| Collection<ConstraintViolation> violations |
| ) |
| { |
| this.instanceToString = instanceToString; |
| this.instanceTypes = instanceTypes; |
| mixinTypeName = method.getDeclaringClass().getName(); |
| methodName = method.getName(); |
| this.constraintViolations = violations; |
| } |
| |
| public ConstraintViolationException( String instanceToString, |
| Iterable<Class<?>> instanceTypes, |
| String mixinTypeName, |
| String methodName, |
| Collection<ConstraintViolation> violations |
| ) |
| { |
| this.instanceToString = instanceToString; |
| this.instanceTypes = instanceTypes; |
| this.mixinTypeName = mixinTypeName; |
| this.methodName = methodName; |
| this.constraintViolations = violations; |
| } |
| |
| public Collection<ConstraintViolation> constraintViolations() |
| { |
| return constraintViolations; |
| } |
| |
| /** |
| * Creates localized messages of all the constraint violations that has occured. |
| * <p> |
| * The key "<code>Qi4j_ConstraintViolation_<i><strong>CompositeType</strong></i></code>" will be used to lookup the text formatting |
| * pattern from the ResourceBundle, where <strong><code><i>CompositeType</i></code></strong> is the |
| * class name of the Composite where the constraint was violated. If such key does not exist, then the |
| * key "<code>Qi4j_ConstraintViolation</code>" will be used, and if that one also doesn't exist, or |
| * the resourceBundle argument is null, then the default patterns will be used; |
| * </p> |
| * <table summary="Localization of constraint vioations."> |
| * <tr><th>Type of Composite</th><th>Pattern used</th></tr> |
| * <tr><td>Composite</td> |
| * <td><code>Constraint Violation in {2}.{3} with constraint {4}, in composite \n{0} of type {1}</code></td> |
| * </tr> |
| * <tr><td>EntityComposite</td> |
| * <td><code>Constraint Violation in {2}.{3} with constraint {4}, in entity {1}[id={0}]</code></td> |
| * </tr> |
| * <tr><td>ServiceComposite</td> |
| * <td><code>Constraint Violation in {2}.{3} with constraint {4}, in service {0}</code></td> |
| * </tr> |
| * </table> |
| * Then format each ConstraintViolation according to such pattern, where the following argument are passed; |
| * <table summary="List of arguments available."><tr><th>Arg</th><th>Value</th></tr> |
| * <tr> |
| * <td>{0}</td> |
| * <td>Composite instance toString()</td> |
| * </tr> |
| * <tr> |
| * <td>{1}</td> |
| * <td>CompositeType class name</td> |
| * </tr> |
| * <tr> |
| * <td>{2}</td> |
| * <td>MixinType class name</td> |
| * </tr> |
| * <tr> |
| * <td>{3}</td> |
| * <td>MixinType method name</td> |
| * </tr> |
| * <tr> |
| * <td>{4}</td> |
| * <td>Annotation toString()</td> |
| * </tr> |
| * <tr> |
| * <td>{5}</td> |
| * <td>toString() of value passed as the argument, or "null" text if argument was null.</td> |
| * </tr> |
| * </table> |
| * <p> |
| * <b>NOTE!!!</b> This class is still under construction and will be modified further. |
| * </p> |
| * |
| * @param bundle The ResourceBundle for Localization, or null if default formatting and locale to be used. |
| * |
| * @return An array of localized messages of the violations incurred. |
| */ |
| public String[] localizedMessagesFrom( ResourceBundle bundle ) |
| { |
| String pattern = "Constraint violation in {0}.{1} for method ''{3}'' with constraint \"{4}({6})\", for value ''{5}''"; |
| |
| ArrayList<String> list = new ArrayList<String>(); |
| for( ConstraintViolation violation : constraintViolations ) |
| { |
| Locale locale; |
| if( bundle != null ) |
| { |
| try |
| { |
| pattern = bundle.getString( "qi4j.constraint." + mixinTypeName + "." + methodName ); |
| } |
| catch( MissingResourceException e1 ) |
| { |
| try |
| { |
| pattern = bundle.getString( "qi4j.constraint" ); |
| } |
| catch( MissingResourceException e2 ) |
| { |
| // ignore. The default pattern will be used. |
| } |
| } |
| locale = bundle.getLocale(); |
| } |
| else |
| { |
| locale = Locale.getDefault(); |
| } |
| MessageFormat format = new MessageFormat( pattern, locale ); |
| |
| Annotation annotation = violation.constraint(); |
| String name = violation.name(); |
| Object value = violation.value(); |
| String classes; |
| if( Iterables.count( instanceTypes ) == 1 ) |
| { |
| classes = Iterables.first( instanceTypes ).getSimpleName(); |
| } |
| else |
| { |
| classes = "[" + Iterables.<Class<?>>toString( instanceTypes, new Function<Class<?>, String>() |
| { |
| @Override |
| public String map( Class<?> from ) |
| { |
| return from.getSimpleName(); |
| } |
| }, "," ) + "]"; |
| } |
| Object[] args = new Object[] |
| { |
| instanceToString, |
| classes, |
| mixinTypeName, |
| methodName, |
| annotation.toString(), |
| "" + value, |
| name |
| }; |
| StringBuffer text = new StringBuffer(); |
| format.format( args, text, null ); |
| list.add( text.toString() ); |
| } |
| String[] result = new String[ list.size() ]; |
| list.toArray( result ); |
| return result; |
| } |
| |
| public String localizedMessage() |
| { |
| String[] messages = localizedMessagesFrom( null ); |
| StringBuilder result = new StringBuilder(); |
| boolean first = true; |
| for( String message : messages ) |
| { |
| if( !first ) |
| { |
| result.append( ',' ); |
| } |
| first = false; |
| result.append( message ); |
| } |
| return result.toString(); |
| } |
| |
| @Override |
| public String getLocalizedMessage() |
| { |
| return localizedMessage(); |
| } |
| |
| @Override |
| public String getMessage() |
| { |
| return localizedMessage(); |
| } |
| |
| public String methodName() |
| { |
| return methodName; |
| } |
| |
| public String mixinTypeName() |
| { |
| return mixinTypeName; |
| } |
| } |