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 public boolean canRelocatePath(String path) {
178 if (rawString) {
179 return Pattern.compile(pathPattern).matcher(path).find();
180 }
181
182 if (path.endsWith(".class")) {
183 path = path.substring(0, path.length() - 6);
184 }
185
186
187
188 if (!path.isEmpty() && path.charAt(0) == '/') {
189 path = path.substring(1);
190 }
191
192 return isIncluded(path) && !isExcluded(path) && path.startsWith(pathPattern);
193 }
194
195 public boolean canRelocateClass(String clazz) {
196 return !rawString && clazz.indexOf('/') < 0 && canRelocatePath(clazz.replace('.', '/'));
197 }
198
199 public String relocatePath(String path) {
200 if (rawString) {
201 return path.replaceAll(pathPattern, shadedPathPattern);
202 } else {
203 return path.replaceFirst(pathPattern, shadedPathPattern);
204 }
205 }
206
207 public String relocateClass(String clazz) {
208 return rawString ? clazz : clazz.replaceFirst(pattern, shadedPattern);
209 }
210
211 public String applyToSourceContent(String sourceContent) {
212 if (rawString) {
213 return sourceContent;
214 }
215 sourceContent = shadeSourceWithExcludes(sourceContent, pattern, shadedPattern, sourcePackageExcludes);
216 return shadeSourceWithExcludes(sourceContent, pathPattern, shadedPathPattern, sourcePathExcludes);
217 }
218
219 private String shadeSourceWithExcludes(
220 String sourceContent, String patternFrom, String patternTo, Set<String> excludedPatterns) {
221
222 StringBuilder shadedSourceContent = new StringBuilder(sourceContent.length() * 11 / 10);
223 boolean isFirstSnippet = true;
224
225 String[] snippets = sourceContent.split("\\b" + patternFrom.replace(".", "[.]") + "\\b");
226 for (int i = 0, snippetsLength = snippets.length; i < snippetsLength; i++) {
227 String snippet = snippets[i];
228 String previousSnippet = isFirstSnippet ? "" : snippets[i - 1];
229 boolean doExclude = false;
230 for (String excludedPattern : excludedPatterns) {
231 if (snippet.startsWith(excludedPattern)) {
232 doExclude = true;
233 break;
234 }
235 }
236 if (isFirstSnippet) {
237 shadedSourceContent.append(snippet);
238 isFirstSnippet = false;
239 } else {
240 String previousSnippetOneLine = previousSnippet.replaceAll("\\s+", " ");
241 boolean afterDotSlashSpace = RX_ENDS_WITH_DOT_SLASH_SPACE
242 .matcher(previousSnippetOneLine)
243 .find();
244 boolean afterJavaKeyWord = RX_ENDS_WITH_JAVA_KEYWORD
245 .matcher(previousSnippetOneLine)
246 .find();
247 boolean shouldExclude = doExclude || afterDotSlashSpace && !afterJavaKeyWord;
248 shadedSourceContent
249 .append(shouldExclude ? patternFrom : patternTo)
250 .append(snippet);
251 }
252 }
253 return shadedSourceContent.toString();
254 }
255 }