1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package de.smartics.maven.exceptions.sdoc;
17
18 import java.io.File;
19 import java.util.HashMap;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.MissingResourceException;
23 import java.util.ResourceBundle;
24
25 import javax.xml.stream.XMLStreamException;
26
27 import org.apache.commons.lang.StringUtils;
28 import org.apache.commons.logging.Log;
29 import org.apache.commons.logging.LogFactory;
30 import org.w3c.dom.DOMException;
31 import org.w3c.dom.Document;
32 import org.w3c.dom.Element;
33 import org.w3c.dom.Text;
34
35 import com.thoughtworks.qdox.model.JavaAnnotatedElement;
36 import com.thoughtworks.qdox.model.JavaClass;
37 import com.thoughtworks.qdox.model.JavaField;
38
39 import de.smartics.exceptions.core.Code;
40 import de.smartics.exceptions.i18n.message.MessageType;
41 import de.smartics.exceptions.report.data.ExceptionCodeReportItem;
42 import de.smartics.exceptions.report.data.ProjectConfiguration;
43 import de.smartics.exceptions.report.message.PlaceHolderDesc;
44 import de.smartics.exceptions.report.message.PlaceHolderInfo;
45 import de.smartics.exceptions.report.renderer.JavadocRenderer;
46 import de.smartics.maven.exceptions.util.TextCoder;
47 import de.smartics.messages.core.BundleMapper;
48 import de.smartics.xml.DomCopyReader;
49
50
51
52
53 public class SdocCodeBuilder
54 {
55
56
57
58
59
60
61
62 private static final Log LOG = LogFactory.getLog(SdocCodeBuilder.class);
63
64
65
66
67
68
69
70 private static final String XML_SCHEMA_INSTANCE =
71 "http://www.w3.org/2001/XMLSchema-instance";
72
73
74
75
76
77
78
79 private static final String CODE_URI =
80 "http://www.smartics.de/project/process/implementation/appcode";
81
82
83
84
85
86
87 private final JavadocRenderer renderer;
88
89
90
91
92
93 private final TextCoder coder;
94
95
96
97
98 private final BundleMapper mapper;
99
100
101
102
103 private final ResourceBundle bundle;
104
105
106
107
108 private final Document document;
109
110
111
112
113 private final String codeRepresentation;
114
115
116
117
118 private final ExceptionCodeReportItem codeContainer;
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134 SdocCodeBuilder(final JavadocRenderer renderer,
135 final ProjectConfiguration<File> config, final Document document,
136 final String codeRepresentation,
137 final ExceptionCodeReportItem codeContainer)
138 {
139 this.coder = new TextCoder(config);
140 this.renderer = renderer;
141 this.mapper = config.getBundleMapper();
142 this.bundle = config.getBundle();
143 this.document = document;
144 this.codeRepresentation = codeRepresentation;
145 this.codeContainer = codeContainer;
146 }
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161 public Document writeDocumentContent()
162 {
163 final JavaField fieldDoc = codeContainer.getJavadoc();
164 final JavaClass classDoc = codeContainer.getTypeJavadoc();
165
166 final TagInfo tagInfo = new TagInfo(classDoc, fieldDoc);
167
168 final Element docRoot = createDocRoot();
169 createContentElement("id", codeRepresentation, docRoot);
170 createContentElement("name", tagInfo.getName(), docRoot);
171 createContentElement("type", tagInfo.getCodeType(), docRoot);
172
173 final String componentId = codeContainer.getCode().getComponentId();
174 createContentElement("component", componentId, docRoot);
175
176 createImplementationElement(classDoc.getFullyQualifiedName(),
177 fieldDoc.getName(), docRoot);
178 createContentElement("category", tagInfo.getCategory(), docRoot);
179 createContentElement("subcategory", tagInfo.getSubcategory(), docRoot);
180 createContentElement("sort-key", tagInfo.getSortKey(), docRoot);
181 createContentElement("parents", "parent", tagInfo.getParents(), docRoot);
182 createContentElement("tags", "tag", tagInfo.getTags(), docRoot);
183 renderShortDescription(tagInfo, fieldDoc, docRoot);
184 renderJavadocContent("package-description", docRoot,
185 createJavadoc(classDoc));
186 renderJavadocContent("description", docRoot, createJavadoc(fieldDoc));
187 createContentElement("notes", tagInfo.getNotes(), docRoot);
188
189 renderMessages(docRoot);
190
191 return document;
192 }
193
194 private void renderShortDescription(final TagInfo tagInfo,
195 final JavaField fieldDoc, final Element docRoot) throws DOMException
196 {
197 String text = tagInfo.getShortDescription();
198 if (StringUtils.isBlank(text))
199 {
200 final String comment = fieldDoc.getComment();
201 if (comment != null)
202 {
203 final int index = comment.indexOf('.');
204
205 if (index != -1 && index < comment.length() - 2)
206 {
207 final String firstSentence = comment.substring(0, index + 1);
208 text = firstSentence;
209 }
210 else
211 {
212 text = comment;
213 }
214 }
215 }
216
217 if (StringUtils.isNotBlank(text))
218 {
219 renderJavadocContent("short-description", docRoot, text);
220 }
221 }
222
223 private void createImplementationElement(final String implementingClassName,
224 final String implementingFieldName, final Element docRoot)
225 {
226 final Element implementation = document.createElement("implementation");
227 docRoot.appendChild(implementation);
228 createContentElement("class", implementingClassName, implementation);
229 createContentElement("field", implementingFieldName, implementation);
230 }
231
232 private void createContentElement(final String groupGi, final String gi,
233 final List<String> contents, final Element docRoot)
234 {
235 if (!contents.isEmpty())
236 {
237 final Element group = document.createElement(groupGi);
238 docRoot.appendChild(group);
239 for (final String content : contents)
240 {
241 createContentElement(gi, content, group);
242 }
243 }
244 }
245
246 private void renderJavadocContent(final String gi,
247 final Element parentElement, final String content) throws DOMException
248 {
249 if (StringUtils.isNotBlank(content))
250 {
251 final Element element = document.createElement(gi);
252 parentElement.appendChild(element);
253 final DomCopyReader copy = new DomCopyReader(document, element);
254 try
255 {
256 copy.copy(content);
257 }
258 catch (final XMLStreamException e)
259 {
260 if (LOG.isWarnEnabled())
261 {
262 LOG.warn("Cannot parse Javdoc content as XML. Using it verbatim: "
263 + content);
264 }
265 createContentElement("description", content, parentElement);
266 }
267 }
268 }
269
270 private Element createContentElement(final String gi, final String content,
271 final Element parent)
272 {
273 if (content != null)
274 {
275 final String encoded = coder.run(content);
276 return createPlainContentElement(gi, encoded, parent);
277 }
278 return null;
279 }
280
281 private Element createPlainContentElement(final String gi,
282 final String content, final Element parent)
283 {
284 if (content != null)
285 {
286 final Element element = document.createElement(gi);
287 final Text text = document.createTextNode(content);
288 element.appendChild(text);
289 parent.appendChild(element);
290 return element;
291 }
292 return null;
293 }
294
295 private String createJavadoc(final JavaAnnotatedElement doc)
296 {
297 final String javadoc = this.renderer.render(doc);
298 return StringUtils.isNotBlank(javadoc) ? coder.run(javadoc) : " ";
299 }
300
301 private Element createDocRoot() throws DOMException
302 {
303 final Element docRoot = document.createElement("appcode");
304 docRoot.setAttribute("xmlns:xsi", XML_SCHEMA_INSTANCE);
305 docRoot.setAttribute("xmlns", CODE_URI);
306 docRoot.setAttribute("xsi:schemaLocation", CODE_URI + ' ' + CODE_URI);
307 document.appendChild(docRoot);
308 return docRoot;
309 }
310
311 private void renderMessages(final Element parent)
312 {
313 if (mapper != null)
314 {
315 final Code code = codeContainer.getCode();
316 final Class<?> codeClass = code.getClass();
317 try
318 {
319 final ResourceBundle messageBundle =
320 mapper.getBundle(codeClass, this.bundle.getLocale());
321 if (messageBundle != null)
322 {
323 addMessages(parent, codeContainer, messageBundle);
324 }
325 }
326 catch (final MissingResourceException e)
327 {
328 if (LOG.isWarnEnabled())
329 {
330 LOG.warn("Cannot load resource bundle for code class '" + codeClass
331 + "'.", e);
332 }
333 }
334 }
335 }
336
337 private void addMessages(final Element parent,
338 final ExceptionCodeReportItem codeContainer, final ResourceBundle bundle)
339 throws DOMException
340 {
341 final Element messages = document.createElement("messages");
342 addMessages(codeContainer, bundle, messages);
343 if (messages.hasChildNodes())
344 {
345 parent.appendChild(messages);
346 }
347 }
348
349 private void addMessages(final ExceptionCodeReportItem codeContainer,
350 final ResourceBundle bundle, final Element messages) throws DOMException
351 {
352 for (MessageType type : MessageType.values())
353 {
354 final String keyPrefix = codeContainer.getCode().getCode();
355 final String key = type.createKey(keyPrefix);
356 addText(codeContainer, bundle, messages, type, key);
357 }
358 }
359
360 private void addText(final ExceptionCodeReportItem codeContainer,
361 final ResourceBundle bundle, final Element messages,
362 final MessageType messageType, final String key) throws DOMException
363 {
364 try
365 {
366 final String text = bundle.getString(key);
367 if (StringUtils.isNotBlank(text))
368 {
369 final String encoded = text;
370 final String typeId = getTypeId(messageType);
371 final Element message = document.createElement("message");
372 createContentElement("name", renderTypeId(typeId), message);
373 final Element textElement =
374 createPlainContentElement("text", encoded, message);
375 final Map<String, String> placeHolderMap =
376 addPlaceHolders(messageType, codeContainer, message, bundle,
377 encoded);
378 if (!placeHolderMap.isEmpty())
379 {
380 replaceText(placeHolderMap, textElement);
381 }
382 messages.appendChild(message);
383 }
384 }
385 catch (final MissingResourceException e)
386 {
387
388 }
389 }
390
391 private String renderTypeId(final String typeId)
392 {
393 try
394 {
395 final String message = bundle.getString("label." + typeId);
396 return message;
397 }
398 catch (final Exception e)
399 {
400 if (LOG.isDebugEnabled())
401 {
402 LOG.debug("No translation for '" + typeId
403 + "' found in resource bundle.");
404 }
405 return typeId;
406 }
407 }
408
409 private void replaceText(final Map<String, String> placeHolderMap,
410 final Element textElement)
411 {
412 final Text textNode = (Text) textElement.getFirstChild();
413 String text = textNode.getTextContent();
414 for (Map.Entry<String, String> entry : placeHolderMap.entrySet())
415 {
416 final String key = entry.getKey();
417 if (isNumeric(key))
418 {
419 final String value = entry.getValue();
420 if(text.contains(key))
421 {
422 text = StringUtils.replace(text, key, '{' + value + '}');
423 }
424 else
425 {
426 final String altKey = StringUtils.chop(key) + ',';
427 text = StringUtils.replace(text, altKey, '{' + value + ',');
428 }
429 }
430 }
431 text = StringUtils.replace(text, "''", "'");
432 textNode.setTextContent(text);
433 }
434
435 private boolean isNumeric(final String key)
436 {
437 final int length = key.length();
438 boolean foundDigit = false;
439 for (int i = 1; i < length; i++)
440 {
441 final char c = key.charAt(i);
442 if (!Character.isDigit(c))
443 {
444 break;
445 }
446 foundDigit = Character.isDigit(c);
447 }
448
449 return foundDigit;
450 }
451
452 private Map<String, String> addPlaceHolders(final MessageType messageType,
453 final ExceptionCodeReportItem codeContainer, final Element parent,
454 final ResourceBundle bundle, final String messageText)
455 throws DOMException
456 {
457 final Element placeholders = document.createElement("placeholders");
458 final Map<String, String> placeHolderMap =
459 addPlaceholder(messageType, codeContainer.getPlaceHolderInfo(), bundle,
460 placeholders, messageText);
461 if (placeholders.hasChildNodes())
462 {
463 parent.appendChild(placeholders);
464 }
465 return placeHolderMap;
466 }
467
468 private Map<String, String> addPlaceholder(final MessageType messageType,
469 final PlaceHolderInfo placeHolderInfo, final ResourceBundle bundle,
470 final Element placeholders, final String messageText)
471 {
472 final Map<String, String> placeHolderMap = new HashMap<String, String>();
473
474 for (PlaceHolderDesc desc : placeHolderInfo
475 .getPlaceHolderDescs(messageType))
476 {
477 final Element placeholder = document.createElement("placeholder");
478 final String index = desc.getPlaceHolderIndex();
479 final String placeHolderString = '{' + index + '}';
480 if (messageText.contains(placeHolderString)
481 || messageText.contains('{' + index + ','))
482 {
483 final String path = desc.getOgnlPath();
484 final String name =
485 desc.getParamName()
486 + (path != null ? ':' + desc.getOgnlPath() : "");
487 final String description = desc.getDescription();
488
489 if (StringUtils.isNumeric(index))
490 {
491 createContentElement("name", name, placeholder);
492 }
493 else
494 {
495 createContentElement("name", index, placeholder);
496 }
497 createContentElement("description", description, placeholder);
498 placeholders.appendChild(placeholder);
499
500 placeHolderMap.put(placeHolderString, name);
501 }
502 }
503
504 return placeHolderMap;
505 }
506
507 private String getTypeId(final MessageType type)
508 {
509 final String id = type.getMessageKeySuffix();
510 if (StringUtils.EMPTY.equals(id))
511 {
512 return "summary";
513 }
514 else
515 {
516 return id.substring(1);
517 }
518 }
519
520
521 }