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}