View Javadoc
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 &lt;fragment/&gt; 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 &lt;kind name="inv"/&gt; 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      *                 &lt;fragment name=&quot;(\s*${elementName}\s*\.)?\s*allInstances.*&quot;
126      *                              handlerMethod=&quot;handleAllInstances&quot;&gt;
127      *                     &lt;kind name=&quot;body&quot;&gt;
128      *                         from $completeElementName as $lowerCaseElementName
129      *                     &lt;/kind&gt;
130      *                 &lt;/fragment&gt;
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 &lt;kind name="inv"&gt; 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 }