1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.felix.bundleplugin;
20
21
22 import java.io.ByteArrayInputStream;
23 import java.io.ByteArrayOutputStream;
24 import java.io.File;
25 import java.io.FileInputStream;
26 import java.io.FileOutputStream;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.lang.reflect.Array;
30 import java.lang.reflect.Method;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.Collection;
34 import java.util.Collections;
35 import java.util.Enumeration;
36 import java.util.HashMap;
37 import java.util.HashSet;
38 import java.util.Iterator;
39 import java.util.LinkedHashMap;
40 import java.util.LinkedHashSet;
41 import java.util.List;
42 import java.util.Map;
43 import java.util.Properties;
44 import java.util.Set;
45 import java.util.jar.Attributes;
46 import java.util.jar.Manifest;
47
48 import org.apache.maven.archiver.ManifestSection;
49 import org.apache.maven.archiver.MavenArchiveConfiguration;
50 import org.apache.maven.archiver.MavenArchiver;
51 import org.apache.maven.artifact.Artifact;
52 import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
53 import org.apache.maven.execution.MavenSession;
54 import org.apache.maven.model.License;
55 import org.apache.maven.model.Model;
56 import org.apache.maven.model.Resource;
57 import org.apache.maven.plugin.AbstractMojo;
58 import org.apache.maven.plugin.MojoExecutionException;
59 import org.apache.maven.plugin.MojoFailureException;
60 import org.apache.maven.plugin.logging.Log;
61 import org.apache.maven.project.MavenProject;
62 import org.apache.maven.project.MavenProjectHelper;
63 import org.apache.maven.shared.osgi.DefaultMaven2OsgiConverter;
64 import org.apache.maven.shared.osgi.Maven2OsgiConverter;
65 import org.codehaus.plexus.archiver.UnArchiver;
66 import org.codehaus.plexus.archiver.manager.ArchiverManager;
67 import org.codehaus.plexus.util.DirectoryScanner;
68 import org.codehaus.plexus.util.FileUtils;
69 import org.codehaus.plexus.util.PropertyUtils;
70 import org.codehaus.plexus.util.StringUtils;
71
72 import aQute.bnd.header.Attrs;
73 import aQute.bnd.osgi.Analyzer;
74 import aQute.bnd.osgi.Builder;
75 import aQute.bnd.osgi.Constants;
76 import aQute.bnd.osgi.Descriptors.PackageRef;
77 import aQute.bnd.osgi.EmbeddedResource;
78 import aQute.bnd.osgi.FileResource;
79 import aQute.bnd.osgi.Jar;
80 import aQute.bnd.osgi.Packages;
81 import aQute.bnd.osgi.Processor;
82 import aQute.lib.spring.SpringXMLType;
83
84
85
86
87
88
89
90
91
92
93
94 public class BundlePlugin extends AbstractMojo
95 {
96
97
98
99
100
101 protected File manifestLocation;
102
103
104
105
106
107
108 protected File dumpInstructions;
109
110
111
112
113
114
115 protected File dumpClasspath;
116
117
118
119
120
121
122 protected boolean unpackBundle;
123
124
125
126
127
128
129 protected String excludeDependencies;
130
131
132
133
134
135
136 private String finalName;
137
138
139
140
141
142
143
144 protected String classifier;
145
146
147
148
149
150
151
152 protected String packaging;
153
154
155
156
157 private MavenProjectHelper m_projectHelper;
158
159
160
161
162 private ArchiverManager m_archiverManager;
163
164
165
166
167 private ArtifactHandlerManager m_artifactHandlerManager;
168
169
170
171
172
173
174 protected List supportedProjectTypes = Arrays.asList( new String[]
175 { "jar", "bundle" } );
176
177
178
179
180
181
182
183 private File outputDirectory;
184
185
186
187
188
189
190
191 private String buildDirectory;
192
193
194
195
196
197
198
199
200 private MavenProject project;
201
202
203
204
205
206
207 private Map instructions = new LinkedHashMap();
208
209
210
211
212 private Maven2OsgiConverter m_maven2OsgiConverter = new DefaultMaven2OsgiConverter();
213
214
215
216
217
218
219 private MavenArchiveConfiguration archive;
220
221
222
223
224
225
226 private MavenSession m_mavenSession;
227
228
229
230
231 private boolean niceManifest = false;
232
233 private static final String MAVEN_SYMBOLICNAME = "maven-symbolicname";
234 private static final String MAVEN_RESOURCES = "{maven-resources}";
235 private static final String MAVEN_TEST_RESOURCES = "{maven-test-resources}";
236 private static final String LOCAL_PACKAGES = "{local-packages}";
237 private static final String MAVEN_SOURCES = "{maven-sources}";
238 private static final String MAVEN_TEST_SOURCES = "{maven-test-sources}";
239
240 private static final String[] EMPTY_STRING_ARRAY =
241 {};
242 private static final String[] DEFAULT_INCLUDES =
243 { "**/**" };
244
245 private static final String NL = System.getProperty( "line.separator" );
246
247
248 protected Maven2OsgiConverter getMaven2OsgiConverter()
249 {
250 return m_maven2OsgiConverter;
251 }
252
253
254 protected void setMaven2OsgiConverter( Maven2OsgiConverter maven2OsgiConverter )
255 {
256 m_maven2OsgiConverter = maven2OsgiConverter;
257 }
258
259
260 protected MavenProject getProject()
261 {
262 return project;
263 }
264
265
266
267
268
269 public void execute() throws MojoExecutionException
270 {
271 Properties properties = new Properties();
272 String projectType = getProject().getArtifact().getType();
273
274
275 if ( !supportedProjectTypes.contains( projectType ) )
276 {
277 getLog().warn(
278 "Ignoring project type " + projectType + " - supportedProjectTypes = " + supportedProjectTypes );
279 return;
280 }
281
282 execute( getProject(), instructions, properties );
283 }
284
285
286 protected void execute( MavenProject currentProject, Map originalInstructions, Properties properties )
287 throws MojoExecutionException
288 {
289 try
290 {
291 execute( currentProject, originalInstructions, properties, getClasspath( currentProject ) );
292 }
293 catch ( IOException e )
294 {
295 throw new MojoExecutionException( "Error calculating classpath for project " + currentProject, e );
296 }
297 }
298
299
300
301 protected static Map transformDirectives( Map originalInstructions )
302 {
303 Map transformedInstructions = new LinkedHashMap();
304 for ( Iterator i = originalInstructions.entrySet().iterator(); i.hasNext(); )
305 {
306 Map.Entry e = ( Map.Entry ) i.next();
307
308 String key = ( String ) e.getKey();
309 if ( key.startsWith( "_" ) )
310 {
311 key = "-" + key.substring( 1 );
312 }
313
314 String value = ( String ) e.getValue();
315 if ( null == value )
316 {
317 value = "";
318 }
319 else
320 {
321 value = value.replaceAll( "\\p{Blank}*[\r\n]\\p{Blank}*", "" );
322 }
323
324 if ( Analyzer.WAB.equals( key ) && value.length() == 0 )
325 {
326
327 value = "src/main/webapp/";
328 }
329
330 transformedInstructions.put( key, value );
331 }
332 return transformedInstructions;
333 }
334
335
336 protected boolean reportErrors( String prefix, Analyzer analyzer )
337 {
338 List errors = analyzer.getErrors();
339 List warnings = analyzer.getWarnings();
340
341 for ( Iterator w = warnings.iterator(); w.hasNext(); )
342 {
343 String msg = ( String ) w.next();
344 getLog().warn( prefix + " : " + msg );
345 }
346
347 boolean hasErrors = false;
348 String fileNotFound = "Input file does not exist: ";
349 for ( Iterator e = errors.iterator(); e.hasNext(); )
350 {
351 String msg = ( String ) e.next();
352 if ( msg.startsWith( fileNotFound ) && msg.endsWith( "~" ) )
353 {
354
355 String duplicate = Processor.removeDuplicateMarker( msg.substring( fileNotFound.length() ) );
356 getLog().warn( prefix + " : Duplicate path '" + duplicate + "' in Include-Resource" );
357 }
358 else
359 {
360 getLog().error( prefix + " : " + msg );
361 hasErrors = true;
362 }
363 }
364 return hasErrors;
365 }
366
367
368 protected void execute( MavenProject currentProject, Map originalInstructions, Properties properties,
369 Jar[] classpath ) throws MojoExecutionException
370 {
371 try
372 {
373 File jarFile = new File( getBuildDirectory(), getBundleName( currentProject ) );
374 Builder builder = buildOSGiBundle( currentProject, originalInstructions, properties, classpath );
375 boolean hasErrors = reportErrors( "Bundle " + currentProject.getArtifact(), builder );
376 if ( hasErrors )
377 {
378 String failok = builder.getProperty( "-failok" );
379 if ( null == failok || "false".equalsIgnoreCase( failok ) )
380 {
381 jarFile.delete();
382
383 throw new MojoFailureException( "Error(s) found in bundle configuration" );
384 }
385 }
386
387
388 jarFile.getParentFile().mkdirs();
389 builder.getJar().write( jarFile );
390
391 Artifact mainArtifact = currentProject.getArtifact();
392
393 if ( "bundle".equals( mainArtifact.getType() ) )
394 {
395
396 mainArtifact.setArtifactHandler( m_artifactHandlerManager.getArtifactHandler( "jar" ) );
397 }
398
399 boolean customClassifier = null != classifier && classifier.trim().length() > 0;
400 boolean customPackaging = null != packaging && packaging.trim().length() > 0;
401
402 if ( customClassifier && customPackaging )
403 {
404 m_projectHelper.attachArtifact( currentProject, packaging, classifier, jarFile );
405 }
406 else if ( customClassifier )
407 {
408 m_projectHelper.attachArtifact( currentProject, jarFile, classifier );
409 }
410 else if ( customPackaging )
411 {
412 m_projectHelper.attachArtifact( currentProject, packaging, jarFile );
413 }
414 else
415 {
416 mainArtifact.setFile( jarFile );
417 }
418
419 if ( unpackBundle )
420 {
421 unpackBundle( jarFile );
422 }
423
424 if ( manifestLocation != null )
425 {
426 File outputFile = new File( manifestLocation, "MANIFEST.MF" );
427
428 try
429 {
430 Manifest manifest = builder.getJar().getManifest();
431 FileOutputStream fos = new FileOutputStream( outputFile );
432 try
433 {
434 ManifestWriter.outputManifest( manifest, fos, niceManifest );
435 }
436 finally
437 {
438 fos.close();
439 }
440 }
441 catch ( IOException e )
442 {
443 getLog().error( "Error trying to write Manifest to file " + outputFile, e );
444 }
445 }
446
447
448 builder.close();
449 }
450 catch ( MojoFailureException e )
451 {
452 getLog().error( e.getLocalizedMessage() );
453 throw new MojoExecutionException( "Error(s) found in bundle configuration", e );
454 }
455 catch ( Exception e )
456 {
457 getLog().error( "An internal error occurred", e );
458 throw new MojoExecutionException( "Internal error in maven-bundle-plugin", e );
459 }
460 }
461
462
463 protected Builder getOSGiBuilder( MavenProject currentProject, Map originalInstructions, Properties properties,
464 Jar[] classpath ) throws Exception
465 {
466 properties.putAll( getDefaultProperties( currentProject ) );
467 properties.putAll( transformDirectives( originalInstructions ) );
468 if (properties.getProperty("Bundle-Activator") != null
469 && properties.getProperty("Bundle-Activator").isEmpty())
470 {
471 properties.remove("Bundle-Activator");
472 }
473 if (properties.containsKey("-disable-plugin"))
474 {
475 String[] disabled = properties.remove("-disable-plugin").toString().replaceAll(" ", "").split(",");
476 String[] enabled = properties.getProperty(Analyzer.PLUGIN, "").replaceAll(" ", "").split(",");
477 Set<String> plugin = new LinkedHashSet<String>();
478 plugin.addAll(Arrays.asList(enabled));
479 plugin.removeAll(Arrays.asList(disabled));
480 StringBuilder sb = new StringBuilder();
481 for (String s : plugin)
482 {
483 if (sb.length() > 0)
484 {
485 sb.append(",");
486 }
487 sb.append(sb);
488 }
489 properties.setProperty(Analyzer.PLUGIN, sb.toString());
490 }
491
492 Builder builder = new Builder();
493 synchronized ( BundlePlugin.class )
494 {
495 builder.setBase( getBase( currentProject ) );
496 }
497 builder.setProperties( sanitize( properties ) );
498 if ( classpath != null )
499 {
500 builder.setClasspath( classpath );
501 }
502
503 return builder;
504 }
505
506
507 protected static Properties sanitize( Properties properties )
508 {
509
510 Properties sanitizedEntries = new Properties();
511 for ( Iterator itr = properties.entrySet().iterator(); itr.hasNext(); )
512 {
513 Map.Entry entry = ( Map.Entry ) itr.next();
514 if ( entry.getKey() instanceof String == false )
515 {
516 String key = sanitize( entry.getKey() );
517 if ( !properties.containsKey( key ) )
518 {
519 sanitizedEntries.setProperty( key, sanitize( entry.getValue() ) );
520 }
521 itr.remove();
522 }
523 else if ( entry.getValue() instanceof String == false )
524 {
525 entry.setValue( sanitize( entry.getValue() ) );
526 }
527 }
528 properties.putAll( sanitizedEntries );
529 return properties;
530 }
531
532
533 protected static String sanitize( Object value )
534 {
535 if ( value instanceof String )
536 {
537 return ( String ) value;
538 }
539 else if ( value instanceof Iterable )
540 {
541 String delim = "";
542 StringBuilder buf = new StringBuilder();
543 for ( Object i : ( Iterable<?> ) value )
544 {
545 buf.append( delim ).append( i );
546 delim = ", ";
547 }
548 return buf.toString();
549 }
550 else if ( value.getClass().isArray() )
551 {
552 String delim = "";
553 StringBuilder buf = new StringBuilder();
554 for ( int i = 0, len = Array.getLength( value ); i < len; i++ )
555 {
556 buf.append( delim ).append( Array.get( value, i ) );
557 delim = ", ";
558 }
559 return buf.toString();
560 }
561 else
562 {
563 return String.valueOf( value );
564 }
565 }
566
567
568 protected void addMavenInstructions( MavenProject currentProject, Builder builder ) throws Exception
569 {
570 if ( currentProject.getBasedir() != null )
571 {
572
573 includeMavenResources( currentProject, builder, getLog() );
574
575
576 addLocalPackages( outputDirectory, builder );
577
578
579 addMavenSourcePath( currentProject, builder, getLog() );
580 }
581
582
583 Collection embeddableArtifacts = getEmbeddableArtifacts( currentProject, builder );
584 new DependencyEmbedder( getLog(), embeddableArtifacts ).processHeaders( builder );
585
586 if ( dumpInstructions != null || getLog().isDebugEnabled() )
587 {
588 StringBuilder buf = new StringBuilder();
589 getLog().debug( "BND Instructions:" + NL + dumpInstructions( builder.getProperties(), buf ) );
590 if ( dumpInstructions != null )
591 {
592 getLog().info( "Writing BND instructions to " + dumpInstructions );
593 dumpInstructions.getParentFile().mkdirs();
594 FileUtils.fileWrite( dumpInstructions, "# BND instructions" + NL + buf );
595 }
596 }
597
598 if ( dumpClasspath != null || getLog().isDebugEnabled() )
599 {
600 StringBuilder buf = new StringBuilder();
601 getLog().debug( "BND Classpath:" + NL + dumpClasspath( builder.getClasspath(), buf ) );
602 if ( dumpClasspath != null )
603 {
604 getLog().info( "Writing BND classpath to " + dumpClasspath );
605 dumpClasspath.getParentFile().mkdirs();
606 FileUtils.fileWrite( dumpClasspath, "# BND classpath" + NL + buf );
607 }
608 }
609 }
610
611
612 protected Builder buildOSGiBundle( MavenProject currentProject, Map originalInstructions, Properties properties,
613 Jar[] classpath ) throws Exception
614 {
615 Builder builder = getOSGiBuilder( currentProject, originalInstructions, properties, classpath );
616
617 addMavenInstructions( currentProject, builder );
618
619 builder.build();
620
621 mergeMavenManifest( currentProject, builder );
622
623 return builder;
624 }
625
626
627 protected static StringBuilder dumpInstructions( Properties properties, StringBuilder buf )
628 {
629 try
630 {
631 buf.append( "#-----------------------------------------------------------------------" + NL );
632 Properties stringProperties = new Properties();
633 for ( Enumeration e = properties.propertyNames(); e.hasMoreElements(); )
634 {
635
636 String key = ( String ) e.nextElement();
637 String value = properties.getProperty( key );
638 if ( value != null )
639 {
640 stringProperties.setProperty( key, value );
641 }
642 }
643 ByteArrayOutputStream out = new ByteArrayOutputStream();
644 stringProperties.store( out, null );
645 buf.append( out.toString( "8859_1" ) );
646 buf.append( "#-----------------------------------------------------------------------" + NL );
647 }
648 catch ( Throwable e )
649 {
650
651 }
652 return buf;
653 }
654
655
656 protected static StringBuilder dumpClasspath( List classpath, StringBuilder buf )
657 {
658 try
659 {
660 buf.append( "#-----------------------------------------------------------------------" + NL );
661 buf.append( "-classpath:\\" + NL );
662 for ( Iterator i = classpath.iterator(); i.hasNext(); )
663 {
664 File path = ( ( Jar ) i.next() ).getSource();
665 if ( path != null )
666 {
667 buf.append( ' ' + path.toString() + ( i.hasNext() ? ",\\" : "" ) + NL );
668 }
669 }
670 buf.append( "#-----------------------------------------------------------------------" + NL );
671 }
672 catch ( Throwable e )
673 {
674
675 }
676 return buf;
677 }
678
679
680 protected StringBuilder dumpManifest( Manifest manifest, StringBuilder buf )
681 {
682 try
683 {
684 buf.append( "#-----------------------------------------------------------------------" + NL );
685 ByteArrayOutputStream out = new ByteArrayOutputStream();
686 ManifestWriter.outputManifest( manifest, out, false );
687 buf.append( out.toString( "UTF8" ) );
688 buf.append( "#-----------------------------------------------------------------------" + NL );
689 }
690 catch ( Throwable e )
691 {
692
693 }
694 return buf;
695 }
696
697
698 protected static void includeMavenResources( MavenProject currentProject, Analyzer analyzer, Log log )
699 {
700
701 final String mavenResourcePaths = getMavenResourcePaths( currentProject, false );
702 final String mavenTestResourcePaths = getMavenResourcePaths( currentProject, true );
703 final String includeResource = analyzer.getProperty( Analyzer.INCLUDE_RESOURCE );
704 if ( includeResource != null )
705 {
706 if ( includeResource.contains( MAVEN_RESOURCES ) || includeResource.contains( MAVEN_TEST_RESOURCES ) )
707 {
708 String combinedResource = StringUtils.replace( includeResource, MAVEN_RESOURCES, mavenResourcePaths );
709 combinedResource = StringUtils.replace( combinedResource, MAVEN_TEST_RESOURCES, mavenTestResourcePaths );
710 if ( combinedResource.length() > 0 )
711 {
712 analyzer.setProperty( Analyzer.INCLUDE_RESOURCE, combinedResource );
713 }
714 else
715 {
716 analyzer.unsetProperty( Analyzer.INCLUDE_RESOURCE );
717 }
718 }
719 else if ( mavenResourcePaths.length() > 0 )
720 {
721 log.warn( Analyzer.INCLUDE_RESOURCE + ": overriding " + mavenResourcePaths + " with " + includeResource
722 + " (add " + MAVEN_RESOURCES + " if you want to include the maven resources)" );
723 }
724 }
725 else if ( mavenResourcePaths.length() > 0 )
726 {
727 analyzer.setProperty( Analyzer.INCLUDE_RESOURCE, mavenResourcePaths );
728 }
729 }
730
731
732 protected void mergeMavenManifest( MavenProject currentProject, Builder builder ) throws Exception
733 {
734 Jar jar = builder.getJar();
735
736 if ( getLog().isDebugEnabled() )
737 {
738 getLog().debug( "BND Manifest:" + NL + dumpManifest( jar.getManifest(), new StringBuilder() ) );
739 }
740
741 boolean addMavenDescriptor = currentProject.getBasedir() != null;
742
743 try
744 {
745
746
747
748 MavenArchiveConfiguration archiveConfig = JarPluginConfiguration.getArchiveConfiguration( currentProject );
749 String mavenManifestText = new MavenArchiver().getManifest( currentProject, archiveConfig ).toString();
750 addMavenDescriptor = addMavenDescriptor && archiveConfig.isAddMavenDescriptor();
751
752 Manifest mavenManifest = new Manifest();
753
754
755 File externalManifestFile = archiveConfig.getManifestFile();
756 if ( null != externalManifestFile )
757 {
758 if ( !externalManifestFile.isAbsolute() )
759 {
760 externalManifestFile = new File( currentProject.getBasedir(), externalManifestFile.getPath() );
761 }
762 if ( externalManifestFile.exists() && !externalManifestFile.equals( new File( manifestLocation, "MANIFEST.MF" ) ) )
763 {
764 InputStream mis = new FileInputStream( externalManifestFile );
765 mavenManifest.read( mis );
766 mis.close();
767 }
768 }
769
770
771 mavenManifest.read( new ByteArrayInputStream( mavenManifestText.getBytes( "UTF8" ) ) );
772
773 if ( !archiveConfig.isManifestSectionsEmpty() )
774 {
775
776
777
778 List sections = archiveConfig.getManifestSections();
779 for ( Iterator i = sections.iterator(); i.hasNext(); )
780 {
781 ManifestSection section = ( ManifestSection ) i.next();
782 Attributes attributes = new Attributes();
783
784 if ( !section.isManifestEntriesEmpty() )
785 {
786 Map entries = section.getManifestEntries();
787 for ( Iterator j = entries.entrySet().iterator(); j.hasNext(); )
788 {
789 Map.Entry entry = ( Map.Entry ) j.next();
790 attributes.putValue( ( String ) entry.getKey(), ( String ) entry.getValue() );
791 }
792 }
793
794 mavenManifest.getEntries().put( section.getName(), attributes );
795 }
796 }
797
798 Attributes mainMavenAttributes = mavenManifest.getMainAttributes();
799 mainMavenAttributes.putValue( "Created-By", "Apache Maven Bundle Plugin" );
800
801 String[] removeHeaders = builder.getProperty( Constants.REMOVEHEADERS, "" ).split( "," );
802
803
804 for ( int i = 0; i < removeHeaders.length; i++ )
805 {
806 for ( Iterator j = mainMavenAttributes.keySet().iterator(); j.hasNext(); )
807 {
808 if ( j.next().toString().matches( removeHeaders[i].trim() ) )
809 {
810 j.remove();
811 }
812 }
813 }
814
815
816
817
818 Manifest bundleManifest = jar.getManifest();
819 bundleManifest.getMainAttributes().putAll( mainMavenAttributes );
820 bundleManifest.getEntries().putAll( mavenManifest.getEntries() );
821
822
823
824 String importPackages = bundleManifest.getMainAttributes().getValue( "Import-Package" );
825 if ( importPackages != null )
826 {
827 Set optionalPackages = getOptionalPackages( currentProject );
828
829 Map<String, ? extends Map<String, String>> values = new Analyzer().parseHeader( importPackages );
830 for ( Map.Entry<String, ? extends Map<String, String>> entry : values.entrySet() )
831 {
832 String pkg = entry.getKey();
833 Map<String, String> options = entry.getValue();
834 if ( !options.containsKey( "resolution:" ) && optionalPackages.contains( pkg ) )
835 {
836 options.put( "resolution:", "optional" );
837 }
838 }
839 String result = Processor.printClauses( values );
840 bundleManifest.getMainAttributes().putValue( "Import-Package", result );
841 }
842
843 jar.setManifest( bundleManifest );
844 }
845 catch ( Exception e )
846 {
847 getLog().warn( "Unable to merge Maven manifest: " + e.getLocalizedMessage() );
848 }
849
850 if ( addMavenDescriptor )
851 {
852 doMavenMetadata( currentProject, jar );
853 }
854
855 if ( getLog().isDebugEnabled() )
856 {
857 getLog().debug( "Final Manifest:" + NL + dumpManifest( jar.getManifest(), new StringBuilder() ) );
858 }
859
860 builder.setJar( jar );
861 }
862
863
864 protected Set getOptionalPackages( MavenProject currentProject ) throws IOException, MojoExecutionException
865 {
866 ArrayList inscope = new ArrayList();
867 final Collection artifacts = getSelectedDependencies( currentProject.getArtifacts() );
868 for ( Iterator it = artifacts.iterator(); it.hasNext(); )
869 {
870 Artifact artifact = ( Artifact ) it.next();
871 if ( artifact.getArtifactHandler().isAddedToClasspath() )
872 {
873 if ( !Artifact.SCOPE_TEST.equals( artifact.getScope() ) )
874 {
875 inscope.add( artifact );
876 }
877 }
878 }
879
880 HashSet optionalArtifactIds = new HashSet();
881 for ( Iterator it = inscope.iterator(); it.hasNext(); )
882 {
883 Artifact artifact = ( Artifact ) it.next();
884 if ( artifact.isOptional() )
885 {
886 String id = artifact.toString();
887 if ( artifact.getScope() != null )
888 {
889
890 id = id.replaceFirst( ":[^:]*$", "" );
891 }
892 optionalArtifactIds.add( id );
893 }
894
895 }
896
897 HashSet required = new HashSet();
898 HashSet optional = new HashSet();
899 for ( Iterator it = inscope.iterator(); it.hasNext(); )
900 {
901 Artifact artifact = ( Artifact ) it.next();
902 File file = getFile( artifact );
903 if ( file == null )
904 {
905 continue;
906 }
907
908 Jar jar = new Jar( artifact.getArtifactId(), file );
909 if ( isTransitivelyOptional( optionalArtifactIds, artifact ) )
910 {
911 optional.addAll( jar.getPackages() );
912 }
913 else
914 {
915 required.addAll( jar.getPackages() );
916 }
917 jar.close();
918 }
919
920 optional.removeAll( required );
921 return optional;
922 }
923
924
925
926
927
928
929
930
931 protected boolean isTransitivelyOptional( HashSet optionalArtifactIds, Artifact artifact )
932 {
933 List trail = artifact.getDependencyTrail();
934 for ( Iterator iterator = trail.iterator(); iterator.hasNext(); )
935 {
936 String next = ( String ) iterator.next();
937 if ( optionalArtifactIds.contains( next ) )
938 {
939 return true;
940 }
941 }
942 return false;
943 }
944
945
946 private void unpackBundle( File jarFile )
947 {
948 File outputDir = getOutputDirectory();
949 if ( null == outputDir )
950 {
951 outputDir = new File( getBuildDirectory(), "classes" );
952 }
953
954 try
955 {
956
957
958
959
960 if ( !outputDir.exists() )
961 {
962 outputDir.mkdirs();
963 }
964
965 UnArchiver unArchiver = m_archiverManager.getUnArchiver( "jar" );
966 unArchiver.setDestDirectory( outputDir );
967 unArchiver.setSourceFile( jarFile );
968 unArchiver.extract();
969 }
970 catch ( Exception e )
971 {
972 getLog().error( "Problem unpacking " + jarFile + " to " + outputDir, e );
973 }
974 }
975
976
977 protected static String removeTagFromInstruction( String instruction, String tag )
978 {
979 StringBuffer buf = new StringBuffer();
980
981 String[] clauses = instruction.split( "," );
982 for ( int i = 0; i < clauses.length; i++ )
983 {
984 String clause = clauses[i].trim();
985 if ( !tag.equals( clause ) )
986 {
987 if ( buf.length() > 0 )
988 {
989 buf.append( ',' );
990 }
991 buf.append( clause );
992 }
993 }
994
995 return buf.toString();
996 }
997
998
999 private static Map getProperties( Model projectModel, String prefix )
1000 {
1001 Map properties = new LinkedHashMap();
1002 Method methods[] = Model.class.getDeclaredMethods();
1003 for ( int i = 0; i < methods.length; i++ )
1004 {
1005 String name = methods[i].getName();
1006 if ( name.startsWith( "get" ) )
1007 {
1008 try
1009 {
1010 Object v = methods[i].invoke( projectModel, null );
1011 if ( v != null )
1012 {
1013 name = prefix + Character.toLowerCase( name.charAt( 3 ) ) + name.substring( 4 );
1014 if ( v.getClass().isArray() )
1015 properties.put( name, Arrays.asList( ( Object[] ) v ).toString() );
1016 else
1017 properties.put( name, v );
1018
1019 }
1020 }
1021 catch ( Exception e )
1022 {
1023
1024 }
1025 }
1026 }
1027 return properties;
1028 }
1029
1030
1031 private static StringBuffer printLicenses( List licenses )
1032 {
1033 if ( licenses == null || licenses.size() == 0 )
1034 return null;
1035 StringBuffer sb = new StringBuffer();
1036 String del = "";
1037 for ( Iterator i = licenses.iterator(); i.hasNext(); )
1038 {
1039 License l = ( License ) i.next();
1040 String url = l.getUrl();
1041 if ( url == null )
1042 continue;
1043 sb.append( del );
1044 sb.append( url );
1045 del = ", ";
1046 }
1047 if ( sb.length() == 0 )
1048 return null;
1049 return sb;
1050 }
1051
1052
1053
1054
1055
1056
1057 private void doMavenMetadata( MavenProject currentProject, Jar jar ) throws IOException
1058 {
1059 String path = "META-INF/maven/" + currentProject.getGroupId() + "/" + currentProject.getArtifactId();
1060 File pomFile = new File( currentProject.getBasedir(), "pom.xml" );
1061 jar.putResource( path + "/pom.xml", new FileResource( pomFile ) );
1062
1063 Properties p = new Properties();
1064 p.put( "version", currentProject.getVersion() );
1065 p.put( "groupId", currentProject.getGroupId() );
1066 p.put( "artifactId", currentProject.getArtifactId() );
1067 ByteArrayOutputStream out = new ByteArrayOutputStream();
1068 p.store( out, "Generated by org.apache.felix.bundleplugin" );
1069 jar.putResource( path + "/pom.properties", new EmbeddedResource( out.toByteArray(), System.currentTimeMillis() ) );
1070 }
1071
1072
1073 protected Jar[] getClasspath( MavenProject currentProject ) throws IOException, MojoExecutionException
1074 {
1075 List list = new ArrayList();
1076
1077 if ( getOutputDirectory() != null && getOutputDirectory().exists() )
1078 {
1079 list.add( new Jar( ".", getOutputDirectory() ) );
1080 }
1081
1082 final Collection artifacts = getSelectedDependencies( currentProject.getArtifacts() );
1083 for ( Iterator it = artifacts.iterator(); it.hasNext(); )
1084 {
1085 Artifact artifact = ( Artifact ) it.next();
1086 if ( artifact.getArtifactHandler().isAddedToClasspath() )
1087 {
1088 if ( !Artifact.SCOPE_TEST.equals( artifact.getScope() ) )
1089 {
1090 File file = getFile( artifact );
1091 if ( file == null )
1092 {
1093 getLog().warn(
1094 "File is not available for artifact " + artifact + " in project "
1095 + currentProject.getArtifact() );
1096 continue;
1097 }
1098 Jar jar = new Jar( artifact.getArtifactId(), file );
1099 list.add( jar );
1100 }
1101 }
1102 }
1103 Jar[] cp = new Jar[list.size()];
1104 list.toArray( cp );
1105 return cp;
1106 }
1107
1108
1109 private Collection getSelectedDependencies( Collection artifacts ) throws MojoExecutionException
1110 {
1111 if ( null == excludeDependencies || excludeDependencies.length() == 0 )
1112 {
1113 return artifacts;
1114 }
1115 else if ( "true".equalsIgnoreCase( excludeDependencies ) )
1116 {
1117 return Collections.EMPTY_LIST;
1118 }
1119
1120 Collection selectedDependencies = new LinkedHashSet( artifacts );
1121 DependencyExcluder excluder = new DependencyExcluder( artifacts );
1122 excluder.processHeaders( excludeDependencies );
1123 selectedDependencies.removeAll( excluder.getExcludedArtifacts() );
1124
1125 return selectedDependencies;
1126 }
1127
1128
1129
1130
1131
1132
1133
1134 protected File getFile( Artifact artifact )
1135 {
1136 return artifact.getFile();
1137 }
1138
1139
1140 private static void header( Properties properties, String key, Object value )
1141 {
1142 if ( value == null )
1143 return;
1144
1145 if ( value instanceof Collection && ( ( Collection ) value ).isEmpty() )
1146 return;
1147
1148 properties.put( key, value.toString().replaceAll( "[\r\n]", "" ) );
1149 }
1150
1151
1152
1153
1154
1155
1156
1157
1158 protected String convertVersionToOsgi( String version )
1159 {
1160 return getMaven2OsgiConverter().getVersion( version );
1161 }
1162
1163
1164
1165
1166
1167 protected String getBundleName( MavenProject currentProject )
1168 {
1169 String extension;
1170 try
1171 {
1172 extension = currentProject.getArtifact().getArtifactHandler().getExtension();
1173 }
1174 catch ( Throwable e )
1175 {
1176 extension = currentProject.getArtifact().getType();
1177 }
1178 if ( StringUtils.isEmpty( extension ) || "bundle".equals( extension ) || "pom".equals( extension ) )
1179 {
1180 extension = "jar";
1181 }
1182 if ( null != classifier && classifier.trim().length() > 0 )
1183 {
1184 return finalName + '-' + classifier + '.' + extension;
1185 }
1186 return finalName + '.' + extension;
1187 }
1188
1189
1190 protected String getBuildDirectory()
1191 {
1192 return buildDirectory;
1193 }
1194
1195
1196 protected void setBuildDirectory( String _buildirectory )
1197 {
1198 buildDirectory = _buildirectory;
1199 }
1200
1201
1202 protected Properties getDefaultProperties( MavenProject currentProject )
1203 {
1204 Properties properties = new Properties();
1205
1206 String bsn;
1207 try
1208 {
1209 bsn = getMaven2OsgiConverter().getBundleSymbolicName( currentProject.getArtifact() );
1210 }
1211 catch ( Exception e )
1212 {
1213 bsn = currentProject.getGroupId() + "." + currentProject.getArtifactId();
1214 }
1215
1216
1217 properties.put( MAVEN_SYMBOLICNAME, bsn );
1218 properties.put( Analyzer.BUNDLE_SYMBOLICNAME, bsn );
1219 properties.put( Analyzer.IMPORT_PACKAGE, "*" );
1220 properties.put( Analyzer.BUNDLE_VERSION, getMaven2OsgiConverter().getVersion( currentProject.getVersion() ) );
1221
1222
1223 properties.put( Constants.REMOVEHEADERS, Analyzer.INCLUDE_RESOURCE + ',' + Analyzer.PRIVATE_PACKAGE );
1224
1225 header( properties, Analyzer.BUNDLE_DESCRIPTION, currentProject.getDescription() );
1226 StringBuffer licenseText = printLicenses( currentProject.getLicenses() );
1227 if ( licenseText != null )
1228 {
1229 header( properties, Analyzer.BUNDLE_LICENSE, licenseText );
1230 }
1231 header( properties, Analyzer.BUNDLE_NAME, currentProject.getName() );
1232
1233 if ( currentProject.getOrganization() != null )
1234 {
1235 if ( currentProject.getOrganization().getName() != null )
1236 {
1237 String organizationName = currentProject.getOrganization().getName();
1238 header( properties, Analyzer.BUNDLE_VENDOR, organizationName );
1239 properties.put( "project.organization.name", organizationName );
1240 properties.put( "pom.organization.name", organizationName );
1241 }
1242 if ( currentProject.getOrganization().getUrl() != null )
1243 {
1244 String organizationUrl = currentProject.getOrganization().getUrl();
1245 header( properties, Analyzer.BUNDLE_DOCURL, organizationUrl );
1246 properties.put( "project.organization.url", organizationUrl );
1247 properties.put( "pom.organization.url", organizationUrl );
1248 }
1249 }
1250
1251 properties.putAll( currentProject.getProperties() );
1252 properties.putAll( currentProject.getModel().getProperties() );
1253
1254 for ( Iterator i = currentProject.getFilters().iterator(); i.hasNext(); )
1255 {
1256 File filterFile = new File( ( String ) i.next() );
1257 if ( filterFile.isFile() )
1258 {
1259 properties.putAll( PropertyUtils.loadProperties( filterFile ) );
1260 }
1261 }
1262
1263 if ( m_mavenSession != null )
1264 {
1265 try
1266 {
1267
1268 Properties sessionProperties = m_mavenSession.getExecutionProperties();
1269 for ( Enumeration e = sessionProperties.propertyNames(); e.hasMoreElements(); )
1270 {
1271 String key = ( String ) e.nextElement();
1272 if ( key.length() > 0 && !Character.isUpperCase( key.charAt( 0 ) ) )
1273 {
1274 properties.put( key, sessionProperties.getProperty( key ) );
1275 }
1276 }
1277 }
1278 catch ( Exception e )
1279 {
1280 getLog().warn( "Problem with Maven session properties: " + e.getLocalizedMessage() );
1281 }
1282 }
1283
1284 properties.putAll( getProperties( currentProject.getModel(), "project.build." ) );
1285 properties.putAll( getProperties( currentProject.getModel(), "pom." ) );
1286 properties.putAll( getProperties( currentProject.getModel(), "project." ) );
1287
1288 properties.put( "project.baseDir", getBase( currentProject ) );
1289 properties.put( "project.build.directory", getBuildDirectory() );
1290 properties.put( "project.build.outputdirectory", getOutputDirectory() );
1291
1292 properties.put( "classifier", classifier == null ? "" : classifier );
1293
1294
1295 header( properties, Analyzer.PLUGIN, ScrPlugin.class.getName() + ","
1296 + BlueprintPlugin.class.getName() + ","
1297 + SpringXMLType.class.getName() );
1298
1299 return properties;
1300 }
1301
1302
1303 protected static File getBase( MavenProject currentProject )
1304 {
1305 return currentProject.getBasedir() != null ? currentProject.getBasedir() : new File( "" );
1306 }
1307
1308
1309 protected File getOutputDirectory()
1310 {
1311 return outputDirectory;
1312 }
1313
1314
1315 protected void setOutputDirectory( File _outputDirectory )
1316 {
1317 outputDirectory = _outputDirectory;
1318 }
1319
1320
1321 private static void addLocalPackages( File outputDirectory, Analyzer analyzer ) throws IOException
1322 {
1323 Packages packages = new Packages();
1324
1325 if ( outputDirectory != null && outputDirectory.isDirectory() )
1326 {
1327
1328 DirectoryScanner scanner = new DirectoryScanner();
1329 scanner.setBasedir( outputDirectory );
1330 scanner.setIncludes( new String[]
1331 { "**/*.class" } );
1332
1333 scanner.addDefaultExcludes();
1334 scanner.scan();
1335
1336 String[] paths = scanner.getIncludedFiles();
1337 for ( int i = 0; i < paths.length; i++ )
1338 {
1339 packages.put( analyzer.getPackageRef( getPackageName( paths[i] ) ) );
1340 }
1341 }
1342
1343 Packages exportedPkgs = new Packages();
1344 Packages privatePkgs = new Packages();
1345
1346 boolean noprivatePackages = "!*".equals( analyzer.getProperty( Analyzer.PRIVATE_PACKAGE ) );
1347
1348 for ( PackageRef pkg : packages.keySet() )
1349 {
1350
1351 privatePkgs.put( pkg );
1352
1353
1354 String fqn = pkg.getFQN();
1355 if ( noprivatePackages || !( ".".equals( fqn ) || fqn.contains( ".internal" ) || fqn.contains( ".impl" ) ) )
1356 {
1357 exportedPkgs.put( pkg );
1358 }
1359 }
1360
1361 Properties properties = analyzer.getProperties();
1362 String exported = properties.getProperty( Analyzer.EXPORT_PACKAGE );
1363 if ( exported == null )
1364 {
1365 if ( !properties.containsKey( Analyzer.EXPORT_CONTENTS ) )
1366 {
1367
1368 for ( Attrs attrs : exportedPkgs.values() )
1369 {
1370 attrs.put( Constants.SPLIT_PACKAGE_DIRECTIVE, "merge-first" );
1371 }
1372 properties.setProperty( Analyzer.EXPORT_PACKAGE, Processor.printClauses( exportedPkgs ) );
1373 }
1374 else
1375 {
1376
1377 properties.setProperty( Analyzer.EXPORT_PACKAGE, "" );
1378 }
1379 }
1380 else if ( exported.indexOf( LOCAL_PACKAGES ) >= 0 )
1381 {
1382 String newExported = StringUtils.replace( exported, LOCAL_PACKAGES, Processor.printClauses( exportedPkgs ) );
1383 properties.setProperty( Analyzer.EXPORT_PACKAGE, newExported );
1384 }
1385
1386 String internal = properties.getProperty( Analyzer.PRIVATE_PACKAGE );
1387 if ( internal == null )
1388 {
1389 if ( !privatePkgs.isEmpty() )
1390 {
1391 for ( Attrs attrs : privatePkgs.values() )
1392 {
1393 attrs.put( Constants.SPLIT_PACKAGE_DIRECTIVE, "merge-first" );
1394 }
1395 properties.setProperty( Analyzer.PRIVATE_PACKAGE, Processor.printClauses( privatePkgs ) );
1396 }
1397 else
1398 {
1399
1400 properties.setProperty( Analyzer.PRIVATE_PACKAGE, "!*" );
1401 }
1402 }
1403 else if ( internal.indexOf( LOCAL_PACKAGES ) >= 0 )
1404 {
1405 String newInternal = StringUtils.replace( internal, LOCAL_PACKAGES, Processor.printClauses( privatePkgs ) );
1406 properties.setProperty( Analyzer.PRIVATE_PACKAGE, newInternal );
1407 }
1408 }
1409
1410
1411 private static String getPackageName( String filename )
1412 {
1413 int n = filename.lastIndexOf( File.separatorChar );
1414 return n < 0 ? "." : filename.substring( 0, n ).replace( File.separatorChar, '.' );
1415 }
1416
1417
1418 private static List getMavenResources( MavenProject currentProject, boolean test )
1419 {
1420 List resources = new ArrayList( test ? currentProject.getTestResources() : currentProject.getResources() );
1421
1422 if ( currentProject.getCompileSourceRoots() != null )
1423 {
1424
1425 List packageInfoIncludes = Collections.singletonList( "**/packageinfo" );
1426 for ( Iterator i = currentProject.getCompileSourceRoots().iterator(); i.hasNext(); )
1427 {
1428 String sourceRoot = ( String ) i.next();
1429 Resource packageInfoResource = new Resource();
1430 packageInfoResource.setDirectory( sourceRoot );
1431 packageInfoResource.setIncludes( packageInfoIncludes );
1432 resources.add( packageInfoResource );
1433 }
1434 }
1435
1436 return resources;
1437 }
1438
1439
1440 protected static String getMavenResourcePaths( MavenProject currentProject, boolean test )
1441 {
1442 final String basePath = currentProject.getBasedir().getAbsolutePath();
1443
1444 Set pathSet = new LinkedHashSet();
1445 for ( Iterator i = getMavenResources( currentProject, test ).iterator(); i.hasNext(); )
1446 {
1447 Resource resource = ( Resource ) i.next();
1448
1449 final String sourcePath = resource.getDirectory();
1450 final String targetPath = resource.getTargetPath();
1451
1452
1453 if ( new File( sourcePath ).exists() && ( ( targetPath == null ) || ( targetPath.indexOf( ".." ) < 0 ) ) )
1454 {
1455 DirectoryScanner scanner = new DirectoryScanner();
1456
1457 scanner.setBasedir( sourcePath );
1458 if ( resource.getIncludes() != null && !resource.getIncludes().isEmpty() )
1459 {
1460 scanner.setIncludes( ( String[] ) resource.getIncludes().toArray( EMPTY_STRING_ARRAY ) );
1461 }
1462 else
1463 {
1464 scanner.setIncludes( DEFAULT_INCLUDES );
1465 }
1466
1467 if ( resource.getExcludes() != null && !resource.getExcludes().isEmpty() )
1468 {
1469 scanner.setExcludes( ( String[] ) resource.getExcludes().toArray( EMPTY_STRING_ARRAY ) );
1470 }
1471
1472 scanner.addDefaultExcludes();
1473 scanner.scan();
1474
1475 List includedFiles = Arrays.asList( scanner.getIncludedFiles() );
1476
1477 for ( Iterator j = includedFiles.iterator(); j.hasNext(); )
1478 {
1479 String name = ( String ) j.next();
1480 String path = sourcePath + '/' + name;
1481
1482
1483 if ( path.startsWith( basePath ) )
1484 {
1485 if ( path.length() == basePath.length() )
1486 {
1487 path = ".";
1488 }
1489 else
1490 {
1491 path = path.substring( basePath.length() + 1 );
1492 }
1493 }
1494
1495
1496
1497 if ( File.separatorChar != '/' )
1498 {
1499 name = name.replace( File.separatorChar, '/' );
1500 path = path.replace( File.separatorChar, '/' );
1501 }
1502
1503
1504 path = name + '=' + path;
1505 if ( targetPath != null )
1506 {
1507 path = targetPath + '/' + path;
1508 }
1509
1510
1511 if ( resource.isFiltering() )
1512 {
1513 path = '{' + path + '}';
1514 }
1515
1516 pathSet.add( path );
1517 }
1518 }
1519 }
1520
1521 StringBuffer resourcePaths = new StringBuffer();
1522 for ( Iterator i = pathSet.iterator(); i.hasNext(); )
1523 {
1524 resourcePaths.append( i.next() );
1525 if ( i.hasNext() )
1526 {
1527 resourcePaths.append( ',' );
1528 }
1529 }
1530
1531 return resourcePaths.toString();
1532 }
1533
1534
1535 protected Collection getEmbeddableArtifacts( MavenProject currentProject, Analyzer analyzer )
1536 throws MojoExecutionException
1537 {
1538 final Collection artifacts;
1539
1540 String embedTransitive = analyzer.getProperty( DependencyEmbedder.EMBED_TRANSITIVE );
1541 if ( Boolean.valueOf( embedTransitive ).booleanValue() )
1542 {
1543
1544 artifacts = currentProject.getArtifacts();
1545 }
1546 else
1547 {
1548
1549 artifacts = currentProject.getDependencyArtifacts();
1550 }
1551
1552 return getSelectedDependencies( artifacts );
1553 }
1554
1555
1556 protected static void addMavenSourcePath( MavenProject currentProject, Analyzer analyzer, Log log )
1557 {
1558
1559 StringBuilder mavenSourcePaths = new StringBuilder();
1560 StringBuilder mavenTestSourcePaths = new StringBuilder();
1561 Map<StringBuilder, List<?>> map = new HashMap<StringBuilder, List<?>>(2);
1562 map.put(mavenSourcePaths, currentProject.getCompileSourceRoots() );
1563 map.put(mavenTestSourcePaths, currentProject.getTestCompileSourceRoots() );
1564 for ( Map.Entry<StringBuilder, List<?>> entry : map.entrySet() )
1565 {
1566 List<?> compileSourceRoots = entry.getValue();
1567 if ( compileSourceRoots != null )
1568 {
1569 StringBuilder sourcePaths = entry.getKey();
1570 for ( Iterator i = compileSourceRoots.iterator(); i.hasNext(); )
1571 {
1572 if ( sourcePaths.length() > 0 )
1573 {
1574 sourcePaths.append( ',' );
1575 }
1576 sourcePaths.append( ( String ) i.next() );
1577 }
1578 }
1579 }
1580 final String sourcePath = analyzer.getProperty( Analyzer.SOURCEPATH );
1581 if ( sourcePath != null )
1582 {
1583 if ( sourcePath.contains(MAVEN_SOURCES) || sourcePath.contains(MAVEN_TEST_RESOURCES) )
1584 {
1585 String combinedSource = StringUtils.replace( sourcePath, MAVEN_SOURCES, mavenSourcePaths.toString() );
1586 combinedSource = StringUtils.replace( combinedSource, MAVEN_TEST_SOURCES, mavenTestSourcePaths.toString() );
1587 if ( combinedSource.length() > 0 )
1588 {
1589 analyzer.setProperty( Analyzer.SOURCEPATH, combinedSource );
1590 }
1591 else
1592 {
1593 analyzer.unsetProperty( Analyzer.SOURCEPATH );
1594 }
1595 }
1596 else if ( mavenSourcePaths.length() > 0 )
1597 {
1598 log.warn( Analyzer.SOURCEPATH + ": overriding " + mavenSourcePaths + " with " + sourcePath + " (add "
1599 + MAVEN_SOURCES + " if you want to include the maven sources)" );
1600 }
1601 else if ( mavenTestSourcePaths.length() > 0 )
1602 {
1603 log.warn( Analyzer.SOURCEPATH + ": overriding " + mavenTestSourcePaths + " with " + sourcePath + " (add "
1604 + MAVEN_TEST_SOURCES + " if you want to include the maven sources)" );
1605 }
1606 }
1607 else if ( mavenSourcePaths.length() > 0 )
1608 {
1609 analyzer.setProperty( Analyzer.SOURCEPATH, mavenSourcePaths.toString() );
1610 }
1611 }
1612 }