1 | |
package org.apache.maven.plugin.jar; |
2 | |
|
3 | |
import java.util.ArrayList; |
4 | |
import java.util.Iterator; |
5 | |
import java.util.List; |
6 | |
|
7 | |
import org.apache.maven.plugin.AbstractMojo; |
8 | |
import org.apache.maven.plugin.MojoExecutionException; |
9 | |
|
10 | |
|
11 | |
|
12 | |
|
13 | |
|
14 | |
|
15 | |
|
16 | |
|
17 | |
|
18 | |
|
19 | |
@SuppressWarnings( "all" ) |
20 | 0 | public class HelpMojo |
21 | |
extends AbstractMojo |
22 | |
{ |
23 | |
|
24 | |
|
25 | |
|
26 | |
|
27 | |
|
28 | |
private boolean detail; |
29 | |
|
30 | |
|
31 | |
|
32 | |
|
33 | |
|
34 | |
|
35 | |
private java.lang.String goal; |
36 | |
|
37 | |
|
38 | |
|
39 | |
|
40 | |
|
41 | |
|
42 | |
private int lineLength; |
43 | |
|
44 | |
|
45 | |
|
46 | |
|
47 | |
|
48 | |
|
49 | |
private int indentSize; |
50 | |
|
51 | |
|
52 | |
|
53 | |
public void execute() |
54 | |
throws MojoExecutionException |
55 | |
{ |
56 | 0 | if ( lineLength <= 0 ) |
57 | |
{ |
58 | 0 | getLog().warn( "The parameter 'lineLength' should be positive, using '80' as default." ); |
59 | 0 | lineLength = 80; |
60 | |
} |
61 | 0 | if ( indentSize <= 0 ) |
62 | |
{ |
63 | 0 | getLog().warn( "The parameter 'indentSize' should be positive, using '2' as default." ); |
64 | 0 | indentSize = 2; |
65 | |
} |
66 | |
|
67 | 0 | StringBuffer sb = new StringBuffer(); |
68 | |
|
69 | 0 | append( sb, "org.apache.maven.plugins:maven-jar-plugin:2.4", 0 ); |
70 | 0 | append( sb, "", 0 ); |
71 | |
|
72 | 0 | append( sb, "Maven JAR Plugin", 0 ); |
73 | 0 | append( sb, "Builds a Java Archive (JAR) file from the compiled project classes and resources.", 1 ); |
74 | 0 | append( sb, "", 0 ); |
75 | |
|
76 | 0 | if ( goal == null || goal.length() <= 0 ) |
77 | |
{ |
78 | 0 | append( sb, "This plugin has 5 goals:", 0 ); |
79 | 0 | append( sb, "", 0 ); |
80 | |
} |
81 | |
|
82 | 0 | if ( goal == null || goal.length() <= 0 || "help".equals( goal ) ) |
83 | |
{ |
84 | 0 | append( sb, "jar:help", 0 ); |
85 | 0 | append( sb, "Display help information on maven-jar-plugin.\nCall\n\u00a0\u00a0mvn\u00a0jar:help\u00a0-Ddetail=true\u00a0-Dgoal=<goal-name>\nto display parameter details.", 1 ); |
86 | 0 | append( sb, "", 0 ); |
87 | 0 | if ( detail ) |
88 | |
{ |
89 | 0 | append( sb, "Available parameters:", 1 ); |
90 | 0 | append( sb, "", 0 ); |
91 | |
|
92 | 0 | append( sb, "detail (Default: false)", 2 ); |
93 | 0 | append( sb, "If true, display all settable properties for each goal.", 3 ); |
94 | 0 | append( sb, "Expression: ${detail}", 3 ); |
95 | 0 | append( sb, "", 0 ); |
96 | |
|
97 | 0 | append( sb, "goal", 2 ); |
98 | 0 | append( sb, "The name of the goal for which to show help. If unspecified, all goals will be displayed.", 3 ); |
99 | 0 | append( sb, "Expression: ${goal}", 3 ); |
100 | 0 | append( sb, "", 0 ); |
101 | |
|
102 | 0 | append( sb, "indentSize (Default: 2)", 2 ); |
103 | 0 | append( sb, "The number of spaces per indentation level, should be positive.", 3 ); |
104 | 0 | append( sb, "Expression: ${indentSize}", 3 ); |
105 | 0 | append( sb, "", 0 ); |
106 | |
|
107 | 0 | append( sb, "lineLength (Default: 80)", 2 ); |
108 | 0 | append( sb, "The maximum length of a display line, should be positive.", 3 ); |
109 | 0 | append( sb, "Expression: ${lineLength}", 3 ); |
110 | 0 | append( sb, "", 0 ); |
111 | |
} |
112 | |
} |
113 | |
|
114 | 0 | if ( goal == null || goal.length() <= 0 || "jar".equals( goal ) ) |
115 | |
{ |
116 | 0 | append( sb, "jar:jar", 0 ); |
117 | 0 | append( sb, "Build a JAR from the current project.", 1 ); |
118 | 0 | append( sb, "", 0 ); |
119 | 0 | if ( detail ) |
120 | |
{ |
121 | 0 | append( sb, "Available parameters:", 1 ); |
122 | 0 | append( sb, "", 0 ); |
123 | |
|
124 | 0 | append( sb, "archive", 2 ); |
125 | 0 | append( sb, "The archive configuration to use. See Maven Archiver Reference.", 3 ); |
126 | 0 | append( sb, "", 0 ); |
127 | |
|
128 | 0 | append( sb, "classesDirectory (Default: ${project.build.outputDirectory})", 2 ); |
129 | 0 | append( sb, "Directory containing the classes and resource files that should be packaged into the JAR.", 3 ); |
130 | 0 | append( sb, "Required: Yes", 3 ); |
131 | 0 | append( sb, "", 0 ); |
132 | |
|
133 | 0 | append( sb, "classifier", 2 ); |
134 | 0 | append( sb, "Classifier to add to the artifact generated. If given, the artifact will be attached. If this is not given,it will merely be written to the output directory according to the finalName.", 3 ); |
135 | 0 | append( sb, "", 0 ); |
136 | |
|
137 | 0 | append( sb, "excludes", 2 ); |
138 | 0 | append( sb, "List of files to exclude. Specified as fileset patterns which are relative to the input directory whose contents is being packaged into the JAR.", 3 ); |
139 | 0 | append( sb, "", 0 ); |
140 | |
|
141 | 0 | append( sb, "finalName (Default: ${project.build.finalName})", 2 ); |
142 | 0 | append( sb, "Name of the generated JAR.", 3 ); |
143 | 0 | append( sb, "Required: Yes", 3 ); |
144 | 0 | append( sb, "Expression: ${jar.finalName}", 3 ); |
145 | 0 | append( sb, "", 0 ); |
146 | |
|
147 | 0 | append( sb, "forceCreation (Default: false)", 2 ); |
148 | 0 | append( sb, "Whether creating the archive should be forced.", 3 ); |
149 | 0 | append( sb, "Expression: ${jar.forceCreation}", 3 ); |
150 | 0 | append( sb, "", 0 ); |
151 | |
|
152 | 0 | append( sb, "includes", 2 ); |
153 | 0 | append( sb, "List of files to include. Specified as fileset patterns which are relative to the input directory whose contents is being packaged into the JAR.", 3 ); |
154 | 0 | append( sb, "", 0 ); |
155 | |
|
156 | 0 | append( sb, "outputDirectory (Default: ${project.build.directory})", 2 ); |
157 | 0 | append( sb, "Directory containing the generated JAR.", 3 ); |
158 | 0 | append( sb, "Required: Yes", 3 ); |
159 | 0 | append( sb, "", 0 ); |
160 | |
|
161 | 0 | append( sb, "skipIfEmpty (Default: false)", 2 ); |
162 | 0 | append( sb, "Skip creating empty archives", 3 ); |
163 | 0 | append( sb, "Expression: ${jar.skipIfEmpty}", 3 ); |
164 | 0 | append( sb, "", 0 ); |
165 | |
|
166 | 0 | append( sb, "useDefaultManifestFile (Default: false)", 2 ); |
167 | 0 | append( sb, "Set this to true to enable the use of the defaultManifestFile.", 3 ); |
168 | 0 | append( sb, "Expression: ${jar.useDefaultManifestFile}", 3 ); |
169 | 0 | append( sb, "", 0 ); |
170 | |
} |
171 | |
} |
172 | |
|
173 | 0 | if ( goal == null || goal.length() <= 0 || "sign".equals( goal ) ) |
174 | |
{ |
175 | 0 | append( sb, "jar:sign", 0 ); |
176 | 0 | append( sb, "Deprecated. As of version 2.3, this goal is no longer supported in favor of the dedicated maven-jarsigner-plugin.", 1 ); |
177 | 0 | if ( detail ) |
178 | |
{ |
179 | 0 | append( sb, "", 0 ); |
180 | 0 | append( sb, "Signs a JAR using jarsigner.", 1 ); |
181 | |
} |
182 | 0 | append( sb, "", 0 ); |
183 | 0 | if ( detail ) |
184 | |
{ |
185 | 0 | append( sb, "Available parameters:", 1 ); |
186 | 0 | append( sb, "", 0 ); |
187 | |
|
188 | 0 | append( sb, "alias", 2 ); |
189 | 0 | append( sb, "See options.", 3 ); |
190 | 0 | append( sb, "Required: Yes", 3 ); |
191 | 0 | append( sb, "Expression: ${alias}", 3 ); |
192 | 0 | append( sb, "", 0 ); |
193 | |
|
194 | 0 | append( sb, "classifier", 2 ); |
195 | 0 | append( sb, "Classifier to use for the generated artifact. If not specified, the generated artifact becomes the primary artifact.", 3 ); |
196 | 0 | append( sb, "Expression: ${classifier}", 3 ); |
197 | 0 | append( sb, "", 0 ); |
198 | |
|
199 | 0 | append( sb, "finalName", 2 ); |
200 | 0 | append( sb, "Name of the generated JAR (without classifier and extension).", 3 ); |
201 | 0 | append( sb, "Required: Yes", 3 ); |
202 | 0 | append( sb, "Expression: ${project.build.finalName}", 3 ); |
203 | 0 | append( sb, "", 0 ); |
204 | |
|
205 | 0 | append( sb, "jarPath (Default: ${project.build.directory}/${project.build.finalName}.${project.packaging})", 2 ); |
206 | 0 | append( sb, "Path of the jar to sign. When specified, the finalName is ignored.", 3 ); |
207 | 0 | append( sb, "", 0 ); |
208 | |
|
209 | 0 | append( sb, "keypass", 2 ); |
210 | 0 | append( sb, "See options.", 3 ); |
211 | 0 | append( sb, "Expression: ${keypass}", 3 ); |
212 | 0 | append( sb, "", 0 ); |
213 | |
|
214 | 0 | append( sb, "keystore", 2 ); |
215 | 0 | append( sb, "See options.", 3 ); |
216 | 0 | append( sb, "Expression: ${keystore}", 3 ); |
217 | 0 | append( sb, "", 0 ); |
218 | |
|
219 | 0 | append( sb, "sigfile", 2 ); |
220 | 0 | append( sb, "See options.", 3 ); |
221 | 0 | append( sb, "Expression: ${sigfile}", 3 ); |
222 | 0 | append( sb, "", 0 ); |
223 | |
|
224 | 0 | append( sb, "signedjar", 2 ); |
225 | 0 | append( sb, "See options. Not specifying this argument will sign the jar in-place (your original jar is going to be overwritten).", 3 ); |
226 | 0 | append( sb, "Expression: ${signedjar}", 3 ); |
227 | 0 | append( sb, "", 0 ); |
228 | |
|
229 | 0 | append( sb, "skip (Default: false)", 2 ); |
230 | 0 | append( sb, "Set this to true to disable signing. Useful to speed up build process in development environment.", 3 ); |
231 | 0 | append( sb, "Expression: ${maven.jar.sign.skip}", 3 ); |
232 | 0 | append( sb, "", 0 ); |
233 | |
|
234 | 0 | append( sb, "storepass", 2 ); |
235 | 0 | append( sb, "See options.", 3 ); |
236 | 0 | append( sb, "Expression: ${storepass}", 3 ); |
237 | 0 | append( sb, "", 0 ); |
238 | |
|
239 | 0 | append( sb, "type", 2 ); |
240 | 0 | append( sb, "See options. The corresponding option in the command line is -storetype.", 3 ); |
241 | 0 | append( sb, "Expression: ${type}", 3 ); |
242 | 0 | append( sb, "", 0 ); |
243 | |
|
244 | 0 | append( sb, "verbose (Default: false)", 2 ); |
245 | 0 | append( sb, "Enable verbose. See options.", 3 ); |
246 | 0 | append( sb, "Expression: ${verbose}", 3 ); |
247 | 0 | append( sb, "", 0 ); |
248 | |
|
249 | 0 | append( sb, "verify (Default: false)", 2 ); |
250 | 0 | append( sb, "Automatically verify a jar after signing it. See options.", 3 ); |
251 | 0 | append( sb, "Expression: ${verify}", 3 ); |
252 | 0 | append( sb, "", 0 ); |
253 | |
|
254 | 0 | append( sb, "workingDirectory (Default: ${basedir})", 2 ); |
255 | 0 | append( sb, "The working directory in which the jarsigner executable will be run.", 3 ); |
256 | 0 | append( sb, "Required: Yes", 3 ); |
257 | 0 | append( sb, "Expression: ${workingdir}", 3 ); |
258 | 0 | append( sb, "", 0 ); |
259 | |
} |
260 | |
} |
261 | |
|
262 | 0 | if ( goal == null || goal.length() <= 0 || "sign-verify".equals( goal ) ) |
263 | |
{ |
264 | 0 | append( sb, "jar:sign-verify", 0 ); |
265 | 0 | append( sb, "Deprecated. As of version 2.3, this goal is no longer supported in favor of the dedicated maven-jarsigner-plugin.", 1 ); |
266 | 0 | if ( detail ) |
267 | |
{ |
268 | 0 | append( sb, "", 0 ); |
269 | 0 | append( sb, "Checks the signature of a signed jar using jarsigner.", 1 ); |
270 | |
} |
271 | 0 | append( sb, "", 0 ); |
272 | 0 | if ( detail ) |
273 | |
{ |
274 | 0 | append( sb, "Available parameters:", 1 ); |
275 | 0 | append( sb, "", 0 ); |
276 | |
|
277 | 0 | append( sb, "checkCerts (Default: false)", 2 ); |
278 | 0 | append( sb, "Check certificates. Requires setVerbose(). See options.", 3 ); |
279 | 0 | append( sb, "Expression: ${checkcerts}", 3 ); |
280 | 0 | append( sb, "", 0 ); |
281 | |
|
282 | 0 | append( sb, "errorWhenNotSigned (Default: true)", 2 ); |
283 | 0 | append( sb, "When true this will make the execute() operation fail, throwing an exception, when verifying a non signed jar. Primarily to keep backwards compatibility with existing code, and allow reusing the bean in unattended operations when set to false.", 3 ); |
284 | 0 | append( sb, "Expression: ${errorWhenNotSigned}", 3 ); |
285 | 0 | append( sb, "", 0 ); |
286 | |
|
287 | 0 | append( sb, "finalName", 2 ); |
288 | 0 | append( sb, "Name of the generated JAR (without classifier and extension).", 3 ); |
289 | 0 | append( sb, "Required: Yes", 3 ); |
290 | 0 | append( sb, "Expression: ${project.build.finalName}", 3 ); |
291 | 0 | append( sb, "", 0 ); |
292 | |
|
293 | 0 | append( sb, "jarPath", 2 ); |
294 | 0 | append( sb, "Path of the signed jar. When specified, the finalName is ignored.", 3 ); |
295 | 0 | append( sb, "Expression: ${jarpath}", 3 ); |
296 | 0 | append( sb, "", 0 ); |
297 | |
|
298 | 0 | append( sb, "verbose (Default: false)", 2 ); |
299 | 0 | append( sb, "Enable verbose See options.", 3 ); |
300 | 0 | append( sb, "Expression: ${verbose}", 3 ); |
301 | 0 | append( sb, "", 0 ); |
302 | |
|
303 | 0 | append( sb, "workingDirectory (Default: ${basedir})", 2 ); |
304 | 0 | append( sb, "The working directory in which the jarsigner executable will be run.", 3 ); |
305 | 0 | append( sb, "Required: Yes", 3 ); |
306 | 0 | append( sb, "Expression: ${workingdir}", 3 ); |
307 | 0 | append( sb, "", 0 ); |
308 | |
} |
309 | |
} |
310 | |
|
311 | 0 | if ( goal == null || goal.length() <= 0 || "test-jar".equals( goal ) ) |
312 | |
{ |
313 | 0 | append( sb, "jar:test-jar", 0 ); |
314 | 0 | append( sb, "Build a JAR of the test classes for the current project.", 1 ); |
315 | 0 | append( sb, "", 0 ); |
316 | 0 | if ( detail ) |
317 | |
{ |
318 | 0 | append( sb, "Available parameters:", 1 ); |
319 | 0 | append( sb, "", 0 ); |
320 | |
|
321 | 0 | append( sb, "archive", 2 ); |
322 | 0 | append( sb, "The archive configuration to use. See Maven Archiver Reference.", 3 ); |
323 | 0 | append( sb, "", 0 ); |
324 | |
|
325 | 0 | append( sb, "excludes", 2 ); |
326 | 0 | append( sb, "List of files to exclude. Specified as fileset patterns which are relative to the input directory whose contents is being packaged into the JAR.", 3 ); |
327 | 0 | append( sb, "", 0 ); |
328 | |
|
329 | 0 | append( sb, "finalName (Default: ${project.build.finalName})", 2 ); |
330 | 0 | append( sb, "Name of the generated JAR.", 3 ); |
331 | 0 | append( sb, "Required: Yes", 3 ); |
332 | 0 | append( sb, "Expression: ${jar.finalName}", 3 ); |
333 | 0 | append( sb, "", 0 ); |
334 | |
|
335 | 0 | append( sb, "forceCreation (Default: false)", 2 ); |
336 | 0 | append( sb, "Whether creating the archive should be forced.", 3 ); |
337 | 0 | append( sb, "Expression: ${jar.forceCreation}", 3 ); |
338 | 0 | append( sb, "", 0 ); |
339 | |
|
340 | 0 | append( sb, "includes", 2 ); |
341 | 0 | append( sb, "List of files to include. Specified as fileset patterns which are relative to the input directory whose contents is being packaged into the JAR.", 3 ); |
342 | 0 | append( sb, "", 0 ); |
343 | |
|
344 | 0 | append( sb, "outputDirectory (Default: ${project.build.directory})", 2 ); |
345 | 0 | append( sb, "Directory containing the generated JAR.", 3 ); |
346 | 0 | append( sb, "Required: Yes", 3 ); |
347 | 0 | append( sb, "", 0 ); |
348 | |
|
349 | 0 | append( sb, "skip", 2 ); |
350 | 0 | append( sb, "Set this to true to bypass unit tests entirely. Its use is NOT RECOMMENDED, but quite convenient on occasion.", 3 ); |
351 | 0 | append( sb, "Expression: ${maven.test.skip}", 3 ); |
352 | 0 | append( sb, "", 0 ); |
353 | |
|
354 | 0 | append( sb, "skipIfEmpty (Default: false)", 2 ); |
355 | 0 | append( sb, "Skip creating empty archives", 3 ); |
356 | 0 | append( sb, "Expression: ${jar.skipIfEmpty}", 3 ); |
357 | 0 | append( sb, "", 0 ); |
358 | |
|
359 | 0 | append( sb, "testClassesDirectory (Default: ${project.build.testOutputDirectory})", 2 ); |
360 | 0 | append( sb, "Directory containing the test classes and resource files that should be packaged into the JAR.", 3 ); |
361 | 0 | append( sb, "Required: Yes", 3 ); |
362 | 0 | append( sb, "", 0 ); |
363 | |
|
364 | 0 | append( sb, "useDefaultManifestFile (Default: false)", 2 ); |
365 | 0 | append( sb, "Set this to true to enable the use of the defaultManifestFile.", 3 ); |
366 | 0 | append( sb, "Expression: ${jar.useDefaultManifestFile}", 3 ); |
367 | 0 | append( sb, "", 0 ); |
368 | |
} |
369 | |
} |
370 | |
|
371 | 0 | if ( getLog().isInfoEnabled() ) |
372 | |
{ |
373 | 0 | getLog().info( sb.toString() ); |
374 | |
} |
375 | 0 | } |
376 | |
|
377 | |
|
378 | |
|
379 | |
|
380 | |
|
381 | |
|
382 | |
|
383 | |
|
384 | |
|
385 | |
|
386 | |
private static String repeat( String str, int repeat ) |
387 | |
{ |
388 | 0 | StringBuffer buffer = new StringBuffer( repeat * str.length() ); |
389 | |
|
390 | 0 | for ( int i = 0; i < repeat; i++ ) |
391 | |
{ |
392 | 0 | buffer.append( str ); |
393 | |
} |
394 | |
|
395 | 0 | return buffer.toString(); |
396 | |
} |
397 | |
|
398 | |
|
399 | |
|
400 | |
|
401 | |
|
402 | |
|
403 | |
|
404 | |
|
405 | |
|
406 | |
private void append( StringBuffer sb, String description, int indent ) |
407 | |
{ |
408 | 0 | for ( Iterator it = toLines( description, indent, indentSize, lineLength ).iterator(); it.hasNext(); ) |
409 | |
{ |
410 | 0 | sb.append( it.next().toString() ).append( '\n' ); |
411 | |
} |
412 | 0 | } |
413 | |
|
414 | |
|
415 | |
|
416 | |
|
417 | |
|
418 | |
|
419 | |
|
420 | |
|
421 | |
|
422 | |
|
423 | |
|
424 | |
private static List toLines( String text, int indent, int indentSize, int lineLength ) |
425 | |
{ |
426 | 0 | List<String> lines = new ArrayList<String>(); |
427 | |
|
428 | 0 | String ind = repeat( "\t", indent ); |
429 | 0 | String[] plainLines = text.split( "(\r\n)|(\r)|(\n)" ); |
430 | 0 | for ( int i = 0; i < plainLines.length; i++ ) |
431 | |
{ |
432 | 0 | toLines( lines, ind + plainLines[i], indentSize, lineLength ); |
433 | |
} |
434 | |
|
435 | 0 | return lines; |
436 | |
} |
437 | |
|
438 | |
|
439 | |
|
440 | |
|
441 | |
|
442 | |
|
443 | |
|
444 | |
|
445 | |
|
446 | |
private static void toLines( List<String> lines, String line, int indentSize, int lineLength ) |
447 | |
{ |
448 | 0 | int lineIndent = getIndentLevel( line ); |
449 | 0 | StringBuffer buf = new StringBuffer( 256 ); |
450 | 0 | String[] tokens = line.split( " +" ); |
451 | 0 | for ( int i = 0; i < tokens.length; i++ ) |
452 | |
{ |
453 | 0 | String token = tokens[i]; |
454 | 0 | if ( i > 0 ) |
455 | |
{ |
456 | 0 | if ( buf.length() + token.length() >= lineLength ) |
457 | |
{ |
458 | 0 | lines.add( buf.toString() ); |
459 | 0 | buf.setLength( 0 ); |
460 | 0 | buf.append( repeat( " ", lineIndent * indentSize ) ); |
461 | |
} |
462 | |
else |
463 | |
{ |
464 | 0 | buf.append( ' ' ); |
465 | |
} |
466 | |
} |
467 | 0 | for ( int j = 0; j < token.length(); j++ ) |
468 | |
{ |
469 | 0 | char c = token.charAt( j ); |
470 | 0 | if ( c == '\t' ) |
471 | |
{ |
472 | 0 | buf.append( repeat( " ", indentSize - buf.length() % indentSize ) ); |
473 | |
} |
474 | 0 | else if ( c == '\u00A0' ) |
475 | |
{ |
476 | 0 | buf.append( ' ' ); |
477 | |
} |
478 | |
else |
479 | |
{ |
480 | 0 | buf.append( c ); |
481 | |
} |
482 | |
} |
483 | |
} |
484 | 0 | lines.add( buf.toString() ); |
485 | 0 | } |
486 | |
|
487 | |
|
488 | |
|
489 | |
|
490 | |
|
491 | |
|
492 | |
|
493 | |
private static int getIndentLevel( String line ) |
494 | |
{ |
495 | 0 | int level = 0; |
496 | 0 | for ( int i = 0; i < line.length() && line.charAt( i ) == '\t'; i++ ) |
497 | |
{ |
498 | 0 | level++; |
499 | |
} |
500 | 0 | for ( int i = level + 1; i <= level + 4 && i < line.length(); i++ ) |
501 | |
{ |
502 | 0 | if ( line.charAt( i ) == '\t' ) |
503 | |
{ |
504 | 0 | level++; |
505 | 0 | break; |
506 | |
} |
507 | |
} |
508 | 0 | return level; |
509 | |
} |
510 | |
} |