001: package org.andromda.translation.ocl;
002:
003: import org.andromda.core.common.ExceptionUtils;
004: import org.andromda.core.translation.Expression;
005: import org.andromda.core.translation.TranslationUtils;
006: import org.andromda.core.translation.Translator;
007: import org.andromda.core.translation.TranslatorException;
008: import org.andromda.core.translation.library.LibraryTranslation;
009: import org.andromda.core.translation.library.LibraryTranslationFinder;
010: import org.andromda.translation.ocl.analysis.DepthFirstAdapter;
011: import org.andromda.translation.ocl.lexer.Lexer;
012: import org.andromda.translation.ocl.lexer.LexerException;
013: import org.andromda.translation.ocl.node.AClassifierContextDeclaration;
014: import org.andromda.translation.ocl.node.ADefClassifierExpressionBody;
015: import org.andromda.translation.ocl.node.AInvClassifierExpressionBody;
016: import org.andromda.translation.ocl.node.AOperationContextDeclaration;
017: import org.andromda.translation.ocl.node.AOperationExpressionBody;
018: import org.andromda.translation.ocl.node.Start;
019: import org.andromda.translation.ocl.parser.OclParser;
020: import org.andromda.translation.ocl.parser.OclParserException;
021: import org.andromda.translation.ocl.parser.ParserException;
022: import org.andromda.translation.ocl.syntax.ConcreteSyntaxUtils;
023: import org.andromda.translation.ocl.syntax.OperationDeclaration;
024: import org.apache.log4j.Logger;
025:
026: import java.io.IOException;
027: import java.io.PushbackReader;
028: import java.io.StringReader;
029: import java.util.HashMap;
030: import java.util.Map;
031:
032: /**
033: * The "base" translator which all Translator's should extend, provides some basic functionality, such as the retrieveal
034: * of translation fragments from the translation template file, pre-processing, post-processing, etc.
035: * <p/>
036: * The primary methods (in addition to methods you'll extend to handle expression parsing) to take note of when
037: * extending this class are: <ul> <li><a href="#handleTranslationFragment(org.andromda.core.translation.node.Node)">handleTranslationFragment(java.lang.String,
038: * Node node) </a></li> <li><a href="#getTranslationFragment(java.lang.String)">getTranslationFragment(java.lang.String)
039: * </a></li> <li><a href="#getExpression()">getExpression() </a></li> <li><a href="#preProcess()">preProcess() </a></li>
040: * <li><a href="#postProcess()">postProcess() </a></li> </ul> </p>
041: *
042: * @author Chad Brandon
043: */
044: public abstract class BaseTranslator extends DepthFirstAdapter
045: implements Translator {
046:
047: /**
048: * The logger instance that can be used by all decendant classes.
049: */
050: protected Logger logger = Logger.getLogger(this .getClass());
051:
052: /**
053: * This is set by the "translate" method in order to provide any Translator classes extending this class access to
054: * the element that is the context for the expression.
055: */
056: private Object contextElement = null;
057:
058: /**
059: * Contains the Expression that was/will be populated during execution of the translation method
060: */
061: private Expression translatedExpression = null;
062:
063: /**
064: * The processed Translation file.
065: */
066: private LibraryTranslation libraryTranslation = null;
067:
068: /**
069: * Called by the translate method to set the element which provides the context for the traslated expression.
070: *
071: * @param contextElement
072: */
073: private void setContextElement(Object contextElement) {
074: this .contextElement = contextElement;
075: }
076:
077: /**
078: * Returns the current value of the Expression. This stores the translation being translated.
079: *
080: * @return Expression stores the the translation.
081: */
082: public Expression getExpression() {
083: final String methodName = "BaseTranslator.getExpression";
084: if (this .translatedExpression == null) {
085: throw new TranslatorException(methodName
086: + " - translatedExpression can not be null");
087: }
088: return translatedExpression;
089: }
090:
091: /**
092: * Returns the context element for the expression being translated.
093: *
094: * @return the context element.
095: */
096: public Object getContextElement() {
097: String methodName = "getContextElement";
098: if (this .contextElement == null) {
099: throw new TranslatorException(methodName
100: + " - the contextElement can not be null");
101: }
102: return this .contextElement;
103: }
104:
105: /**
106: * Calls the handlerMethod defined on the <fragment/> element if <code>fragmentName</code> matches one the
107: * fragments defined within the current translation file.
108: * <p/>
109: * <a name="#handlerMethod"/> A handlerMethod must have two arguments: <ol> <li>The first argument must be a
110: * <code>java.lang.String</code> which will be the body of the corresponding <code>kind</code> element for the
111: * matching fragment. (i.e. if <code>'context LegalAgreement inv: allInstances -> isUnique(documentTitle')'</code>
112: * is being translated, then the body of the element <kind name="inv"/> would be returned)</li> <li>The second
113: * argument is of type <code>java.lang.Object</code> and is the node that is currently being parsed at the time the
114: * matching of the <code>fragmentName</code> occurred.</li> </ol>
115: * <p/>
116: * <p/>
117: * For example this handlerMethod might be defined within your translation file to handle the 'allInstances'
118: * expression:
119: * <p/>
120: * <pre>
121: * <p/>
122: * <fragment name="(\s*${elementName}\s*\.)?\s*allInstances.*"
123: * handlerMethod="handleAllInstances">
124: * <kind name="body">
125: * from $completeElementName as $lowerCaseElementName
126: * </kind>
127: * </fragment>
128: * <p/>
129: * </pre>
130: * <p/>
131: * </p>
132: * <p/>
133: * And the implementation of the <code>handleAllInstances</code> method would be:
134: * <p/>
135: * <pre>
136: * public void handleAllInstances(String translation, Object node)
137: * {
138: * //some handling code
139: * }
140: * </pre>
141: * <p/>
142: * </p>
143: *
144: * @param node the node being parsed, the toString value of this node is what is matched against the translation
145: * fragment. We also need to pass the node to our <a href="#handlerMethod">handlerMethod </a> so that it
146: * can be used it for additional processing (if we need it).
147: * @see getTranslationFragment(java.lang.String)
148: */
149: protected void handleTranslationFragment(Object node) {
150: ExceptionUtils.checkNull("node", node);
151: if (this .libraryTranslation != null) {
152: this .libraryTranslation.handleTranslationFragment(
153: TranslationUtils.trimToEmpty(node), this
154: .getExpression().getKind(), node);
155: }
156: }
157:
158: /**
159: * Finds the "fragment" with the specified <code>fragmentName</code> from the library translation file.
160: * <p/>
161: * <strong>IMPORTANT: </strong> as a best practice, it is recommended that you use <a
162: * href="#handleTranslationFragment(org.andromda.core.translation.parser.node.Node)">handleTranslationFragment(Node
163: * node) </a> if at all possible (instead of this method), it will help your code be cleaner and the methods smaller
164: * and more maintainable. </p>
165: * <p/>
166: * Will retrieve the contents of the fragment <code>kind</code> that corresponds to the kind of expression currently
167: * being translated. (i.e. if <code>'context LegalAgreement inv: allInstances -> isUnique(documentTitle')</code>' is
168: * being translated, then the body of the element <kind name="inv"> would be returned). </p>
169: * <p/>
170: * <strong>NOTE: </strong>You would use this method <strong>instead </strong> of <a
171: * href="#handleTranslationFragment(org.andromda.core.translation.parser.node.Node)">handleTranslationFragment(Node
172: * node) </a> if you just want to retrieve the value of the fragment and don't want to have a <a
173: * href="#handlerMethod">handlerMethod </a> which actually handles the processing of the output. For example you may
174: * want to add a fragment called 'constraintTail' which would always be added to your translation at the end of the
175: * constraint, the 'tail'. There isn't any part of the expression that matches this, but you still want to store it
176: * in a translation template since it could be different between translations within your Translation-Library. </p>
177: *
178: * @param fragmentName the name of the fragment to retrieve from the translation
179: * @return String the output String from the translated fragment.
180: * @see #handleTranslationFragment(Node node)
181: */
182: protected String getTranslationFragment(String fragmentName) {
183: ExceptionUtils.checkEmpty("fragmentName", fragmentName);
184: String fragmentString = null;
185: if (this .libraryTranslation != null) {
186: fragmentString = this .libraryTranslation
187: .getTranslationFragment(fragmentName, this
188: .getExpression().getKind());
189: }
190: return fragmentString;
191: }
192:
193: /**
194: * Performs any initlization. Subclasses should override this method if they want to provide any initilization
195: * before translation begins.
196: */
197: public void preProcess() {
198: this .contextElement = null;
199: }
200:
201: /**
202: * <p/>
203: * <strong>NOTE: </strong> null is allowed for contextElement (even though it isn't within
204: * ExpressionTranslator.translate() since the TraceTranslator doesn't need a <code>contextElement</code> and we
205: * don't want to slow down the trace by having to read and load a model each time. </p>
206: *
207: * @see org.andromda.core.translation.ExpressionTranslator#translate( java.lang.String, java.lang.Object,
208: * java.lang.String)
209: */
210: public Expression translate(String translationName,
211: String expression, Object contextElement) {
212: ExceptionUtils.checkEmpty("translationName", translationName);
213: ExceptionUtils.checkEmpty("expression", expression);
214: try {
215: // pre processing
216: this .preProcess();
217: // set the context element so translators extending this have the
218: // context element available
219: this .setContextElement(contextElement);
220: Map templateObjects = new HashMap();
221: this .libraryTranslation = LibraryTranslationFinder
222: .findLibraryTranslation(translationName);
223: final String variable = this .libraryTranslation
224: .getVariable();
225: if (variable != null) {
226: templateObjects.put(variable, contextElement);
227: }
228: if (this .libraryTranslation != null) {
229: libraryTranslation.getLibrary().initialize();
230: libraryTranslation.processTranslation(templateObjects);
231: this .process(expression);
232: libraryTranslation.getLibrary().shutdown();
233: }
234: // post processing
235: this .postProcess();
236: } catch (Exception ex) {
237: String errMsg = "Error translating with translation '"
238: + translationName + "'," + " contextElement '"
239: + contextElement + "' and expression --> '"
240: + expression + "'" + "\nMESSAGE --> '"
241: + ex.getMessage() + "'";
242: logger.error(errMsg);
243: throw new TranslatorException(errMsg, ex);
244: }
245: return translatedExpression;
246: }
247:
248: /**
249: * Parses the expression and applies this Translator to it.
250: *
251: * @param expression the expression to process.
252: * @throws IOException if an IO error occurs during processing.
253: */
254: protected void process(final String expression) throws IOException {
255: ExceptionUtils.checkEmpty("expression", expression);
256: try {
257: Lexer lexer = new Lexer(new PushbackReader(
258: new StringReader(expression)));
259: OclParser parser = new OclParser(lexer);
260: Start startNode = parser.parse();
261: this .translatedExpression = new Expression(expression);
262: startNode.apply(this );
263: } catch (ParserException ex) {
264: throw new OclParserException(ex.getMessage());
265: } catch (LexerException ex) {
266: throw new OclParserException(ex.getMessage());
267: }
268: }
269:
270: /**
271: * Performs any post processing. Subclasses should override to perform any final cleanup/processing.
272: */
273: public void postProcess() {
274: // currently does nothing --> sub classes should override to perform
275: // final processing
276: }
277:
278: /* The Following Are Overriden Parser Generated Methods */
279:
280: /**
281: * Sets the kind and name of the expression for <code>inv</code> expressions. If subclasses override this method,
282: * they <strong>MUST </strong> call this method before their own implementation.
283: *
284: * @param expressionBody
285: */
286: public void inAInvClassifierExpressionBody(
287: AInvClassifierExpressionBody expressionBody) {
288: ExceptionUtils.checkNull("expressionBody", expressionBody);
289: if (this .translatedExpression != null) {
290: this .translatedExpression.setName(TranslationUtils
291: .trimToEmpty(expressionBody.getName()));
292: this .translatedExpression.setKind(ExpressionKinds.INV);
293: }
294: }
295:
296: /**
297: * Sets the kind and name of the expression for <code>def</code> expressions. If subclasses override this method,
298: * they <strong>MUST </strong> call this method before their own implementation.
299: *
300: * @param expressionBody
301: */
302: public void inADefClassifierExpressionBody(
303: ADefClassifierExpressionBody expressionBody) {
304: ExceptionUtils.checkNull("expressionBody", expressionBody);
305: if (this .translatedExpression != null) {
306: this .translatedExpression.setName(TranslationUtils
307: .trimToEmpty(expressionBody.getName()));
308: this .translatedExpression.setKind(ExpressionKinds.DEF);
309: }
310: }
311:
312: /**
313: * Sets the kind and name of the expression for operation contexts. If subclasses override this method, they
314: * <strong>MUST </strong> call this method before their own implementation.
315: *
316: * @param operationExpressionBody
317: */
318: public void inAOperationExpressionBody(
319: AOperationExpressionBody operationExpressionBody) {
320: ExceptionUtils.checkNull("operationExpressionBody",
321: operationExpressionBody);
322:
323: if (this .translatedExpression != null) {
324: // sets the name of the expression
325: this .translatedExpression.setName(TranslationUtils
326: .getPropertyAsString(operationExpressionBody,
327: "name"));
328:
329: // sets the kind of the expression (body, post, or pre)
330: this .translatedExpression.setKind(TranslationUtils
331: .getPropertyAsString(operationExpressionBody,
332: "operationStereotype"));
333: }
334: }
335:
336: /**
337: * Sets the element type which represents the context of the expression for expressions having operations as their
338: * context. If subclasses override this method, they <strong>MUST </strong> call this method before their own
339: * implementation.
340: *
341: * @param declaration the AOperationContextDeclaration instance from which we retrieve the element type.
342: */
343: public void inAOperationContextDeclaration(
344: AOperationContextDeclaration declaration) {
345: final String methodName = "BaseTranslator.inAOperationContextDeclaration";
346: if (logger.isDebugEnabled()) {
347: logger.debug("performing " + methodName
348: + " with declaration --> " + declaration);
349: }
350: if (this .translatedExpression != null) {
351: this .translatedExpression
352: .setContextElement(ConcreteSyntaxUtils.getType(
353: declaration.getName(), declaration
354: .getPathNameTail()));
355: }
356: this .operation = ConcreteSyntaxUtils
357: .getOperationDeclaration(declaration.getOperation());
358: }
359:
360: /**
361: * Stores the operation declartion of constraint (if the context is an operation).
362: */
363: private OperationDeclaration operation;
364:
365: /**
366: * Gets the operation declaration of the constraint (if the context is an operation), otherwise returns null.
367: *
368: * @return the operation declaration or null.
369: */
370: protected OperationDeclaration getOperation() {
371: return this .operation;
372: }
373:
374: /**
375: * Indicates if the given <code>argument</code> is an operation argument (if the context declaration is an
376: * operation)
377: *
378: * @param argument the argument to check.
379: * @return true/false
380: */
381: protected boolean isOperationArgument(final String argument) {
382: return this .operation != null
383: && ConcreteSyntaxUtils.getArgumentNames(
384: operation.getArguments()).contains(argument);
385: }
386:
387: /**
388: * Sets the element type which represents the context of the expression for expressions having classifiers as their
389: * context. If subclasses override this method, they <strong>MUST </strong> call this method before their own
390: * implementation.
391: *
392: * @param declaration the AClassifierContextDeclaration instance from which we retrieve the element type.
393: */
394: public void inAClassifierContextDeclaration(
395: AClassifierContextDeclaration declaration) {
396: final String methodName = "BaseTranslator.inAClassifierContextDeclaration";
397: if (logger.isDebugEnabled()) {
398: logger.debug("performing " + methodName
399: + " with declaration --> " + declaration);
400: }
401: if (this.translatedExpression != null) {
402: this.translatedExpression
403: .setContextElement(ConcreteSyntaxUtils.getType(
404: declaration.getName(), declaration
405: .getPathNameTail()));
406: }
407: }
408: }
|