1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.plugins.shade;
20
21 import javax.inject.Named;
22 import javax.inject.Singleton;
23
24 import java.io.BufferedOutputStream;
25 import java.io.ByteArrayInputStream;
26 import java.io.File;
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.io.InputStreamReader;
30 import java.io.OutputStreamWriter;
31 import java.io.PushbackInputStream;
32 import java.io.Writer;
33 import java.nio.ByteBuffer;
34 import java.nio.ByteOrder;
35 import java.nio.charset.StandardCharsets;
36 import java.nio.file.Files;
37 import java.util.ArrayList;
38 import java.util.Arrays;
39 import java.util.Calendar;
40 import java.util.Collection;
41 import java.util.Collections;
42 import java.util.Enumeration;
43 import java.util.HashMap;
44 import java.util.HashSet;
45 import java.util.Iterator;
46 import java.util.LinkedList;
47 import java.util.List;
48 import java.util.Map;
49 import java.util.Objects;
50 import java.util.Set;
51 import java.util.concurrent.Callable;
52 import java.util.jar.JarEntry;
53 import java.util.jar.JarFile;
54 import java.util.jar.JarOutputStream;
55 import java.util.regex.Matcher;
56 import java.util.regex.Pattern;
57 import java.util.zip.CRC32;
58 import java.util.zip.ZipEntry;
59 import java.util.zip.ZipException;
60
61 import org.apache.maven.plugin.MojoExecutionException;
62 import org.apache.maven.plugins.shade.filter.Filter;
63 import org.apache.maven.plugins.shade.relocation.Relocator;
64 import org.apache.maven.plugins.shade.resource.ManifestResourceTransformer;
65 import org.apache.maven.plugins.shade.resource.ReproducibleResourceTransformer;
66 import org.apache.maven.plugins.shade.resource.ResourceTransformer;
67 import org.codehaus.plexus.util.IOUtil;
68 import org.codehaus.plexus.util.io.CachingOutputStream;
69 import org.objectweb.asm.ClassReader;
70 import org.objectweb.asm.ClassVisitor;
71 import org.objectweb.asm.ClassWriter;
72 import org.objectweb.asm.commons.ClassRemapper;
73 import org.objectweb.asm.commons.Remapper;
74 import org.slf4j.Logger;
75 import org.slf4j.LoggerFactory;
76
77
78
79
80 @Singleton
81 @Named
82 public class DefaultShader implements Shader {
83 private static final int BUFFER_SIZE = 32 * 1024;
84
85 private final Logger logger;
86
87 public DefaultShader() {
88 this(LoggerFactory.getLogger(DefaultShader.class));
89 }
90
91 public DefaultShader(final Logger logger) {
92 this.logger = Objects.requireNonNull(logger);
93 }
94
95
96 private long getTime(ZipEntry entry) {
97 if (entry.getLastModifiedTime() == null) {
98 return -1;
99 }
100 long mtime = entry.getLastModifiedTime().toMillis();
101 if (hasX5455ExtendedTimestamp(entry)) {
102 Calendar cal = Calendar.getInstance();
103 cal.setTimeInMillis(mtime);
104 mtime = mtime - (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET));
105 }
106 return mtime;
107 }
108
109
110
111
112
113
114 private static boolean hasX5455ExtendedTimestamp(ZipEntry zipEntry) {
115 if (zipEntry.getExtra() != null) {
116 ByteBuffer extraData = ByteBuffer.wrap(zipEntry.getExtra());
117 extraData.order(ByteOrder.LITTLE_ENDIAN);
118 while (extraData.hasRemaining()) {
119 int id = extraData.getShort() & 0xffff;
120 int length = extraData.getShort() & 0xffff;
121
122 if (id == 0x5455) {
123
124 return true;
125 } else {
126
127 extraData.position(extraData.position() + length);
128 }
129 }
130 }
131 return false;
132 }
133
134 public void shade(ShadeRequest shadeRequest) throws IOException, MojoExecutionException {
135 Set<String> resources = new HashSet<>();
136
137 ManifestResourceTransformer manifestTransformer = null;
138 List<ResourceTransformer> transformers = new ArrayList<>(shadeRequest.getResourceTransformers());
139 for (Iterator<ResourceTransformer> it = transformers.iterator(); it.hasNext(); ) {
140 ResourceTransformer transformer = it.next();
141 if (transformer instanceof ManifestResourceTransformer) {
142 manifestTransformer = (ManifestResourceTransformer) transformer;
143 it.remove();
144 }
145 }
146
147 final DefaultPackageMapper packageMapper = new DefaultPackageMapper(shadeRequest.getRelocators());
148
149
150 shadeRequest.getUberJar().getParentFile().mkdirs();
151
152 try (JarOutputStream out =
153 new JarOutputStream(new BufferedOutputStream(new CachingOutputStream(shadeRequest.getUberJar())))) {
154 goThroughAllJarEntriesForManifestTransformer(shadeRequest, resources, manifestTransformer, out);
155
156
157 Map<String, HashSet<File>> duplicates = new HashMap<>();
158
159
160 shadeJars(shadeRequest, resources, transformers, out, duplicates, packageMapper);
161
162
163 Map<Collection<File>, HashSet<String>> overlapping = new HashMap<>();
164
165
166 for (String clazz : duplicates.keySet()) {
167 Collection<File> jarz = duplicates.get(clazz);
168 if (jarz.size() > 1) {
169 overlapping.computeIfAbsent(jarz, k -> new HashSet<>()).add(clazz);
170 }
171 }
172
173
174 logSummaryOfDuplicates(overlapping);
175
176 if (!overlapping.keySet().isEmpty()) {
177 showOverlappingWarning();
178 }
179
180 for (ResourceTransformer transformer : transformers) {
181 if (transformer.hasTransformedResource()) {
182 transformer.modifyOutputStream(out);
183 }
184 }
185 }
186
187 for (Filter filter : shadeRequest.getFilters()) {
188 filter.finished();
189 }
190 }
191
192
193
194
195 private static class ZipHeaderPeekInputStream extends PushbackInputStream {
196
197 private static final byte[] ZIP_HEADER = new byte[] {0x50, 0x4b, 0x03, 0x04};
198
199 private static final int HEADER_LEN = 4;
200
201 protected ZipHeaderPeekInputStream(InputStream in) {
202 super(in, HEADER_LEN);
203 }
204
205 public boolean hasZipHeader() throws IOException {
206 final byte[] header = new byte[HEADER_LEN];
207 int len = super.read(header, 0, HEADER_LEN);
208 if (len != -1) {
209 super.unread(header, 0, len);
210 }
211 return Arrays.equals(header, ZIP_HEADER);
212 }
213 }
214
215
216
217
218 private static class CrcAndSize {
219
220 private final CRC32 crc = new CRC32();
221
222 private long size;
223
224 CrcAndSize(InputStream inputStream) throws IOException {
225 load(inputStream);
226 }
227
228 private void load(InputStream inputStream) throws IOException {
229 byte[] buffer = new byte[BUFFER_SIZE];
230 int bytesRead;
231 while ((bytesRead = inputStream.read(buffer)) != -1) {
232 this.crc.update(buffer, 0, bytesRead);
233 this.size += bytesRead;
234 }
235 }
236
237 public void setupStoredEntry(JarEntry entry) {
238 entry.setSize(this.size);
239 entry.setCompressedSize(this.size);
240 entry.setCrc(this.crc.getValue());
241 entry.setMethod(ZipEntry.STORED);
242 }
243 }
244
245 private void shadeJars(
246 ShadeRequest shadeRequest,
247 Set<String> resources,
248 List<ResourceTransformer> transformers,
249 JarOutputStream jos,
250 Map<String, HashSet<File>> duplicates,
251 DefaultPackageMapper packageMapper)
252 throws IOException {
253 for (File jar : shadeRequest.getJars()) {
254
255 logger.debug("Processing JAR " + jar);
256
257 List<Filter> jarFilters = getFilters(jar, shadeRequest.getFilters());
258 if (jar.isDirectory()) {
259 shadeDir(
260 shadeRequest,
261 resources,
262 transformers,
263 packageMapper,
264 jos,
265 duplicates,
266 jar,
267 jar,
268 "",
269 jarFilters);
270 } else {
271 shadeJar(shadeRequest, resources, transformers, packageMapper, jos, duplicates, jar, jarFilters);
272 }
273 }
274 }
275
276 @SuppressWarnings("checkstyle:ParameterNumber")
277 private void shadeDir(
278 ShadeRequest shadeRequest,
279 Set<String> resources,
280 List<ResourceTransformer> transformers,
281 DefaultPackageMapper packageMapper,
282 JarOutputStream jos,
283 Map<String, HashSet<File>> duplicates,
284 File jar,
285 File current,
286 String prefix,
287 List<Filter> jarFilters)
288 throws IOException {
289 final File[] children = current.listFiles();
290 if (children == null) {
291 return;
292 }
293 for (final File file : children) {
294 final String name = prefix + file.getName();
295 if (file.isDirectory()) {
296 try {
297 shadeDir(
298 shadeRequest,
299 resources,
300 transformers,
301 packageMapper,
302 jos,
303 duplicates,
304 jar,
305 file,
306 prefix + file.getName() + '/',
307 jarFilters);
308 continue;
309 } catch (Exception e) {
310 throw new IOException(String.format("Problem shading JAR %s entry %s: %s", current, name, e), e);
311 }
312 }
313 if (isFiltered(jarFilters, name) || isExcludedEntry(name)) {
314 continue;
315 }
316
317 try {
318 shadeJarEntry(
319 shadeRequest,
320 resources,
321 transformers,
322 packageMapper,
323 jos,
324 duplicates,
325 jar,
326 new Callable<InputStream>() {
327 @Override
328 public InputStream call() throws Exception {
329 return Files.newInputStream(file.toPath());
330 }
331 },
332 name,
333 file.lastModified(),
334 -1 );
335 } catch (Exception e) {
336 throw new IOException(String.format("Problem shading JAR %s entry %s: %s", current, name, e), e);
337 }
338 }
339 }
340
341 @SuppressWarnings("checkstyle:ParameterNumber")
342 private void shadeJar(
343 ShadeRequest shadeRequest,
344 Set<String> resources,
345 List<ResourceTransformer> transformers,
346 DefaultPackageMapper packageMapper,
347 JarOutputStream jos,
348 Map<String, HashSet<File>> duplicates,
349 File jar,
350 List<Filter> jarFilters)
351 throws IOException {
352 try (JarFile jarFile = newJarFile(jar)) {
353
354 for (Enumeration<JarEntry> j = jarFile.entries(); j.hasMoreElements(); ) {
355 final JarEntry entry = j.nextElement();
356
357 String name = entry.getName();
358
359 if (entry.isDirectory() || isFiltered(jarFilters, name) || isExcludedEntry(name)) {
360 continue;
361 }
362
363 try {
364 shadeJarEntry(
365 shadeRequest,
366 resources,
367 transformers,
368 packageMapper,
369 jos,
370 duplicates,
371 jar,
372 new Callable<InputStream>() {
373 @Override
374 public InputStream call() throws Exception {
375 return jarFile.getInputStream(entry);
376 }
377 },
378 name,
379 getTime(entry),
380 entry.getMethod());
381 } catch (Exception e) {
382 throw new IOException(String.format("Problem shading JAR %s entry %s: %s", jar, name, e), e);
383 }
384 }
385 }
386 }
387
388 private boolean isExcludedEntry(final String name) {
389 if ("META-INF/INDEX.LIST".equals(name)) {
390
391
392
393 return true;
394 }
395
396 if ("module-info.class".equals(name)) {
397 logger.warn("Discovered module-info.class. " + "Shading will break its strong encapsulation.");
398 return true;
399 }
400 return false;
401 }
402
403 @SuppressWarnings("checkstyle:ParameterNumber")
404 private void shadeJarEntry(
405 ShadeRequest shadeRequest,
406 Set<String> resources,
407 List<ResourceTransformer> transformers,
408 DefaultPackageMapper packageMapper,
409 JarOutputStream jos,
410 Map<String, HashSet<File>> duplicates,
411 File jar,
412 Callable<InputStream> inputProvider,
413 String name,
414 long time,
415 int method)
416 throws Exception {
417 try (InputStream in = inputProvider.call()) {
418 String mappedName = packageMapper.map(name, true, false);
419
420 int idx = mappedName.lastIndexOf('/');
421 if (idx != -1) {
422
423 String dir = mappedName.substring(0, idx);
424 if (!resources.contains(dir)) {
425 addDirectory(resources, jos, dir, time);
426 }
427 }
428
429 duplicates.computeIfAbsent(name, k -> new HashSet<>()).add(jar);
430 if (name.endsWith(".class")) {
431 addRemappedClass(jos, jar, name, time, in, packageMapper);
432 } else if (shadeRequest.isShadeSourcesContent() && name.endsWith(".java")) {
433
434 if (resources.contains(mappedName)) {
435 return;
436 }
437
438 addJavaSource(resources, jos, mappedName, time, in, shadeRequest.getRelocators());
439 } else {
440 if (!resourceTransformed(transformers, mappedName, in, shadeRequest.getRelocators(), time)) {
441
442 if (resources.contains(mappedName)) {
443 logger.debug("We have a duplicate " + name + " in " + jar);
444 return;
445 }
446
447 addResource(resources, jos, mappedName, inputProvider, time, method);
448 } else {
449 duplicates.computeIfAbsent(name, k -> new HashSet<>()).remove(jar);
450 }
451 }
452 }
453 }
454
455 private void goThroughAllJarEntriesForManifestTransformer(
456 ShadeRequest shadeRequest,
457 Set<String> resources,
458 ManifestResourceTransformer manifestTransformer,
459 JarOutputStream jos)
460 throws IOException {
461 if (manifestTransformer != null) {
462 for (File jar : shadeRequest.getJars()) {
463 try (JarFile jarFile = newJarFile(jar)) {
464 for (Enumeration<JarEntry> en = jarFile.entries(); en.hasMoreElements(); ) {
465 JarEntry entry = en.nextElement();
466 String resource = entry.getName();
467 if (manifestTransformer.canTransformResource(resource)) {
468 resources.add(resource);
469 try (InputStream inputStream = jarFile.getInputStream(entry)) {
470 manifestTransformer.processResource(
471 resource, inputStream, shadeRequest.getRelocators(), getTime(entry));
472 }
473 break;
474 }
475 }
476 }
477 }
478 if (manifestTransformer.hasTransformedResource()) {
479 manifestTransformer.modifyOutputStream(jos);
480 }
481 }
482 }
483
484 private void showOverlappingWarning() {
485 logger.warn("maven-shade-plugin has detected that some files are");
486 logger.warn("present in two or more JARs. When this happens, only one");
487 logger.warn("single version of the file is copied to the uber jar.");
488 logger.warn("Usually this is not harmful and you can skip these warnings,");
489 logger.warn("otherwise try to manually exclude artifacts based on");
490 logger.warn("mvn dependency:tree -Ddetail=true and the above output.");
491 logger.warn("See https://maven.apache.org/plugins/maven-shade-plugin/");
492 }
493
494 private void logSummaryOfDuplicates(Map<Collection<File>, HashSet<String>> overlapping) {
495 for (Collection<File> jarz : overlapping.keySet()) {
496 List<String> jarzS = new ArrayList<>();
497
498 for (File jjar : jarz) {
499 jarzS.add(jjar.getName());
500 }
501
502 Collections.sort(jarzS);
503
504 List<String> classes = new LinkedList<>();
505 List<String> resources = new LinkedList<>();
506
507 for (String name : overlapping.get(jarz)) {
508 if (name.endsWith(".class")) {
509 classes.add(name.replace(".class", "").replace("/", "."));
510 } else {
511 resources.add(name);
512 }
513 }
514
515
516 final Collection<String> overlaps = new ArrayList<>();
517 if (!classes.isEmpty()) {
518 if (resources.size() == 1) {
519 overlaps.add("class");
520 } else {
521 overlaps.add("classes");
522 }
523 }
524 if (!resources.isEmpty()) {
525 if (resources.size() == 1) {
526 overlaps.add("resource");
527 } else {
528 overlaps.add("resources");
529 }
530 }
531
532 final List<String> all = new ArrayList<>(classes.size() + resources.size());
533 all.addAll(classes);
534 all.addAll(resources);
535
536 logger.warn(String.join(", ", jarzS) + " define " + all.size() + " overlapping "
537 + String.join(" and ", overlaps) + ": ");
538
539
540 Collections.sort(all);
541
542 int max = 10;
543
544 for (int i = 0; i < Math.min(max, all.size()); i++) {
545 logger.warn(" - " + all.get(i));
546 }
547
548 if (all.size() > max) {
549 logger.warn(" - " + (all.size() - max) + " more...");
550 }
551 }
552 }
553
554 private JarFile newJarFile(File jar) throws IOException {
555 try {
556 return new JarFile(jar);
557 } catch (ZipException zex) {
558
559
560 throw new ZipException("error in opening zip file " + jar);
561 }
562 }
563
564 private List<Filter> getFilters(File jar, List<Filter> filters) {
565 List<Filter> list = new ArrayList<>();
566
567 for (Filter filter : filters) {
568 if (filter.canFilter(jar)) {
569 list.add(filter);
570 }
571 }
572
573 return list;
574 }
575
576 private void addDirectory(Set<String> resources, JarOutputStream jos, String name, long time) throws IOException {
577 if (name.lastIndexOf('/') > 0) {
578 String parent = name.substring(0, name.lastIndexOf('/'));
579 if (!resources.contains(parent)) {
580 addDirectory(resources, jos, parent, time);
581 }
582 }
583
584
585 JarEntry entry = new JarEntry(name + "/");
586 entry.setTime(time);
587 jos.putNextEntry(entry);
588
589 resources.add(name);
590 }
591
592 private void addRemappedClass(
593 JarOutputStream jos, File jar, String name, long time, InputStream is, DefaultPackageMapper packageMapper)
594 throws IOException, MojoExecutionException {
595 if (packageMapper.relocators.isEmpty()) {
596 try {
597 JarEntry entry = new JarEntry(name);
598 entry.setTime(time);
599 jos.putNextEntry(entry);
600 IOUtil.copy(is, jos);
601 } catch (ZipException e) {
602 logger.debug("We have a duplicate " + name + " in " + jar);
603 }
604
605 return;
606 }
607
608
609
610
611 byte[] originalClass = IOUtil.toByteArray(is);
612
613 ClassReader cr = new ClassReader(new ByteArrayInputStream(originalClass));
614
615
616
617
618
619
620 ClassWriter cw = new ClassWriter(0);
621
622 final String pkg = name.substring(0, name.lastIndexOf('/') + 1);
623 final ShadeClassRemapper cv = new ShadeClassRemapper(cw, pkg, packageMapper);
624
625 try {
626 cr.accept(cv, ClassReader.EXPAND_FRAMES);
627 } catch (Throwable ise) {
628 throw new MojoExecutionException("Error in ASM processing class " + name, ise);
629 }
630
631
632 final byte[] renamedClass;
633 if (cv.remapped) {
634 logger.debug("Rewrote class bytecode: " + name);
635 renamedClass = cw.toByteArray();
636 } else {
637 logger.debug("Keeping original class bytecode: " + name);
638 renamedClass = originalClass;
639 }
640
641
642 String mappedName = packageMapper.map(name.substring(0, name.indexOf('.')), true, false);
643
644 try {
645
646 JarEntry entry = new JarEntry(mappedName + ".class");
647 entry.setTime(time);
648 jos.putNextEntry(entry);
649
650 jos.write(renamedClass);
651 } catch (ZipException e) {
652 logger.debug("We have a duplicate " + mappedName + " in " + jar);
653 }
654 }
655
656 private boolean isFiltered(List<Filter> filters, String name) {
657 for (Filter filter : filters) {
658 if (filter.isFiltered(name)) {
659 return true;
660 }
661 }
662
663 return false;
664 }
665
666 private boolean resourceTransformed(
667 List<ResourceTransformer> resourceTransformers,
668 String name,
669 InputStream is,
670 List<Relocator> relocators,
671 long time)
672 throws IOException {
673 boolean resourceTransformed = false;
674
675 for (ResourceTransformer transformer : resourceTransformers) {
676 if (transformer.canTransformResource(name)) {
677 logger.debug("Transforming " + name + " using "
678 + transformer.getClass().getName());
679
680 if (transformer instanceof ReproducibleResourceTransformer) {
681 ((ReproducibleResourceTransformer) transformer).processResource(name, is, relocators, time);
682 } else {
683 transformer.processResource(name, is, relocators);
684 }
685
686 resourceTransformed = true;
687
688 break;
689 }
690 }
691 return resourceTransformed;
692 }
693
694 private void addJavaSource(
695 Set<String> resources,
696 JarOutputStream jos,
697 String name,
698 long time,
699 InputStream is,
700 List<Relocator> relocators)
701 throws IOException {
702 JarEntry entry = new JarEntry(name);
703 entry.setTime(time);
704 jos.putNextEntry(entry);
705
706 String sourceContent = IOUtil.toString(new InputStreamReader(is, StandardCharsets.UTF_8));
707
708 for (Relocator relocator : relocators) {
709 sourceContent = relocator.applyToSourceContent(sourceContent);
710 }
711
712 final Writer writer = new OutputStreamWriter(jos, StandardCharsets.UTF_8);
713 writer.write(sourceContent);
714 writer.flush();
715
716 resources.add(name);
717 }
718
719 private void addResource(
720 Set<String> resources, JarOutputStream jos, String name, Callable<InputStream> input, long time, int method)
721 throws Exception {
722 ZipHeaderPeekInputStream inputStream = new ZipHeaderPeekInputStream(input.call());
723 try {
724 final JarEntry entry = new JarEntry(name);
725
726
727 if (inputStream.hasZipHeader() && method == ZipEntry.STORED) {
728 new CrcAndSize(inputStream).setupStoredEntry(entry);
729 inputStream.close();
730 inputStream = new ZipHeaderPeekInputStream(input.call());
731 }
732
733 entry.setTime(time);
734
735 jos.putNextEntry(entry);
736
737 IOUtil.copy(inputStream, jos);
738
739 resources.add(name);
740 } finally {
741 inputStream.close();
742 }
743 }
744
745 private interface PackageMapper {
746
747
748
749
750
751
752
753
754 String map(String entityName, boolean mapPaths, boolean mapPackages);
755 }
756
757
758
759
760 private static class DefaultPackageMapper implements PackageMapper {
761 private static final Pattern CLASS_PATTERN = Pattern.compile("(\\[*)?L(.+);");
762
763 private final List<Relocator> relocators;
764
765 private DefaultPackageMapper(final List<Relocator> relocators) {
766 this.relocators = relocators;
767 }
768
769 @Override
770 public String map(String entityName, boolean mapPaths, final boolean mapPackages) {
771 String value = entityName;
772
773 String prefix = "";
774 String suffix = "";
775
776 Matcher m = CLASS_PATTERN.matcher(entityName);
777 if (m.matches()) {
778 prefix = m.group(1) + "L";
779 suffix = ";";
780 entityName = m.group(2);
781 }
782
783 for (Relocator r : relocators) {
784 if (mapPackages && r.canRelocateClass(entityName)) {
785 value = prefix + r.relocateClass(entityName) + suffix;
786 break;
787 } else if (mapPaths && r.canRelocatePath(entityName)) {
788 value = prefix + r.relocatePath(entityName) + suffix;
789 break;
790 }
791 }
792 return value;
793 }
794 }
795
796 private static class LazyInitRemapper extends Remapper {
797 private PackageMapper relocators;
798
799 @Override
800 public Object mapValue(Object object) {
801 return object instanceof String ? relocators.map((String) object, true, true) : super.mapValue(object);
802 }
803
804 @Override
805 public String map(String name) {
806
807
808
809
810
811
812
813
814 return relocators.map(name, true, false);
815 }
816 }
817
818
819
820
821
822
823
824
825
826
827
828 private static class ShadeClassRemapper extends ClassRemapper implements PackageMapper {
829 private final String pkg;
830 private final PackageMapper packageMapper;
831 private boolean remapped;
832
833 ShadeClassRemapper(
834 final ClassVisitor classVisitor, final String pkg, final DefaultPackageMapper packageMapper) {
835 super(classVisitor, new LazyInitRemapper() );
836 this.pkg = pkg;
837 this.packageMapper = packageMapper;
838
839
840 LazyInitRemapper.class.cast(remapper).relocators = this;
841 }
842
843 @Override
844 public void visitSource(final String source, final String debug) {
845 if (source == null) {
846 super.visitSource(null, debug);
847 return;
848 }
849
850 final String fqSource = pkg + source;
851 final String mappedSource = map(fqSource, true, false);
852 final String filename = mappedSource.substring(mappedSource.lastIndexOf('/') + 1);
853 super.visitSource(filename, debug);
854 }
855
856 @Override
857 public String map(final String entityName, boolean mapPaths, final boolean mapPackages) {
858 final String mapped = packageMapper.map(entityName, true, mapPackages);
859 if (!remapped) {
860 remapped = !mapped.equals(entityName);
861 }
862 return mapped;
863 }
864 }
865 }