1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.archetype.mojos;
20
21 import javax.inject.Inject;
22
23 import java.io.BufferedReader;
24 import java.io.File;
25 import java.io.FileReader;
26 import java.io.FileWriter;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.io.Reader;
30 import java.io.StringWriter;
31 import java.io.Writer;
32 import java.nio.file.Files;
33 import java.util.Arrays;
34 import java.util.Collection;
35 import java.util.HashMap;
36 import java.util.LinkedHashMap;
37 import java.util.List;
38 import java.util.Map;
39 import java.util.Objects;
40 import java.util.Properties;
41 import java.util.Set;
42
43 import org.apache.commons.collections.CollectionUtils;
44 import org.apache.maven.archetype.ArchetypeGenerationRequest;
45 import org.apache.maven.archetype.ArchetypeGenerationResult;
46 import org.apache.maven.archetype.common.Constants;
47 import org.apache.maven.archetype.downloader.DownloadException;
48 import org.apache.maven.archetype.downloader.Downloader;
49 import org.apache.maven.archetype.exception.ArchetypeNotConfigured;
50 import org.apache.maven.archetype.generator.ArchetypeGenerator;
51 import org.apache.maven.archetype.ui.generation.ArchetypeGenerationConfigurator;
52 import org.apache.maven.execution.MavenSession;
53 import org.apache.maven.plugin.AbstractMojo;
54 import org.apache.maven.plugin.MojoExecutionException;
55 import org.apache.maven.plugins.annotations.Mojo;
56 import org.apache.maven.plugins.annotations.Parameter;
57 import org.apache.maven.project.MavenProject;
58 import org.apache.maven.settings.Settings;
59 import org.apache.maven.settings.io.xpp3.SettingsXpp3Writer;
60 import org.apache.maven.shared.invoker.DefaultInvocationRequest;
61 import org.apache.maven.shared.invoker.InvocationRequest;
62 import org.apache.maven.shared.invoker.InvocationResult;
63 import org.apache.maven.shared.invoker.Invoker;
64 import org.apache.maven.shared.invoker.MavenInvocationException;
65 import org.apache.maven.shared.scriptinterpreter.ScriptException;
66 import org.apache.maven.shared.scriptinterpreter.ScriptRunner;
67 import org.codehaus.plexus.util.FileUtils;
68 import org.codehaus.plexus.util.IOUtil;
69 import org.codehaus.plexus.util.InterpolationFilterReader;
70 import org.codehaus.plexus.util.StringUtils;
71 import org.codehaus.plexus.util.introspection.ReflectionValueExtractor;
72 import org.codehaus.plexus.util.xml.XmlStreamReader;
73 import org.codehaus.plexus.util.xml.XmlStreamWriter;
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132 @Mojo(name = "integration-test", requiresProject = true)
133 public class IntegrationTestMojo extends AbstractMojo {
134
135 private ArchetypeGenerator archetypeGenerator;
136
137 private Downloader downloader;
138
139 private Invoker invoker;
140
141 private ArchetypeGenerationConfigurator archetypeGenerationConfigurator;
142
143 @Inject
144 public IntegrationTestMojo(
145 ArchetypeGenerator archetypeGenerator,
146 Downloader downloader,
147 Invoker invoker,
148 ArchetypeGenerationConfigurator archetypeGenerationConfigurator) {
149 this.archetypeGenerator = archetypeGenerator;
150 this.downloader = downloader;
151 this.invoker = invoker;
152 this.archetypeGenerationConfigurator = archetypeGenerationConfigurator;
153 }
154
155
156
157
158 @Parameter(defaultValue = "${project}", readonly = true, required = true)
159 private MavenProject project;
160
161 @Parameter(defaultValue = "${session}", readonly = true, required = true)
162 private MavenSession session;
163
164
165
166
167 @Parameter(property = "archetype.test.skip")
168 private boolean skip = false;
169
170
171
172
173
174
175 @Parameter(
176 property = "archetype.test.projectsDirectory",
177 defaultValue = "${project.build.testOutputDirectory}/projects",
178 required = true)
179 private File testProjectsDirectory;
180
181
182
183
184
185
186
187
188
189
190 @Parameter(property = "archetype.test.verifyScript", defaultValue = "verify")
191 private String postBuildHookScript;
192
193
194
195
196
197
198 @Parameter(property = "archetype.test.noLog", defaultValue = "false")
199 private boolean noLog;
200
201
202
203
204
205
206 @Parameter(property = "archetype.test.streamLogs", defaultValue = "true")
207 private boolean streamLogs;
208
209
210
211
212
213
214 @Parameter(property = "encoding", defaultValue = "${project.build.sourceEncoding}")
215 private String encoding;
216
217
218
219
220
221
222 @Parameter(
223 property = "archetype.test.localRepositoryPath",
224 defaultValue = "${settings.localRepository}",
225 required = true)
226 private File localRepositoryPath;
227
228
229
230
231
232
233 @Parameter(property = "archetype.test.showVersion", defaultValue = "false")
234 private boolean showVersion;
235
236
237
238
239
240
241 @Parameter(property = "archetype.test.ignoreEOLStyle", defaultValue = "false")
242 private boolean ignoreEOLStyle;
243
244
245
246
247
248
249 @Parameter(property = "archetype.test.debug", defaultValue = "false")
250 private boolean debug;
251
252
253
254
255
256
257 @Parameter
258 private Map<String, String> filterProperties;
259
260
261
262
263
264
265 @Parameter(defaultValue = "${settings}", required = true, readonly = true)
266 private Settings settings;
267
268
269
270
271
272
273
274
275 @Parameter(property = "archetype.test.settingsFile")
276 private File settingsFile;
277
278
279
280
281
282
283 @Parameter
284 private Map<String, String> properties = new HashMap<>();
285
286 @Override
287 public void execute() throws MojoExecutionException {
288 if (skip) {
289 return;
290 }
291
292 if (!testProjectsDirectory.exists()) {
293 getLog().warn("No Archetype IT projects: root 'projects' directory not found.");
294
295 return;
296 }
297
298 File archetypeFile = project.getArtifact().getFile();
299
300 if (archetypeFile == null) {
301 throw new MojoExecutionException("Unable to get the archetypes' artifact which should have just been built:"
302 + " you probably launched 'mvn archetype:integration-test' instead of"
303 + " 'mvn integration-test'.");
304 }
305
306 try {
307 List<File> projectsGoalFiles = FileUtils.getFiles(testProjectsDirectory, "**/goal.txt", "");
308
309 if (projectsGoalFiles.isEmpty()) {
310 getLog().warn("No Archetype IT projects: no directory with goal.txt found.");
311
312 return;
313 }
314
315 StringWriter errorWriter = new StringWriter();
316 for (File goalFile : projectsGoalFiles) {
317 try {
318 processIntegrationTest(goalFile, archetypeFile);
319 } catch (IntegrationTestFailure ex) {
320 errorWriter.write(
321 "\nArchetype IT '" + goalFile.getParentFile().getName() + "' failed: ");
322 errorWriter.write(ex.getMessage());
323 }
324 }
325
326 String errors = errorWriter.toString();
327 if (!(errors == null || errors.isEmpty())) {
328 throw new MojoExecutionException(errors);
329 }
330 } catch (IOException ex) {
331 throw new MojoExecutionException(ex.getMessage(), ex);
332 }
333 }
334
335
336
337
338
339
340
341
342 private void assertDirectoryEquals(File reference, File actual) throws IntegrationTestFailure, IOException {
343 List<String> referenceFiles =
344 FileUtils.getFileAndDirectoryNames(reference, "**", null, false, true, true, true);
345 getLog().debug("reference content: " + referenceFiles);
346
347 List<String> actualFiles = FileUtils.getFileAndDirectoryNames(actual, "**", null, false, true, true, true);
348 getLog().debug("actual content: " + actualFiles);
349
350 boolean fileNamesEquals = CollectionUtils.isEqualCollection(referenceFiles, actualFiles);
351
352 if (!fileNamesEquals) {
353 getLog().debug("Actual list of files is not the same as reference:");
354 int missing = 0;
355 for (String ref : referenceFiles) {
356 if (actualFiles.contains(ref)) {
357 actualFiles.remove(ref);
358 getLog().debug("Contained " + ref);
359 } else {
360 missing++;
361 getLog().error("Not contained " + ref);
362 }
363 }
364 getLog().error("Remains " + actualFiles);
365
366 throw new IntegrationTestFailure("Reference and generated project differs (missing: " + missing
367 + ", unexpected: " + actualFiles.size() + ")");
368 }
369
370 if (!ignoreEOLStyle) {
371 getLog().warn("Property ignoreEOLStyle was not set - files will be compared considering their EOL style!");
372 }
373
374 boolean contentEquals = true;
375
376 for (String file : referenceFiles) {
377 File referenceFile = new File(reference, file);
378 File actualFile = new File(actual, file);
379
380 if (referenceFile.isDirectory()) {
381 if (actualFile.isFile()) {
382 getLog().error("File " + file + " is a directory in the reference but a file in actual");
383 contentEquals = false;
384 }
385 } else if (actualFile.isDirectory()) {
386 if (referenceFile.isFile()) {
387 getLog().error("File " + file + " is a file in the reference but a directory in actual");
388 contentEquals = false;
389 }
390 } else if (!contentEquals(referenceFile, actualFile)) {
391 getLog().error("Contents of file " + file + " are not equal");
392 contentEquals = false;
393 }
394 }
395 if (!contentEquals) {
396 throw new IntegrationTestFailure("Some content are not equals");
397 }
398 }
399
400
401
402
403
404 private boolean contentEquals(File referenceFile, File actualFile) throws IOException {
405
406 if (!ignoreEOLStyle) {
407 return FileUtils.contentEquals(referenceFile, actualFile);
408 }
409
410 getLog().debug("Comparing files with EOL style ignored.");
411
412 try (BufferedReader referenceFileReader = new BufferedReader(new FileReader(referenceFile));
413 BufferedReader actualFileReader = new BufferedReader(new FileReader(actualFile))) {
414 String refLine = null;
415 String actualLine = null;
416
417 do {
418 refLine = referenceFileReader.readLine();
419 actualLine = actualFileReader.readLine();
420 if (!Objects.equals(refLine, actualLine)) {
421 getLog().error("Conflict found. Reference line :");
422 getLog().error(refLine);
423 getLog().error("Actual line :");
424 getLog().error(actualLine);
425 return false;
426 }
427 } while (refLine != null || actualLine != null);
428
429 return true;
430 }
431 }
432
433 private Properties loadProperties(final File propertiesFile) throws IOException {
434 Properties properties = new Properties();
435
436 try (InputStream in = Files.newInputStream(propertiesFile.toPath())) {
437 properties.load(in);
438 }
439
440 return properties;
441 }
442
443 private void processIntegrationTest(File goalFile, File archetypeFile)
444 throws IntegrationTestFailure, MojoExecutionException {
445 getLog().info("Processing Archetype IT project: "
446 + goalFile.getParentFile().getName());
447
448 try {
449 Properties properties = getProperties(goalFile);
450
451 File basedir = new File(goalFile.getParentFile(), "project");
452
453 FileUtils.deleteDirectory(basedir);
454
455 FileUtils.mkdir(basedir.toString());
456
457 basedir = setupParentProjects(goalFile.getParentFile().getParentFile(), basedir);
458
459 ArchetypeGenerationRequest request = generate(
460 project.getGroupId(),
461 project.getArtifactId(),
462 project.getVersion(),
463 archetypeFile,
464 properties,
465 basedir.toString());
466
467 File reference = new File(goalFile.getParentFile(), "reference");
468
469 if (reference.exists()) {
470
471 getLog().info("Comparing generated project with reference content: " + reference);
472
473 assertDirectoryEquals(reference, new File(basedir, request.getArtifactId()));
474 }
475
476 String goals = FileUtils.fileRead(goalFile);
477
478 if (goals != null && !goals.isEmpty()) {
479 invokePostArchetypeGenerationGoals(goals.trim(), new File(basedir, request.getArtifactId()), goalFile);
480 }
481 } catch (IOException ioe) {
482 throw new IntegrationTestFailure(ioe);
483 }
484 }
485
486 private ArchetypeGenerationRequest generate(
487 String archetypeGroupId,
488 String archetypeArtifactId,
489 String archetypeVersion,
490 File archetypeFile,
491 Properties properties,
492 String basedir)
493 throws IntegrationTestFailure, MojoExecutionException {
494
495 ArchetypeGenerationRequest request = new ArchetypeGenerationRequest()
496 .setArchetypeGroupId(archetypeGroupId)
497 .setArchetypeArtifactId(archetypeArtifactId)
498 .setArchetypeVersion(archetypeVersion)
499 .setGroupId(properties.getProperty(Constants.GROUP_ID))
500 .setArtifactId(properties.getProperty(Constants.ARTIFACT_ID))
501 .setVersion(properties.getProperty(Constants.VERSION))
502 .setPackage(properties.getProperty(Constants.PACKAGE))
503 .setRepositorySession(session.getRepositorySession())
504 .setOutputDirectory(basedir)
505 .setProperties(properties);
506
507
508 ArchetypeGenerationResult result = new ArchetypeGenerationResult();
509 try {
510 archetypeGenerationConfigurator.configureArchetype(request, false, properties);
511 } catch (Exception e) {
512 throw new MojoExecutionException("Cannot configure archetype", e);
513 }
514 archetypeGenerator.generateArchetype(request, archetypeFile, result);
515
516 if (result.getCause() != null) {
517 if (result.getCause() instanceof ArchetypeNotConfigured) {
518 ArchetypeNotConfigured anc = (ArchetypeNotConfigured) result.getCause();
519
520 throw new IntegrationTestFailure(
521 "Missing required properties in archetype.properties: "
522 + StringUtils.join(anc.getMissingProperties().iterator(), ", "),
523 anc);
524 }
525
526 throw new IntegrationTestFailure(result.getCause().getMessage(), result.getCause());
527 }
528 return request;
529 }
530
531 private File setupParentProjects(File configFolder, File buildFolder)
532 throws IOException, MojoExecutionException, IntegrationTestFailure {
533
534 File archetypePomPropertiesFile = new File(configFolder, "archetype.pom.properties");
535 if (!archetypePomPropertiesFile.exists()) {
536 getLog().debug("No 'archetype.pom.properties' file found in " + configFolder);
537 return buildFolder;
538 }
539
540
541 buildFolder = setupParentProjects(configFolder.getParentFile(), buildFolder);
542
543 Properties archetypePomProperties = loadProperties(archetypePomPropertiesFile);
544 String groupId = archetypePomProperties.getProperty(Constants.GROUP_ID);
545 if (groupId == null || groupId.isEmpty()) {
546 throw new MojoExecutionException(
547 "Property " + Constants.GROUP_ID + " not set in " + archetypePomPropertiesFile);
548 }
549 String artifactId = archetypePomProperties.getProperty(Constants.ARTIFACT_ID);
550 if (artifactId == null || artifactId.isEmpty()) {
551 throw new MojoExecutionException(
552 "Property " + Constants.ARTIFACT_ID + " not set in " + archetypePomPropertiesFile);
553 }
554 String version = archetypePomProperties.getProperty(Constants.VERSION);
555 if (version == null || version.isEmpty()) {
556 throw new MojoExecutionException(
557 "Property " + Constants.VERSION + " not set in " + archetypePomPropertiesFile);
558 }
559
560 File archetypeFile;
561 try {
562 archetypeFile = getArchetypeFile(groupId, artifactId, version);
563 } catch (DownloadException e) {
564 throw new MojoExecutionException("Could not resolve archetype artifact ", e);
565 }
566 Properties archetypeProperties = getProperties(archetypePomPropertiesFile);
567 getLog().info("Setting up parent project in " + buildFolder);
568 ArchetypeGenerationRequest request =
569 generate(groupId, artifactId, version, archetypeFile, archetypeProperties, buildFolder.toString());
570 return new File(buildFolder, request.getArtifactId());
571 }
572
573 private File getArchetypeFile(String groupId, String artifactId, String version) throws DownloadException {
574 return downloader.download(
575 groupId, artifactId, version, project.getRemoteProjectRepositories(), session.getRepositorySession());
576 }
577
578 private Properties getProperties(File goalFile) throws IOException {
579 File propertiesFile = new File(goalFile.getParentFile(), "archetype.properties");
580
581 return loadProperties(propertiesFile);
582 }
583
584 private void invokePostArchetypeGenerationGoals(String goals, File basedir, File goalFile)
585 throws IntegrationTestFailure, IOException, MojoExecutionException {
586 FileLogger logger = setupLogger(basedir);
587
588 if (!StringUtils.isBlank(goals)) {
589
590 getLog().info("Invoking post-archetype-generation goals: " + goals);
591
592 if (!localRepositoryPath.exists()) {
593 localRepositoryPath.mkdirs();
594 }
595
596
597 InvocationRequest request = new DefaultInvocationRequest()
598 .setBaseDirectory(basedir)
599 .setGoals(Arrays.asList(StringUtils.split(goals, ",")))
600 .setLocalRepositoryDirectory(localRepositoryPath)
601 .setBatchMode(true)
602 .setShowErrors(true);
603
604
605 request.setDebug(debug);
606
607 request.setShowVersion(showVersion);
608
609 if (logger != null) {
610 request.setErrorHandler(logger);
611 request.setOutputHandler(logger);
612 }
613
614 if (!properties.isEmpty()) {
615 Properties props = new Properties();
616 for (Map.Entry<String, String> entry : properties.entrySet()) {
617 if (entry.getValue() != null) {
618 props.setProperty(entry.getKey(), entry.getValue());
619 }
620 }
621 request.setProperties(props);
622 }
623
624 File archetypeItDirectory = new File(project.getBuild().getDirectory(), "archetype-it");
625 if (archetypeItDirectory.exists()) {
626 FileUtils.deleteDirectory(archetypeItDirectory);
627 }
628 archetypeItDirectory.mkdir();
629 File userSettings;
630 if (settingsFile != null) {
631 userSettings = new File(archetypeItDirectory, "interpolated-" + settingsFile.getName());
632
633 buildInterpolatedFile(settingsFile, userSettings);
634 } else {
635
636 userSettings = new File(archetypeItDirectory, "archetype-settings.xml");
637
638 SettingsXpp3Writer settingsWriter = new SettingsXpp3Writer();
639
640 try (FileWriter fileWriter = new FileWriter(userSettings)) {
641 settingsWriter.write(fileWriter, settings);
642 }
643 }
644 request.setUserSettingsFile(userSettings);
645
646 try {
647 InvocationResult result = invoker.execute(request);
648
649 getLog().info("Post-archetype-generation invoker exit code: " + result.getExitCode());
650
651 if (result.getExitCode() != 0) {
652 throw new IntegrationTestFailure(
653 "Execution failure: exit code = " + result.getExitCode(), result.getExecutionException());
654 }
655 } catch (MavenInvocationException e) {
656 throw new IntegrationTestFailure("Cannot run additions goals.", e);
657 }
658 } else {
659 getLog().info("No post-archetype-generation goals to invoke.");
660 }
661
662 try (ScriptRunner scriptRunner = new ScriptRunner()) {
663 scriptRunner.setScriptEncoding(encoding);
664
665 Map<String, Object> context = new LinkedHashMap<>();
666 context.put("projectDir", basedir);
667
668 scriptRunner.run("post-build script", goalFile.getParentFile(), postBuildHookScript, context, logger);
669 } catch (ScriptException e) {
670 throw new IntegrationTestFailure("post build script failure failure: " + e.getMessage(), e);
671 }
672 }
673
674 private FileLogger setupLogger(File basedir) throws IOException {
675 FileLogger logger = null;
676
677 if (!noLog) {
678 File outputLog = new File(basedir, "build.log");
679
680 if (streamLogs) {
681 logger = new FileLogger(outputLog, getLog());
682 } else {
683 logger = new FileLogger(outputLog);
684 }
685
686 getLog().debug("build log initialized in: " + outputLog);
687 }
688
689 return logger;
690 }
691
692 static class IntegrationTestFailure extends Exception {
693 IntegrationTestFailure() {
694 super();
695 }
696
697 IntegrationTestFailure(String message) {
698 super(message);
699 }
700
701 IntegrationTestFailure(Throwable cause) {
702 super(cause);
703 }
704
705 IntegrationTestFailure(String message, Throwable cause) {
706 super(message, cause);
707 }
708 }
709
710
711
712
713
714
715 private Map<String, Object> getInterpolationValueSource() {
716 Map<String, Object> props = new HashMap<>();
717 if (filterProperties != null) {
718 props.putAll(filterProperties);
719 }
720 if (filterProperties != null) {
721 props.putAll(filterProperties);
722 }
723 props.put("basedir", this.project.getBasedir().getAbsolutePath());
724 props.put("baseurl", toUrl(this.project.getBasedir().getAbsolutePath()));
725 if (settings.getLocalRepository() != null) {
726 props.put("localRepository", settings.getLocalRepository());
727 props.put("localRepositoryUrl", toUrl(settings.getLocalRepository()));
728 }
729 return new CompositeMap(this.project, props);
730 }
731
732 protected void buildInterpolatedFile(File originalFile, File interpolatedFile) throws MojoExecutionException {
733 getLog().debug("Interpolate " + originalFile.getPath() + " to " + interpolatedFile.getPath());
734
735 try {
736 String xml;
737
738
739 Map<String, Object> composite = getInterpolationValueSource();
740
741 try (Reader xmlStreamReader = new XmlStreamReader(originalFile);
742 Reader reader = new InterpolationFilterReader(xmlStreamReader, composite, "@", "@")) {
743 xml = IOUtil.toString(reader);
744 }
745
746 try (Writer writer = new XmlStreamWriter(interpolatedFile)) {
747 interpolatedFile.getParentFile().mkdirs();
748
749 writer.write(xml);
750 }
751 } catch (IOException e) {
752 throw new MojoExecutionException("Failed to interpolate file " + originalFile.getPath(), e);
753 }
754 }
755
756 private static class CompositeMap implements Map<String, Object> {
757
758
759
760
761 private MavenProject mavenProject;
762
763
764
765
766 private Map<String, Object> properties;
767
768
769
770
771
772
773
774
775
776 protected CompositeMap(MavenProject mavenProject, Map<String, Object> properties) {
777 if (mavenProject == null) {
778 throw new IllegalArgumentException("no project specified");
779 }
780 this.mavenProject = mavenProject;
781 this.properties = properties == null ? new HashMap<>() : properties;
782 }
783
784
785
786
787
788
789 @Override
790 public void clear() {
791
792 }
793
794
795
796
797
798
799 @Override
800 public boolean containsKey(Object key) {
801 if (!(key instanceof String)) {
802 return false;
803 }
804
805 String expression = (String) key;
806 if (expression.startsWith("project.") || expression.startsWith("pom.")) {
807 try {
808 Object evaluated = ReflectionValueExtractor.evaluate(expression, this.mavenProject);
809 if (evaluated != null) {
810 return true;
811 }
812 } catch (Exception e) {
813
814 }
815 }
816
817 return properties.containsKey(key) || mavenProject.getProperties().containsKey(key);
818 }
819
820
821
822
823
824
825 @Override
826 public boolean containsValue(Object value) {
827 throw new UnsupportedOperationException();
828 }
829
830
831
832
833
834
835 @Override
836 public Set<Entry<String, Object>> entrySet() {
837 throw new UnsupportedOperationException();
838 }
839
840
841
842
843
844
845 @Override
846 public Object get(Object key) {
847 if (!(key instanceof String)) {
848 return null;
849 }
850
851 String expression = (String) key;
852 if (expression.startsWith("project.") || expression.startsWith("pom.")) {
853 try {
854 Object evaluated = ReflectionValueExtractor.evaluate(expression, this.mavenProject);
855 if (evaluated != null) {
856 return evaluated;
857 }
858 } catch (Exception e) {
859
860 }
861 }
862
863 Object value = properties.get(key);
864
865 return value != null ? value : this.mavenProject.getProperties().get(key);
866 }
867
868
869
870
871
872
873 @Override
874 public boolean isEmpty() {
875 return this.mavenProject == null
876 && this.mavenProject.getProperties().isEmpty()
877 && this.properties.isEmpty();
878 }
879
880
881
882
883
884
885 @Override
886 public Set<String> keySet() {
887 throw new UnsupportedOperationException();
888 }
889
890
891
892
893
894
895 @Override
896 public Object put(String key, Object value) {
897 throw new UnsupportedOperationException();
898 }
899
900
901
902
903
904
905 @Override
906 public void putAll(Map<? extends String, ? extends Object> t) {
907 throw new UnsupportedOperationException();
908 }
909
910
911
912
913
914
915 @Override
916 public Object remove(Object key) {
917 throw new UnsupportedOperationException();
918 }
919
920
921
922
923
924
925 @Override
926 public int size() {
927 throw new UnsupportedOperationException();
928 }
929
930
931
932
933
934
935 @Override
936 public Collection<Object> values() {
937 throw new UnsupportedOperationException();
938 }
939 }
940
941
942
943
944
945
946
947
948 private static String toUrl(String filename) {
949
950
951
952
953 String url = "file://" + new File(filename).toURI().getPath();
954 if (url.endsWith("/")) {
955 url = url.substring(0, url.length() - 1);
956 }
957 return url;
958 }
959 }