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