1 package org.ocltf.translation;
2
3 import java.io.IOException;
4 import java.io.PushbackReader;
5 import java.io.StringReader;
6 import java.util.HashMap;
7 import java.util.Map;
8
9 import org.apache.commons.logging.Log;
10 import org.apache.commons.logging.LogFactory;
11 import org.ocltf.ExpressionKinds;
12 import org.ocltf.concretesyntax.impl.ConcreteSyntaxUtils;
13 import org.ocltf.parser.OclParser;
14 import org.ocltf.parser.OclParserException;
15 import org.ocltf.parser.analysis.DepthFirstAdapter;
16 import org.ocltf.parser.lexer.Lexer;
17 import org.ocltf.parser.lexer.LexerException;
18 import org.ocltf.parser.node.AClassifierContextDeclaration;
19 import org.ocltf.parser.node.ADefClassifierExpressionBody;
20 import org.ocltf.parser.node.AInvClassifierExpressionBody;
21 import org.ocltf.parser.node.AOperationContextDeclaration;
22 import org.ocltf.parser.node.AOperationExpressionBody;
23 import org.ocltf.parser.node.Node;
24 import org.ocltf.parser.node.Start;
25 import org.ocltf.parser.parser.ParserException;
26 import org.ocltf.translation.library.LibraryTranslation;
27 import org.ocltf.translation.library.LibraryTranslationFinder;
28 import org.ocltf.utils.ExceptionUtils;
29
30 /***
31 * The "base" translator which all Translator's should extend,
32 * provides some basic functionality, such as the retrieveal of translation
33 * fragments from the translation template file, pre-processing, post-processing, etc.
34 *
35 * <p>
36 * The primary methods (in addition to methods you'll extend to handle expression parsing)
37 * to take note of when extending this class are:
38 * <ul>
39 * <li>
40 * <a href="#handleTranslationFragment(java.lang.String, org.ocltf.parser.node.Node)">handleTranslationFragment(java.lang.String, Node node)</a>
41 * </li>
42 * <li>
43 * <a href="#getTranslationFragment(java.lang.String)">getTranslationFragment(java.lang.String)</a>
44 * </li>
45 * <li>
46 * <a href="#getExpression()">getExpression()</a>
47 * </li>
48 * <li>
49 * <a href="#preProcess()">preProcess()</a>
50 * </li>
51 * <li>
52 * <a href="#postProcess()">postProcess()</a>
53 * </li>
54 * </ul>
55 * </p>
56 * @author Chad Brandon
57 */
58 public abstract class BaseTranslator extends DepthFirstAdapter implements Translator {
59
60 /***
61 * The apache common's Log instance that can be used
62 * by all decendant classes.
63 */
64 protected Log logger = LogFactory.getLog(this.getClass());
65
66 /***
67 * This is set by the "translate" method in order
68 * to provide any Translator classes extending this class
69 * access to the element that is the context for the expression.
70 */
71 private Object contextElement = null;
72
73 /***
74 * Contains the Expression that was/will be populated
75 * during execution of the translation method
76 */
77 private Expression translatedExpression = null;
78
79 /***
80 * The processed Translation file.
81 */
82 private LibraryTranslation libraryTranslation = null;
83
84 /***
85 * Called by the translate method to set the element which
86 * provides the context for the traslated expression.
87 *
88 * @param contextElement
89 */
90 private void setContextElement(Object contextElement) {
91 this.contextElement = contextElement;
92 }
93
94 /***
95 * Returns the current value of the Expression.
96 * Subclasses will update the translatedExpression of this object
97 * as translation occurs.
98 * @return Expression
99 */
100 protected Expression getExpression() {
101 String methodName = "getExpression";
102 if (this.translatedExpression == null) {
103 throw new TranslatorException(methodName +
104 " - translatedExpression can not be null");
105 }
106 return translatedExpression;
107 }
108
109 /***
110 * Returns the context element for the expression
111 * being translated.
112 *
113 * @return the context element.
114 */
115 protected Object getContextElement() {
116 String methodName = "getContextElement";
117 if (this.contextElement == null) {
118 throw new TranslatorException(methodName
119 + " - the contextElement can not be null");
120 }
121 return this.contextElement;
122 }
123
124 /***
125 * Calls the handlerMethod defined on the <fragment/> element
126 * if <code>fragmentName</code> matches one the fragments defined
127 * within the current translation file.
128 *
129 * <p>
130 * <a name="#handlerMethod"/>
131 * A handlerMethod must have two arguments:
132 * <ol>
133 * <li>
134 * The first argument must be a <code>java.lang.String</code> which will
135 * be the body of the corresponding <code>kind</code> element
136 * for the matching fragment. (i.e.
137 * if <code>'context LegalAgreement inv: allInstances ->
138 * isUnique(documentTitle')'</code> is being translated, then the body of
139 * the element <kind name="inv"/> would be returned)
140 * </li>
141 * <li>
142 * The second argument is of type <code>java.lang.Object</code>
143 * and is the node that is currently being parsed
144 * at the time the matching of the <code>fragmentName</code> occurred.
145 * </li>
146 * </ol>
147 * <p>
148 * <p>
149 * For example this handlerMethod might be defined within your translation file to
150 * handle the 'allInstances' expression:
151 * <pre>
152 * <fragment name="(\s*${elementName}\s*\.)?\s*allInstances.*"
153 * handlerMethod="handleAllInstances">
154 * <kind name="body">
155 * from $completeElementName as $lowerCaseElementName
156 * </kind>
157 * </fragment>
158 * </pre>
159 * </p>
160 * <p>
161 * And the implementation of the <code>handleAllInstances</code>
162 * method would be:
163 * <pre>
164 * public void handleAllInstances(String translation, Object node) {
165 * //some handling code
166 * }
167 * </pre>
168 * </p>
169 *
170 * @param node the node being parsed, the toString value of this node is
171 * what is matched against the translation fragment.
172 * We also need to pass the node to our
173 * <a href="#handlerMethod">handlerMethod</a>
174 * so that it can be used it for additional processing (if we need it).
175 *
176 * @see getTranslationFragment(java.lang.String)
177 */
178 protected void handleTranslationFragment(
179 Node node) {
180 String methodName = "getTranslatedFragment";
181 ExceptionUtils.checkNull(methodName, "node", node);
182 if (this.libraryTranslation!= null) {
183 this.libraryTranslation.handleTranslationFragment(
184 TranslationUtils.trimToEmpty(node),
185 this.getExpression().getKind(),
186 node);
187 }
188 }
189
190 /***
191 * Finds the "fragment" with the specified <code>fragmentName</code>
192 * from the library translation file.
193 *
194 * <p>
195 * <strong>IMPORTANT:</strong> as a best practice, it is recommended that you use
196 * <a href="#handleTranslationFragment(java.lang.String, org.ocltf.parser.node.Node)">handleTranslationFragment(java.lang.String, Node node)</a>
197 * if at all possible (instead of this method), it will help your code be cleaner and the methods smaller and
198 * more maintainable.
199 * </p>
200 * <p>
201 * Will retrieve the contents of the fragment <code>kind</code>
202 * that corresponds to the kind of expression currently being translated.
203 * (i.e. if <code>'context LegalAgreement inv: allInstances ->
204 * isUnique(documentTitle')</code>' is being translated, then the body of the
205 * element <kind name="inv"> would be returned).
206 * </p>
207 * <p>
208 * <strong>NOTE:</strong>You would use this method <strong>instead</strong> of
209 * <a href="#handleTranslationFragment(java.lang.String, org.ocltf.parser.node.Node)">handleTranslationFragment(java.lang.String, Node node)</a>
210 * if you just want to retrieve the value of the fragment and don't want to have a <a href="#handlerMethod">handlerMethod</a>
211 * which actually handles the processing of the output. For example you may want
212 * to add a fragment called 'constraintTail' which would always be added to your
213 * translation at the end of the constraint, the 'tail'. There isn't any part of the
214 * expression that matches this, but you still want to store it in a translation
215 * template since it could be different between translations within your Translation-Library.
216 * </p>
217 *
218 * @param fragmentName the name of the fragment to retrieve from the translation
219 * @return String the output String from the translated fragment.
220 *
221 * @see handleTranslationFragment(Node node)
222 */
223 protected String getTranslationFragment(String fragmentName) {
224 String methodName = "getTranslatedFragment";
225 ExceptionUtils.checkEmpty(methodName, "fragmentName", fragmentName);
226 String fragmentString = null;
227 if (this.libraryTranslation!= null) {
228 fragmentString = this.libraryTranslation.getTranslationFragment(
229 fragmentName, this.getExpression().getKind());
230 }
231 return fragmentString;
232 }
233
234 /***
235 * Performs any initlization. Subclasses
236 * should override this method if they want to provide
237 * any initilization before translation begins.
238 */
239 protected void preProcess() {
240 this.contextElement = null;
241 }
242
243 /***
244 * @see org.ocltf.translation.ExpressionTranslator#translate(
245 * java.lang.String, org.ocltf.model.ElementFacade, java.lang.String)
246 *
247 * <strong>NOTE:</strong> null is allowed for contextElement (even though
248 * it isn't within ExpressionTranslator.translate() since the TraceTranslator
249 * doesn't need a <code>contextElement</code> and we don't want to slow
250 * down the trace by having to read and load a model each time.
251 */
252 public Expression translate(String translationName, Object contextElement, String expression) {
253 String methodName = "translate";
254 ExceptionUtils.checkEmpty(methodName, "translationName", translationName);
255 ExceptionUtils.checkEmpty(methodName, "expression", expression);
256 try {
257
258 this.preProcess();
259
260
261 this.setContextElement(contextElement);
262 Map templateObjects = new HashMap();
263 templateObjects.put(Translator.CONTEXT_ELEMENT, contextElement);
264 this.libraryTranslation =
265 LibraryTranslationFinder.findLibraryTranslation(translationName);
266 if (this.libraryTranslation != null) {
267 libraryTranslation.processTranslation(templateObjects);
268 this.process(expression);
269 }
270
271 this.postProcess();
272 } catch (Exception ex) {
273 String errMsg = "Error performing " + methodName
274 + " with translationName '" + translationName
275 + "', contextElement '"
276 + contextElement
277 + "' and expression --> '"
278 + expression + "'";
279
280
281
282
283 if (!OclParserException.class.isAssignableFrom(ex.getClass())) {
284 logger.error(errMsg, ex);
285 throw new TranslatorException(errMsg, ex);
286 } else {
287 logger.error(errMsg
288 + "\n MESSAGE --> '"
289 + ex.getMessage() + "'");
290 }
291 }
292 return translatedExpression;
293 }
294
295 /***
296 * Parses the expression and applies this Translator to it.
297 *
298 * @param expression - the expression to process.
299 * @throws IOException if an IO error occurs during processing.
300 */
301 protected void process(String expression) throws IOException {
302 String methodName = "process";
303 ExceptionUtils.checkEmpty(methodName, "expression", expression);
304 try {
305 Lexer lexer =
306 new Lexer(new PushbackReader(new StringReader(expression)));
307 OclParser parser = new OclParser(lexer);
308 Start startNode = parser.parse();
309 this.translatedExpression = new Expression(expression);
310 startNode.apply(this);
311 } catch (ParserException ex) {
312 throw new OclParserException(ex.getMessage());
313 } catch (LexerException ex) {
314 throw new OclParserException(ex.getMessage());
315 }
316 }
317
318 /***
319 * Handles the exception in the default way
320 * @param methodName
321 * @param ex
322 */
323 protected void handleException(String methodName, Exception ex) {
324 String errMsg = "Error performing " + methodName;
325 logger.error(errMsg, ex);
326 throw new TranslatorException(errMsg, ex);
327 }
328
329 /***
330 * Performs any post processing.
331 * Subclasses should override to perform
332 * any final cleanup/processing.
333 */
334 protected void postProcess() {
335
336
337 }
338
339 /*** The Following Are Overriden Parser Generated Methods **/
340
341 /***
342 * Sets the kind and name of the expression for <code>inv</code> expressions. If subclasses
343 * override this method, they <strong>MUST</strong> call this method before their
344 * own implementation.
345 * @param expressionBody
346 */
347 public void inAInvClassifierExpressionBody(AInvClassifierExpressionBody expressionBody) {
348 String methodName = "inAInvClassifierExpressionBody";
349 ExceptionUtils.checkNull(methodName, "expressionBody", expressionBody);
350 if (this.translatedExpression != null) {
351 this.translatedExpression.setName(TranslationUtils.trimToEmpty(expressionBody.getName()));
352 this.translatedExpression.setKind(ExpressionKinds.INV);
353 }
354 }
355
356 /***
357 * Sets the kind and name of the expression for <code>def</code> expressions. If subclasses
358 * override this method, they <strong>MUST</strong> call this method before their
359 * own implementation.
360 * @param expressionBody
361 */
362 public void inADefClassifierExpressionBody(ADefClassifierExpressionBody expressionBody) {
363 String methodName = "inADefClassifierExpressionBody";
364 ExceptionUtils.checkNull(methodName, "expressionBody", expressionBody);
365 if (this.translatedExpression != null) {
366 this.translatedExpression.setName(TranslationUtils.trimToEmpty(expressionBody.getName()));
367 this.translatedExpression.setKind(ExpressionKinds.DEF);
368 }
369 }
370
371 /***
372 * Sets the kind and name of the expression for classifier contexts. If subclasses
373 * override this method, they <strong>MUST</strong> call this method before their
374 * own implementation.
375 * @param classifierExpressionBody
376 *
377 public void inAClassifierExpressionBody(AClassifierExpressionBody classifierExpressionBody) {
378 String methodName = "inAClassifierExpressionBody";
379 if (logger.isDebugEnabled()) {
380 logger.debug("performing " + methodName +
381 " with classifierExpressionBody --> " + classifierExpressionBody);
382 }
383 ExceptionUtils.checkNull(methodName, "classifierExpressionBody", classifierExpressionBody);
384
385 if (this.translatedExpression != null) {
386 //sets the name of the expression
387 this.translatedExpression.setName(
388 TranslationUtils.getPropertyAsString(classifierExpressionBody, "name"));
389
390 //sets the kind of the expression (inv, def)
391 this.translatedExpression.setKind(
392 TranslationUtils.getPropertyAsString(classifierExpressionBody, "classifierStereotype"));
393 }
394
395 }*/
396
397 /***
398 * Sets the kind and name of the expression for operation contexts. If subclasses
399 * override this method, they <strong>MUST</strong> call this method before their
400 * own implementation.
401 * @param operationExpressionBody
402 */
403 public void inAOperationExpressionBody(AOperationExpressionBody operationExpressionBody) {
404 String methodName = "inAOperationExpressionBody";
405 if (logger.isDebugEnabled()) {
406 logger.debug("performing " + methodName +
407 " with operationExpressionBody --> " + operationExpressionBody);
408 }
409 ExceptionUtils.checkNull(methodName, "operationExpressionBody", operationExpressionBody);
410
411 if (this.translatedExpression != null) {
412
413 this.translatedExpression.setName(
414 TranslationUtils.getPropertyAsString(operationExpressionBody, "name"));
415
416
417 this.translatedExpression.setKind(
418 TranslationUtils.getPropertyAsString(operationExpressionBody, "operationStereotype"));
419 }
420 }
421
422 /***
423 * Sets the element type which represents the context of the expression for expressions
424 * having operations as their context. If subclasses override this method,
425 * they <strong>MUST</strong> call this method before their own implementation.
426 *
427 * @param declaration the AOperationContextDeclaration instance from which
428 * we retrieve the element type.
429 */
430 public void inAOperationContextDeclaration(AOperationContextDeclaration declaration) {
431 String methodName = "inAOperationContextDeclaration";
432 if (logger.isDebugEnabled()) {
433 logger.debug("performing " + methodName +
434 " with declaration --> " + declaration);
435 }
436 if (this.translatedExpression != null) {
437 this.translatedExpression.setContextType(ConcreteSyntaxUtils.getType(
438 declaration.getName(),
439 declaration.getPathNameTail()));
440 }
441 }
442
443 /***
444 * Sets the element type which represents the context of the expression for expressions
445 * having classifiers as their context. If subclasses override this method,
446 * they <strong>MUST</strong> call this method before their own implementation.
447 *
448 * @param declaration the AClassifierContextDeclaration instance from which we retrieve
449 * the element type.
450 */
451 public void inAClassifierContextDeclaration(AClassifierContextDeclaration declaration) {
452 String methodName = "inAClassifierContextDeclaration";
453 if (logger.isDebugEnabled()) {
454 logger.debug("performing " + methodName +
455 " with declaration --> " + declaration);
456 }
457 if (this.translatedExpression != null) {
458 this.translatedExpression.setContextType(ConcreteSyntaxUtils.getType(
459 declaration.getName(),
460 declaration.getPathNameTail()));
461 }
462 }
463
464 }