001package org.andromda.scriptwrappers; 002 003import groovy.lang.GroovyClassLoader; 004import groovy.lang.GroovyObject; 005import java.io.BufferedReader; 006import java.io.File; 007import java.io.FileNotFoundException; 008import java.io.FileReader; 009import java.io.IOException; 010import java.io.Reader; 011import java.lang.reflect.Method; 012import java.util.Arrays; 013import java.util.Iterator; 014import java.util.LinkedHashSet; 015import java.util.Set; 016 017/** 018 * This is a wrapper class for a Groovy script. The generated Java classes contain a private 019 * final copy of this wrapper and delegates all methods to this class so that their Groovy-counterparts 020 * can be executed. 021 * 022 * @author Chad Brandon 023 */ 024public class GroovyScriptWrapper 025{ 026 private String scriptPath; 027 private Object stub; 028 029 /** 030 * StubClass is always the generated class (not any subclasses), 031 * while stub may be an instance of a subclassed scripted class. 032 * @param stub 033 * @param scriptPath 034 * @throws InstantiationError 035 */ 036 public GroovyScriptWrapper( 037 Object stub, 038 String scriptPath) 039 throws InstantiationError 040 { 041 this.stub = stub; 042 this.scriptPath = scriptPath; 043 } 044 045 /** 046 * Invokes the method with the given <code>methodName</code> on the instance. 047 * 048 * @param methodName the name of the method to invoke. 049 * @param args the arguments to pass to the method. 050 * @return the return result of invoking the operation. 051 */ 052 public Object invoke( 053 String methodName, 054 Object[] args) 055 { 056 try 057 { 058 final GroovyClassLoader grooveyClassLoader = new GroovyClassLoader(this.getClassLoader()); 059 final Class groovyClass = 060 grooveyClassLoader.parseClass( 061 this.getContents(new File(this.scriptPath)), 062 scriptPath); 063 final GroovyObject groovyObject = (GroovyObject)groovyClass.newInstance(); 064 065 this.copyProperties( 066 this.stub, 067 groovyObject); 068 Object rtn = groovyObject.invokeMethod( 069 methodName, 070 args); 071 grooveyClassLoader.close(); 072 return rtn; 073 } 074 catch (final Throwable throwable) 075 { 076 throw new RuntimeException(throwable); 077 } 078 } 079 080 /** 081 * Retrieves the appropriate class loader instance as the parent of the groovyClassLoader. 082 * 083 * @return the class loader instance. 084 */ 085 private ClassLoader getClassLoader() 086 { 087 ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 088 if (classLoader == null) 089 { 090 classLoader = this.getClass().getClassLoader(); 091 } 092 return classLoader; 093 } 094 095 /** 096 * The line separator. 097 */ 098 private static final char LINE_SEPARATOR = '\n'; 099 100 /** 101 * Loads the resource and returns the contents as a String. 102 * 103 * @param resource the name of the resource. 104 * @return the contents of the resource as a string. 105 * @throws FileNotFoundException 106 */ 107 @SuppressWarnings("static-method") 108 private String getContents(final File file) 109 throws FileNotFoundException 110 { 111 Reader resource = new FileReader(file); 112 final StringBuilder contents = new StringBuilder(); 113 try 114 { 115 BufferedReader resourceInput = new BufferedReader(resource); 116 for (String line = resourceInput.readLine(); line != null; line = resourceInput.readLine()) 117 { 118 contents.append(line).append(LINE_SEPARATOR); 119 } 120 resourceInput.close(); 121 resourceInput = null; 122 } 123 catch (final Throwable throwable) 124 { 125 throw new RuntimeException(throwable); 126 } 127 finally 128 { 129 try 130 { 131 resource.close(); 132 } 133 catch (IOException ex) 134 { 135 // Ignore 136 } 137 } 138 139 // - return the contents and remove any throws clauses (since groovy doesn't support those) 140 return contents.toString().trim().replaceAll( 141 "\\s+throws\\s+.+\\s+", 142 " "); 143 } 144 145 /** 146 * Copies all properties from the given <code>from</code> instance to the given 147 * <code>to</code> instance. 148 * 149 * @param from the instance from which to copy all properties. 150 * @param to the instance of which to copy all properties. 151 * @throws Exception 152 */ 153 @SuppressWarnings("static-method") 154 private void copyProperties( 155 final Object from, 156 final GroovyObject to) 157 throws Exception 158 { 159 final Set methods = new LinkedHashSet(); 160 loadSuperMethods( 161 from.getClass(), 162 methods); 163 for (final Iterator iterator = methods.iterator(); iterator.hasNext();) 164 { 165 final Method method = (Method)iterator.next(); 166 167 final String methodName = method.getName(); 168 final String getPrefix = "get"; 169 if (methodName.startsWith(getPrefix) && method.getParameterTypes().length == 0) 170 { 171 String propertyName = methodName.replaceAll( 172 getPrefix, 173 ""); 174 175 Method setterMethod = null; 176 try 177 { 178 setterMethod = 179 from.getClass().getMethod( 180 "set" + propertyName, 181 new Class[] {method.getReturnType()}); 182 } 183 catch (final Exception exception) 184 { 185 // - ignore 186 } 187 if (setterMethod != null) 188 { 189 method.setAccessible(true); 190 final Object value = method.invoke( 191 from); 192 to.invokeMethod( 193 setterMethod.getName(), 194 new Object[] {value}); 195 } 196 } 197 } 198 } 199 200 /** 201 * Loads all methods from the clazz's super classes. 202 * 203 * @param methods the list to load full of methods. 204 * @param clazz the class to retrieve the methods. 205 * @return the loaded methods. 206 */ 207 private static Set loadSuperMethods( 208 final Class clazz, 209 final Set methods) 210 { 211 if (clazz.getSuperclass() != null) 212 { 213 methods.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredMethods())); 214 methods.addAll(loadSuperMethods( 215 clazz.getSuperclass(), 216 methods)); 217 } 218 return methods; 219 } 220}