1 package org.apache.maven.tools.plugin.extractor.annotations.converter;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.net.URI;
23 import java.net.URISyntaxException;
24 import java.net.URL;
25 import java.nio.file.Paths;
26 import java.util.ArrayList;
27 import java.util.Collections;
28 import java.util.HashMap;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Optional;
32 import java.util.stream.Collectors;
33
34 import com.thoughtworks.qdox.JavaProjectBuilder;
35 import com.thoughtworks.qdox.builder.TypeAssembler;
36 import com.thoughtworks.qdox.library.ClassNameLibrary;
37 import com.thoughtworks.qdox.model.JavaClass;
38 import com.thoughtworks.qdox.model.JavaField;
39 import com.thoughtworks.qdox.model.JavaModule;
40 import com.thoughtworks.qdox.model.JavaPackage;
41 import com.thoughtworks.qdox.model.JavaType;
42 import com.thoughtworks.qdox.parser.structs.TypeDef;
43 import com.thoughtworks.qdox.type.TypeResolver;
44 import org.apache.maven.tools.plugin.extractor.annotations.scanner.MojoAnnotatedClass;
45 import org.apache.maven.tools.plugin.javadoc.FullyQualifiedJavadocReference;
46 import org.apache.maven.tools.plugin.javadoc.JavadocLinkGenerator;
47 import org.apache.maven.tools.plugin.javadoc.JavadocReference;
48 import org.apache.maven.tools.plugin.javadoc.FullyQualifiedJavadocReference.MemberType;
49
50
51 public class JavaClassConverterContext
52 implements ConverterContext
53 {
54
55 final JavaClass mojoClass;
56
57 final JavaClass declaringClass;
58
59 final JavaProjectBuilder javaProjectBuilder;
60
61 final Map<String, MojoAnnotatedClass> mojoAnnotatedClasses;
62
63 final JavadocLinkGenerator linkGenerator;
64
65 final int lineNumber;
66
67 final Optional<JavaModule> javaModule;
68
69 final Map<String, Object> attributes;
70
71 public JavaClassConverterContext( JavaClass mojoClass, JavaProjectBuilder javaProjectBuilder,
72 Map<String, MojoAnnotatedClass> mojoAnnotatedClasses,
73 JavadocLinkGenerator linkGenerator, int lineNumber )
74 {
75 this( mojoClass, mojoClass, javaProjectBuilder, mojoAnnotatedClasses, linkGenerator, lineNumber );
76 }
77
78 public JavaClassConverterContext( JavaClass mojoClass, JavaClass declaringClass,
79 JavaProjectBuilder javaProjectBuilder,
80 Map<String, MojoAnnotatedClass> mojoAnnotatedClasses,
81 JavadocLinkGenerator linkGenerator, int lineNumber )
82 {
83 this.mojoClass = mojoClass;
84 this.declaringClass = declaringClass;
85 this.javaProjectBuilder = javaProjectBuilder;
86 this.mojoAnnotatedClasses = mojoAnnotatedClasses;
87 this.linkGenerator = linkGenerator;
88 this.lineNumber = lineNumber;
89 this.attributes = new HashMap<>();
90
91 javaModule =
92 mojoClass.getJavaClassLibrary().getJavaModules().stream().filter(
93 m -> m.getDescriptor().getExports().stream().anyMatch(
94 e -> e.getSource().getName().equals( getPackageName() )
95 ) )
96 .findFirst();
97 }
98
99 @Override
100 public Optional<String> getModuleName()
101 {
102
103 return javaModule.map( JavaModule::getName );
104 }
105
106 @Override
107 public String getPackageName()
108 {
109 return mojoClass.getPackageName();
110 }
111
112 @Override
113 public String getLocation()
114 {
115 try
116 {
117 URL url = declaringClass.getSource().getURL();
118 if ( url == null )
119 {
120 return declaringClass.getPackageName() + declaringClass.getSimpleName() + ":" + lineNumber;
121 }
122 return Paths.get( "" ).toUri().relativize( url.toURI() ) + ":" + lineNumber;
123 }
124 catch ( URISyntaxException e )
125 {
126 return declaringClass.getSource().getURL() + ":" + lineNumber;
127 }
128 }
129
130
131
132
133
134 @Override
135 public boolean isReferencedBy( FullyQualifiedJavadocReference reference )
136 {
137 JavaClass javaClassInHierarchy = this.mojoClass;
138 while ( javaClassInHierarchy != null )
139 {
140 if ( isClassReferencedByReference( javaClassInHierarchy, reference ) )
141 {
142 return true;
143 }
144
145 for ( JavaClass implementedInterfaces : javaClassInHierarchy.getInterfaces() )
146 {
147 if ( isClassReferencedByReference( implementedInterfaces, reference ) )
148 {
149 return true;
150 }
151 }
152 javaClassInHierarchy = javaClassInHierarchy.getSuperJavaClass();
153 }
154 return false;
155 }
156
157 private static boolean isClassReferencedByReference( JavaClass javaClass, FullyQualifiedJavadocReference reference )
158 {
159 return javaClass.getPackageName().equals( reference.getPackageName().orElse( "" ) )
160 && javaClass.getSimpleName().equals( reference.getClassName().orElse( "" ) );
161 }
162
163
164 @Override
165 public boolean canGetUrl()
166 {
167 return linkGenerator != null;
168 }
169
170 @Override
171 public URI getUrl( FullyQualifiedJavadocReference reference )
172 {
173 try
174 {
175 if ( isReferencedBy( reference ) && MemberType.FIELD == reference.getMemberType().orElse( null ) )
176 {
177
178 return new URI( null, null, reference.getMember().orElse( null ) );
179 }
180 Optional<String> fqClassName = reference.getFullyQualifiedClassName();
181 if ( fqClassName.isPresent() )
182 {
183 MojoAnnotatedClass mojoAnnotatedClass = mojoAnnotatedClasses.get( fqClassName.get() );
184 if ( mojoAnnotatedClass != null && mojoAnnotatedClass.getMojo() != null
185 && ( !reference.getLabel().isPresent()
186 || MemberType.FIELD == reference.getMemberType().orElse( null ) ) )
187 {
188
189 return new URI( null, "./" + mojoAnnotatedClass.getMojo().name() + "-mojo.html",
190 reference.getMember().orElse( null ) );
191 }
192 }
193 }
194 catch ( URISyntaxException e )
195 {
196 throw new IllegalStateException( "Error constructing a valid URL", e );
197 }
198 if ( linkGenerator == null )
199 {
200 throw new IllegalStateException( "No Javadoc Sites given to create URLs to" );
201 }
202 return linkGenerator.createLink( reference );
203 }
204
205 @Override
206 public FullyQualifiedJavadocReference resolveReference( JavadocReference reference )
207 {
208 Optional<FullyQualifiedJavadocReference> resolvedName;
209
210 if ( reference.getPackageNameClassName().isPresent() )
211 {
212 resolvedName =
213 resolveMember( reference.getPackageNameClassName().get(), reference.getMember(), reference.getLabel() );
214 if ( resolvedName.isPresent() )
215 {
216 return resolvedName.get();
217 }
218 }
219
220 if ( reference.getMember().isPresent() && !reference.getPackageNameClassName().isPresent() )
221 {
222
223
224 resolvedName = resolveMember( declaringClass, reference.getMember(), reference.getLabel() );
225 if ( resolvedName.isPresent() )
226 {
227 return resolvedName.get();
228 }
229
230 for ( JavaClass nestedClass : declaringClass.getNestedClasses() )
231 {
232 resolvedName = resolveMember( nestedClass, reference.getMember(), reference.getLabel() );
233 if ( resolvedName.isPresent() )
234 {
235 return resolvedName.get();
236 }
237 }
238
239 JavaClass superClass = declaringClass.getSuperJavaClass();
240 while ( superClass != null )
241 {
242 resolvedName = resolveMember( superClass, reference.getMember(), reference.getLabel() );
243 if ( resolvedName.isPresent() )
244 {
245 return resolvedName.get();
246 }
247 superClass = superClass.getSuperJavaClass();
248 }
249 }
250 else
251 {
252 String packageNameClassName = reference.getPackageNameClassName().get();
253
254 resolvedName = resolveMember( declaringClass.getPackageName() + "." + packageNameClassName,
255 reference.getMember(), reference.getLabel() );
256 if ( resolvedName.isPresent() )
257 {
258 return resolvedName.get();
259 }
260
261 List<String> importNames = new ArrayList<>();
262 importNames.add( "java.lang.*" );
263 importNames.addAll( declaringClass.getSource().getImports() );
264 for ( String importName : importNames )
265 {
266 if ( importName.endsWith( ".*" ) )
267 {
268 resolvedName = resolveMember( importName.replace( "*", packageNameClassName ),
269 reference.getMember(), reference.getLabel() );
270 if ( resolvedName.isPresent() )
271 {
272 return resolvedName.get();
273 }
274 }
275 else
276 {
277 if ( importName.endsWith( packageNameClassName ) )
278 {
279 resolvedName = resolveMember( importName, reference.getMember(), reference.getLabel() );
280 if ( resolvedName.isPresent() )
281 {
282 return resolvedName.get();
283 }
284 }
285 else
286 {
287
288 int firstDotIndex = packageNameClassName.indexOf( "." );
289 if ( firstDotIndex > 0
290 && importName.endsWith( packageNameClassName.substring( 0, firstDotIndex ) ) )
291 {
292 resolvedName =
293 resolveMember( importName, packageNameClassName.substring( firstDotIndex + 1 ),
294 reference.getMember(), reference.getLabel() );
295 if ( resolvedName.isPresent() )
296 {
297 return resolvedName.get();
298 }
299 }
300 }
301 }
302 }
303 }
304 throw new IllegalArgumentException( "Could not resolve javadoc reference " + reference );
305 }
306
307 @Override
308 public String getStaticFieldValue( FullyQualifiedJavadocReference reference )
309 {
310 String fqcn = reference.getFullyQualifiedClassName().orElseThrow(
311 () -> new IllegalArgumentException( "Given reference does not specify a fully qualified class name!" ) );
312 String fieldName = reference.getMember().orElseThrow(
313 () -> new IllegalArgumentException( "Given reference does not specify a member!" ) );
314 JavaClass javaClass = javaProjectBuilder.getClassByName( fqcn );
315 JavaField javaField = javaClass.getFieldByName( fieldName );
316 if ( javaField == null )
317 {
318 throw new IllegalArgumentException( "Could not find field with name " + fieldName + " in class " + fqcn );
319 }
320 if ( !javaField.isStatic() )
321 {
322 throw new IllegalArgumentException( "Field with name " + fieldName + " in class " + fqcn
323 + " is not static" );
324 }
325 return javaField.getInitializationExpression();
326 }
327
328 @Override
329 public URI getInternalJavadocSiteBaseUrl()
330 {
331 return linkGenerator.getInternalJavadocSiteBaseUrl();
332 }
333
334 private Optional<FullyQualifiedJavadocReference> resolveMember( String fullyQualifiedPackageNameClassName,
335 Optional<String> member, Optional<String> label )
336 {
337 return resolveMember( fullyQualifiedPackageNameClassName, "", member, label );
338 }
339
340 private Optional<FullyQualifiedJavadocReference> resolveMember( String fullyQualifiedPackageNameClassName,
341 String nestedClassName, Optional<String> member,
342 Optional<String> label )
343 {
344 JavaClass javaClass = javaProjectBuilder.getClassByName( fullyQualifiedPackageNameClassName );
345 if ( !isClassFound( javaClass ) )
346 {
347 JavaPackage javaPackage = javaProjectBuilder.getPackageByName( fullyQualifiedPackageNameClassName );
348 if ( javaPackage == null || !nestedClassName.isEmpty() )
349 {
350
351 int lastIndexOfDot = fullyQualifiedPackageNameClassName.lastIndexOf( '.' );
352 if ( lastIndexOfDot > 0 )
353 {
354 String newNestedClassName = nestedClassName;
355 if ( !newNestedClassName.isEmpty() )
356 {
357 newNestedClassName += '.';
358 }
359 newNestedClassName += fullyQualifiedPackageNameClassName.substring( lastIndexOfDot + 1 );
360 return resolveMember( fullyQualifiedPackageNameClassName.substring( 0, lastIndexOfDot ),
361 newNestedClassName, member, label );
362 }
363 return Optional.empty();
364 }
365 else
366 {
367
368 return Optional.of( new FullyQualifiedJavadocReference( javaPackage.getName(), label,
369 isExternal( javaPackage ) ) );
370 }
371 }
372 else
373 {
374 if ( !nestedClassName.isEmpty() )
375 {
376 javaClass = javaClass.getNestedClassByName( nestedClassName );
377 if ( javaClass == null )
378 {
379 return Optional.empty();
380 }
381 }
382
383 return resolveMember( javaClass, member, label );
384 }
385 }
386
387 private boolean isExternal( JavaClass javaClass )
388 {
389 return isExternal( javaClass.getPackage() );
390 }
391
392 private boolean isExternal( JavaPackage javaPackage )
393 {
394 return !javaPackage.getJavaClassLibrary().equals( mojoClass.getJavaClassLibrary() );
395 }
396
397 private Optional<FullyQualifiedJavadocReference> resolveMember( JavaClass javaClass, Optional<String> member,
398 Optional<String> label )
399 {
400 final Optional<MemberType> memberType;
401 Optional<String> resolvedMember = member;
402 if ( member.isPresent() )
403 {
404
405 if ( javaClass.getFieldByName( member.get() ) == null )
406 {
407
408 List<JavaType> parameterTypes = getParameterTypes( member.get() );
409 String methodName = getMethodName( member.get() );
410 if ( javaClass.getMethodBySignature( methodName, parameterTypes ) == null )
411 {
412
413 if ( ( !methodName.equals( javaClass.getSimpleName() ) )
414 || ( javaClass.getConstructor( parameterTypes ) == null ) )
415 {
416 return Optional.empty();
417 }
418 else
419 {
420 memberType = Optional.of( MemberType.CONSTRUCTOR );
421 }
422 }
423 else
424 {
425 memberType = Optional.of( MemberType.METHOD );
426 }
427
428 StringBuilder memberBuilder = new StringBuilder( methodName );
429 memberBuilder.append( "(" );
430 memberBuilder.append( parameterTypes.stream().map( JavaType::getFullyQualifiedName )
431 .collect( Collectors.joining( "," ) ) );
432 memberBuilder.append( ")" );
433 resolvedMember = Optional.of( memberBuilder.toString() );
434 }
435 else
436 {
437 memberType = Optional.of( MemberType.FIELD );
438 }
439 }
440 else
441 {
442 memberType = Optional.empty();
443 }
444 String className = javaClass.getCanonicalName().substring( javaClass.getPackageName().length() + 1 );
445 return Optional.of( new FullyQualifiedJavadocReference( javaClass.getPackageName(), Optional.of( className ),
446 resolvedMember, memberType, label,
447 isExternal( javaClass ) ) );
448 }
449
450 private static boolean isClassFound( JavaClass javaClass )
451 {
452
453
454 return !( javaClass.getJavaClassLibrary() instanceof ClassNameLibrary );
455 }
456
457
458 private List<JavaType> getParameterTypes( String member )
459 {
460 List<JavaType> parameterTypes = new ArrayList<>();
461
462 TypeResolver typeResolver =
463 TypeResolver.byClassName( declaringClass.getPackageName(), declaringClass.getJavaClassLibrary(),
464 declaringClass.getSource().getImports() );
465
466
467 int indexOfOpeningParenthesis = member.indexOf( '(' );
468 int indexOfClosingParenthesis = member.indexOf( ')' );
469 final String signatureArguments;
470 if ( indexOfOpeningParenthesis >= 0 && indexOfClosingParenthesis > 0
471 && indexOfClosingParenthesis > indexOfOpeningParenthesis )
472 {
473 signatureArguments = member.substring( indexOfOpeningParenthesis + 1, indexOfClosingParenthesis );
474 }
475 else if ( indexOfOpeningParenthesis == -1 && indexOfClosingParenthesis >= 0
476 || indexOfOpeningParenthesis >= 0 && indexOfOpeningParenthesis == -1 )
477 {
478 throw new IllegalArgumentException( "Found opening without closing parentheses or vice versa in "
479 + member );
480 }
481 else
482 {
483
484
485
486
487
488 return Collections.emptyList();
489 }
490 for ( String parameter : signatureArguments.split( "," ) )
491 {
492
493 String canonicalParameter = parameter.trim();
494 int spaceIndex = canonicalParameter.indexOf( ' ' );
495 final String typeName;
496 if ( spaceIndex > 0 )
497 {
498 typeName = canonicalParameter.substring( 0, spaceIndex ).trim();
499 }
500 else
501 {
502 typeName = canonicalParameter;
503 }
504 if ( !typeName.isEmpty() )
505 {
506 String rawTypeName = getRawTypeName( typeName );
507
508 if ( typeResolver.resolveType( rawTypeName ) == null )
509 {
510 throw new IllegalArgumentException( "Found unresolvable method argument type in " + member );
511 }
512 TypeDef typeDef = new TypeDef( getRawTypeName( typeName ) );
513 int dimensions = getDimensions( typeName );
514 JavaType javaType = TypeAssembler.createUnresolved( typeDef, dimensions, typeResolver );
515
516 parameterTypes.add( javaType );
517 }
518 }
519 return parameterTypes;
520 }
521
522 private static int getDimensions( String type )
523 {
524 return (int) type.chars().filter( ch -> ch == '[' ).count();
525 }
526
527 private static String getRawTypeName( String typeName )
528 {
529
530 int indexOfOpeningBracket = typeName.indexOf( '[' );
531 if ( indexOfOpeningBracket >= 0 )
532 {
533 return typeName.substring( 0, indexOfOpeningBracket );
534 }
535 else
536 {
537 return typeName;
538 }
539 }
540
541 private static String getMethodName( String member )
542 {
543
544 int indexOfOpeningParentheses = member.indexOf( '(' );
545 if ( indexOfOpeningParentheses == -1 )
546 {
547 return member;
548 }
549 else
550 {
551 return member.substring( 0, indexOfOpeningParentheses );
552 }
553 }
554
555 @SuppressWarnings( "unchecked" )
556 @Override
557 public <T> T setAttribute( String name, T value )
558 {
559 return (T) attributes.put( name, value );
560 }
561
562 @SuppressWarnings( "unchecked" )
563 @Override
564 public <T> T getAttribute( String name, Class<T> clazz, T defaultValue )
565 {
566 return (T) attributes.getOrDefault( name, defaultValue );
567 }
568 }