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