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 | |
|
32 | |
|
33 | |
|
34 | |
|
35 | |
|
36 | |
|
37 | |
|
38 | |
|
39 | |
package org.argouml.persistence; |
40 | |
|
41 | |
import java.io.BufferedInputStream; |
42 | |
import java.io.BufferedReader; |
43 | |
import java.io.BufferedWriter; |
44 | |
import java.io.File; |
45 | |
import java.io.FileNotFoundException; |
46 | |
import java.io.FileOutputStream; |
47 | |
import java.io.FilterOutputStream; |
48 | |
import java.io.IOException; |
49 | |
import java.io.InputStream; |
50 | |
import java.io.InputStreamReader; |
51 | |
import java.io.OutputStream; |
52 | |
import java.io.OutputStreamWriter; |
53 | |
import java.io.PrintWriter; |
54 | |
import java.io.Reader; |
55 | |
import java.io.UnsupportedEncodingException; |
56 | |
import java.io.Writer; |
57 | |
import java.net.MalformedURLException; |
58 | |
import java.net.URL; |
59 | |
import java.nio.ByteBuffer; |
60 | |
import java.nio.CharBuffer; |
61 | |
import java.nio.charset.Charset; |
62 | |
import java.nio.charset.CharsetDecoder; |
63 | |
import java.nio.charset.CoderResult; |
64 | |
import java.nio.charset.CodingErrorAction; |
65 | |
import java.util.Hashtable; |
66 | |
import java.util.List; |
67 | |
import java.util.regex.Matcher; |
68 | |
import java.util.regex.Pattern; |
69 | |
|
70 | |
import javax.xml.transform.Result; |
71 | |
import javax.xml.transform.Transformer; |
72 | |
import javax.xml.transform.TransformerException; |
73 | |
import javax.xml.transform.TransformerFactory; |
74 | |
import javax.xml.transform.stream.StreamResult; |
75 | |
import javax.xml.transform.stream.StreamSource; |
76 | |
|
77 | |
import org.apache.log4j.Logger; |
78 | |
import org.argouml.application.api.Argo; |
79 | |
import org.argouml.application.helpers.ApplicationVersion; |
80 | |
import org.argouml.i18n.Translator; |
81 | |
import org.argouml.kernel.Project; |
82 | |
import org.argouml.kernel.ProjectFactory; |
83 | |
import org.argouml.kernel.ProjectMember; |
84 | |
import org.argouml.model.UmlException; |
85 | |
import org.argouml.util.ThreadUtils; |
86 | |
import org.tigris.gef.ocl.ExpansionException; |
87 | |
import org.tigris.gef.ocl.OCLExpander; |
88 | |
import org.tigris.gef.ocl.TemplateReader; |
89 | |
import org.xml.sax.InputSource; |
90 | |
import org.xml.sax.SAXException; |
91 | |
|
92 | |
|
93 | |
|
94 | |
|
95 | |
|
96 | |
|
97 | |
public class UmlFilePersister extends AbstractFilePersister { |
98 | |
|
99 | |
|
100 | |
|
101 | |
|
102 | |
|
103 | |
|
104 | |
public static final int PERSISTENCE_VERSION = 6; |
105 | |
|
106 | |
|
107 | |
|
108 | |
|
109 | |
|
110 | |
protected static final int UML_PHASES_LOAD = 2; |
111 | |
|
112 | |
|
113 | |
|
114 | |
|
115 | 900 | private static final Logger LOG = Logger.getLogger(UmlFilePersister.class); |
116 | |
|
117 | |
private static final String ARGO_TEE = "/org/argouml/persistence/argo.tee"; |
118 | |
|
119 | |
|
120 | |
|
121 | |
|
122 | 2700 | public UmlFilePersister() { |
123 | 2700 | } |
124 | |
|
125 | |
|
126 | |
|
127 | |
|
128 | |
public String getExtension() { |
129 | 9875 | return "uml"; |
130 | |
} |
131 | |
|
132 | |
|
133 | |
|
134 | |
|
135 | |
protected String getDesc() { |
136 | 168 | return Translator.localize("combobox.filefilter.uml"); |
137 | |
} |
138 | |
|
139 | |
|
140 | |
|
141 | |
|
142 | |
|
143 | |
|
144 | |
|
145 | |
|
146 | |
|
147 | |
|
148 | |
|
149 | |
|
150 | |
|
151 | |
public void doSave(Project project, File file) throws SaveException, |
152 | |
InterruptedException { |
153 | |
|
154 | 0 | ProgressMgr progressMgr = new ProgressMgr(); |
155 | 0 | progressMgr.setNumberOfPhases(4); |
156 | 0 | progressMgr.nextPhase(); |
157 | |
|
158 | 0 | File lastArchiveFile = new File(file.getAbsolutePath() + "~"); |
159 | 0 | File tempFile = null; |
160 | |
|
161 | |
try { |
162 | 0 | tempFile = createTempFile(file); |
163 | 0 | } catch (FileNotFoundException e) { |
164 | 0 | throw new SaveException( |
165 | |
"Failed to archive the previous file version", e); |
166 | 0 | } catch (IOException e) { |
167 | 0 | throw new SaveException( |
168 | |
"Failed to archive the previous file version", e); |
169 | 0 | } |
170 | |
|
171 | |
try { |
172 | 0 | project.setFile(file); |
173 | 0 | project.setVersion(ApplicationVersion.getVersion()); |
174 | 0 | project.setPersistenceVersion(PERSISTENCE_VERSION); |
175 | |
|
176 | 0 | OutputStream stream = new FileOutputStream(file); |
177 | |
|
178 | 0 | writeProject(project, stream, progressMgr); |
179 | |
|
180 | 0 | stream.close(); |
181 | |
|
182 | 0 | progressMgr.nextPhase(); |
183 | |
|
184 | 0 | String path = file.getParent(); |
185 | 0 | if (LOG.isInfoEnabled()) { |
186 | 0 | LOG.info("Dir ==" + path); |
187 | |
} |
188 | |
|
189 | |
|
190 | |
|
191 | |
|
192 | 0 | if (lastArchiveFile.exists()) { |
193 | 0 | lastArchiveFile.delete(); |
194 | |
} |
195 | 0 | if (tempFile.exists() && !lastArchiveFile.exists()) { |
196 | 0 | tempFile.renameTo(lastArchiveFile); |
197 | |
} |
198 | 0 | if (tempFile.exists()) { |
199 | 0 | tempFile.delete(); |
200 | |
} |
201 | |
|
202 | 0 | progressMgr.nextPhase(); |
203 | |
|
204 | 0 | } catch (Exception e) { |
205 | 0 | LOG.error("Exception occured during save attempt", e); |
206 | |
|
207 | |
|
208 | |
|
209 | |
|
210 | 0 | file.delete(); |
211 | 0 | tempFile.renameTo(file); |
212 | 0 | if (e instanceof InterruptedException) { |
213 | 0 | throw (InterruptedException) e; |
214 | |
} else { |
215 | |
|
216 | |
|
217 | 0 | throw new SaveException(e); |
218 | |
} |
219 | 0 | } |
220 | 0 | } |
221 | |
|
222 | |
|
223 | |
|
224 | |
|
225 | |
|
226 | |
|
227 | |
@Override |
228 | |
public boolean isSaveEnabled() { |
229 | 5 | return true; |
230 | |
} |
231 | |
|
232 | |
|
233 | |
|
234 | |
|
235 | |
|
236 | |
|
237 | |
|
238 | |
|
239 | |
|
240 | |
void writeProject(Project project, OutputStream oStream, |
241 | |
ProgressMgr progressMgr) throws SaveException, InterruptedException { |
242 | |
OutputStreamWriter outputStreamWriter; |
243 | |
try { |
244 | 0 | outputStreamWriter = new OutputStreamWriter(oStream, Argo |
245 | |
.getEncoding()); |
246 | 0 | } catch (UnsupportedEncodingException e) { |
247 | 0 | throw new SaveException(e); |
248 | 0 | } |
249 | 0 | PrintWriter writer = new PrintWriter(new BufferedWriter( |
250 | |
outputStreamWriter)); |
251 | |
|
252 | 0 | XmlFilterOutputStream filteredStream = new XmlFilterOutputStream( |
253 | |
oStream, Argo.getEncoding()); |
254 | |
try { |
255 | 0 | writer.println("<?xml version = \"1.0\" " + "encoding = \"" |
256 | |
+ Argo.getEncoding() + "\" ?>"); |
257 | 0 | writer.println("<uml version=\"" + PERSISTENCE_VERSION + "\">"); |
258 | |
|
259 | |
try { |
260 | 0 | Hashtable templates = TemplateReader.getInstance().read( |
261 | |
ARGO_TEE); |
262 | 0 | OCLExpander expander = new OCLExpander(templates); |
263 | 0 | expander.expand(writer, project, " "); |
264 | 0 | } catch (ExpansionException e) { |
265 | 0 | throw new SaveException(e); |
266 | 0 | } |
267 | 0 | writer.flush(); |
268 | |
|
269 | 0 | if (progressMgr != null) { |
270 | 0 | progressMgr.nextPhase(); |
271 | |
} |
272 | |
|
273 | |
|
274 | 0 | for (ProjectMember projectMember : project.getMembers()) { |
275 | 0 | if (LOG.isInfoEnabled()) { |
276 | 0 | LOG.info("Saving member : " + projectMember); |
277 | |
} |
278 | 0 | MemberFilePersister persister = getMemberFilePersister(projectMember); |
279 | 0 | filteredStream.startEntry(); |
280 | 0 | persister.save(projectMember, filteredStream); |
281 | |
try { |
282 | 0 | filteredStream.flush(); |
283 | 0 | } catch (IOException e) { |
284 | 0 | throw new SaveException(e); |
285 | 0 | } |
286 | 0 | } |
287 | |
|
288 | 0 | writer.println("</uml>"); |
289 | |
|
290 | 0 | writer.flush(); |
291 | |
} finally { |
292 | 0 | writer.close(); |
293 | |
try { |
294 | 0 | filteredStream.reallyClose(); |
295 | 0 | } catch (IOException e) { |
296 | 0 | throw new SaveException(e); |
297 | 0 | } |
298 | |
} |
299 | 0 | } |
300 | |
|
301 | |
|
302 | |
|
303 | |
|
304 | |
public Project doLoad(File file) throws OpenException, InterruptedException { |
305 | |
|
306 | 0 | ProgressMgr progressMgr = new ProgressMgr(); |
307 | 0 | progressMgr.setNumberOfPhases(UML_PHASES_LOAD); |
308 | |
|
309 | 0 | ThreadUtils.checkIfInterrupted(); |
310 | 0 | return doLoad(file, file, progressMgr); |
311 | |
} |
312 | |
|
313 | |
protected Project doLoad(File originalFile, File file, |
314 | |
ProgressMgr progressMgr) throws OpenException, InterruptedException { |
315 | |
|
316 | 0 | XmlInputStream inputStream = null; |
317 | |
try { |
318 | 0 | Project p = ProjectFactory.getInstance() |
319 | |
.createProject(file.toURI()); |
320 | |
|
321 | |
|
322 | 0 | int fileVersion = getPersistenceVersionFromFile(file); |
323 | |
|
324 | 0 | LOG.info("Loading uml file of version " + fileVersion); |
325 | 0 | if (!checkVersion(fileVersion, getReleaseVersionFromFile(file))) { |
326 | |
|
327 | |
|
328 | 0 | String release = getReleaseVersionFromFile(file); |
329 | 0 | copyFile(originalFile, new File(originalFile.getAbsolutePath() |
330 | |
+ '~' + release)); |
331 | |
|
332 | 0 | progressMgr.setNumberOfPhases(progressMgr.getNumberOfPhases() |
333 | |
+ (PERSISTENCE_VERSION - fileVersion)); |
334 | |
|
335 | 0 | while (fileVersion < PERSISTENCE_VERSION) { |
336 | 0 | ++fileVersion; |
337 | 0 | LOG.info("Upgrading to version " + fileVersion); |
338 | 0 | long startTime = System.currentTimeMillis(); |
339 | 0 | file = transform(file, fileVersion); |
340 | 0 | long endTime = System.currentTimeMillis(); |
341 | 0 | LOG.info("Upgrading took " + ((endTime - startTime) / 1000) |
342 | |
+ " seconds"); |
343 | 0 | progressMgr.nextPhase(); |
344 | 0 | } |
345 | |
} |
346 | |
|
347 | 0 | progressMgr.nextPhase(); |
348 | |
|
349 | 0 | inputStream = new XmlInputStream(file.toURI().toURL().openStream(), |
350 | |
"argo", file.length(), 100000); |
351 | |
|
352 | 0 | ArgoParser parser = new ArgoParser(); |
353 | 0 | Reader reader = new InputStreamReader(inputStream, Argo |
354 | |
.getEncoding()); |
355 | 0 | parser.readProject(p, reader); |
356 | |
|
357 | 0 | List memberList = parser.getMemberList(); |
358 | |
|
359 | 0 | LOG.info(memberList.size() + " members"); |
360 | |
|
361 | 0 | for (int i = 0; i < memberList.size(); ++i) { |
362 | 0 | MemberFilePersister persister = getMemberFilePersister((String) memberList |
363 | |
.get(i)); |
364 | 0 | LOG.info("Loading member with " |
365 | |
+ persister.getClass().getName()); |
366 | 0 | inputStream.reopen(persister.getMainTag()); |
367 | |
|
368 | |
|
369 | |
|
370 | |
|
371 | |
|
372 | 0 | InputSource inputSource = new InputSource(inputStream); |
373 | |
|
374 | |
|
375 | 0 | inputSource.setPublicId(originalFile.toURI().toURL() |
376 | |
.toExternalForm()); |
377 | |
try { |
378 | 0 | persister.load(p, inputSource); |
379 | 0 | } catch (OpenException e) { |
380 | |
|
381 | |
|
382 | 0 | if ("uml:Model".equals(persister.getMainTag()) |
383 | |
&& e.getCause() instanceof UmlException |
384 | |
&& e.getCause().getCause() instanceof IOException) { |
385 | 0 | inputStream.reopen("uml:Profile"); |
386 | 0 | persister.load(p, inputSource); |
387 | 0 | p.setProjectType(Project.PROFILE_PROJECT); |
388 | |
} else { |
389 | 0 | throw e; |
390 | |
} |
391 | 0 | } |
392 | |
} |
393 | |
|
394 | |
|
395 | 0 | progressMgr.nextPhase(); |
396 | 0 | ThreadUtils.checkIfInterrupted(); |
397 | 0 | inputStream.realClose(); |
398 | 0 | p.postLoad(); |
399 | 0 | return p; |
400 | 0 | } catch (InterruptedException e) { |
401 | 0 | throw e; |
402 | 0 | } catch (OpenException e) { |
403 | 0 | throw e; |
404 | 0 | } catch (IOException e) { |
405 | 0 | throw new OpenException(e); |
406 | 0 | } catch (SAXException e) { |
407 | 0 | throw new OpenException(e); |
408 | |
} |
409 | |
} |
410 | |
|
411 | |
protected boolean checkVersion(int fileVersion, String releaseVersion) |
412 | |
throws OpenException, VersionException { |
413 | |
|
414 | |
|
415 | 0 | if (fileVersion > PERSISTENCE_VERSION) { |
416 | 0 | throw new VersionException( |
417 | |
"The file selected is from a more up to date version of " |
418 | |
+ "ArgoUML. It has been saved with ArgoUML version " |
419 | |
+ releaseVersion |
420 | |
+ ". Please load with that or a more up to date" |
421 | |
+ "release of ArgoUML"); |
422 | |
} |
423 | 0 | return fileVersion >= PERSISTENCE_VERSION; |
424 | |
} |
425 | |
|
426 | |
|
427 | |
|
428 | |
|
429 | |
|
430 | |
|
431 | |
|
432 | |
|
433 | |
|
434 | |
|
435 | |
public final File transform(File file, int version) throws OpenException { |
436 | |
|
437 | |
try { |
438 | 0 | String upgradeFilesPath = "/org/argouml/persistence/upgrades/"; |
439 | 0 | String upgradeFile = "upgrade" + version + ".xsl"; |
440 | |
|
441 | 0 | String xsltFileName = upgradeFilesPath + upgradeFile; |
442 | 0 | URL xsltUrl = UmlFilePersister.class.getResource(xsltFileName); |
443 | 0 | LOG.info("Resource is " + xsltUrl); |
444 | |
|
445 | |
|
446 | |
|
447 | |
|
448 | 0 | StreamSource xsltStreamSource = new StreamSource(xsltUrl |
449 | |
.openStream()); |
450 | 0 | xsltStreamSource.setSystemId(xsltUrl.toExternalForm()); |
451 | |
|
452 | 0 | TransformerFactory factory = TransformerFactory.newInstance(); |
453 | 0 | Transformer transformer = factory.newTransformer(xsltStreamSource); |
454 | |
|
455 | 0 | File transformedFile = File.createTempFile("upgrade_" + version |
456 | |
+ "_", ".uml"); |
457 | 0 | transformedFile.deleteOnExit(); |
458 | |
|
459 | 0 | FileOutputStream stream = new FileOutputStream(transformedFile); |
460 | 0 | Writer writer = new BufferedWriter(new OutputStreamWriter(stream, |
461 | |
Argo.getEncoding())); |
462 | 0 | Result result = new StreamResult(writer); |
463 | |
|
464 | 0 | StreamSource inputStreamSource = new StreamSource(file); |
465 | 0 | inputStreamSource.setSystemId(file); |
466 | 0 | transformer.transform(inputStreamSource, result); |
467 | |
|
468 | 0 | writer.close(); |
469 | 0 | return transformedFile; |
470 | 0 | } catch (IOException e) { |
471 | 0 | throw new OpenException(e); |
472 | 0 | } catch (TransformerException e) { |
473 | 0 | throw new OpenException(e); |
474 | |
} |
475 | |
} |
476 | |
|
477 | |
|
478 | |
|
479 | |
|
480 | |
|
481 | |
|
482 | |
|
483 | |
|
484 | |
|
485 | |
private int getPersistenceVersionFromFile(File file) throws OpenException { |
486 | 0 | InputStream stream = null; |
487 | |
try { |
488 | 0 | stream = new BufferedInputStream(file.toURI().toURL().openStream()); |
489 | 0 | int version = getPersistenceVersion(stream); |
490 | 0 | stream.close(); |
491 | 0 | return version; |
492 | 0 | } catch (MalformedURLException e) { |
493 | 0 | throw new OpenException(e); |
494 | 0 | } catch (IOException e) { |
495 | 0 | throw new OpenException(e); |
496 | |
} finally { |
497 | 0 | if (stream != null) { |
498 | |
try { |
499 | 0 | stream.close(); |
500 | 0 | } catch (IOException e) { |
501 | |
|
502 | 0 | } |
503 | |
} |
504 | |
} |
505 | |
} |
506 | |
|
507 | |
|
508 | |
|
509 | |
|
510 | |
|
511 | |
|
512 | |
|
513 | |
|
514 | |
|
515 | |
protected int getPersistenceVersion(InputStream inputStream) |
516 | |
throws OpenException { |
517 | |
|
518 | 0 | BufferedReader reader = null; |
519 | |
try { |
520 | 0 | reader = new BufferedReader(new InputStreamReader(inputStream, Argo |
521 | |
.getEncoding())); |
522 | 0 | String rootLine = reader.readLine(); |
523 | 0 | while (rootLine != null && !rootLine.trim().startsWith("<argo ")) { |
524 | 0 | rootLine = reader.readLine(); |
525 | |
} |
526 | 0 | if (rootLine == null) { |
527 | 0 | return 1; |
528 | |
} |
529 | 0 | return Integer.parseInt(getVersion(rootLine)); |
530 | 0 | } catch (IOException e) { |
531 | 0 | throw new OpenException(e); |
532 | 0 | } catch (NumberFormatException e) { |
533 | 0 | throw new OpenException(e); |
534 | |
} finally { |
535 | 0 | try { |
536 | 0 | if (reader != null) { |
537 | 0 | reader.close(); |
538 | |
} |
539 | 0 | } catch (IOException e) { |
540 | |
|
541 | 0 | } |
542 | |
} |
543 | |
} |
544 | |
|
545 | |
|
546 | |
|
547 | |
|
548 | |
|
549 | |
|
550 | |
|
551 | |
|
552 | |
|
553 | |
private String getReleaseVersionFromFile(File file) throws OpenException { |
554 | 0 | InputStream stream = null; |
555 | |
try { |
556 | 0 | stream = new BufferedInputStream(file.toURI().toURL().openStream()); |
557 | 0 | String version = getReleaseVersion(stream); |
558 | 0 | stream.close(); |
559 | 0 | return version; |
560 | 0 | } catch (MalformedURLException e) { |
561 | 0 | throw new OpenException(e); |
562 | 0 | } catch (IOException e) { |
563 | 0 | throw new OpenException(e); |
564 | |
} finally { |
565 | 0 | if (stream != null) { |
566 | |
try { |
567 | 0 | stream.close(); |
568 | 0 | } catch (IOException e) { |
569 | |
|
570 | 0 | } |
571 | |
} |
572 | |
} |
573 | |
} |
574 | |
|
575 | |
|
576 | |
|
577 | |
|
578 | |
|
579 | |
|
580 | |
|
581 | |
|
582 | |
|
583 | |
protected String getReleaseVersion(InputStream inputStream) |
584 | |
throws OpenException { |
585 | |
|
586 | 0 | BufferedReader reader = null; |
587 | |
try { |
588 | 0 | reader = new BufferedReader(new InputStreamReader(inputStream, Argo |
589 | |
.getEncoding())); |
590 | 0 | String versionLine = reader.readLine(); |
591 | 0 | while (!versionLine.trim().startsWith("<version>")) { |
592 | 0 | versionLine = reader.readLine(); |
593 | 0 | if (versionLine == null) { |
594 | 0 | throw new OpenException( |
595 | |
"Failed to find the release <version> tag"); |
596 | |
} |
597 | |
} |
598 | 0 | versionLine = versionLine.trim(); |
599 | 0 | int end = versionLine.lastIndexOf("</version>"); |
600 | 0 | return versionLine.trim().substring(9, end); |
601 | 0 | } catch (IOException e) { |
602 | 0 | throw new OpenException(e); |
603 | 0 | } catch (NumberFormatException e) { |
604 | 0 | throw new OpenException(e); |
605 | |
} finally { |
606 | 0 | try { |
607 | 0 | if (inputStream != null) { |
608 | 0 | inputStream.close(); |
609 | |
} |
610 | 0 | if (reader != null) { |
611 | 0 | reader.close(); |
612 | |
} |
613 | 0 | } catch (IOException e) { |
614 | |
|
615 | 0 | } |
616 | |
} |
617 | |
} |
618 | |
|
619 | |
|
620 | |
|
621 | |
|
622 | |
|
623 | |
|
624 | |
|
625 | |
protected String getVersion(String rootLine) { |
626 | |
String version; |
627 | 0 | int versionPos = rootLine.indexOf("version=\""); |
628 | 0 | if (versionPos > 0) { |
629 | 0 | int startPos = versionPos + 9; |
630 | 0 | int endPos = rootLine.indexOf("\"", startPos); |
631 | 0 | version = rootLine.substring(startPos, endPos); |
632 | 0 | } else { |
633 | 0 | version = "1"; |
634 | |
} |
635 | 0 | return version; |
636 | |
} |
637 | |
|
638 | |
|
639 | |
|
640 | |
|
641 | |
|
642 | |
|
643 | |
public boolean hasAnIcon() { |
644 | 0 | return true; |
645 | |
} |
646 | |
|
647 | |
|
648 | |
|
649 | |
|
650 | |
|
651 | |
|
652 | |
|
653 | |
class XmlFilterOutputStream extends FilterOutputStream { |
654 | |
|
655 | |
private CharsetDecoder decoder; |
656 | |
|
657 | 0 | private boolean headerProcessed = false; |
658 | |
|
659 | |
private static final int BUFFER_SIZE = 120; |
660 | |
|
661 | |
|
662 | |
|
663 | |
|
664 | |
|
665 | |
|
666 | |
|
667 | |
|
668 | |
|
669 | |
|
670 | |
|
671 | |
|
672 | |
|
673 | |
|
674 | |
|
675 | |
|
676 | |
|
677 | |
|
678 | 0 | private byte[] bytes = new byte[BUFFER_SIZE * 2]; |
679 | |
|
680 | 0 | private ByteBuffer outBB = ByteBuffer.wrap(bytes); |
681 | |
|
682 | 0 | private ByteBuffer inBB = ByteBuffer.wrap(bytes); |
683 | |
|
684 | |
|
685 | |
|
686 | 0 | private CharBuffer outCB = CharBuffer.allocate(BUFFER_SIZE); |
687 | |
|
688 | |
|
689 | |
|
690 | 0 | private final Pattern xmlDeclarationPattern = Pattern |
691 | |
.compile("\\s*<\\?xml.*\\?>\\s*(<!DOCTYPE.*>\\s*)?"); |
692 | |
|
693 | |
|
694 | |
|
695 | |
|
696 | |
|
697 | |
|
698 | |
|
699 | |
|
700 | |
public XmlFilterOutputStream(OutputStream outputStream, |
701 | |
String charsetName) { |
702 | 0 | this(outputStream, Charset.forName(charsetName)); |
703 | 0 | } |
704 | |
|
705 | |
|
706 | |
|
707 | |
|
708 | |
|
709 | |
|
710 | |
|
711 | 0 | public XmlFilterOutputStream(OutputStream outputStream, Charset charset) { |
712 | 0 | super(outputStream); |
713 | 0 | decoder = charset.newDecoder(); |
714 | 0 | decoder.onMalformedInput(CodingErrorAction.REPORT); |
715 | 0 | decoder.onUnmappableCharacter(CodingErrorAction.REPORT); |
716 | 0 | startEntry(); |
717 | 0 | } |
718 | |
|
719 | |
|
720 | |
|
721 | |
|
722 | |
public void startEntry() { |
723 | 0 | headerProcessed = false; |
724 | 0 | resetBuffers(); |
725 | 0 | } |
726 | |
|
727 | |
private void resetBuffers() { |
728 | 0 | inBB.limit(0); |
729 | 0 | outBB.position(0); |
730 | 0 | outCB.position(0); |
731 | 0 | } |
732 | |
|
733 | |
@Override |
734 | |
public void write(byte[] b, int off, int len) throws IOException { |
735 | 0 | if ((off | len | (b.length - (len + off)) | (off + len)) < 0) { |
736 | 0 | throw new IndexOutOfBoundsException(); |
737 | |
} |
738 | |
|
739 | 0 | if (headerProcessed) { |
740 | 0 | out.write(b, off, len); |
741 | |
} else { |
742 | |
|
743 | 0 | for (int i = 0; i < len; i++) { |
744 | 0 | write(b[off + i]); |
745 | |
} |
746 | |
} |
747 | |
|
748 | 0 | } |
749 | |
|
750 | |
@Override |
751 | |
public void write(int b) throws IOException { |
752 | |
|
753 | 0 | if (headerProcessed) { |
754 | 0 | out.write(b); |
755 | |
} else { |
756 | 0 | outBB.put((byte) b); |
757 | 0 | inBB.limit(outBB.position()); |
758 | |
|
759 | 0 | CoderResult result = decoder.decode(inBB, outCB, false); |
760 | 0 | if (result.isError()) { |
761 | 0 | throw new RuntimeException( |
762 | |
"Unknown character decoding error"); |
763 | |
} |
764 | |
|
765 | |
|
766 | |
|
767 | |
|
768 | 0 | if (outCB.position() == outCB.limit()) { |
769 | 0 | processHeader(); |
770 | |
} |
771 | |
|
772 | |
} |
773 | 0 | } |
774 | |
|
775 | |
private void processHeader() throws IOException { |
776 | 0 | headerProcessed = true; |
777 | 0 | outCB.position(0); |
778 | |
|
779 | 0 | Matcher matcher = xmlDeclarationPattern.matcher(outCB); |
780 | |
|
781 | 0 | String headerRemainder = matcher.replaceAll(""); |
782 | 0 | int index = headerRemainder.length() - 1; |
783 | 0 | if (headerRemainder.charAt(index) == '\0') { |
784 | |
|
785 | |
do { |
786 | 0 | index--; |
787 | 0 | } while (index >= 0 && headerRemainder.charAt(index) == '\0'); |
788 | 0 | headerRemainder = headerRemainder.substring(0, index + 1); |
789 | |
} |
790 | |
|
791 | |
|
792 | 0 | ByteBuffer bb = decoder.charset().encode(headerRemainder); |
793 | |
|
794 | |
|
795 | 0 | byte[] outBytes = new byte[bb.limit()]; |
796 | 0 | bb.get(outBytes); |
797 | 0 | out.write(outBytes, 0, outBytes.length); |
798 | |
|
799 | |
|
800 | |
|
801 | 0 | if (inBB.remaining() > 0) { |
802 | 0 | out.write(inBB.array(), inBB.position(), inBB.remaining()); |
803 | 0 | inBB.position(0); |
804 | 0 | inBB.limit(0); |
805 | |
} |
806 | 0 | } |
807 | |
|
808 | |
|
809 | |
|
810 | |
|
811 | |
|
812 | |
|
813 | |
@Override |
814 | |
public void close() throws IOException { |
815 | 0 | flush(); |
816 | 0 | } |
817 | |
|
818 | |
|
819 | |
|
820 | |
|
821 | |
|
822 | |
|
823 | |
public void reallyClose() throws IOException { |
824 | 0 | out.close(); |
825 | 0 | } |
826 | |
|
827 | |
|
828 | |
|
829 | |
|
830 | |
|
831 | |
|
832 | |
@Override |
833 | |
public void flush() throws IOException { |
834 | 0 | if (!headerProcessed) { |
835 | 0 | processHeader(); |
836 | |
} |
837 | 0 | out.flush(); |
838 | 0 | } |
839 | |
|
840 | |
} |
841 | |
} |