001    package org.andromda.translation.ocl.testsuite;
002    
003    import java.util.Iterator;
004    import java.util.Map;
005    import junit.framework.TestCase;
006    import junit.framework.TestResult;
007    import junit.framework.TestSuite;
008    import org.andromda.core.AndroMDA;
009    import org.andromda.core.configuration.Configuration;
010    import org.andromda.core.configuration.Model;
011    import org.andromda.core.configuration.Namespaces;
012    import org.andromda.core.configuration.Repository;
013    import org.andromda.core.metafacade.MetafacadeFactory;
014    import org.andromda.core.metafacade.ModelAccessFacade;
015    import org.andromda.core.repository.Repositories;
016    import org.andromda.core.repository.RepositoryFacade;
017    import org.andromda.core.translation.Expression;
018    import org.andromda.core.translation.ExpressionTranslator;
019    import org.andromda.core.translation.TranslationUtils;
020    import org.apache.commons.lang.StringUtils;
021    import org.apache.log4j.Logger;
022    
023    /**
024     * This object is used to test Translations during development.
025     *
026     * @author Chad Brandon
027     */
028    public final class TranslationTestProcessor
029            extends TestCase
030    {
031        private static final Logger logger = Logger.getLogger(TranslationTestProcessor.class);
032    
033        /**
034         * The shared instance of this class.
035         */
036        private static TranslationTestProcessor instance;
037    
038        /**
039         * Gets the shared instance of this class.
040         *
041         * @return the shared instance of this class.
042         */
043        public static final TranslationTestProcessor instance()
044        {
045            if (instance == null)
046            {
047                instance = new TranslationTestProcessor();
048            }
049            return instance;
050        }
051    
052        private TranslationTestProcessor()
053        {
054            super();
055        }
056    
057        /**
058         * Sets whether or not to use the trace translator.
059         *
060         * @param useTraceTranslator true/false
061         */
062        public void setUseTraceTranslator(final boolean useTraceTranslator)
063        {
064            this.useTraceTranslator = useTraceTranslator;
065        }
066    
067        /**
068         * Indicates whether or not the TraceTranslator will run instead
069         * of the specified translator. This is helpful, in allowing us to see which
070         * expressions are being parsed in what order, etc.
071         */
072        private boolean useTraceTranslator;
073    
074        /**
075         * Thbe name of the translation to test.
076         */
077        private String translationName;
078    
079        /**
080         * Sets the name of the translation to test.
081         *
082         * @param translationName the name of the translation to test.
083         */
084        public void setTranslationName(final String translationName)
085        {
086            this.translationName = translationName;
087        }
088    
089        /**
090         * The location of the directory that contains the test source.
091         */
092        private String testSourceDirectory;
093    
094        /**
095         * Sets the location of the directory that contains the test souce.
096         *
097         * @param testSourceDirectory
098         */
099        public void setTestSourceDirectory(final String testSourceDirectory)
100        {
101            this.testSourceDirectory = testSourceDirectory;
102        }
103    
104        /**
105         * Handles the discovering of the translation tests.
106         */
107        private static final TranslationTestDiscoverer testDiscoverer = TranslationTestDiscoverer.instance();
108    
109        /**
110         * The translation that is currently being tested.
111         */
112        private String testTranslation = null;
113    
114        /**
115         * Basic constructor - called by the test runners.
116         */
117        private TranslationTestProcessor(String testName)
118        {
119            super(testName);
120        }
121    
122        /**
123         * The test result
124         */
125        private TestResult testResult;
126    
127        /**
128         * Sets the test result in which the result of the run will be stored.
129         *
130         * @param testResult the test result instance.
131         */
132        public void setResult(final TestResult testResult)
133        {
134            this.testResult = testResult;
135        }
136    
137        /**
138         * Runs the test suite.
139         *
140         * @see junit.framework.TestCase#run()
141         */
142        public void runSuite()
143        {
144            if (this.testResult == null)
145            {
146                throw new TranslationTestProcessorException(
147                        "You must set the test result before attempting to run the suite");
148            }
149            final AndroMDA andromda = AndroMDA.newInstance();
150            MetafacadeFactory factory = MetafacadeFactory.getInstance();
151            andromda.initialize(this.configuration);
152            factory.setNamespace(Namespaces.DEFAULT);
153            if (this.model == null)
154            {
155                final Repositories repositoriesContainer = Repositories.instance();
156                final Repository[] repositories = this.configuration.getRepositories();
157                if (repositories != null && repositories.length > 0)
158                {
159                    final int numberOfRepositories = repositories.length;
160                    for (int ctr = 0; ctr < numberOfRepositories; ctr++)
161                    {
162                        final Repository repository = repositories[ctr];
163                        final Model[] models = repository.getModels();
164                        if (models != null)
165                        {
166                            // - we just load only the first model (since it doesn't
167                            // make sense
168                            // to test with more than one model)
169                            final Model model = models[0];
170                            repositoriesContainer.loadModel(model);
171                            final RepositoryFacade repositoryImplementation =
172                                    repositoriesContainer.getImplementation(repository.getName());
173                            this.model = repositoryImplementation.getModel();
174    
175                            // - make sure the factory has access to the model
176                            factory.setModel(this.model, model.getType());
177                        }
178                    }
179                }
180            }
181            this.getSuite().run(this.testResult);
182            andromda.shutdown();
183        }
184    
185        /**
186         * Assembles and retrieves the test suite of all known transation-library tests.
187         *
188         * @return non-null test suite
189         */
190        private TestSuite getSuite()
191        {
192            testDiscoverer.discoverTests(this.testSourceDirectory);
193            final Map tests = testDiscoverer.getTests();
194            final TestSuite suite = new TestSuite();
195            for (final Iterator iterator = tests.keySet().iterator(); iterator.hasNext();)
196            {
197                final TranslationTestProcessor unitTest = new TranslationTestProcessor("testTranslation");
198    
199                // - pass on the variables to each test
200                unitTest.setConfiguration(this.configuration);
201                unitTest.setTestTranslation((String) iterator.next());
202                unitTest.model = this.model;
203                suite.addTest(unitTest);
204            }
205            return suite;
206        }
207    
208        private Configuration configuration;
209    
210        /**
211         * Sets AndroMDA configuration instance.
212         *
213         * @param configuration the AndroMDA configuration instance.
214         */
215        public void setConfiguration(final Configuration configuration)
216        {
217            this.configuration = configuration;
218        }
219    
220        /**
221         * Sets the value for the test translation which is the translation that
222         * will be tested.
223         *
224         * @param testTranslation
225         */
226        private void setTestTranslation(String testTranslation)
227        {
228            this.testTranslation = testTranslation;
229        }
230    
231        /**
232         * The model that was loaded.
233         */
234        private ModelAccessFacade model;
235    
236        /**
237         * Finds the classifier having <code>fullyQualifiedName</code> in the
238         * model.
239         *
240         * @param translation the translation we're using
241         * @param expression  the expression from which we'll find the model element.
242         * @return Object the found model element.
243         */
244        protected Object findModelElement(
245                String translation,
246                String expression)
247        {
248            final String methodName = "TranslationTestProcessor.findClassifier";
249            Object element = null;
250            if (StringUtils.isNotBlank(expression))
251            {
252                if (this.model == null)
253                {
254                    throw new RuntimeException(methodName + " could not retrieve model from repository");
255                }
256    
257                ContextElementFinder finder = new ContextElementFinder(model);
258                finder.translate(
259                        translation,
260                        expression,
261                        null);
262                element = finder.getContextElement();
263    
264                if (element == null)
265                {
266                    final String message =
267                            "No element found in model in expression --> '" + expression +
268                                    "' for translation ' " + translation + "', please check your model or your TranslationTest file";
269                    logger.error("ERROR! " + message);
270                    TestCase.fail(message);
271                }
272            }
273            return element;
274        }
275    
276        /**
277         * Tests the current translation set in the currentTestTranslation property.
278         */
279        public void testTranslation()
280        {
281            String translation = this.testTranslation;
282    
283            if (this.shouldTest(translation))
284            {
285                if (logger.isInfoEnabled())
286                {
287                    logger.info("testing translation --> '" + translation + '\'');
288                }
289    
290                TranslationTest test = testDiscoverer.getTest(translation);
291    
292                Map<String, ExpressionTest> expressions = test.getExpressionConfigs();
293    
294                if (expressions != null)
295                {
296                    for (String fromExpression : expressions.keySet())
297                    {
298                        // if the fromExpression body isn't defined, skip expression
299                        // test
300                        if (StringUtils.isEmpty(fromExpression))
301                        {
302                            if (logger.isInfoEnabled())
303                            {
304                                logger.info(
305                                        "No body for the 'from' element was defined " + "within translation test --> '" +
306                                                test.getUri() + "', please define the body of this element with " +
307                                                "the expression you want to translate from");
308                            }
309                            continue;
310                        }
311    
312                        Expression translated;
313                        if (useTraceTranslator)
314                        {
315                            translated = TraceTranslator.getInstance().translate(
316                                    translation,
317                                    fromExpression,
318                                    null);
319                        }
320                        else
321                        {
322                            final ExpressionTest expressionConfig = expressions.get(fromExpression);
323                            String toExpression = expressionConfig.getTo();
324    
325                            Object modelElement = null;
326    
327                            // - only find the model element if we have a model
328                            // defined in our AndroMDA configuration
329                            final Repository[] repositories = this.configuration.getRepositories();
330                            if (repositories != null && repositories.length > 0)
331                            {
332                                modelElement = this.findModelElement(
333                                        translation,
334                                        fromExpression);
335                            }
336                            else
337                            {
338                                logger.info("No repositories defined in configuration, not finding for model elements");
339                            }
340    
341                            translated =
342                                    ExpressionTranslator.instance().translate(
343                                            translation,
344                                            fromExpression,
345                                            modelElement);
346    
347                            if (translated != null)
348                            {
349                                // remove the extra whitespace from both so as to
350                                // have an accurrate comarison
351                                toExpression = TranslationUtils.removeExtraWhitespace(toExpression);
352                                if (logger.isInfoEnabled())
353                                {
354                                    logger.info("translated: --> '" + translated.getTranslatedExpression() + '\'');
355                                    logger.info("expected:   --> '" + toExpression + '\'');
356                                }
357                                TestCase.assertEquals(
358                                        toExpression,
359                                        translated.getTranslatedExpression());
360                            }
361                        }
362                    }
363                }
364            } else
365            {
366                if (logger.isInfoEnabled())
367                {
368                    logger.info("skipping translation --> '" + translation + '\'');
369                }
370            }
371        }
372    
373        /**
374         * This method returns true if we should allow the translation to be tested.
375         * This is so we can specify on the command line, the translation to be
376         * tested, if we don't want all to be tested.
377         *
378         * @param translation
379         * @return boolean
380         */
381        private boolean shouldTest(String translation)
382        {
383            translation = StringUtils.trimToEmpty(translation);
384            return StringUtils.isEmpty(this.translationName) ||
385                    (StringUtils.isNotBlank(this.translationName) && this.translationName.equals(translation));
386        }
387    
388        /**
389         * Shuts down this instance.
390         */
391        public void shutdown()
392        {
393            testDiscoverer.shutdown();
394            TranslationTestProcessor.instance = null;
395        }
396    }