001package org.andromda.core.translation.library;
002
003import java.io.BufferedReader;
004import java.io.Reader;
005import java.io.StringReader;
006import java.io.StringWriter;
007import java.lang.reflect.Method;
008import java.util.Arrays;
009import java.util.LinkedHashMap;
010import java.util.Map;
011import org.andromda.core.common.ComponentContainer;
012import org.andromda.core.common.ExceptionUtils;
013import org.andromda.core.common.XmlObjectFactory;
014import org.andromda.core.templateengine.TemplateEngine;
015import org.andromda.core.translation.Translator;
016import org.apache.commons.lang.StringUtils;
017import org.apache.commons.lang.builder.ToStringBuilder;
018import org.apache.log4j.Logger;
019
020/**
021 * The LibraryTranslation object which is the intermediary object between the Library and the child Translation
022 * instances.
023 *
024 * @author Chad Brandon
025 * @author Bob Fields
026 */
027public class LibraryTranslation
028{
029    private static final Logger logger = Logger.getLogger(LibraryTranslation.class);
030
031    /**
032     * The parent library to which this LibraryTranslation belongs.
033     */
034    private Library library;
035
036    /**
037     * After processing by the CartridgeTemplate engine, will contain the processed translation.
038     */
039    private Translation translation;
040
041    /**
042     * The name of this library translation instance.
043     */
044    private String name;
045
046    /**
047     * Gets the name of this LibraryTranslation.
048     *
049     * @return String
050     */
051    public String getName()
052    {
053        return name;
054    }
055
056    /**
057     * Sets the name.
058     *
059     * @param name the name
060     */
061    public void setName(final String name)
062    {
063        this.name = name;
064    }
065
066    /**
067     * The path to the template.
068     */
069    private String template;
070
071    /**
072     * Gets the path to the template for this instance.
073     *
074     * @return String
075     */
076    public String getTemplate()
077    {
078        return template;
079    }
080
081    /**
082     * Sets the path to the template.
083     *
084     * @param template the path to the template
085     */
086    public void setTemplate(final String template)
087    {
088        this.template = template;
089    }
090
091    /**
092     * Returns the Library that this LibraryTranslation belongs too.
093     *
094     * @return Library
095     */
096    public Library getLibrary()
097    {
098        return library;
099    }
100
101    /**
102     * Sets the {@link Library} to which this LibraryInstance belongs.
103     *
104     * @param library
105     */
106    public void setLibrary(final Library library)
107    {
108        this.library = library;
109    }
110
111    /**
112     * The name given to the variable containing the context element.
113     */
114    private String variable;
115
116    /**
117     * Gets the variable name which is made available to the translation template.
118     *
119     * @return the variable name.
120     */
121    public String getVariable()
122    {
123        return this.variable;
124    }
125
126    /**
127     * Sets the variable name which is made available to the translation template.
128     *
129     * @param variable the variable name.
130     */
131    public void setVariable(final String variable)
132    {
133        this.variable = variable;
134    }
135
136    /**
137     * The Translator implementation to use. This is required.
138     */
139    private String translatorClass;
140
141    /**
142     * Sets the Translator class that will perform the translation processing.
143     *
144     * @param translatorClass the class of the translator.
145     */
146    public void setTranslator(final String translatorClass)
147    {
148        this.translatorClass = translatorClass;
149        final ComponentContainer container = ComponentContainer.instance();
150        container.unregisterComponent(translatorClass);
151        container.registerComponentType(translatorClass);
152    }
153
154    /**
155     * Gets the Translator instance that will perform processing of the template.
156     *
157     * @return Translator
158     */
159    public Translator getTranslator()
160    {
161        final String methodName = "LibraryTranslation.getTranslator";
162        final Translator translator =
163            (Translator)ComponentContainer.instance().findComponent(this.translatorClass, Translator.class);
164        if (translator == null)
165        {
166            throw new LibraryException(
167                methodName + " - a translator implementation must be defined, " +
168                " please check your translator library --> '" + this.library.getResource() + '\'');
169        }
170        return translator;
171    }
172
173    /**
174     * Calls the handlerMethod from a translation fragment. Each handle method must take a String as the first
175     * argument (the body of the fragment from the translation template) and a Object for the second argument
176     * (the node being parsed that we may need to retrieve any additional information from).
177     *
178     * @param name the name of the fragment to retrieve.
179     * @param node the node Object which from the parsed expression.
180     * @param kind the kind of the translation fragment to handle.
181     */
182    public void handleTranslationFragment(
183        final String name,
184        final String kind,
185        final Object node)
186    {
187        ExceptionUtils.checkNull("node", node);
188        if (this.translation != null && this.getTranslator() != null)
189        {
190            final String translation = this.getTranslationFragment(name, kind);
191            final Fragment fragment = this.translation.getFragment(name);
192            if (fragment != null)
193            {
194                String handlerMethod = fragment.getHandlerMethod();
195                if (StringUtils.isNotBlank(handlerMethod))
196                {
197                    Class[] argTypes = {String.class, Object.class};
198                    Method method = null;
199                    // add the translation as the first arg
200                    Object[] args = {translation, node};
201
202                    try
203                    {
204                        method = this.getTranslator().getClass().getMethod(handlerMethod, argTypes);
205
206                        method.invoke(
207                            this.getTranslator(),
208                            args);
209                    }
210                    catch (final NoSuchMethodException exception)
211                    {
212                        String errMsg =
213                            "the translator '" + this.getTranslator().getClass() + "' must implement the method '" +
214                            handlerMethod + '\'' + StringUtils.join(argTypes, ",") + '\'' +
215                            " in order to handle processing of the fragment --> '" + name + '\'';
216                        logger.error(errMsg);
217                    }
218                    catch (Throwable throwable)
219                    {
220                        if (throwable.getCause()!=null)
221                        {
222                            throwable = throwable.getCause();
223                        }
224                        // At least output the location where the error happened, not the entire stack trace.
225                        StackTraceElement[] trace = throwable.getStackTrace();
226                        String location = " AT " + trace[0].getClassName() + '.' + trace[0].getMethodName() + ':' + trace[0].getLineNumber();
227                        if (throwable.getMessage()!=null)
228                        {
229                            location += ' ' + throwable.getMessage();
230                        }
231                        logger.error(this.getTranslator().getClass() + " " + throwable + " invoking " + this.getTranslator() + " METHOD " + method + " WITH " + Arrays.toString(args) + location + " fragment " + name);
232                        throw new LibraryException(throwable);
233                    }
234                }
235            }
236        }
237    }
238
239    /**
240     * Gets the current "translated" value of this fragmentName for resulting from the last processTranslation method
241     *
242     * @param name the name of the fragment to retrieve.
243     * @param kind the kind or type of fragment to retrieve (this is the based on the expression type: body, inv, post,
244     *             pre, etc).
245     * @return String the value of the translated fragment or null of one wasn't found with the specified name.
246     */
247    public String getTranslationFragment(
248        final String name,
249        final String kind)
250    {
251        String fragment = null;
252        if (this.translation != null)
253        {
254            fragment = this.translation.getTranslated(name, kind);
255        }
256        return fragment;
257    }
258
259    /**
260     * The processed translation template as a Reader.
261     *
262     * @param translationInput
263     */
264    protected void setTranslation(final Reader translationInput)
265    {
266        ExceptionUtils.checkNull("translationInput", translationInput);
267        try
268        {
269            this.translation = (Translation)XmlObjectFactory.getInstance(Translation.class).getObject(translationInput);
270            this.translation.setLibraryTranslation(this);
271        }
272        catch (final Throwable throwable)
273        {
274            throw new LibraryException(throwable);
275        }
276    }
277
278    /**
279     * Processes the template belonging to this LibraryTranslation and returns the Translation objects. If
280     * <code>template</code> hasn't been set (i.e. is null, then this method won't do anything but return a null
281     * value).
282     *
283     * @param templateContext any key/value pairs that should be passed to the TemplateEngine while processing the
284     *                        translation template.
285     * @return Translation the Translation created from the processing the translation template.
286     */
287    public Translation processTranslation(Map<String, Object> templateContext)
288    {
289        logger.debug(
290            "processing translation template --> '" + this.getTemplate() + '\'' + "' with templateContext --> '" +
291            templateContext + '\'');
292        if (this.getTemplate() != null)
293        {
294            if (templateContext == null)
295            {
296                templateContext = new LinkedHashMap<String, Object>();
297            }
298            this.getLibrary().populateTemplateContext(templateContext);
299
300            try
301            {
302                final TemplateEngine engine = this.getLibrary().getTemplateEngine();
303
304                final StringWriter output = new StringWriter();
305                engine.processTemplate(
306                    this.getTemplate(),
307                    templateContext,
308                    output);
309                final String outputString = output.toString();
310                final BufferedReader input = new BufferedReader(new StringReader(outputString));
311                if (logger.isDebugEnabled())
312                {
313                    logger.debug("processed output --> '" + outputString + '\'');
314                }
315
316                // load Reader into the translation
317                this.setTranslation(input);
318            }
319            catch (final Throwable throwable)
320            {
321                throw new LibraryException(throwable);
322            }
323        }
324        return this.translation;
325    }
326
327    /**
328     * @see Object#toString()
329     */
330    public String toString()
331    {
332        return ToStringBuilder.reflectionToString(this);
333    }
334}