1 package org.codehaus.mojo.gwt;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.BufferedWriter;
23 import java.io.File;
24 import java.io.IOException;
25 import java.io.OutputStreamWriter;
26 import java.io.PrintWriter;
27 import java.net.MalformedURLException;
28 import java.net.URL;
29 import java.net.URLClassLoader;
30 import java.nio.charset.Charset;
31 import java.text.MessageFormat;
32 import java.util.Collection;
33 import java.util.HashMap;
34 import java.util.List;
35 import java.util.Map;
36
37 import org.apache.maven.artifact.Artifact;
38 import org.apache.maven.plugin.MojoExecutionException;
39 import org.apache.maven.plugins.annotations.Component;
40 import org.apache.maven.plugins.annotations.LifecyclePhase;
41 import org.apache.maven.plugins.annotations.Mojo;
42 import org.apache.maven.plugins.annotations.Parameter;
43 import org.apache.maven.plugins.annotations.ResolutionScope;
44 import org.codehaus.plexus.util.Scanner;
45 import org.sonatype.plexus.build.incremental.BuildContext;
46
47 import com.thoughtworks.qdox.JavaDocBuilder;
48 import com.thoughtworks.qdox.model.Annotation;
49 import com.thoughtworks.qdox.model.JavaClass;
50 import com.thoughtworks.qdox.model.JavaMethod;
51 import com.thoughtworks.qdox.model.JavaParameter;
52 import com.thoughtworks.qdox.model.Type;
53
54
55
56
57
58
59
60 @Mojo(name = "generateAsync", defaultPhase = LifecyclePhase.GENERATE_SOURCES, requiresDependencyResolution = ResolutionScope.COMPILE,
61 threadSafe = true)
62 public class GenerateAsyncMojo
63 extends AbstractGwtMojo
64 {
65 private static final String REMOTE_SERVICE_INTERFACE = "com.google.gwt.user.client.rpc.RemoteService";
66
67 private final static Map<String, String> WRAPPERS = new HashMap<String, String>();
68 static
69 {
70 WRAPPERS.put( "boolean", Boolean.class.getName() );
71 WRAPPERS.put( "byte", Byte.class.getName() );
72 WRAPPERS.put( "char", Character.class.getName() );
73 WRAPPERS.put( "short", Short.class.getName() );
74 WRAPPERS.put( "int", Integer.class.getName() );
75 WRAPPERS.put( "long", Long.class.getName() );
76 WRAPPERS.put( "float", Float.class.getName() );
77 WRAPPERS.put( "double", Double.class.getName() );
78 }
79
80
81
82
83 @Parameter(defaultValue = "**/*Service.java")
84 private String servicePattern;
85
86
87
88
89 @Parameter(defaultValue = "false")
90 private boolean returnRequest;
91
92
93
94
95
96
97 @Parameter(defaultValue = "{0}", property = "gwt.rpcPattern")
98 private String rpcPattern;
99
100
101
102
103 @Parameter(defaultValue = "true", property = "maven.gwt.failOnError")
104 private boolean failOnError;
105
106
107
108
109 @Parameter(defaultValue = "false", property = "generateAsync.force")
110 private boolean force;
111
112 @Parameter(property = "project.build.sourceEncoding")
113 private String encoding;
114
115 @Component
116 private BuildContext buildContext;
117
118 @Override
119 protected boolean isGenerator()
120 {
121 return true;
122 }
123
124 public void execute()
125 throws MojoExecutionException
126 {
127 if ( "pom".equals( getProject().getPackaging() ) )
128 {
129 getLog().info( "GWT generateAsync is skipped" );
130 return;
131 }
132
133 setupGenerateDirectory();
134
135 if ( encoding == null )
136 {
137 getLog().warn( "Encoding is not set, your build will be platform dependent" );
138 encoding = Charset.defaultCharset().name();
139 }
140
141 JavaDocBuilder builder = createJavaDocBuilder();
142
143 List<String> sourceRoots = getProject().getCompileSourceRoots();
144 for ( String sourceRoot : sourceRoots )
145 {
146 try
147 {
148 scanAndGenerateAsync( new File( sourceRoot ), builder );
149 }
150 catch ( Throwable e )
151 {
152 getLog().error( "Failed to generate Async interface", e );
153 if ( failOnError )
154 {
155 throw new MojoExecutionException( "Failed to generate Async interface", e );
156 }
157 }
158 }
159 }
160
161
162
163
164
165
166 private boolean scanAndGenerateAsync( File sourceRoot, JavaDocBuilder builder )
167 throws Exception
168 {
169 Scanner scanner = buildContext.newScanner( sourceRoot );
170 scanner.setIncludes( new String[] { servicePattern } );
171 scanner.scan();
172 String[] sources = scanner.getIncludedFiles();
173 if ( sources.length == 0 )
174 {
175 return false;
176 }
177 boolean fileGenerated = false;
178 for ( String source : sources )
179 {
180 File sourceFile = new File( sourceRoot, source );
181 File targetFile = getTargetFile( source );
182 if ( !force && buildContext.isUptodate( targetFile, sourceFile ) )
183 {
184 getLog().debug( targetFile.getAbsolutePath() + " is up to date. Generation skipped" );
185
186 fileGenerated = true;
187 continue;
188 }
189
190 String className = getTopLevelClassName( source );
191 JavaClass clazz = builder.getClassByName( className );
192 if ( isEligibleForGeneration( clazz ) )
193 {
194 getLog().debug( "Generating async interface for service " + className );
195 targetFile.getParentFile().mkdirs();
196 generateAsync( clazz, targetFile );
197 fileGenerated = true;
198 }
199 }
200 return fileGenerated;
201 }
202
203 private File getTargetFile( String source )
204 {
205 String targetFileName = source.substring( 0, source.length() - 5 ) + "Async.java";
206 File targetFile = new File( getGenerateDirectory(), targetFileName );
207 return targetFile;
208 }
209
210
211
212
213
214
215 private void generateAsync( JavaClass clazz, File targetFile )
216 throws IOException
217 {
218 PrintWriter writer = new PrintWriter( new BufferedWriter(
219 new OutputStreamWriter( buildContext.newFileOutputStream( targetFile ), encoding ) ) );
220
221 boolean hasRemoteServiceRelativePath = hasRemoteServiceRelativePath(clazz);
222
223 String className = clazz.getName();
224 if ( clazz.getPackage() != null )
225 {
226 writer.println( "package " + clazz.getPackageName() + ";" );
227 writer.println();
228 }
229 writer.println( "import com.google.gwt.core.client.GWT;" );
230 writer.println( "import com.google.gwt.user.client.rpc.AsyncCallback;" );
231
232 if (!hasRemoteServiceRelativePath)
233 {
234 writer.println( "import com.google.gwt.user.client.rpc.ServiceDefTarget;" );
235 }
236
237 writer.println();
238 writer.println( "public interface " + className + "Async" );
239 writer.println( "{" );
240
241 JavaMethod[] methods = clazz.getMethods( true );
242 for ( JavaMethod method : methods )
243 {
244 boolean deprecated = isDeprecated( method );
245
246 writer.println( "" );
247 writer.println( " /**" );
248 writer.println( " * GWT-RPC service asynchronous (client-side) interface" );
249 writer.println( " * @see " + clazz.getFullyQualifiedName() );
250 if ( deprecated )
251 writer.println( " * @deprecated" );
252 writer.println( " */" );
253 if ( deprecated )
254 writer.println( " @Deprecated" );
255 if ( returnRequest )
256 {
257 writer.print( " com.google.gwt.http.client.Request " + method.getName() + "( " );
258 }
259 else
260 {
261 writer.print( " void " + method.getName() + "( " );
262 }
263 JavaParameter[] params = method.getParameters();
264 for ( int j = 0; j < params.length; j++ )
265 {
266 JavaParameter param = params[j];
267 if ( j > 0 )
268 {
269 writer.print( ", " );
270 }
271
272 writer.print( method.getParameterTypes( true )[j].getGenericValue() );
273 if ( param.getType().getDimensions() != method.getParameterTypes( true )[j].getDimensions() )
274 {
275 for ( int dimensions = 0; dimensions < param.getType().getDimensions(); dimensions++ )
276 {
277 writer.print( "[]" );
278 }
279 }
280 writer.print( " " + param.getName() );
281 }
282 if ( params.length > 0 )
283 {
284 writer.print( ", " );
285 }
286
287 if ( method.getReturnType().isVoid() )
288 {
289 writer.println( "AsyncCallback<Void> callback );" );
290 }
291 else if ( method.getReturnType().isPrimitive() )
292 {
293 String primitive = method.getReturnType().getGenericValue();
294 writer.println( "AsyncCallback<" + WRAPPERS.get( primitive ) + "> callback );" );
295 }
296 else
297 {
298 Type returnType = method.getReturnType( true );
299 String type = returnType.getGenericValue();
300
301 if ( method.getReturnType().getDimensions() != method.getReturnType( true ).getDimensions() )
302 {
303 for ( int dimensions = 0; dimensions < method.getReturnType().getDimensions(); dimensions++ )
304 {
305 type += "[]";
306 }
307 }
308 writer.println( "AsyncCallback<" + type + "> callback );" );
309 }
310 writer.println();
311 }
312
313 writer.println();
314 writer.println( " /**" );
315 writer.println( " * Utility class to get the RPC Async interface from client-side code" );
316 writer.println( " */" );
317 writer.println( " public static final class Util " );
318 writer.println( " { " );
319 writer.println( " private static " + className + "Async instance;" );
320 writer.println();
321 writer.println( " public static final " + className + "Async getInstance()" );
322 writer.println( " {" );
323 writer.println( " if ( instance == null )" );
324 writer.println( " {" );
325 writer.println( " instance = (" + className + "Async) GWT.create( " + className + ".class );" );
326 if ( !hasRemoteServiceRelativePath )
327 {
328 String uri = MessageFormat.format( rpcPattern, className );
329 writer.println( " ServiceDefTarget target = (ServiceDefTarget) instance;" );
330 writer.println( " target.setServiceEntryPoint( GWT.getModuleBaseURL() + \"" + uri + "\" );" );
331 }
332 writer.println( " }" );
333 writer.println( " return instance;" );
334 writer.println( " }" );
335 writer.println( "" );
336 writer.println( " private Util()" );
337 writer.println( " {" );
338 writer.println( " // Utility class should not be instantiated" );
339 writer.println( " }" );
340 writer.println( " }" );
341
342 writer.println( "}" );
343 writer.close();
344 }
345
346 private boolean isEligibleForGeneration( JavaClass javaClass )
347 {
348 return javaClass.isInterface() && javaClass.isPublic() && javaClass.isA( REMOTE_SERVICE_INTERFACE );
349 }
350
351 private JavaDocBuilder createJavaDocBuilder()
352 throws MojoExecutionException
353 {
354 JavaDocBuilder builder = new JavaDocBuilder();
355 builder.setEncoding( encoding );
356 builder.getClassLibrary().addClassLoader( getProjectClassLoader() );
357 for ( String sourceRoot : getProject().getCompileSourceRoots() )
358 {
359 builder.getClassLibrary().addSourceFolder( new File( sourceRoot ) );
360 }
361 return builder;
362 }
363
364 private String getTopLevelClassName( String sourceFile )
365 {
366 String className = sourceFile.substring( 0, sourceFile.length() - 5 );
367 return className.replace( File.separatorChar, '.' );
368 }
369
370
371
372
373
374
375 private boolean isDeprecated( JavaMethod method )
376 {
377 if ( method == null )
378 return false;
379
380 for ( Annotation annotation : method.getAnnotations() )
381 {
382 if ( "java.lang.Deprecated".equals( annotation.getType().getFullyQualifiedName() ) )
383 {
384 return true;
385 }
386 }
387
388 return method.getTagByName( "deprecated" ) != null;
389 }
390
391 private boolean hasRemoteServiceRelativePath(final JavaClass clazz)
392 {
393 if ( clazz != null && clazz.getAnnotations() != null )
394 {
395 for ( Annotation annotation : clazz.getAnnotations() )
396 {
397 getLog().debug( "annotation found on service interface " + annotation );
398 if ( annotation.getType().getValue().equals( "com.google.gwt.user.client.rpc.RemoteServiceRelativePath" ) )
399 {
400 getLog().debug( "@RemoteServiceRelativePath annotation found on service interface" );
401 return true;
402 }
403 }
404 }
405
406 return false;
407 }
408
409 private ClassLoader getProjectClassLoader() throws MojoExecutionException
410 {
411 Collection<File> classpath = getClasspath( Artifact.SCOPE_COMPILE );
412 URL[] urls = new URL[classpath.size()];
413 try
414 {
415 int i = 0;
416 for ( File classpathFile : classpath )
417 {
418 urls[i] = classpathFile.toURI().toURL();
419 i++;
420 }
421 }
422 catch ( MalformedURLException e )
423 {
424 throw new MojoExecutionException( e.getMessage(), e );
425 }
426 return new URLClassLoader( urls, ClassLoader.getSystemClassLoader() );
427 }
428 }