001package org.andromda.translation.ocl.query;
002
003import java.util.Iterator;
004import java.util.List;
005import org.andromda.core.translation.TranslationUtils;
006import org.andromda.translation.ocl.BaseTranslator;
007import org.andromda.translation.ocl.node.AFeatureCall;
008import org.andromda.translation.ocl.node.ALogicalExpressionTail;
009import org.andromda.translation.ocl.node.AParenthesesPrimaryExpression;
010import org.andromda.translation.ocl.node.APropertyCallExpression;
011import org.andromda.translation.ocl.node.ARelationalExpressionTail;
012import org.andromda.translation.ocl.node.AStandardDeclarator;
013import org.andromda.translation.ocl.node.PRelationalExpression;
014import org.andromda.translation.ocl.syntax.ConcreteSyntaxUtils;
015import org.apache.commons.lang.StringUtils;
016
017/**
018 * Performs translation to the following: <ul> <li>Hibernate-QL</li> </ul>
019 *
020 * @author Chad Brandon
021 */
022public class QueryTranslator
023        extends BaseTranslator
024{
025    /**
026     * Contains the select clause of the query.
027     */
028    private StringBuffer selectClause;
029
030    /**
031     * Called by super class to reset any objects.
032     */
033    public void preProcess()
034    {
035        super.preProcess();
036        this.selectClause = new StringBuffer();
037        this.sortedByClause = new StringBuffer();
038        this.declaratorCtr = 0;
039    }
040
041    /**
042     * Stores the name of the initial declarator.
043     */
044    private short declaratorCtr;
045
046    /**
047     * True/false whether or not its the initial declarator. We care about this because we must know what the first one
048     * is for differentiating between the first declarator (the beginning of the query) and any other declarators (the
049     * connecting associations).
050     * @return isInitialDeclarator
051     */
052    protected boolean isInitialDeclarator()
053    {
054        boolean isInitialDeclarator = (this.declaratorCtr <= 0);
055        this.declaratorCtr++;
056        return isInitialDeclarator;
057    }
058
059    /**
060     * Helps out with 'includesAll', replaces the {1} in the expression fragment when a declarator is encountered (i.e.
061     * '| <variable name>')
062     * @param declarator
063     */
064    public void inAStandardDeclarator(AStandardDeclarator declarator)
065    {
066        final String methodName = "QueryTranslator.inAStandardDeclarator";
067        if (logger.isDebugEnabled())
068            logger.debug("performing " + methodName + " with declarator --> " + declarator);
069
070        String temp = this.selectClause.toString();
071
072        String declaratorName = ConcreteSyntaxUtils.getVariableDeclarations(declarator)[0].getName();
073
074        // by default we'll assume we're replacing the {1} arg.
075        short replacement = 1;
076        if (this.isInitialDeclarator())
077        {
078            // handle differently if its the initial declarator,
079            // replacement is {0}
080            replacement = 0;
081        }
082
083        // now replace {1} reference
084        temp = TranslationUtils.replacePattern(temp, String.valueOf(replacement), declaratorName);
085        this.selectClause = new StringBuffer(temp);
086    }
087
088    /**
089     * Override to handle any propertyCall expressions ( i.e. includes( <expression>), select( <expression>), etc.)
090     * @param expression
091     * @see BaseTranslator#handleTranslationFragment(Object)
092     */
093    public void inAPropertyCallExpression(APropertyCallExpression expression)
094    {
095        this.handleTranslationFragment(expression);
096    }
097
098    /**
099     * Override to handle any featureCall expressions ( i.e. sortedBy( <expression>), etc.)
100     * @param expression
101     * @see BaseTranslator#handleTranslationFragment(Object)
102     */
103    public void inAFeatureCall(AFeatureCall expression)
104    {
105        // don't handle all instances here, since it's handled
106        // in the property call expression.
107        if (!TranslationUtils.trimToEmpty(expression).matches(OCLPatterns.ALL_INSTANCES))
108        {
109            this.handleTranslationFragment(expression);
110        }
111    }
112
113    /**
114     * Override to deal with logical 'and, 'or', 'xor, ... expressions.
115     * @param logicalExpressionTail
116     * @see BaseTranslator#handleTranslationFragment(Object)
117     */
118    public void inALogicalExpressionTail(ALogicalExpressionTail logicalExpressionTail)
119    {
120        this.handleTranslationFragment(logicalExpressionTail);
121    }
122
123    /**
124     * Override to deal with relational ' <, '>', '=', ... expressions.
125     * @param relationalExpressionTail
126     * @see BaseTranslator#handleTranslationFragment(Object)
127     */
128    public void inARelationalExpressionTail(ARelationalExpressionTail relationalExpressionTail)
129    {
130        this.handleTranslationFragment(relationalExpressionTail);
131    }
132
133    /**
134     * Override to deal with entering parenthesis expressions '( <expression>)'.
135     * @param expression
136     * @see org.andromda.core.translation.Expression#appendToTranslatedExpression(Object)
137     */
138    public void inAParenthesesPrimaryExpression(AParenthesesPrimaryExpression expression)
139    {
140        this.getExpression().appendSpaceToTranslatedExpression();
141        this.getExpression().appendToTranslatedExpression("(");
142    }
143
144    /**
145     * Override to deal with leaving parenthesis expressions '( <expression>)'.
146     * @param expression
147     * @see org.andromda.core.translation.Expression#appendToTranslatedExpression(Object)
148     */
149    public void outAParenthesesPrimaryExpression(AParenthesesPrimaryExpression expression)
150    {
151        this.getExpression().appendSpaceToTranslatedExpression();
152        this.getExpression().appendToTranslatedExpression(")");
153    }
154
155    /**
156     * Checks to see if the replacement is an argument and if so replaces the {index} in the fragment with the
157     * 'argument' fragment from the template. Otherwise replaces the {index} with the passed in replacement value.
158     *
159     * @param fragment    the fragment to perform replacement.
160     * @param replacement the replacement string
161     * @param index       the index in the string to replace.
162     * @return String the fragment with any replacements.
163     */
164    protected String replaceFragment(String fragment, String replacement, int index)
165    {
166        fragment = StringUtils.trimToEmpty(fragment);
167        replacement = StringUtils.trimToEmpty(replacement);
168        // if 'replacement' is an argument use that for the replacement
169        // in the fragment
170        if (this.isOperationArgument(replacement))
171        {
172            final String argument = replacement;
173            replacement = this.getTranslationFragment("argument");
174            replacement = TranslationUtils.replacePattern(replacement, String.valueOf(0), argument);
175        }
176        fragment = TranslationUtils.replacePattern(fragment, String.valueOf(index), replacement);
177        return fragment;
178    }
179
180    /**
181     * Stores the name of the fragment that maps the tail of the select clause.
182     */
183    private static final String SELECT_CLAUSE_TAIL = "selectClauseTail";
184
185    /**
186     * Stores the name of the fragment that maps to the head of the sortedBy clause.
187     */
188    private static final String SORTED_BY_CLAUSE_HEAD = "sortedByClauseHead";
189
190    /**
191     * Handles any final processing.
192     */
193    @Override
194    public void postProcess()
195    {
196        // create the final translated expression
197        String selectClauseTail = this.getTranslationFragment(SELECT_CLAUSE_TAIL);
198        String existingExpression = StringUtils.trimToEmpty(this.getExpression().getTranslatedExpression());
199
200        if (StringUtils.isNotBlank(selectClauseTail) && StringUtils.isNotBlank(existingExpression))
201        {
202            this.selectClause.append(' ');
203            this.selectClause.append(selectClauseTail);
204            this.selectClause.append(' ');
205        }
206
207        this.getExpression().insertInTranslatedExpression(0, selectClause.toString());
208
209        if (this.sortedByClause.length() > 0)
210        {
211            this.getExpression().appendSpaceToTranslatedExpression();
212            this.getExpression().appendToTranslatedExpression(this.getTranslationFragment(SORTED_BY_CLAUSE_HEAD));
213            this.getExpression().appendSpaceToTranslatedExpression();
214            this.getExpression().appendToTranslatedExpression(this.sortedByClause);
215        }
216
217        // remove any extra space from parenthesis
218        this.getExpression().replaceInTranslatedExpression("\\(\\s*", "(");
219        this.getExpression().replaceInTranslatedExpression("\\s*\\)", ")");
220    }
221
222    /*------------------------- Handler methods ---------------------------------------*/
223
224    /*------------------------- PropertyCallExpression Handler methods ---------------------*/
225
226    /**
227     * @param translation
228     * @param node
229     */
230    public void handleSubSelect(String translation, Object node)
231    {
232        APropertyCallExpression propertyCallExpression = (APropertyCallExpression) node;
233
234        String primaryExpression = ConcreteSyntaxUtils.getPrimaryExpression(propertyCallExpression);
235
236        // set the association which the 'includesAll' indicates (which
237        // is the first feature call from the list of feature calls)
238        translation = this.replaceFragment(translation, TranslationUtils.trimToEmpty(primaryExpression), 0);
239
240        this.selectClause.append(' ');
241        this.selectClause.append(translation);
242    }
243
244    /**
245     * @param translation
246     * @param node
247     */
248    public void handleIsLike(String translation, Object node)
249    {
250        APropertyCallExpression propertyCallExpression = (APropertyCallExpression) node;
251        List featureCalls = ConcreteSyntaxUtils.getFeatureCalls(propertyCallExpression);
252
253        List params = ConcreteSyntaxUtils.getParameters((AFeatureCall) featureCalls.get(0));
254
255        translation = this.replaceFragment(translation, TranslationUtils.deleteWhitespace(params.get(0)), 0);
256        translation = this.replaceFragment(translation, TranslationUtils.deleteWhitespace(params.get(1)), 1);
257
258        if (StringUtils.isNotBlank(this.getExpression().getTranslatedExpression()))
259        {
260            this.getExpression().appendSpaceToTranslatedExpression();
261        }
262        this.getExpression().appendToTranslatedExpression(translation);
263    }
264
265    /**
266     * @param translation
267     * @param node
268     */
269    public void handleSelect(String translation, Object node)
270    {
271        this.selectClause.append(translation);
272    }
273
274    /**
275     * @param translation
276     * @param node
277     */
278    public void handleIncludes(String translation, Object node)
279    {
280        APropertyCallExpression propertyCallExpression = (APropertyCallExpression) node;
281        List featureCalls = ConcreteSyntaxUtils.getFeatureCalls(propertyCallExpression);
282
283        // since the parameters can only be either dotFeatureCall or
284        // arrowFeatureCall we try one or the other.
285        String parameters = StringUtils.deleteWhitespace(ConcreteSyntaxUtils.getParametersAsString(
286                (AFeatureCall) featureCalls.get(0)));
287
288        String primaryExpression = ConcreteSyntaxUtils.getPrimaryExpression(propertyCallExpression);
289
290        translation = this.replaceFragment(translation, primaryExpression, 1);
291        translation = this.replaceFragment(translation, parameters, 0);
292
293        this.getExpression().appendSpaceToTranslatedExpression();
294        this.getExpression().appendToTranslatedExpression(translation);
295    }
296
297    /**
298     * @param translation
299     * @param node
300     */
301    public void handleDotOperation(String translation, Object node)
302    {
303        APropertyCallExpression propertyCallExpression = (APropertyCallExpression) node;
304        String firstArgument = ConcreteSyntaxUtils.getPrimaryExpression(propertyCallExpression);
305        translation = this.replaceFragment(translation, firstArgument, 0);
306        List featureCalls = ConcreteSyntaxUtils.getFeatureCalls(propertyCallExpression);
307        if (featureCalls != null && !featureCalls.isEmpty())
308        {
309            // here we loop through the feature calls and find the ones
310            // that are operation feature calls, we then retrieve and replace
311            // all parameters in the translated expression
312            for (final Iterator callIterator = featureCalls.iterator(); callIterator.hasNext();)
313            {
314                AFeatureCall featureCall = (AFeatureCall) callIterator.next();
315
316                if (TranslationUtils.trimToEmpty(featureCall).matches(OCLPatterns.OPERATION_FEATURE_CALL))
317                {
318                    List parameters = ConcreteSyntaxUtils.getParameters(featureCall);
319                    if (parameters != null && !parameters.isEmpty())
320                    {
321                        Iterator parameterIterator = parameters.iterator();
322                        for (int ctr = 1; parameterIterator.hasNext(); ctr++)
323                        {
324                            translation = this.replaceFragment(translation, (String) parameterIterator.next(), ctr);
325                        }
326                    }
327                    break;
328                }
329            }
330        }
331        this.getExpression().appendSpaceToTranslatedExpression();
332        this.getExpression().appendToTranslatedExpression(translation);
333    }
334
335    private StringBuffer sortedByClause;
336
337    /**
338     * @param translation
339     * @param node
340     */
341    public void handleSortedBy(String translation, Object node)
342    {
343        if (this.sortedByClause.length() > 0)
344        {
345            this.sortedByClause.append(", ");
346        }
347        this.sortedByClause.append(TranslationUtils.deleteWhitespace(ConcreteSyntaxUtils.getParametersAsString(
348                (AFeatureCall) node)));
349    }
350
351    /*------------------------- Logical Expression Handler (and, or, xor, etc.) ----------------------*/
352
353    /**
354     * @param translation
355     * @param node
356     */
357    public void handleLogicalExpression(String translation, Object node)
358    {
359        this.getExpression().appendSpaceToTranslatedExpression();
360        this.getExpression().appendToTranslatedExpression(translation);
361    }
362
363    /*------------------------- Relational Expression Handler (=, <, >, >=, etc.) --------------------*/
364
365    /**
366     * @param translation
367     * @param node
368     */
369    public void handleRelationalExpression(String translation, Object node)
370    {
371        ARelationalExpressionTail relationalExpressionTail = (ARelationalExpressionTail) node;
372
373        String[] leftAndRightExpressions = ConcreteSyntaxUtils.getLeftAndRightExpressions(
374                (PRelationalExpression) relationalExpressionTail.parent());
375        String leftExpression = StringUtils.deleteWhitespace(leftAndRightExpressions[0]);
376        String rightExpression = StringUtils.deleteWhitespace(leftAndRightExpressions[1]);
377        if (leftExpression.matches(OCLPatterns.OPERATION_FEATURE_CALL))
378        {
379            leftExpression = "";
380        }
381        translation = this.replaceFragment(translation, leftExpression, 0);
382        if (rightExpression.matches(OCLPatterns.OPERATION_FEATURE_CALL))
383        {
384            rightExpression = "";
385        }
386        translation = this.replaceFragment(translation, rightExpression, 1);
387
388        this.getExpression().appendSpaceToTranslatedExpression();
389        this.getExpression().appendToTranslatedExpression(translation);
390    }
391}