View Javadoc
1   /*  This file is part of the aspect  project.
2    This program is free software; you can redistribute it and/or
3    modify it under the terms of the GNU Lesser General Public License
4    as published by the Free Software Foundation; version 2.1
5    of the License.
6    This program is distributed in the hope that it will be useful,
7    but WITHOUT ANY WARRANTY; without even the implied warranty of
8    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9    GNU Lesser General Public License for more details.
10   You should have received a copy of the GNU Lesser General Public License
11   along with this program; if not, write to the Free Software
12   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
13  
14   Copyright 2007-2014 Semochkin Vitaliy Evgenevich aka Yilativs
15    
16   */
17  package net.sf.aspect4log.text;
18  
19  import java.lang.reflect.InvocationTargetException;
20  import java.lang.reflect.Method;
21  import java.util.Collection;
22  import java.util.Iterator;
23  import java.util.Map;
24  import java.util.Map.Entry;
25  import java.util.regex.Matcher;
26  import java.util.regex.Pattern;
27  
28  import org.apache.commons.beanutils.PropertyUtils;
29  import org.apache.commons.lang3.ArrayUtils;
30  /**
31   * This class is responsible for building log blocks.
32   * 
33   * @author yilativs
34   *
35   */
36  public class LogFormatter {
37  
38  	private static final String ERROR_EVALUTTING_TO_STRING_SYMBOL = "☣";
39  	public static final String ELEMENTS_DELITMETER = ", ";
40  	public static final String MAP_KEY_VALUE_DELIMETER = "=";
41  	public static final String ARRAY_BEGINS_BRACKET = "[";
42  	public static final String ARRAY_ENDS_BRACKET = "]";
43  
44  	public static final String ITERABLE_BEGINS_BRACKET = "{";
45  	public static final String ITERABLE_ENDS_BRACKET = "}";
46  
47  
48  	private String elementsDelitmiter = ELEMENTS_DELITMETER;
49  	private String mapKeyValueDelimeter = MAP_KEY_VALUE_DELIMETER;
50  	private String arrayBeginsBracket = ARRAY_BEGINS_BRACKET;
51  	private String arrayEndsBracket = ARRAY_ENDS_BRACKET;
52  
53  	private String iterableBeginsBracket = ITERABLE_BEGINS_BRACKET;
54  	private String iterableEndsBracket = ITERABLE_ENDS_BRACKET;
55  
56  	
57  	private String undefindedToStringMethodSymbol;
58  	/**
59  	 * a symbol to be shown in case exception happen during to
60  	 */
61  	private String errorEvaluatingToStringSymbol=ERROR_EVALUTTING_TO_STRING_SYMBOL;
62  	private String nullSymbol;
63  
64  	public String getElementsDelitmiter() {
65  		return elementsDelitmiter;
66  	}
67  
68  	public void setElementsDelitmiter(String elementsDelitmiter) {
69  		this.elementsDelitmiter = elementsDelitmiter;
70  	}
71  
72  	public String getMapKeyValueDelimeter() {
73  		return mapKeyValueDelimeter;
74  	}
75  
76  	public void setMapKeyValueDelimeter(String mapKeyValueDelimeter) {
77  		this.mapKeyValueDelimeter = mapKeyValueDelimeter;
78  	}
79  
80  	public String getArrayBeginsBracket() {
81  		return arrayBeginsBracket;
82  	}
83  
84  	public void setArrayBeginsBracket(String arrayBeginsBracket) {
85  		this.arrayBeginsBracket = arrayBeginsBracket;
86  	}
87  
88  	public String getArrayEndsBracket() {
89  		return arrayEndsBracket;
90  	}
91  
92  	public void setArrayEndsBracket(String arrayEndsBracket) {
93  		this.arrayEndsBracket = arrayEndsBracket;
94  	}
95  
96  	public String getIterableBeginsBracket() {
97  		return iterableBeginsBracket;
98  	}
99  
100 	public void setIterableBeginsBracket(String iterableBeginsBracket) {
101 		this.iterableBeginsBracket = iterableBeginsBracket;
102 	}
103 
104 	public String getIterableEndsBracket() {
105 		return iterableEndsBracket;
106 	}
107 
108 	public void setIterableEndsBracket(String iterableEndsBracket) {
109 		this.iterableEndsBracket = iterableEndsBracket;
110 	}
111 
112 	public String getUndefindedToStringMethodSymbol() {
113 		return undefindedToStringMethodSymbol;
114 	}
115 
116 	public void setUndefindedToStringMethodSymbol(String undefindedToStringValue) {
117 		this.undefindedToStringMethodSymbol = undefindedToStringValue;
118 	}
119 
120 	public String getNullSymbol() {
121 		return nullSymbol;
122 	}
123 
124 	public void setNullSymbol(String nullValue) {
125 		this.nullSymbol = nullValue;
126 	}
127 
128 	public String getErrorEvaluatingToStringSymbol() {
129 		return errorEvaluatingToStringSymbol;
130 	}
131 
132 	public void setErrorEvaluatingToStringSymbol(String errorValue) {
133 		this.errorEvaluatingToStringSymbol = errorValue;
134 	}
135 
136 	private static Method OBJECT_EQUALS_METHOD_TMP = null;
137 	static {
138 		try {
139 			OBJECT_EQUALS_METHOD_TMP = Object.class.getMethod("toString");
140 		} catch (NoSuchMethodException e) {
141 			// that's impossible
142 			throw new ExceptionInInitializerError(e);
143 		} catch (SecurityException e) {
144 			// that's impossible
145 			throw new ExceptionInInitializerError(e);
146 		}
147 	}
148 	private static final Method OBJECT_EQUALS_METHOD = OBJECT_EQUALS_METHOD_TMP;
149 
150 	public String toString(Map<?, ?> map) {
151 		StringBuilder stringBuilder = new StringBuilder();
152 		for (Iterator<?> iterator = map.entrySet().iterator(); iterator.hasNext();) {
153 			Entry<?, ?> entry = (Entry<?, ?>) iterator.next();
154 			toString(stringBuilder, entry.getKey());
155 			stringBuilder.append(mapKeyValueDelimeter);
156 			toString(stringBuilder, entry.getValue());
157 			if (iterator.hasNext()) {
158 				stringBuilder.append(elementsDelitmiter);
159 			}
160 		}
161 		return stringBuilder.toString();
162 	}
163 
164 	public String toString(Iterable<?> iterable) {
165 		// check if object overrides toString http://stackoverflow.com/questions/12133817/determining-if-a-method-overrides-another-at-runtime
166 		// type Ø for null. for not implemented toString ¿
167 		StringBuilder stringBuilder = new StringBuilder();
168 		;
169 		for (Iterator<?> iterator = iterable.iterator(); iterator.hasNext();) {
170 			toString(stringBuilder, iterator.next());
171 			if (iterator.hasNext()) {
172 				stringBuilder.append(elementsDelitmiter);
173 			}
174 		}
175 		return stringBuilder.toString();
176 	}
177 
178 	public String toString(Object[] array) {
179 		StringBuilder stringBuilder = new StringBuilder();
180 		for (int i = 0; i < array.length; i++) {
181 			toString(stringBuilder, array[i]);
182 			if (i < array.length - 1) {
183 				stringBuilder.append(elementsDelitmiter);
184 			}
185 		}
186 		return stringBuilder.toString();
187 	}
188 
189 	public StringBuilder toString(StringBuilder stringBuilder, Object o) {
190 		if (o != null) {
191 			if (o instanceof Object[]) {
192 				addArrayBrackets(stringBuilder, toString((Object[]) o));
193 			} else if (o instanceof boolean[]) {
194 				addArrayBrackets(stringBuilder, toString(ArrayUtils.toObject((boolean[]) o)));
195 			} else if (o instanceof char[]) {
196 				addArrayBrackets(stringBuilder, toString(ArrayUtils.toObject((char[]) o)));
197 			} else if (o instanceof byte[]) {
198 				addArrayBrackets(stringBuilder, toString(ArrayUtils.toObject((byte[]) o)));
199 			} else if (o instanceof short[]) {
200 				addArrayBrackets(stringBuilder, toString(ArrayUtils.toObject((short[]) o)));
201 			} else if (o instanceof int[]) {
202 				addArrayBrackets(stringBuilder, toString(ArrayUtils.toObject((int[]) o)));
203 			} else if (o instanceof long[]) {
204 				addArrayBrackets(stringBuilder, toString(ArrayUtils.toObject((long[]) o)));
205 			} else if (o instanceof float[]) {
206 				addArrayBrackets(stringBuilder, toString(ArrayUtils.toObject((float[]) o)));
207 			} else if (o instanceof double[]) {
208 				addArrayBrackets(stringBuilder, toString(ArrayUtils.toObject((double[]) o)));
209 			} else if (o instanceof Map<?, ?>) {
210 				addIterrableBrackets(stringBuilder, toString((Map<?, ?>) o));
211 			} else if (o instanceof Collection<?>) {
212 				addIterrableBrackets(stringBuilder, toString(((Iterable<?>) o)));
213 			} else {
214 				if (undefindedToStringMethodSymbol==null || isToStringOverriden(o.getClass()) ) {
215 					try{
216 						stringBuilder.append(o);
217 					}catch(RuntimeException e){
218 						addExceptionOnToStringEvaluation(stringBuilder,e.toString());
219 					}
220 				} else {
221 					stringBuilder.append(undefindedToStringMethodSymbol);
222 				}
223 			}
224 		} else {
225 			stringBuilder.append(nullSymbol);
226 		}
227 		return stringBuilder;
228 	}
229 
230 	private void addBrackets(StringBuilder stringBuilder, String beginBracket, String string, String endBracket) {
231 		stringBuilder.append(beginBracket);
232 		stringBuilder.append(string);
233 		stringBuilder.append(endBracket);
234 	}
235 
236 	private void addArrayBrackets(StringBuilder stringBuilder, String string) {
237 		addBrackets(stringBuilder, arrayBeginsBracket, string, arrayEndsBracket);
238 	}
239 
240 	private void addIterrableBrackets(StringBuilder stringBuilder, String string) {
241 		addBrackets(stringBuilder, iterableBeginsBracket, string, iterableEndsBracket);
242 	}
243 	
244 
245 // @TODO consider implementing cache on WeakHashReferences (because class objects are rare to be gc's it's ok to use WeakHashReferences
246 // private ConcurrentMap<Class<?>, Boolean> classDefaultToStringOverridenMap = new ConcurrentHashMap<Class<?>, Boolean>();
247 //
248 //	public boolean isToStringOverriden(Class<?> clazz) {
249 //		try {
250 //			Boolean isDefaultToStringOverriden = classDefaultToStringOverridenMap.get(clazz);
251 //			if (isDefaultToStringOverriden == null) {
252 //				isDefaultToStringOverriden = !clazz.getMethod("toString").equals(OBJECT_EQUALS_METHOD);
253 //				classDefaultToStringOverridenMap.put(clazz, isDefaultToStringOverriden);
254 //			}
255 //			return isDefaultToStringOverriden;
256 //		} catch (NoSuchMethodException | SecurityException e) {
257 //			// that's impossible
258 //			return false;
259 //		}
260 //	}
261 	
262 	public boolean isToStringOverriden(Class<?> clazz) {
263 		try {
264 			return !OBJECT_EQUALS_METHOD.equals(clazz.getMethod("toString"));
265 		} catch (NoSuchMethodException e) {
266 			// that's impossible
267 			return false;
268 		} catch (SecurityException e) {
269 			// that's impossible
270 			return false;
271 		}
272 	}
273 	
274 
275 	// \$\{(.*)\};
276 	private static final Pattern VARIABLE_SEARCH_PATTERN = Pattern.compile("(?!\\$\\{\\$\\{)\\$\\{(.*?)\\}");
277 
278 	public String toString(String template, Object object) {
279 		if (template.isEmpty()) {
280 			return "";
281 		}
282 		Matcher matcher = VARIABLE_SEARCH_PATTERN.matcher(template);
283 		StringBuilder stringBuilder = new StringBuilder(template);
284 		int shift = 0;
285 		EnclosingBean bean = new EnclosingBean(object);
286 		while (matcher.find()) {
287 			String property = matcher.group(1);
288 			String replacement = toString(bean, property);
289 			int start = matcher.start();
290 			int end = matcher.end();
291 			stringBuilder.replace(start + shift, end + shift, replacement);
292 			shift = shift + replacement.length() - end + start;
293 		}
294 		return stringBuilder.toString();
295 	}
296 
297 	private String toString(Object bean, String property) {
298 		StringBuilder stringBuilder = new StringBuilder();
299 		try {
300 			toString(stringBuilder, PropertyUtils.getProperty(bean, property));
301 		} catch (IllegalAccessException e) {
302 			addExceptionOnToStringEvaluation(stringBuilder, e.getCause().toString());
303 		} catch (InvocationTargetException e) {
304 			addExceptionOnToStringEvaluation(stringBuilder, e.getCause().toString());
305 		} catch (NoSuchMethodException e) {
306 			addExceptionOnToStringEvaluation(stringBuilder, e.getCause().toString());
307 		} catch(Exception e){
308 			addExceptionOnToStringEvaluation(stringBuilder, "#Warning!!! Template caused:  " + e + " #");
309 		}
310 		return stringBuilder.toString();
311 
312 	}
313 
314 	private void addExceptionOnToStringEvaluation(StringBuilder stringBuilder, String errorMessage) {
315 		if(errorEvaluatingToStringSymbol==null){
316 			stringBuilder.append(errorMessage);
317 		}else{
318 			stringBuilder.append(errorEvaluatingToStringSymbol);
319 		}
320 	}
321 
322 	public static class EnclosingBean {
323 		private Object object;
324 
325 		public EnclosingBean(Object object) {
326 			this.object = object;
327 		}
328 
329 		public EnclosingBean() {
330 		}
331 
332 		public Object getArgs() {
333 			return object;
334 		}
335 
336 		public Object getResult() {
337 			return object;
338 		}
339 
340 		public Object getException() {
341 			return object;
342 		}
343 	}
344 
345 }