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