001/* 
002 * JKNIV, utils - Helper utilities for jdk code.
003 * 
004 * Copyright (C) 2017, the original author or authors.
005 *
006 * This library is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 2.1 of the License.
010 * 
011 * This library is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014 * Lesser General Public License for more details.
015 * 
016 * You should have received a copy of the GNU Lesser General Public
017 * License along with this library; if not, write to the Free Software Foundation, Inc., 
018 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
019 */
020package net.sf.jkniv.exception;
021
022import java.lang.reflect.Constructor;
023import java.util.HashMap;
024import java.util.Map;
025
026import org.slf4j.Logger;
027import org.slf4j.LoggerFactory;
028
029import net.sf.jkniv.asserts.Assertable;
030import net.sf.jkniv.asserts.AssertsFactory;
031
032/**
033 * 
034 * @author Alisson Gomes
035 * @since 0.6.0
036 */
037public class HandlerException implements HandleableException
038{
039    private final static Logger               LOG     = LoggerFactory.getLogger(HandlerException.class);
040    private final static Assertable           notNull = AssertsFactory.getNotNull();
041    private Map<Class<?>, MapException>       exceptions;
042    private Class<? extends RuntimeException> defaultException;
043    private String                            defaultMessage;
044    private boolean                           mute;
045    private boolean                           enableLogInfo;
046    
047    public HandlerException()
048    {
049        this(RuntimeException.class, "%s", false);
050    }
051    
052    public HandlerException(boolean mute)
053    {
054        this(RuntimeException.class, "%s", mute);
055    }
056    
057    /**
058     * Build a new handler exception where default Runtime exception is {@code RuntimeException}.
059     * and your default message is {@code message}
060     * @param message default message
061     */
062    public HandlerException(String message)
063    {
064        this(RuntimeException.class, message, false);
065    }
066    
067    /**
068     * Build a new handler exception where default Runtime exception is {@code defaultException}.
069     * and your default message is {@code message}
070     * @param defaultException default runtime exception when exception is catch for {@code try / catch} block.
071     * @param message default message
072     */
073    public HandlerException(Class<? extends RuntimeException> defaultException, String message)
074    {
075        this(defaultException, message, false);
076    }
077    
078    public HandlerException(Class<? extends RuntimeException> defaultException, String message, boolean mute)
079    {
080        notNull.verify(defaultException, message);
081        this.exceptions = new HashMap<Class<?>, MapException>();
082        this.defaultException = defaultException;
083        this.defaultMessage = message;
084        this.mute = mute;
085        this.enableLogInfo = false;
086        //config(defaultException, defaultException, message);
087    }
088    
089    @Override
090    public HandleableException config(Class<? extends Exception> caught, Class<? extends RuntimeException> translateTo,
091            String message)
092    {
093        if (this.exceptions.containsKey(caught))
094            throw new UnsupportedOperationException(
095                    "Already exist an exception configured to exception [" + caught.getName() + "]");
096        
097        MapException map = new MapException(caught, translateTo, message, false);
098        this.exceptions.put(caught, map);
099        return this;
100    }
101    
102    @Override
103    public HandleableException config(Class<? extends Exception> caught, String message)
104    {
105        if (this.exceptions.containsKey(caught))
106            throw new UnsupportedOperationException(
107                    "Already exist an exception configured to exception [" + caught.getName() + "]");
108        
109        MapException map = new MapException(caught, this.defaultException, message, false);
110        this.exceptions.put(caught, map);
111        return this;
112    }
113    
114    @Override
115    public void handle(Exception caught)
116    {
117        if (this.defaultException.isAssignableFrom(caught.getClass()))
118            throw (RuntimeException) caught;
119        
120        RuntimeException theException = prepareToThrowException(null, caught);
121        if (theException != null)
122            throw theException;
123    }
124    
125    @Override
126    public void handle(Exception caught, String customMessage)
127    {
128        if (this.defaultException.isAssignableFrom(caught.getClass()))
129            throw (RuntimeException) caught;
130        
131        RuntimeException theException = prepareToThrowException(customMessage, caught);
132        if (theException != null)
133            throw theException;
134    }
135    
136    @Override
137    public void throwMessage(String message, Object... args)
138    {
139        String msg = String.format(message, args);
140        RuntimeException re = (RuntimeException) prepareToThrowException(msg, null);
141        throw re;
142    }
143    
144    private RuntimeException prepareToThrowException(String customMessage, Exception caught)
145    {
146        RuntimeException theException = null;
147        if (!isMute() && !isMute(caught))
148        {
149            MapException theMappedException = getMappedException(customMessage, caught);
150            /*
151             * TODO test me when caught instance of the mapped exception
152             */
153            try
154            {
155                Constructor<? extends RuntimeException> constructor = theMappedException.getTranslate()
156                        .getConstructor(String.class, Throwable.class);
157                theException = constructor
158                        .newInstance(buildMessage(theMappedException.getMessage(), customMessage, caught), caught);
159                if (caught != null)//  TODO alternative method when caught is null
160                    theException.setStackTrace(caught.getStackTrace());
161            }
162            catch (Exception e)
163            {
164                theException = new RuntimeException(customMessage, caught);
165                if (caught != null)//  TODO alternative method when caught is null
166                    theException.setStackTrace(caught.getStackTrace());
167            }
168        }
169        else if (enableLogInfo){
170            LOG.info("Be careful the Handler exception is mute for configured exceptions [{}], message: {}",
171                    caught.getClass().getName(), caught.getMessage());
172        }
173        return theException;
174    }
175    
176    private MapException getMappedException(String message, Exception rootCause)
177    {
178        Class<? extends Exception> theException = null;
179        String theMessage = null;
180        MapException theMappedException = null;
181        if (message == null)
182            theMessage = this.defaultMessage;
183        else
184            theMessage = message;
185        
186        if (rootCause == null)
187            theException = this.defaultException;
188        else
189        {
190            theException = rootCause.getClass();
191            theMappedException = this.exceptions.get(rootCause.getClass());
192        }
193        
194        if (theMappedException == null)
195        {
196            theMappedException = new MapException(theException, this.defaultException, theMessage, false);
197        }
198        return theMappedException;
199    }
200    
201    @Override
202    public Class<? extends RuntimeException> getDefaultException()
203    {
204        return defaultException;
205    }
206    
207    @Override
208    public HandleableException mute()
209    {
210        this.mute = true;
211        return this;
212    }
213    
214    @Override
215    public HandleableException mute(Class<? extends Exception> clazz)
216    {
217        //        if (this.exceptions.containsKey(clazz))
218        //            throw new UnsupportedOperationException("Already exist an exception configured to exception ["
219        //                    + clazz.getName() + "] cannot change mute status from exception");
220        
221        MapException map = this.exceptions.get(clazz);
222        if (map == null)
223            map = new MapException(clazz, RuntimeException.class, "", true);
224        else
225            map.mute();
226        this.exceptions.put(clazz, map);
227        
228        return this;
229    }
230    
231    @Override
232    public boolean isMute()
233    {
234        return this.mute;
235    }
236    
237    private boolean isMute(Exception ex)
238    {
239        boolean answer = false;
240        if (ex == null)
241            return answer;
242        
243        MapException mapException = this.exceptions.get(ex.getClass());
244        if (mapException != null)
245            answer = mapException.isMute();
246        return answer;
247    }
248    
249    @Override
250    public boolean isMute(Class<? extends Exception> clazz)
251    {
252        boolean answer = false;
253        MapException mapException = this.exceptions.get(clazz);
254        if (mapException != null)
255            answer = mapException.isMute();
256        return answer;
257    }
258    
259    @Override
260    public HandleableException logInfoOn()
261    {
262        this.enableLogInfo = true;
263        return this;
264    }
265
266    @Override
267    public HandleableException logInfoOff()
268    {
269        this.enableLogInfo = false;
270        return this;
271    }
272
273    /**
274     * 
275     * @param message
276     * @param customMessage
277     * @return
278     */
279    private String buildMessage(String message, String customMessage, Exception caught)
280    {
281        String newMessage = "";
282        if (hasParameterAtMessage(message))
283            newMessage = String.format(message,
284                    (customMessage != null ? customMessage + " " : "") + caught.getMessage()); //newMessage = String.format(message, customMessage);
285        else
286            newMessage = message;
287        return newMessage;
288    }
289    
290    /**
291     * Verify if {@code message}has inline parameter 
292     * @param message the {@code message}
293     * @return return <strong>true</strong> when {@code message} has parameter
294     */
295    private boolean hasParameterAtMessage(String message)
296    {
297        return (message.indexOf("%s") >= 0);
298    }
299}