1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 package org.jomc.util;
32
33 import java.io.IOException;
34 import java.lang.reflect.UndeclaredThrowableException;
35 import java.text.MessageFormat;
36 import java.util.Collection;
37 import java.util.LinkedList;
38 import java.util.List;
39 import java.util.Map;
40 import java.util.ResourceBundle;
41 import java.util.Stack;
42 import java.util.concurrent.Callable;
43 import java.util.concurrent.CancellationException;
44 import java.util.concurrent.ConcurrentHashMap;
45 import java.util.concurrent.ExecutionException;
46 import java.util.concurrent.ExecutorService;
47 import java.util.concurrent.Future;
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63 public class SectionEditor extends LineEditor
64 {
65
66
67
68
69 private static final String DEFAULT_SECTION_START = "SECTION-START[";
70
71
72
73
74 private static final String DEFAULT_SECTION_END = "SECTION-END";
75
76
77
78
79 private Stack<Section> stack;
80
81
82
83
84 private final Map<String, Boolean> presenceFlags = new ConcurrentHashMap<String, Boolean>( 32 );
85
86
87
88
89
90
91 private ExecutorService executorService;
92
93
94
95
96 public SectionEditor()
97 {
98 this( null, null );
99 }
100
101
102
103
104
105
106 public SectionEditor( final String lineSeparator )
107 {
108 this( null, lineSeparator );
109 }
110
111
112
113
114
115
116 public SectionEditor( final LineEditor editor )
117 {
118 this( editor, null );
119 }
120
121
122
123
124
125
126
127 public SectionEditor( final LineEditor editor, final String lineSeparator )
128 {
129 super( editor, lineSeparator );
130 }
131
132
133
134
135
136
137
138
139
140
141
142 public final ExecutorService getExecutorService()
143 {
144 return this.executorService;
145 }
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162 public final void setExecutorService( final ExecutorService value )
163 {
164 this.executorService = value;
165 }
166
167 @Override
168 protected final String editLine( final String line ) throws IOException
169 {
170 if ( this.stack == null )
171 {
172 final Section root = new Section();
173 root.setMode( Section.MODE_HEAD );
174 this.stack = new Stack<Section>();
175 this.stack.push( root );
176 }
177
178 Section current = this.stack.peek();
179 String replacement = null;
180
181 if ( line != null )
182 {
183 final Section child = this.getSection( line );
184
185 if ( child != null )
186 {
187 child.setStartingLine( line );
188 child.setMode( Section.MODE_HEAD );
189
190 if ( current.getMode() == Section.MODE_TAIL && current.getTailContent().length() > 0 )
191 {
192 final Section s = new Section();
193 s.getHeadContent().append( current.getTailContent() );
194 current.getTailContent().setLength( 0 );
195 current.getSections().add( s );
196 current = s;
197 this.stack.push( current );
198 }
199
200 current.getSections().add( child );
201 current.setMode( Section.MODE_TAIL );
202 this.stack.push( child );
203 }
204 else if ( this.isSectionFinished( line ) )
205 {
206 final Section s = this.stack.pop();
207 s.setEndingLine( line );
208
209 if ( this.stack.isEmpty() )
210 {
211 this.stack = null;
212 throw new IOException( getMessage( "unexpectedEndOfSection", this.getLineNumber() ) );
213 }
214
215 if ( this.stack.peek().getName() == null && this.stack.size() > 1 )
216 {
217 this.stack.pop();
218 }
219 }
220 else
221 {
222 switch ( current.getMode() )
223 {
224 case Section.MODE_HEAD:
225 current.getHeadContent().append( line ).append( this.getLineSeparator() );
226 break;
227
228 case Section.MODE_TAIL:
229 current.getTailContent().append( line ).append( this.getLineSeparator() );
230 break;
231
232 default:
233 throw new AssertionError( current.getMode() );
234
235 }
236 }
237 }
238 else
239 {
240 final Section root = this.stack.pop();
241
242 if ( !this.stack.isEmpty() )
243 {
244 this.stack = null;
245 throw new IOException( getMessage( "unexpectedEndOfFile", this.getLineNumber(), root.getName() ) );
246 }
247
248 replacement = this.getOutput( root );
249 this.stack = null;
250 }
251
252 return replacement;
253 }
254
255
256
257
258
259
260
261
262
263
264
265 protected Section getSection( final String line ) throws IOException
266 {
267 Section s = null;
268
269 if ( line != null )
270 {
271 final int markerIndex = line.indexOf( DEFAULT_SECTION_START );
272
273 if ( markerIndex != -1 )
274 {
275 final int startIndex = markerIndex + DEFAULT_SECTION_START.length();
276 final int endIndex = line.indexOf( ']', startIndex );
277
278 if ( endIndex == -1 )
279 {
280 throw new IOException( getMessage( "sectionMarkerParseFailure", line, this.getLineNumber() ) );
281 }
282
283 s = new Section();
284 s.setName( line.substring( startIndex, endIndex ) );
285 }
286 }
287
288 return s;
289 }
290
291
292
293
294
295
296
297
298
299
300
301 protected boolean isSectionFinished( final String line ) throws IOException
302 {
303 return line != null && line.contains( DEFAULT_SECTION_END );
304 }
305
306
307
308
309
310
311
312
313
314
315
316
317
318 protected void editSection( final Section section ) throws IOException
319 {
320 if ( section == null )
321 {
322 throw new NullPointerException( "section" );
323 }
324
325 if ( section.getName() != null )
326 {
327 this.presenceFlags.put( section.getName(), Boolean.TRUE );
328 }
329 }
330
331
332
333
334
335
336
337
338
339
340 private void editSections( final Section section, final Collection<EditSectionTask> tasks ) throws IOException
341 {
342 if ( section == null )
343 {
344 throw new NullPointerException( "section" );
345 }
346 if ( tasks == null )
347 {
348 throw new NullPointerException( "tasks" );
349 }
350
351 tasks.add( new EditSectionTask( section ) );
352 for ( int i = 0, s0 = section.getSections().size(); i < s0; i++ )
353 {
354 this.editSections( section.getSections().get( i ), tasks );
355 }
356 }
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372 protected String getOutput( final Section section ) throws IOException
373 {
374 if ( section == null )
375 {
376 throw new NullPointerException( "section" );
377 }
378
379 try
380 {
381 this.presenceFlags.clear();
382 final List<EditSectionTask> tasks = new LinkedList<EditSectionTask>();
383 this.editSections( section, tasks );
384
385 if ( this.getExecutorService() != null && tasks.size() > 1 )
386 {
387 for ( final Future<Void> task : this.getExecutorService().invokeAll( tasks ) )
388 {
389 task.get();
390 }
391 }
392 else
393 {
394 for ( int i = 0, s0 = tasks.size(); i < s0; i++ )
395 {
396 tasks.get( i ).call();
397 }
398 }
399
400 return this.renderSections( section, new StringBuilder( 512 ) ).toString();
401 }
402 catch ( final CancellationException e )
403 {
404 throw (IOException) new IOException( getMessage( e ) ).initCause( e );
405 }
406 catch ( final InterruptedException e )
407 {
408 throw (IOException) new IOException( getMessage( e ) ).initCause( e );
409 }
410 catch ( final ExecutionException e )
411 {
412 if ( e.getCause() instanceof IOException )
413 {
414 throw (IOException) e.getCause();
415 }
416 else if ( e.getCause() instanceof RuntimeException )
417 {
418
419
420 if ( e.getCause().getCause() instanceof IOException )
421 {
422 throw (IOException) e.getCause().getCause();
423 }
424 else if ( e.getCause().getCause() instanceof RuntimeException )
425 {
426 throw (RuntimeException) e.getCause().getCause();
427 }
428 else if ( e.getCause().getCause() instanceof Error )
429 {
430 throw (Error) e.getCause().getCause();
431 }
432 else if ( e.getCause().getCause() instanceof Exception )
433 {
434
435 throw new UndeclaredThrowableException( e.getCause().getCause() );
436 }
437 else
438 {
439 throw (RuntimeException) e.getCause();
440 }
441 }
442 else if ( e.getCause() instanceof Error )
443 {
444 throw (Error) e.getCause();
445 }
446 else
447 {
448
449 throw new UndeclaredThrowableException( e.getCause() );
450 }
451 }
452 }
453
454
455
456
457
458
459
460
461
462 public boolean isSectionPresent( final String sectionName )
463 {
464 return sectionName != null && this.presenceFlags.get( sectionName ) != null
465 && this.presenceFlags.get( sectionName );
466
467 }
468
469
470
471
472
473
474
475
476
477 private StringBuilder renderSections( final Section section, final StringBuilder buffer )
478 {
479 if ( section.getStartingLine() != null )
480 {
481 buffer.append( section.getStartingLine() ).append( this.getLineSeparator() );
482 }
483
484 buffer.append( section.getHeadContent() );
485
486 for ( int i = 0, s0 = section.getSections().size(); i < s0; i++ )
487 {
488 this.renderSections( section.getSections().get( i ), buffer );
489 }
490
491 buffer.append( section.getTailContent() );
492
493 if ( section.getEndingLine() != null )
494 {
495 buffer.append( section.getEndingLine() ).append( this.getLineSeparator() );
496 }
497
498 return buffer;
499 }
500
501 private final class EditSectionTask implements Callable<Void>
502 {
503
504 private final Section section;
505
506 EditSectionTask( final Section section )
507 {
508 super();
509 this.section = section;
510 }
511
512 public Void call() throws IOException
513 {
514 editSection( this.section );
515 return null;
516 }
517
518 }
519
520 private static String getMessage( final String key, final Object... arguments )
521 {
522 return MessageFormat.format( ResourceBundle.getBundle( SectionEditor.class.getName() ).getString( key ),
523 arguments );
524
525 }
526
527 private static String getMessage( final Throwable t )
528 {
529 return t != null
530 ? t.getMessage() != null && t.getMessage().trim().length() > 0
531 ? t.getMessage()
532 : getMessage( t.getCause() )
533 : null;
534
535 }
536
537 }