/** * Copyright (C) 2015 Valkyrie RCP * * 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.valkyriercp.application.exceptionhandling; import com.google.common.base.Strings; import org.springframework.context.support.DefaultMessageSourceResolvable; import org.springframework.core.ErrorCoded; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.StringTokenizer; /** * Displays a message to the user which is fetched from the I18N files * based on the class and superclasses of the throwable. * <p/> * For example if an IllegalArgumentException is thrown, it will search for * java.lang.IllegalArgumentException.caption and java.lang.IllegalArgumentException.description first, * and if it cant find that it will try in order: * java.lang.RuntimeException.caption/description, java.lang.Exception.caption/description and * java.lang.Throwable.caption/description. * <p/> * The exception message is passed as a parameter, but is idented and wrapped first. * Note for the repacing of {0} to work in a property file double quotes(") need to be escaped (\") * and that single quotes (') should be avoided (escaping doesn't seem to work). * @author Geoffrey De Smet * @since 0.3 */ public class MessagesDialogExceptionHandler<SELF extends MessagesDialogExceptionHandler<SELF>> extends AbstractDialogExceptionHandler<SELF> { private int wrapLength = 120; private int identLength = 2; private String messagesKey = null; /** * Sets the wrap length applied on the exception message passed as a parameter. * Defaults to 120. * @param wrapLength */ public void setWrapLength(int wrapLength) { this.wrapLength = wrapLength; } public SELF wrappingAt(int wrapLength) { setWrapLength(wrapLength); return self(); } /** * Sets the identation applied on the exception message passed as a parameter. * Defaults to 2. * @param identLength */ public void setIdentLength(int identLength) { this.identLength = identLength; } public SELF withIdentLength(int identLength) { setIdentLength(identLength); return self(); } /** * If messagesKey is set, the caption and description shown in the dialog * are not based dynamically on the throwable, * but instead statically on the keys messageKey.caption and messageKey.description. * * @param messagesKey the key used for the caption and title */ public void setMessagesKey(String messagesKey) { this.messagesKey = messagesKey; } public SELF withMessagesKey(String messagesKey) { setMessagesKey(messagesKey); return self(); } public String resolveExceptionCaption(Throwable throwable) { String[] messagesKeys = getMessagesKeys(throwable, ".caption"); return getApplicationConfig().messageSourceAccessor().getMessage(new DefaultMessageSourceResolvable( messagesKeys, messagesKeys[0])); } public Object createExceptionContent(Throwable throwable) { String[] messagesKeys = getMessagesKeys(throwable, ".description"); String[] parameters = new String[]{ formatMessage(throwable.getMessage()) }; return getApplicationConfig().messageSourceAccessor().getMessage(new DefaultMessageSourceResolvable( messagesKeys, parameters, messagesKeys[0])); } protected String[] getMessagesKeys(Throwable throwable, String keySuffix) { if (messagesKey != null) { return new String[] {messagesKey}; } List<String> messageKeyList = new ArrayList<String>(); Class clazz = throwable.getClass(); if (throwable instanceof ErrorCoded) { messageKeyList.add(((ErrorCoded) throwable).getErrorCode() + keySuffix); } if (throwable instanceof SQLException) { messageKeyList.add(SQLException.class.getName() + "." + ((SQLException) throwable).getErrorCode() + keySuffix); } while (clazz != Object.class) { messageKeyList.add(clazz.getName() + keySuffix); clazz = clazz.getSuperclass(); } return messageKeyList.toArray(new String[messageKeyList.size()]); } protected String formatMessage(String message) { if (message == null) { return ""; } String identString = Strings.padStart("", identLength, ' '); String newLineWithIdentString = "\n" + identString; StringBuilder formattedMessageBuilder = new StringBuilder(identString); StringTokenizer messageTokenizer = new StringTokenizer(message, "\n"); while (messageTokenizer.hasMoreTokens()) { String messageToken = messageTokenizer.nextToken(); formattedMessageBuilder.append(wrap(messageToken, wrapLength)); if (messageTokenizer.hasMoreTokens()) { formattedMessageBuilder.append(newLineWithIdentString); } } return formattedMessageBuilder.toString(); } private static String wrap(String str, int wrapLength) { int offset = 0; StringBuilder resultBuilder = new StringBuilder(); while ((str.length() - offset) > wrapLength) { if (str.charAt(offset) == ' ') { offset++; continue; } int spaceToWrapAt = str.lastIndexOf(' ', wrapLength + offset); // if the next string with length maxLength doesn't contain ' ' if (spaceToWrapAt < offset) { spaceToWrapAt = str.indexOf(' ', wrapLength + offset); // if no more ' ' if (spaceToWrapAt < 0) { break; } } resultBuilder.append(str.substring(offset, spaceToWrapAt)); resultBuilder.append("\n"); offset = spaceToWrapAt + 1; } resultBuilder.append(str.substring(offset)); return resultBuilder.toString(); } }