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.relocation;
20
21 import java.util.Collection;
22 import java.util.LinkedHashSet;
23 import java.util.List;
24 import java.util.Set;
25 import java.util.regex.Pattern;
26
27 import org.codehaus.plexus.util.SelectorUtils;
28
29
30
31
32
33 public class SimpleRelocator implements Relocator {
34
35
36
37 private static final Pattern RX_ENDS_WITH_DOT_SLASH_SPACE = Pattern.compile("[./ ]$");
38
39
40
41
42
43
44
45
46
47
48 private static final Pattern RX_ENDS_WITH_JAVA_KEYWORD = Pattern.compile(
49 "\\b(import|package|public|protected|private|static|final|synchronized|abstract|volatile|extends|implements|throws) $"
50 + "|"
51 + "\\{@link( \\*)* $"
52 + "|"
53 + "([{}(=;,]|\\*/) $");
54
55 private final String pattern;
56
57 private final String pathPattern;
58
59 private final String shadedPattern;
60
61 private final String shadedPathPattern;
62
63 private final Set<String> includes;
64
65 private final Set<String> excludes;
66
67 private final Set<String> sourcePackageExcludes = new LinkedHashSet<>();
68
69 private final Set<String> sourcePathExcludes = new LinkedHashSet<>();
70
71 private final boolean rawString;
72
73 public SimpleRelocator(String patt, String shadedPattern, List<String> includes, List<String> excludes) {
74 this(patt, shadedPattern, includes, excludes, false);
75 }
76
77 public SimpleRelocator(
78 String patt, String shadedPattern, List<String> includes, List<String> excludes, boolean rawString) {
79 this.rawString = rawString;
80
81 if (rawString) {
82 this.pathPattern = patt;
83 this.shadedPathPattern = shadedPattern;
84
85 this.pattern = null;
86 this.shadedPattern = null;
87 } else {
88 if (patt == null) {
89 this.pattern = "";
90 this.pathPattern = "";
91 } else {
92 this.pattern = patt.replace('/', '.');
93 this.pathPattern = patt.replace('.', '/');
94 }
95
96 if (shadedPattern != null) {
97 this.shadedPattern = shadedPattern.replace('/', '.');
98 this.shadedPathPattern = shadedPattern.replace('.', '/');
99 } else {
100 this.shadedPattern = "hidden." + this.pattern;
101 this.shadedPathPattern = "hidden/" + this.pathPattern;
102 }
103 }
104
105 this.includes = normalizePatterns(includes);
106 this.excludes = normalizePatterns(excludes);
107
108
109 if (includes != null && !includes.isEmpty()) {
110 this.includes.addAll(includes);
111 }
112
113 if (excludes != null && !excludes.isEmpty()) {
114 this.excludes.addAll(excludes);
115 }
116
117 if (!rawString && this.excludes != null) {
118
119 for (String exclude : this.excludes) {
120
121 if (exclude.startsWith(pattern)) {
122 sourcePackageExcludes.add(
123 exclude.substring(pattern.length()).replaceFirst("[.][*]$", ""));
124 }
125
126 if (exclude.startsWith(pathPattern)) {
127 sourcePathExcludes.add(
128 exclude.substring(pathPattern.length()).replaceFirst("[/][*]$", ""));
129 }
130 }
131 }
132 }
133
134 private static Set<String> normalizePatterns(Collection<String> patterns) {
135 Set<String> normalized = null;
136
137 if (patterns != null && !patterns.isEmpty()) {
138 normalized = new LinkedHashSet<>();
139 for (String pattern : patterns) {
140 String classPattern = pattern.replace('.', '/');
141 normalized.add(classPattern);
142
143
144 if (classPattern.endsWith("/*") || classPattern.endsWith("/**")) {
145 String packagePattern = classPattern.substring(0, classPattern.lastIndexOf('/'));
146 normalized.add(packagePattern);
147 }
148 }
149 }
150
151 return normalized;
152 }
153
154 private boolean isIncluded(String path) {
155 if (includes != null && !includes.isEmpty()) {
156 for (String include : includes) {
157 if (SelectorUtils.matchPath(include, path, true)) {
158 return true;
159 }
160 }
161 return false;
162 }
163 return true;
164 }
165
166 private boolean isExcluded(String path) {
167 if (excludes != null && !excludes.isEmpty()) {
168 for (String exclude : excludes) {
169 if (SelectorUtils.matchPath(exclude, path, true)) {
170 return true;
171 }
172 }
173 }
174 return false;
175 }
176
177 @Override
178 public boolean canRelocatePath(String path) {
179 if (rawString) {
180 return Pattern.compile(pathPattern).matcher(path).find();
181 }
182
183 if (path.endsWith(".class")) {
184 path = path.substring(0, path.length() - 6);
185 }
186
187
188
189 if (!path.isEmpty() && path.charAt(0) == '/') {
190 path = path.substring(1);
191 }
192
193 return isIncluded(path) && !isExcluded(path) && path.startsWith(pathPattern);
194 }
195
196 @Override
197 public boolean canRelocateClass(String clazz) {
198 return !rawString && clazz.indexOf('/') < 0 && canRelocatePath(clazz.replace('.', '/'));
199 }
200
201 @Override
202 public String relocatePath(String path) {
203 if (rawString) {
204 return path.replaceAll(pathPattern, shadedPathPattern);
205 } else {
206 return path.replaceFirst(pathPattern, shadedPathPattern);
207 }
208 }
209
210 @Override
211 public String relocateClass(String clazz) {
212 return rawString ? clazz : clazz.replaceFirst(pattern, shadedPattern);
213 }
214
215 @Override
216 public String applyToSourceContent(String sourceContent) {
217 if (rawString) {
218 return sourceContent;
219 }
220 sourceContent = shadeSourceWithExcludes(sourceContent, pattern, shadedPattern, sourcePackageExcludes);
221 return shadeSourceWithExcludes(sourceContent, pathPattern, shadedPathPattern, sourcePathExcludes);
222 }
223
224 private String shadeSourceWithExcludes(
225 String sourceContent, String patternFrom, String patternTo, Set<String> excludedPatterns) {
226
227 StringBuilder shadedSourceContent = new StringBuilder(sourceContent.length() * 11 / 10);
228 boolean isFirstSnippet = true;
229
230 String[] snippets = sourceContent.split("\\b" + patternFrom.replace(".", "[.]") + "\\b");
231 for (int i = 0, snippetsLength = snippets.length; i < snippetsLength; i++) {
232 String snippet = snippets[i];
233 String previousSnippet = isFirstSnippet ? "" : snippets[i - 1];
234 boolean doExclude = false;
235 for (String excludedPattern : excludedPatterns) {
236 if (snippet.startsWith(excludedPattern)) {
237 doExclude = true;
238 break;
239 }
240 }
241 if (isFirstSnippet) {
242 shadedSourceContent.append(snippet);
243 isFirstSnippet = false;
244 } else {
245 String previousSnippetOneLine = previousSnippet.replaceAll("\\s+", " ");
246 boolean afterDotSlashSpace = RX_ENDS_WITH_DOT_SLASH_SPACE
247 .matcher(previousSnippetOneLine)
248 .find();
249 boolean afterJavaKeyWord = RX_ENDS_WITH_JAVA_KEYWORD
250 .matcher(previousSnippetOneLine)
251 .find();
252 boolean shouldExclude = doExclude || afterDotSlashSpace && !afterJavaKeyWord;
253 shadedSourceContent
254 .append(shouldExclude ? patternFrom : patternTo)
255 .append(snippet);
256 }
257 }
258 return shadedSourceContent.toString();
259 }
260 }