From 4ce90c776bb9eaa78ad38bc7b0c0f6ca9f5341fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Sun, 3 Dec 2023 22:59:17 +0100 Subject: [PATCH] Added FLA export - show export time Fixed #2136 FLA export - optimized Shape fixer speed, repeated shape on timeline not exported twice --- CHANGELOG.md | 3 + .../decompiler/flash/math/BezierEdge.java | 96 +++++--- .../decompiler/flash/math/Intersections.java | 44 +++- .../decompiler/flash/xfl/XFLConverter.java | 23 +- .../flash/xfl/shapefixer/ShapeFixer.java | 233 ++++++++++-------- .../jpexs/decompiler/flash/gui/MainPanel.java | 13 +- 6 files changed, 271 insertions(+), 141 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ac8e2d26..b040caa17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ All notable changes to this project will be documented in this file. - ExportAssets tag - show first item as description in the tree when there is only single item - [#2134] FLA Export - split main timeline into scenes when DefineSceneAndFrameLabelData tag is present - [#2132] Show and export streamed sound (SoundStreamHead/SoundStreamBlock) in frame ranges +- FLA export - show export time ### Fixed - [#2021], [#2000] Caret position in editors when using tabs and / or unicode @@ -66,6 +67,7 @@ All notable changes to this project will be documented in this file. - [#2133] Linux/Mac - ffdec.sh not correctly parsing java build number on javas without it - [#2135] FLA Export - framescripts handling when addFrameScript uses Multinames instead of QNames - [#1194] FLA Export - stream sound export +- [#2136] FLA export - optimized Shape fixer speed, repeated shape on timeline not exported twice ### Changed - [#2120] Exported assets no longer take names from assigned classes if there is more than 1 assigned class @@ -3339,6 +3341,7 @@ Major version of SWF to XML export changed to 2. [#2133]: https://www.free-decompiler.com/flash/issues/2133 [#2135]: https://www.free-decompiler.com/flash/issues/2135 [#1194]: https://www.free-decompiler.com/flash/issues/1194 +[#2136]: https://www.free-decompiler.com/flash/issues/2136 [#2120]: https://www.free-decompiler.com/flash/issues/2120 [#1130]: https://www.free-decompiler.com/flash/issues/1130 [#1220]: https://www.free-decompiler.com/flash/issues/1220 diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/math/BezierEdge.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/math/BezierEdge.java index cb18476fc..e1cffc59e 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/math/BezierEdge.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/math/BezierEdge.java @@ -17,10 +17,6 @@ package com.jpexs.decompiler.flash.math; import com.jpexs.helpers.Reference; -import java.awt.Shape; -import java.awt.geom.Area; -import java.awt.geom.GeneralPath; -import java.awt.geom.PathIterator; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.io.Serializable; @@ -38,36 +34,43 @@ import java.util.Stack; */ public class BezierEdge implements Serializable { - public List points = new ArrayList<>(); + public List points = new ArrayList<>(3); + + private List revPoints = new ArrayList<>(); + + private int hash; + + private int revHash; + + private Rectangle2D bbox; + private boolean empty; + public BezierEdge(List points) { this.points = points; + calcParams(); } - + @Override public BezierEdge clone() { return new BezierEdge(new ArrayList<>(points)); } public boolean isEmpty() { - Point2D p1 = getBeginPoint(); - for (int i = 1; i < points.size(); i++) { - if (!points.get(i).equals(p1)) { - return false; - } - } - return true; + return empty; } public BezierEdge(double x0, double y0, double x1, double y1) { points.add(new Point2D.Double(x0, y0)); points.add(new Point2D.Double(x1, y1)); + calcParams(); } public BezierEdge(double x0, double y0, double cx, double cy, double x1, double y1) { points.add(new Point2D.Double(x0, y0)); points.add(new Point2D.Double(cx, cy)); points.add(new Point2D.Double(x1, y1)); + calcParams(); } public Point2D getBeginPoint() { @@ -80,10 +83,12 @@ public class BezierEdge implements Serializable { public void setBeginPoint(Point2D p) { points.set(0, p); + calcParams(); } public void setEndPoint(Point2D p) { points.set(points.size() - 1, p); + calcParams(); } public Point2D pointAt(double t) { @@ -102,14 +107,12 @@ public class BezierEdge implements Serializable { return new Point2D.Double(x, y); } - public Rectangle2D bbox() { + private void calcParams() { double minX = Double.MAX_VALUE; double minY = Double.MAX_VALUE; double maxX = -Double.MAX_VALUE; double maxY = -Double.MAX_VALUE; - //System.err.println("calculating bbox"); for (Point2D p : points) { - //System.err.println(" point " + p); if (p.getX() < minX) { minX = p.getX(); } @@ -124,11 +127,29 @@ public class BezierEdge implements Serializable { } } - Rectangle2D b = new Rectangle2D.Double(minX, minY, maxX - minX, maxY - minY); - /*System.err.println(" bbox = "+b); - System.err.println(" maxx = "+maxX); - System.err.println(" maxy = "+maxY);*/ - return b; + + this.bbox = new Rectangle2D.Double(minX, minY, maxX - minX, maxY - minY); + this.hash = points.hashCode(); + + revPoints = new ArrayList<>(); + for (int i = points.size() - 1; i >= 0; i--) { + revPoints.add(points.get(i)); + } + this.revHash = revPoints.hashCode(); + + + empty = true; + Point2D p1 = getBeginPoint(); + for (int i = 1; i < points.size(); i++) { + if (!points.get(i).equals(p1)) { + empty = false; + break; + } + } + } + + public Rectangle2D bbox() { + return bbox; } private final double MIN_SIZE = 0.5; @@ -166,6 +187,9 @@ public class BezierEdge implements Serializable { } public List getIntersections(BezierEdge b2) { + if (!Intersections.rectIntersection(bbox, b2.bbox)) { + return new ArrayList<>(); + } if (points.size() == 2) { if (b2.points.size() == 2) { return Intersections.intersectLineLine(points.get(0), points.get(1), b2.points.get(0), b2.points.get(1), true); @@ -374,11 +398,7 @@ public class BezierEdge implements Serializable { right.setVal(new BezierEdge(rightPoints)); } - public BezierEdge reverse() { - List revPoints = new ArrayList<>(); - for (int i = points.size() - 1; i >= 0; i--) { - revPoints.add(points.get(i)); - } + public BezierEdge reverse() { return new BezierEdge(revPoints); } @@ -389,6 +409,7 @@ public class BezierEdge implements Serializable { Math.round(this.points.get(i).getY()) )); } + calcParams(); } public void roundHalf() { @@ -398,17 +419,18 @@ public class BezierEdge implements Serializable { Math.round(this.points.get(i).getY() * 2.0) / 2.0 )); } + calcParams(); } @Override public int hashCode() { int hash = 7; - hash = 41 * hash + Objects.hashCode(this.points); + hash = 41 * hash + this.hash; return hash; } @Override - public boolean equals(Object obj) { + public boolean equals(Object obj) { if (this == obj) { return true; } @@ -419,8 +441,26 @@ public class BezierEdge implements Serializable { return false; } final BezierEdge other = (BezierEdge) obj; + if (other.hash != hash) { + return false; + } return Objects.equals(this.points, other.points); } + + public boolean equalsReverse(BezierEdge other) { + if (hash != other.revHash) { + return false; + } + if (other.points.size() != points.size()) { + return false; + } + for (int i = 0; i < points.size(); i++) { + if (!points.get(i).equals(other.points.get(points.size() - 1 - i))) { + return false; + } + } + return true; + } public static void main(String[] args) { List t1 = new ArrayList<>(); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/math/Intersections.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/math/Intersections.java index 3d7055138..7fc6315d2 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/math/Intersections.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/math/Intersections.java @@ -17,6 +17,7 @@ package com.jpexs.decompiler.flash.math; import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -165,7 +166,48 @@ public class Intersections { return intersectPolylinePolyline(a, b); } - public static List intersectBezier2Bezier2(Point2D a1, Point2D a2, Point2D a3, Point2D b1, Point2D b2, Point2D b3) { + + public static boolean rectIntersection(Rectangle2D r1, Rectangle2D r2) { + double xmin = Math.max(r1.getX(), r2.getX()); + double xmax1 = r1.getX() + r1.getWidth(); + double xmax2 = r2.getX() + r2.getWidth(); + double xmax = Math.min(xmax1, xmax2); + if (Double.compare(xmax, xmin) >= 0) { + double ymin = Math.max(r1.getY(), r2.getY()); + double ymax1 = r1.getY() + r1.getHeight(); + double ymax2 = r2.getY() + r2.getHeight(); + double ymax = Math.min(ymax1, ymax2); + if (Double.compare(ymax, ymin) >= 0) { + return true; + } + } + return false; + } + + public static Rectangle2D getBBox(Point2D ...points) { + double minX = Double.MAX_VALUE; + double minY = Double.MAX_VALUE; + double maxX = -Double.MAX_VALUE; + double maxY = -Double.MAX_VALUE; + for (Point2D p : points) { + if (p.getX() < minX) { + minX = p.getX(); + } + if (p.getX() > maxX) { + maxX = p.getX(); + } + if (p.getY() < minY) { + minY = p.getY(); + } + if (p.getY() > maxY) { + maxY = p.getY(); + } + } + + return new Rectangle2D.Double(minX, minY, maxX - minX, maxY - minY); + } + + public static List intersectBezier2Bezier2(Point2D a1, Point2D a2, Point2D a3, Point2D b1, Point2D b2, Point2D b3) { Point2D pa; Point2D pb; List result = new ArrayList<>(); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/XFLConverter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/XFLConverter.java index 20c872c01..4a957607b 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/XFLConverter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/XFLConverter.java @@ -1846,13 +1846,21 @@ public class XFLConverter { int characterId = character.getCharacterId(); if ((character instanceof ShapeTag) && (nonLibraryShapes.contains(characterId))) { ShapeTag shape = (ShapeTag) character; + statusStack.pushStatus(character.toString()); convertShape(swf, characters, matrix, shape.getShapeNum(), shape.getShapes().shapeRecords, shape.getShapes().fillStyles, shape.getShapes().lineStyles, false, false, recCharWriter); - } else if (character instanceof TextTag) { + statusStack.popStatus(); + } else if (character instanceof TextTag) { + statusStack.pushStatus(character.toString()); convertText(null, (TextTag) character, matrix, filters, null, recCharWriter); + statusStack.popStatus(); } else if (character instanceof DefineVideoStreamTag) { + statusStack.pushStatus(character.toString()); convertVideoInstance(null, matrix, (DefineVideoStreamTag) character, null, recCharWriter); + statusStack.popStatus(); } else if (character instanceof ImageTag) { + statusStack.pushStatus(character.toString()); convertImageInstance(null, matrix, (ImageTag) character, null, recCharWriter); + statusStack.popStatus(); } else { convertSymbolInstance(null, matrix, colorTransformAlpha, false, blendMode, filters, true, null, null, null, characters.get(rec.characterId), characters, tags, flaVersion, recCharWriter); } @@ -2718,9 +2726,16 @@ public class XFLConverter { convertSymbolInstance(instanceName, standaloneShapeTweenerMatrix, colorTransForm, cacheAsBitmap, blendMode, filters, isVisible, backGroundColor, clipActions, metadata, standaloneShapeTweener, characters, tags, flaVersion, elementsWriter); standaloneShapeTweener = null; } else if ((character instanceof ShapeTag) && (nonLibraryShapes.contains(characterId))) { // || shapeTweener != null)) { - ShapeTag shape = (ShapeTag) character; - convertShape(swf, characters, matrix, shape.getShapeNum(), shape.getShapes().shapeRecords, shape.getShapes().fillStyles, shape.getShapes().lineStyles, false, false, elementsWriter); - + if (lastCharacter == character && Objects.equals(matrix, lastMatrix)) { + elementsWriter.writeCharactersRaw(lastElements); + } else { + ShapeTag shape = (ShapeTag) character; + statusStack.pushStatus(character.toString()); + convertShape(swf, characters, matrix, shape.getShapeNum(), shape.getShapes().shapeRecords, shape.getShapes().fillStyles, shape.getShapes().lineStyles, false, false, elementsWriter); + statusStack.popStatus(); + } + lastCharacter = character; + lastMatrix = matrix; shapeTween = false; shapeTweener = null; } else if (character != null) { diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/shapefixer/ShapeFixer.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/shapefixer/ShapeFixer.java index 0387a0eac..cabd01a49 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/shapefixer/ShapeFixer.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/shapefixer/ShapeFixer.java @@ -186,9 +186,24 @@ public class ShapeFixer { //------------------- detecting overlapping edges -------------------- List splittedPairs = new ArrayList<>(); + //long t1 = System.currentTimeMillis(); + //int oldpct = 0; loopi1: for (int i1 = 0; i1 < shapes.size(); i1++) { layer = layers.get(i1); + /*if (i1 % 10 == 0) { + int pct = i1 * 100 / shapes.size(); + if (oldpct != pct) { + //System.err.println("pct: " + pct + "%"); + if (pct == 10) { + long t2 = System.currentTimeMillis(); + long dt = t2 - t1; + System.err.println("Time per 10%: " + Helper.formatTimeSec(dt)); + } + } + oldpct = pct; + }*/ + loopj1: for (int j1 = 0; j1 < shapes.get(i1).size(); j1++) { BezierEdge be1 = shapes.get(i1).get(j1); @@ -222,7 +237,7 @@ public class ShapeFixer { } //duplicated edge - if (be1.equals(be2) || be1.equals(be2.reverse())) { + if (be1.equals(be2) || be1.equalsReverse(be2)) { shapes.get(i2).remove(j2); if (i1 == i2 && j1 > j2) { j1--; @@ -248,128 +263,136 @@ public class ShapeFixer { } boolean isint = be1.intersects(be2, t1Ref, t2Ref, intPoints); - if (DEBUG_PRINT) { - //System.err.println(" " + isint); + if (!isint) { + continue; } - if (!t1Ref.isEmpty()) { + if (t1Ref.isEmpty()) { + continue; + } - if ((be1.getBeginPoint().equals(be2.getBeginPoint()) - || be1.getBeginPoint().equals(be2.getEndPoint()) - || be1.getEndPoint().equals(be2.getBeginPoint()) - || be1.getEndPoint().equals(be2.getEndPoint())) && (t1Ref.size() == 1)) { + if ((be1.getBeginPoint().equals(be2.getBeginPoint()) + || be1.getBeginPoint().equals(be2.getEndPoint()) + || be1.getEndPoint().equals(be2.getBeginPoint()) + || be1.getEndPoint().equals(be2.getEndPoint())) && (t1Ref.size() == 1)) { + continue; + } + + if (t1Ref.size() == 2) { + if ((t1Ref.get(0) == 0 || t1Ref.get(0) == 1) + && (t2Ref.get(0) == 0 || t2Ref.get(0) == 1)) { continue; + } + } + + if (DEBUG_PRINT) { + System.err.println("intersects " + be1.toSvg() + " " + be2.toSvg()); + System.err.println(" fillstyle0: " + fillStyles0.get(i1) + " , " + fillStyles0.get(i2)); + System.err.println(" fillstyle1: " + fillStyles1.get(i1) + " , " + fillStyles1.get(i2)); + System.err.println(" linestyle: " + lineStyles.get(i1) + " , " + lineStyles.get(i2)); + + for (int n = 0; n < t1Ref.size(); n++) { + System.err.println("- " + t1Ref.get(n) + " , " + t2Ref.get(n) + " : " + intPoints.get(n)); } + } - if (DEBUG_PRINT) { - System.err.println("intersects " + be1.toSvg() + " " + be2.toSvg()); - System.err.println(" fillstyle0: " + fillStyles0.get(i1) + " , " + fillStyles0.get(i2)); - System.err.println(" fillstyle1: " + fillStyles1.get(i1) + " , " + fillStyles1.get(i2)); - System.err.println(" linestyle: " + lineStyles.get(i1) + " , " + lineStyles.get(i2)); + if ((t1Ref.size() == 2) && !((t1Ref.get(0) == 0 || t1Ref.get(0) == 1) + && (t2Ref.get(0) == 0 || t2Ref.get(0) == 1))) { + t1Ref.add(0, t1Ref.remove(1)); + t2Ref.add(0, t2Ref.remove(1)); + intPoints.add(0, intPoints.remove(1)); + } - for (int n = 0; n < t1Ref.size(); n++) { - System.err.println("- " + t1Ref.get(n) + " , " + t2Ref.get(n) + " : " + intPoints.get(n)); - } - } + int splitPointIndex = 0; + if (intPoints.size() > 1) { + splitPointIndex = 1; + } - if ((t1Ref.size() == 2) && !((t1Ref.get(0) == 0 || t1Ref.get(0) == 1) - && (t2Ref.get(0) == 0 || t2Ref.get(0) == 1))) { - t1Ref.add(0, t1Ref.remove(1)); - t2Ref.add(0, t2Ref.remove(1)); - intPoints.add(0, intPoints.remove(1)); - } + splittedPairs.add(pair); - int splitPointIndex = 0; - if (intPoints.size() > 1) { - splitPointIndex = 1; - } + Reference be1LRef = new Reference<>(null); + Reference be1RRef = new Reference<>(null); + be1.split(t1Ref.get(splitPointIndex), be1LRef, be1RRef); + Reference be2LRef = new Reference<>(null); + Reference be2RRef = new Reference<>(null); + be2.split(t2Ref.get(splitPointIndex), be2LRef, be2RRef); - splittedPairs.add(pair); + BezierEdge be1L = be1LRef.getVal(); + BezierEdge be1R = be1RRef.getVal(); + BezierEdge be2L = be2LRef.getVal(); + BezierEdge be2R = be2RRef.getVal(); - Reference be1LRef = new Reference<>(null); - Reference be1RRef = new Reference<>(null); - be1.split(t1Ref.get(splitPointIndex), be1LRef, be1RRef); - Reference be2LRef = new Reference<>(null); - Reference be2RRef = new Reference<>(null); - be2.split(t2Ref.get(splitPointIndex), be2LRef, be2RRef); + Point2D intP = intPoints.get(splitPointIndex); - BezierEdge be1L = be1LRef.getVal(); - BezierEdge be1R = be1RRef.getVal(); - BezierEdge be2L = be2LRef.getVal(); - BezierEdge be2R = be2RRef.getVal(); + be1L.setEndPoint(intP); + be1R.setBeginPoint(intP); + be2L.setEndPoint(intP); + be2R.setBeginPoint(intP); - Point2D intP = intPoints.get(splitPointIndex); - - be1L.setEndPoint(intP); - be1R.setBeginPoint(intP); - be2L.setEndPoint(intP); - be2R.setBeginPoint(intP); - - be1L.roundHalf(); - be1R.roundHalf(); - be2L.roundHalf(); - be2R.roundHalf(); - if (i1 == i2) { - if (j1 < j2) { - shapes.get(i1).remove(j2); - shapes.get(i2).remove(j1); - j2--; - } else { - shapes.get(i1).remove(j1); - shapes.get(i2).remove(j2); - j1--; - } + be1L.roundHalf(); + be1R.roundHalf(); + be2L.roundHalf(); + be2R.roundHalf(); + if (i1 == i2) { + if (j1 < j2) { + shapes.get(i1).remove(j2); + shapes.get(i2).remove(j1); + j2--; } else { shapes.get(i1).remove(j1); shapes.get(i2).remove(j2); + j1--; } - int n1 = j1; - int n2 = j2; - - if (!be1L.isEmpty()) { - shapes.get(i1).add(n1, be1L); - if (DEBUG_PRINT) { - System.err.println("added " + be1L.toSvg() + " to j1=" + n1); - } - if (i1 == i2 && n2 >= n1) { - n2++; - } - n1++; - - } - - if (!be1R.isEmpty()) { - shapes.get(i1).add(n1, be1R); - if (DEBUG_PRINT) { - System.err.println("added " + be1R.toSvg() + " to j1=" + n1); - } - if (i1 == i2 && n2 >= n1) { - n2++; - } - n1++; - - } - - if (!be2L.isEmpty()) { - shapes.get(i2).add(n2, be2L); - if (DEBUG_PRINT) { - System.err.println("added " + be2L.toSvg() + " to j2=" + n2); - } - n2++; - - } - - if (!be2R.isEmpty()) { - shapes.get(i2).add(n2, be2R); - if (DEBUG_PRINT) { - System.err.println("added " + be2R.toSvg() + " to j2=" + n2); - } - n2++; - } - - j1--; - continue loopj1; + } else { + shapes.get(i1).remove(j1); + shapes.get(i2).remove(j2); } + int n1 = j1; + int n2 = j2; + + if (!be1L.isEmpty()) { + shapes.get(i1).add(n1, be1L); + if (DEBUG_PRINT) { + System.err.println("added " + be1L.toSvg() + " to j1=" + n1); + } + if (i1 == i2 && n2 >= n1) { + n2++; + } + n1++; + + } + + if (!be1R.isEmpty()) { + shapes.get(i1).add(n1, be1R); + if (DEBUG_PRINT) { + System.err.println("added " + be1R.toSvg() + " to j1=" + n1); + } + if (i1 == i2 && n2 >= n1) { + n2++; + } + n1++; + + } + + if (!be2L.isEmpty()) { + shapes.get(i2).add(n2, be2L); + if (DEBUG_PRINT) { + System.err.println("added " + be2L.toSvg() + " to j2=" + n2); + } + n2++; + + } + + if (!be2R.isEmpty()) { + shapes.get(i2).add(n2, be2R); + if (DEBUG_PRINT) { + System.err.println("added " + be2R.toSvg() + " to j2=" + n2); + } + n2++; + } + + j1--; + continue loopj1; } } } diff --git a/src/com/jpexs/decompiler/flash/gui/MainPanel.java b/src/com/jpexs/decompiler/flash/gui/MainPanel.java index 6adc89523..1a237d98c 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/MainPanel.java @@ -3402,6 +3402,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se path += compressed ? ".fla" : ".xfl"; final FLAVersion selectedVersion = versions.get(compressed ? flaFilters.indexOf(selectedFilter) : xflFilters.indexOf(selectedFilter)); final File selfile = new File(path); + long timeBefore = System.currentTimeMillis(); new CancellableWorker() { @Override protected Void doInBackground() throws Exception { @@ -3442,15 +3443,21 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se @Override protected void done() { + Main.stopWork(); + long timeAfter = System.currentTimeMillis(); + final long timeMs = timeAfter - timeBefore; + + View.execInEventDispatch(() -> { + setStatus(translate("export.finishedin").replace("%time%", Helper.formatTimeSec(timeMs))); + }); + if (Configuration.openFolderAfterFlaExport.get()) { try { Desktop.getDesktop().open(selfile.getAbsoluteFile().getParentFile()); } catch (IOException ex) { logger.log(Level.SEVERE, null, ex); } - } - - Main.stopWork(); + } } }.execute(); }