From 0b6be5ae781fa1e4fb551f4fa65bd146db0f3b69 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Thu, 22 Oct 2026 21:48:39 +0300 Subject: [PATCH 01/10] Desktop compatibility runtimes: Swing, SwingX, JavaFX and FXML over Codename One swing-compat (java.awt, javax.swing, java.beans, javax.imageio and a SwingX subset) is authored already relocated under com.codename1.desktopcompat; javafx-compat is authored under the javafx names and relocated at build time. desktop-compat-runtime holds what both layers share, including shims for the host-backed JDK APIs a device lacks (java.io.File, java.net.URL, java.nio.file, ResourceBundle, java.util.logging, java.util.prefs). fxml-compiler turns FXML and JavaFX CSS into code and tables at build time. Co-Authored-By: Claude Opus 5.5 --- CodenameOne/src/com/codename1/ui/Image.java | 7 + .../com/codename1/ui/geom/GeneralPath.java | 19 +- .../com/codename1/impl/javase/JavaSEPort.java | 57 +- .../nativeSources/cn1_linux_desktopwindow.c | 130 + .../LinuxPort/nativeSources/cn1_linux_edit.c | 40 +- .../LinuxPort/nativeSources/cn1_linux_image.c | 153 + Ports/LinuxPort/nativeSources/cn1_linux_io.c | 26 + .../LinuxPort/nativeSources/cn1_linux_text.c | 59 +- .../nativeSources/cn1_linux_window.c | 51 +- .../impl/linux/LinuxImplementation.java | 398 ++- .../com/codename1/impl/linux/LinuxNative.java | 31 + .../impl/linux/LinuxWindowManager.java | 2 +- Ports/WindowsPort/nativeSources/cn1_windows.h | 7 + .../nativeSources/cn1_windows_graphics.cpp | 59 +- .../impl/windows/WindowsImplementation.java | 11 +- maven/android-compat/pom.xml | 7 + .../main/java/android/content/Context.java | 29 +- .../src/main/java/android/net/Uri.java | 9 +- .../androidcompat/jdk/BufferedReader.java | 55 + .../android/animation/AnimatorMathTest.java | 2 +- .../animation/AutoCancelReleaseTest.java | 2 +- .../ObjectAnimatorSetPropertyTest.java | 2 +- .../ReverseRepeatedAnimatorTest.java | 2 +- .../android/app/ActivityBoundaryTest.java | 2 +- .../android/app/DialogDefaultTouchTest.java | 2 +- .../app/FragmentLauncherRecreationTest.java | 2 +- .../java/android/app/FragmentManagerTest.java | 2 +- .../android/app/FragmentViewStateTest.java | 2 +- .../java/android/app/PauseSelfFinishTest.java | 2 +- .../android/app/ResultAfterRelaunchTest.java | 2 +- .../android/app/ResumeConfigurationTest.java | 2 +- .../android/app/SupportFragmentsTest.java | 2 +- .../android/app/WindowKeepScreenOnTest.java | 4 +- .../content/BroadcastFilterMatchTest.java | 2 +- .../content/ContentResolverFileUriTest.java | 4 +- .../android/content/ContentValuesTest.java | 2 +- .../android/content/ContextFileNameTest.java | 2 +- .../content/IntentCloneFilterTest.java | 2 +- .../content/IntentResolveActivityTest.java | 2 +- .../java/android/database/CursorsTest.java | 2 +- .../database/sqlite/SQLiteDatabaseTest.java | 2 +- .../sqlite/SQLiteQueryBuilderTest.java | 2 +- .../graphics/BitmapCompressFormatTest.java | 4 +- .../graphics/BitmapDerivedDensityTest.java | 4 +- .../BitmapDisplayMetricsDensityTest.java | 2 +- .../graphics/BitmapFactoryBoundsTest.java | 4 +- .../BitmapFactoryHeaderBoundsTest.java | 4 +- .../graphics/BitmapFactoryMimeTypeTest.java | 2 +- .../BitmapFactoryUnscaledDensityTest.java | 4 +- .../graphics/BitmapImmutableWriteTest.java | 4 +- .../android/graphics/BitmapMatrixTest.java | 4 +- .../graphics/BitmapMutableSnapshotTest.java | 4 +- .../graphics/BitmapPixelBoundsTest.java | 4 +- .../graphics/CanvasBitmapPixelCacheTest.java | 4 +- .../android/graphics/CanvasClipPathTest.java | 4 +- .../CanvasClipRestoreAndClearTest.java | 4 +- .../graphics/CanvasDrawPaintShaderTest.java | 4 +- .../graphics/CanvasImmutableBitmapTest.java | 2 +- .../graphics/CanvasRecycledBitmapTest.java | 2 +- .../graphics/CanvasRotatedClipRectTest.java | 4 +- .../android/graphics/CanvasSaveLayerTest.java | 4 +- .../graphics/CanvasSpacedSurrogateTest.java | 4 +- .../graphics/CanvasTextScaleXTest.java | 4 +- .../graphics/LetterSpacingMeasureTest.java | 4 +- .../graphics/LetterSpacingWidthsTest.java | 2 +- .../BitmapDrawableTargetDensityTest.java | 4 +- .../drawable/BitmapDrawableTileModeTest.java | 4 +- .../GradientDrawableOrientationTest.java | 4 +- .../drawable/StateListTintModeTest.java | 2 +- .../drawable/WrapperChildStateTest.java | 2 +- .../java/android/net/UriFromFileTest.java | 25 +- .../android/net/UriNormalizeSchemeTest.java | 2 +- .../java/android/os/BundleDeepCopyTest.java | 2 +- .../android/os/BundleEmptyImmutableTest.java | 2 +- .../android/os/HandlerFrontOfQueueTest.java | 2 +- .../java/android/text/AllCapsSpansTest.java | 2 +- .../text/LengthFilterSurrogateTest.java | 2 +- .../text/TextUtilsCommaEllipsizeTest.java | 2 +- .../text/TextUtilsEllipsizeSpansTest.java | 2 +- .../android/text/TextUtilsSplitRegexTest.java | 2 +- .../view/AncestorChangeDispatchTest.java | 2 +- .../android/view/BackgroundTintModeTest.java | 2 +- .../view/DrawableLayoutDirectionTest.java | 2 +- .../view/HiddenViewLosesFocusTest.java | 2 +- .../java/android/view/IncludeThemeTest.java | 2 +- .../view/MergedIncludeFinishInflateTest.java | 2 +- .../android/view/RemovedTouchTargetTest.java | 2 +- .../view/TransformedChildTouchTest.java | 2 +- .../java/android/view/ViewCallbacksTest.java | 2 +- .../android/view/ViewStubReferenceTest.java | 2 +- .../java/android/view/ViewTreeScrollTest.java | 2 +- .../view/VisibleRectAndKeepScreenOnTest.java | 4 +- .../view/animation/AnimationCloneTest.java | 2 +- .../view/animation/AnimationTimingTest.java | 2 +- .../webkit/WebViewHistoryDistanceTest.java | 2 +- .../android/webkit/WebViewHistoryUrlTest.java | 2 +- .../AbsListViewSelectionAndStackTest.java | 2 +- .../DatePickerRangeClampNotifiesTest.java | 2 +- .../widget/DatePickerSavedStateTest.java | 2 +- .../widget/EditTextEditableWatcherTest.java | 2 +- .../android/widget/EditTextSelectionTest.java | 2 +- .../android/widget/ForegroundGravityTest.java | 2 +- .../widget/FormWidgetSavedStateTest.java | 2 +- .../widget/GridViewUniformSpacingTest.java | 2 +- .../widget/ImageViewClearColorModTest.java | 2 +- .../ImageViewMeasureInputsRelayoutTest.java | 2 +- .../widget/LinearLayoutDividersTest.java | 2 +- .../ListCheckedRowsAfterDataChangeTest.java | 2 +- .../widget/ListViewDragOffClickTest.java | 2 +- .../ProgressBarIndeterminateDrawableTest.java | 2 +- .../widget/ProgressBarRangeClampTest.java | 2 +- .../widget/ProgressDrawableOwnershipTest.java | 2 +- .../RadioGroupAndFilteredAdapterTest.java | 2 +- .../android/widget/RowStateNotSavedTest.java | 2 +- .../widget/ScrollViewChildMarginsTest.java | 2 +- .../SeekBarInScrollingContainerTest.java | 2 +- .../widget/SimpleAdapterBindingHooksTest.java | 2 +- .../widget/SimpleAdapterFilterTest.java | 2 +- .../widget/SpinnerSelectedRowIdTest.java | 2 +- .../widget/TextViewAppendSpansTest.java | 2 +- .../widget/TextViewDefaultAppearanceTest.java | 2 +- .../widget/TextViewEllipsizeModeTest.java | 2 +- .../widget/TextViewFilterAndWatcherTest.java | 2 +- .../widget/TextViewNormalSnapshotTest.java | 2 +- .../widget/TextViewRelativeDrawablesTest.java | 2 +- .../widget/TextViewSpannableBufferTest.java | 2 +- .../widget/TextViewTransitionTest.java | 2 +- .../widget/TimePickerNullLegacyTimeTest.java | 2 +- .../widget/ViewAnimatorRemovalTest.java | 2 +- .../ComponentActivitySaveLifecycleTest.java | 2 +- .../GalleryLauncherUnregisterTest.java | 4 +- .../GalleryResultAfterRecreateTest.java | 2 +- .../activity/OnBackPressedDispatcherTest.java | 2 +- .../activity/SynchronousResultPostedTest.java | 2 +- .../widget/ConstraintLayoutTest.java | 2 +- .../lifecycle/LifecycleRegistryTest.java | 2 +- .../java/androidx/lifecycle/LiveDataTest.java | 2 +- .../lifecycle/MediatorReactivationTest.java | 2 +- .../recyclerview/widget/DiffUtilTest.java | 2 +- .../recyclerview/widget/GridRowSizeTest.java | 2 +- .../jdk/BufferedReaderMarkTest.java | 79 - .../jdk/BufferedStreamsClosedTest.java | 98 - .../jdk/BufferedWriterClosedTest.java | 87 - .../jdk/FileRenameFailedDeleteTest.java | 78 - .../jdk/FileRenameOntoItselfTest.java | 71 - .../jdk/PrintWriterClosedTest.java | 82 - .../runtime/DrawableForDensityTest.java | 4 +- .../androidcompat/runtime/FontCacheTest.java | 2 +- .../runtime/LocaleQualifierTest.java | 2 +- .../runtime/MenuXmlOnClickTest.java | 2 +- .../runtime/ResTableRoundTripTest.java | 2 +- .../ResourceSelectionRegressionTest.java | 2 +- .../runtime/ScriptQualifierTest.java | 2 +- .../runtime/ShareStreamTest.java | 2 +- .../SharedPreferencesApplyDeferredTest.java | 2 +- .../SharedPreferencesClearListenerTest.java | 2 +- .../SharedPreferencesCommitFailureTest.java | 2 +- .../SharedPreferencesEditOrderTest.java | 2 +- .../SharedPreferencesEditorReuseTest.java | 2 +- .../SharedPreferencesStringTypeTest.java | 2 +- .../SharedPreferencesUnchangedValueTest.java | 2 +- .../SharedPreferencesWeakListenerTest.java | 2 +- .../runtime/XmlPullParserTest.java | 2 +- .../testing/AndroidTestSupport.java | 1 + .../testing/HeadlessImplementation.java | 724 ----- .../material/MaterialComponentsTest.java | 2 +- maven/compat-testing/pom.xml | 102 + .../testing/HeadlessImplementation.java | 1522 +++++++++ .../compat/testing/JdkResourceSupport.java | 170 + .../compat}/testing/MainThreadRule.java | 6 +- .../compat/testing/ReferenceJdk.java | 54 + .../codename1/impl/ImplementationFactory.java | 2 +- maven/desktop-compat-runtime/pom.xml | 127 + .../spotbugs-exclude.xml | 58 + .../shared/CompatRegistryBase.java | 105 + .../desktopcompat/shared/ResourceNames.java | 177 ++ .../desktopcompat/shared/Resources.java | 350 +++ .../desktopcompat/shared/RuntimeExec.java | 137 + .../shared/UncaughtExceptions.java | 85 + .../shared/jdk/java/io/File.java | 767 +++++ .../shared/jdk/java/io/FileFilter.java | 32 + .../shared/jdk/java/io/FileInputStream.java | 119 + .../shared/jdk/java/io/FileOutputStream.java | 130 + .../shared/jdk/java/io/FileReader.java | 62 + .../shared/jdk/java/io/FileWriter.java | 73 + .../shared/jdk/java/io/FilenameFilter.java | 32 + .../shared/jdk/java/io/Host.java | 290 ++ .../shared/jdk/java/io/package-info.java | 26 + .../shared/jdk/java/net/URL.java | 762 +++++ .../shared/jdk/java/net/package-info.java | 27 + .../java/nio/file/AccessDeniedException.java | 36 + .../shared/jdk/java/nio/file/CopyOption.java | 28 + .../nio/file/DirectoryIteratorException.java | 49 + .../nio/file/DirectoryNotEmptyException.java | 32 + .../jdk/java/nio/file/DirectoryStream.java | 38 + .../nio/file/FileAlreadyExistsException.java | 36 + .../java/nio/file/FileSystemException.java | 75 + .../jdk/java/nio/file/FileVisitOption.java | 29 + .../jdk/java/nio/file/FileVisitResult.java | 32 + .../shared/jdk/java/nio/file/FileVisitor.java | 39 + .../shared/jdk/java/nio/file/Files.java | 1104 +++++++ .../shared/jdk/java/nio/file/Glob.java | 132 + .../java/nio/file/InvalidPathException.java | 69 + .../shared/jdk/java/nio/file/LinkOption.java | 30 + .../java/nio/file/NoSuchFileException.java | 36 + .../java/nio/file/NotDirectoryException.java | 32 + .../shared/jdk/java/nio/file/OpenOption.java | 28 + .../shared/jdk/java/nio/file/Path.java | 112 + .../shared/jdk/java/nio/file/PathImpl.java | 515 ++++ .../shared/jdk/java/nio/file/Paths.java | 40 + .../jdk/java/nio/file/SimpleFileVisitor.java | 69 + .../jdk/java/nio/file/StandardCopyOption.java | 32 + .../jdk/java/nio/file/StandardOpenOption.java | 42 + .../file/attribute/BasicFileAttributes.java | 49 + .../nio/file/attribute/FileAttribute.java | 33 + .../jdk/java/nio/file/attribute/FileTime.java | 139 + .../java/nio/file/attribute/package-info.java | 24 + .../jdk/java/nio/file/package-info.java | 30 + .../jdk/java/util/ListResourceBundle.java | 76 + .../jdk/java/util/PropertyResourceBundle.java | 244 ++ .../shared/jdk/java/util/ResourceBundle.java | 315 ++ .../shared/jdk/java/util/logging/Level.java | 125 + .../shared/jdk/java/util/logging/Logger.java | 309 ++ .../util/prefs/BackingStoreException.java | 39 + .../jdk/java/util/prefs/Preferences.java | 526 ++++ .../desktopcompat/shared/package-info.java | 32 + maven/fxml-compiler/pom.xml | 197 ++ maven/fxml-compiler/spotbugs-exclude.xml | 58 + .../java/com/codename1/fxml/ClassModel.java | 385 +++ .../java/com/codename1/fxml/CssCompiler.java | 361 +++ .../fxml/DesktopResourceCompiler.java | 287 ++ .../com/codename1/fxml/FxmlClassCompiler.java | 364 +++ .../java/com/codename1/fxml/FxmlCompiler.java | 1296 ++++++++ .../codename1/fxml/FxmlDispatchGenerator.java | 754 +++++ .../java/com/codename1/fxml/FxmlDocument.java | 253 ++ .../java/com/codename1/fxml/Messages.java | 97 + .../com/codename1/fxml/css/package-info.java | 32 + .../java/com/codename1/fxml/package-info.java | 40 + .../test/java/com/codename1/fxml/CssTest.java | 731 +++++ .../com/codename1/fxml/FxmlCompilerTest.java | 791 +++++ .../java/com/codename1/fxml/FxmlHarness.java | 335 ++ maven/javafx-compat/pom.xml | 131 + maven/javafx-compat/spotbugs-exclude.xml | 275 ++ .../com/codename1/fxcompat/FxInterop.java | 125 + .../com/codename1/fxcompat/package-info.java | 28 + .../fxcompat/runtime/ArrayObservableList.java | 294 ++ .../runtime/BidirectionalBinding.java | 184 ++ .../fxcompat/runtime/BindingObserver.java | 61 + .../codename1/fxcompat/runtime/Changes.java | 220 ++ .../runtime/ConstantObservableList.java | 100 + .../fxcompat/runtime/ContentBindings.java | 338 ++ .../codename1/fxcompat/runtime/CssEngine.java | 1204 ++++++++ .../codename1/fxcompat/runtime/CssSheet.java | 252 ++ .../fxcompat/runtime/Diagnostics.java | 36 + .../com/codename1/fxcompat/runtime/Dirty.java | 57 + .../codename1/fxcompat/runtime/Effects.java | 400 +++ .../runtime/EventDispatchChainImpl.java | 79 + .../fxcompat/runtime/EventHandlerManager.java | 235 ++ .../fxcompat/runtime/FilePicker.java | 106 + .../codename1/fxcompat/runtime/FontFiles.java | 438 +++ .../fxcompat/runtime/FontStyles.java | 182 ++ .../com/codename1/fxcompat/runtime/Fonts.java | 231 ++ .../fxcompat/runtime/FrameClock.java | 181 ++ .../codename1/fxcompat/runtime/FxBoolean.java | 44 + .../codename1/fxcompat/runtime/FxDouble.java | 44 + .../fxcompat/runtime/FxLifecycle.java | 153 + .../codename1/fxcompat/runtime/FxObject.java | 44 + .../codename1/fxcompat/runtime/FxPath.java | 833 +++++ .../codename1/fxcompat/runtime/FxPeer.java | 32 + .../codename1/fxcompat/runtime/FxString.java | 44 + .../fxcompat/runtime/FxmlContext.java | 331 ++ .../fxcompat/runtime/FxmlDispatch.java | 121 + .../fxcompat/runtime/FxmlRegistry.java | 44 + .../fxcompat/runtime/GradientRaster.java | 228 ++ .../codename1/fxcompat/runtime/HostCore.java | 322 ++ .../fxcompat/runtime/ListChangeBuilder.java | 404 +++ .../fxcompat/runtime/ListenerSet.java | 142 + .../codename1/fxcompat/runtime/Matrix2D.java | 119 + .../codename1/fxcompat/runtime/Mnemonics.java | 102 + .../codename1/fxcompat/runtime/NodePeer.java | 103 + .../runtime/ObservableMapWrapper.java | 221 ++ .../runtime/ObservableSetWrapper.java | 138 + .../fxcompat/runtime/ParentPeer.java | 383 +++ .../codename1/fxcompat/runtime/PeerPaint.java | 310 ++ .../fxcompat/runtime/PropertyAccess.java | 82 + .../fxcompat/runtime/PropertyRegistry.java | 44 + .../fxcompat/runtime/PropertyText.java | 59 + .../codename1/fxcompat/runtime/Renderer.java | 774 +++++ .../fxcompat/runtime/ResourceUrls.java | 211 ++ .../fxcompat/runtime/SceneEmbed.java | 190 ++ .../codename1/fxcompat/runtime/SceneHost.java | 37 + .../fxcompat/runtime/SceneInput.java | 517 ++++ .../fxcompat/runtime/StageDialog.java | 196 ++ .../codename1/fxcompat/runtime/StageForm.java | 356 +++ .../codename1/fxcompat/runtime/StageHost.java | 51 + .../fxcompat/runtime/StageHosts.java | 105 + .../fxcompat/runtime/StagePopup.java | 396 +++ .../fxcompat/runtime/StageWindow.java | 269 ++ .../fxcompat/runtime/StyleEngine.java | 130 + .../codename1/fxcompat/runtime/StyleList.java | 109 + .../fxcompat/runtime/StyleTarget.java | 126 + .../codename1/fxcompat/runtime/SvgPath.java | 268 ++ .../com/codename1/fxcompat/runtime/Units.java | 114 + .../fxcompat/runtime/UnmodifiableViews.java | 216 ++ .../fxcompat/runtime/ValueListeners.java | 121 + .../fxcompat/runtime/css/CssDeclaration.java | 68 + .../fxcompat/runtime/css/CssDeclarations.java | 165 + .../fxcompat/runtime/css/CssProperties.java | 232 ++ .../fxcompat/runtime/css/CssSelector.java | 266 ++ .../fxcompat/runtime/css/CssSheetData.java | 400 +++ .../fxcompat/runtime/css/CssValue.java | 307 ++ .../fxcompat/runtime/css/CssValueParser.java | 1212 ++++++++ .../fxcompat/runtime/css/package-info.java | 37 + .../fxcompat/runtime/package-info.java | 26 + .../generated/compat/CompatRegistry.java | 42 + .../generated/compat/package-info.java | 25 + .../main/java/javafx/animation/Animation.java | 762 +++++ .../java/javafx/animation/AnimationTimer.java | 63 + .../java/javafx/animation/ChildClips.java | 195 ++ .../java/javafx/animation/FadeTransition.java | 180 ++ .../java/javafx/animation/FillTransition.java | 173 ++ .../java/javafx/animation/Interpolatable.java | 33 + .../java/javafx/animation/Interpolator.java | 243 ++ .../main/java/javafx/animation/KeyFrame.java | 123 + .../main/java/javafx/animation/KeyValue.java | 72 + .../javafx/animation/ParallelTransition.java | 164 + .../java/javafx/animation/PathTransition.java | 286 ++ .../javafx/animation/PauseTransition.java | 79 + .../javafx/animation/RotateTransition.java | 174 ++ .../javafx/animation/ScaleTransition.java | 229 ++ .../animation/SequentialTransition.java | 164 + .../javafx/animation/StrokeTransition.java | 173 ++ .../main/java/javafx/animation/Timeline.java | 268 ++ .../java/javafx/animation/Transition.java | 112 + .../javafx/animation/TranslateTransition.java | 230 ++ .../java/javafx/animation/package-info.java | 24 + .../java/javafx/application/Application.java | 126 + .../application/ConditionalFeature.java | 64 + .../java/javafx/application/HostServices.java | 69 + .../java/javafx/application/Platform.java | 94 + .../java/javafx/application/package-info.java | 25 + .../java/javafx/beans/DefaultProperty.java | 42 + .../javafx/beans/InvalidationListener.java | 31 + .../src/main/java/javafx/beans/NamedArg.java | 41 + .../main/java/javafx/beans/Observable.java | 38 + .../beans/WeakInvalidationListener.java | 56 + .../main/java/javafx/beans/WeakListener.java | 31 + .../java/javafx/beans/binding/Binding.java | 47 + .../java/javafx/beans/binding/Bindings.java | 1550 ++++++++++ .../javafx/beans/binding/BooleanBinding.java | 137 + .../beans/binding/BooleanExpression.java | 101 + .../main/java/javafx/beans/binding/Const.java | 184 ++ .../javafx/beans/binding/DoubleBinding.java | 137 + .../beans/binding/DoubleExpression.java | 211 ++ .../javafx/beans/binding/FloatBinding.java | 137 + .../javafx/beans/binding/FloatExpression.java | 190 ++ .../main/java/javafx/beans/binding/Fn.java | 264 ++ .../javafx/beans/binding/IntegerBinding.java | 137 + .../beans/binding/IntegerExpression.java | 190 ++ .../javafx/beans/binding/ListExpression.java | 262 ++ .../javafx/beans/binding/LongBinding.java | 137 + .../javafx/beans/binding/LongExpression.java | 190 ++ .../javafx/beans/binding/NumberBinding.java | 27 + .../beans/binding/NumberExpression.java | 210 ++ .../beans/binding/NumberExpressionBase.java | 362 +++ .../javafx/beans/binding/ObjectBinding.java | 137 + .../beans/binding/ObjectExpression.java | 91 + .../javafx/beans/binding/StringBinding.java | 137 + .../beans/binding/StringExpression.java | 174 ++ .../main/java/javafx/beans/binding/When.java | 243 ++ .../javafx/beans/binding/package-info.java | 26 + .../main/java/javafx/beans/package-info.java | 25 + .../beans/property/BooleanProperty.java | 113 + .../beans/property/BooleanPropertyBase.java | 192 ++ .../javafx/beans/property/DoubleProperty.java | 113 + .../beans/property/DoublePropertyBase.java | 192 ++ .../javafx/beans/property/FloatProperty.java | 113 + .../beans/property/FloatPropertyBase.java | 192 ++ .../beans/property/IntegerProperty.java | 113 + .../beans/property/IntegerPropertyBase.java | 192 ++ .../javafx/beans/property/ListProperty.java | 59 + .../beans/property/ListPropertyBase.java | 357 +++ .../javafx/beans/property/LongProperty.java | 113 + .../beans/property/LongPropertyBase.java | 192 ++ .../javafx/beans/property/ObjectProperty.java | 55 + .../beans/property/ObjectPropertyBase.java | 191 ++ .../java/javafx/beans/property/Property.java | 50 + .../property/ReadOnlyBooleanProperty.java | 113 + .../property/ReadOnlyBooleanPropertyBase.java | 64 + .../property/ReadOnlyBooleanWrapper.java | 84 + .../property/ReadOnlyDoubleProperty.java | 113 + .../property/ReadOnlyDoublePropertyBase.java | 64 + .../beans/property/ReadOnlyDoubleWrapper.java | 84 + .../beans/property/ReadOnlyFloatProperty.java | 113 + .../property/ReadOnlyFloatPropertyBase.java | 64 + .../beans/property/ReadOnlyFloatWrapper.java | 84 + .../property/ReadOnlyIntegerProperty.java | 113 + .../property/ReadOnlyIntegerPropertyBase.java | 64 + .../property/ReadOnlyIntegerWrapper.java | 84 + .../beans/property/ReadOnlyListProperty.java | 104 + .../beans/property/ReadOnlyLongProperty.java | 113 + .../property/ReadOnlyLongPropertyBase.java | 64 + .../beans/property/ReadOnlyLongWrapper.java | 84 + .../property/ReadOnlyObjectProperty.java | 42 + .../property/ReadOnlyObjectPropertyBase.java | 64 + .../beans/property/ReadOnlyObjectWrapper.java | 84 + .../beans/property/ReadOnlyProperty.java | 35 + .../property/ReadOnlyStringProperty.java | 42 + .../property/ReadOnlyStringPropertyBase.java | 64 + .../beans/property/ReadOnlyStringWrapper.java | 84 + .../beans/property/SimpleBooleanProperty.java | 66 + .../beans/property/SimpleDoubleProperty.java | 66 + .../beans/property/SimpleFloatProperty.java | 66 + .../beans/property/SimpleIntegerProperty.java | 66 + .../beans/property/SimpleListProperty.java | 68 + .../beans/property/SimpleLongProperty.java | 66 + .../beans/property/SimpleObjectProperty.java | 66 + .../beans/property/SimpleStringProperty.java | 66 + .../javafx/beans/property/StringProperty.java | 67 + .../beans/property/StringPropertyBase.java | 188 ++ .../javafx/beans/property/package-info.java | 25 + .../javafx/beans/value/ChangeListener.java | 31 + .../beans/value/ObservableBooleanValue.java | 30 + .../beans/value/ObservableDoubleValue.java | 30 + .../beans/value/ObservableFloatValue.java | 30 + .../beans/value/ObservableIntegerValue.java | 30 + .../beans/value/ObservableListValue.java | 30 + .../beans/value/ObservableLongValue.java | 30 + .../beans/value/ObservableNumberValue.java | 39 + .../beans/value/ObservableObjectValue.java | 30 + .../beans/value/ObservableStringValue.java | 27 + .../javafx/beans/value/ObservableValue.java | 42 + .../beans/value/ObservableValueBase.java | 64 + .../beans/value/WeakChangeListener.java | 59 + .../beans/value/WritableBooleanValue.java | 37 + .../beans/value/WritableDoubleValue.java | 37 + .../beans/value/WritableFloatValue.java | 37 + .../beans/value/WritableIntegerValue.java | 37 + .../javafx/beans/value/WritableListValue.java | 30 + .../javafx/beans/value/WritableLongValue.java | 37 + .../beans/value/WritableNumberValue.java | 27 + .../beans/value/WritableObjectValue.java | 33 + .../beans/value/WritableStringValue.java | 27 + .../javafx/beans/value/WritableValue.java | 33 + .../java/javafx/beans/value/package-info.java | 25 + .../javafx/collections/FXCollections.java | 276 ++ .../collections/ListChangeListener.java | 138 + .../javafx/collections/MapChangeListener.java | 63 + .../ModifiableObservableListBase.java | 174 ++ .../javafx/collections/ObservableList.java | 95 + .../collections/ObservableListBase.java | 171 ++ .../javafx/collections/ObservableMap.java | 37 + .../javafx/collections/ObservableSet.java | 37 + .../javafx/collections/SetChangeListener.java | 59 + .../collections/WeakListChangeListener.java | 59 + .../collections/WeakMapChangeListener.java | 59 + .../collections/WeakSetChangeListener.java | 59 + .../java/javafx/collections/package-info.java | 26 + .../transformation/FilteredList.java | 277 ++ .../transformation/SortedList.java | 365 +++ .../transformation/TransformationList.java | 105 + .../transformation/package-info.java | 25 + .../main/java/javafx/concurrent/Service.java | 505 +++ .../src/main/java/javafx/concurrent/Task.java | 635 ++++ .../main/java/javafx/concurrent/Worker.java | 109 + .../java/javafx/concurrent/WorkerEvents.java | 100 + .../javafx/concurrent/WorkerStateEvent.java | 79 + .../java/javafx/concurrent/package-info.java | 25 + .../src/main/java/javafx/css/PseudoClass.java | 63 + .../src/main/java/javafx/css/Styleable.java | 53 + .../main/java/javafx/css/package-info.java | 26 + .../main/java/javafx/event/ActionEvent.java | 63 + .../src/main/java/javafx/event/Event.java | 125 + .../java/javafx/event/EventDispatchChain.java | 37 + .../java/javafx/event/EventDispatcher.java | 33 + .../main/java/javafx/event/EventHandler.java | 33 + .../main/java/javafx/event/EventTarget.java | 32 + .../src/main/java/javafx/event/EventType.java | 88 + .../java/javafx/event/WeakEventHandler.java | 51 + .../main/java/javafx/event/package-info.java | 26 + .../src/main/java/javafx/fxml/FXML.java | 41 + .../src/main/java/javafx/fxml/FXMLLoader.java | 219 ++ .../main/java/javafx/fxml/Initializable.java | 41 + .../main/java/javafx/fxml/LoadException.java | 55 + .../main/java/javafx/fxml/package-info.java | 40 + .../java/javafx/geometry/BoundingBox.java | 135 + .../src/main/java/javafx/geometry/Bounds.java | 141 + .../java/javafx/geometry/Dimension2D.java | 70 + .../main/java/javafx/geometry/Geometry.java | 42 + .../src/main/java/javafx/geometry/HPos.java | 33 + .../javafx/geometry/HorizontalDirection.java | 31 + .../src/main/java/javafx/geometry/Insets.java | 94 + .../java/javafx/geometry/NodeOrientation.java | 33 + .../java/javafx/geometry/Orientation.java | 31 + .../main/java/javafx/geometry/Point2D.java | 188 ++ .../src/main/java/javafx/geometry/Pos.java | 69 + .../java/javafx/geometry/Rectangle2D.java | 150 + .../src/main/java/javafx/geometry/Side.java | 45 + .../src/main/java/javafx/geometry/VPos.java | 35 + .../javafx/geometry/VerticalDirection.java | 31 + .../java/javafx/geometry/package-info.java | 25 + .../src/main/java/javafx/scene/Cursor.java | 118 + .../src/main/java/javafx/scene/Group.java | 130 + .../src/main/java/javafx/scene/Node.java | 2168 +++++++++++++ .../src/main/java/javafx/scene/Parent.java | 708 +++++ .../src/main/java/javafx/scene/Scene.java | 1166 +++++++ .../java/javafx/scene/SnapshotParameters.java | 86 + .../main/java/javafx/scene/canvas/Canvas.java | 109 + .../javafx/scene/canvas/GraphicsContext.java | 1219 ++++++++ .../javafx/scene/canvas/package-info.java | 25 + .../java/javafx/scene/chart/AreaChart.java | 111 + .../main/java/javafx/scene/chart/Axis.java | 257 ++ .../java/javafx/scene/chart/BarChart.java | 168 + .../java/javafx/scene/chart/CategoryAxis.java | 130 + .../main/java/javafx/scene/chart/Chart.java | 329 ++ .../java/javafx/scene/chart/LineChart.java | 176 ++ .../java/javafx/scene/chart/ListWatch.java | 87 + .../java/javafx/scene/chart/NumberAxis.java | 226 ++ .../java/javafx/scene/chart/PieChart.java | 520 ++++ .../main/java/javafx/scene/chart/Scale.java | 424 +++ .../java/javafx/scene/chart/ScatterChart.java | 128 + .../java/javafx/scene/chart/ValueAxis.java | 199 ++ .../main/java/javafx/scene/chart/XYChart.java | 766 +++++ .../java/javafx/scene/chart/package-info.java | 29 + .../java/javafx/scene/control/Accordion.java | 154 + .../main/java/javafx/scene/control/Alert.java | 147 + .../java/javafx/scene/control/Button.java | 139 + .../java/javafx/scene/control/ButtonBar.java | 372 +++ .../java/javafx/scene/control/ButtonBase.java | 227 ++ .../java/javafx/scene/control/ButtonType.java | 91 + .../main/java/javafx/scene/control/Cell.java | 385 +++ .../java/javafx/scene/control/CheckBox.java | 213 ++ .../javafx/scene/control/CheckMenuItem.java | 66 + .../java/javafx/scene/control/ChoiceBox.java | 223 ++ .../java/javafx/scene/control/ChoiceCore.java | 282 ++ .../javafx/scene/control/ChoiceDialog.java | 109 + .../javafx/scene/control/ChoiceModel.java | 49 + .../javafx/scene/control/ColorPicker.java | 232 ++ .../java/javafx/scene/control/ComboBox.java | 169 + .../javafx/scene/control/ComboBoxBase.java | 189 ++ .../javafx/scene/control/ContentDisplay.java | 41 + .../javafx/scene/control/ContextMenu.java | 279 ++ .../java/javafx/scene/control/Control.java | 379 +++ .../java/javafx/scene/control/DatePicker.java | 168 + .../java/javafx/scene/control/Dialog.java | 515 ++++ .../javafx/scene/control/DialogEvent.java | 79 + .../javafx/scene/control/DialogField.java | 71 + .../java/javafx/scene/control/DialogPane.java | 425 +++ .../java/javafx/scene/control/DialogRow.java | 85 + .../java/javafx/scene/control/Hyperlink.java | 149 + .../java/javafx/scene/control/IndexRange.java | 116 + .../javafx/scene/control/IndexedCell.java | 69 + .../main/java/javafx/scene/control/Label.java | 83 + .../java/javafx/scene/control/Labeled.java | 784 +++++ .../java/javafx/scene/control/ListCell.java | 84 + .../java/javafx/scene/control/ListView.java | 549 ++++ .../main/java/javafx/scene/control/Menu.java | 151 + .../java/javafx/scene/control/MenuBar.java | 295 ++ .../javafx/scene/control/MenuContent.java | 274 ++ .../java/javafx/scene/control/MenuItem.java | 366 +++ .../java/javafx/scene/control/MenuLink.java | 91 + .../java/javafx/scene/control/MenuRow.java | 65 + .../scene/control/MultipleSelectionModel.java | 112 + .../java/javafx/scene/control/Pagination.java | 248 ++ .../javafx/scene/control/PasswordField.java | 39 + .../javafx/scene/control/PopupControl.java | 99 + .../javafx/scene/control/ProgressBar.java | 131 + .../scene/control/ProgressIndicator.java | 388 +++ .../javafx/scene/control/RadioButton.java | 86 + .../java/javafx/scene/control/RowFlow.java | 361 +++ .../java/javafx/scene/control/RowPress.java | 63 + .../javafx/scene/control/RowSelection.java | 294 ++ .../java/javafx/scene/control/ScrollBar.java | 349 +++ .../java/javafx/scene/control/ScrollPane.java | 775 +++++ .../javafx/scene/control/SelectionMode.java | 31 + .../javafx/scene/control/SelectionModel.java | 105 + .../java/javafx/scene/control/Separator.java | 189 ++ .../scene/control/SeparatorMenuItem.java | 36 + .../scene/control/SingleSelectionModel.java | 145 + .../java/javafx/scene/control/Slider.java | 777 +++++ .../java/javafx/scene/control/Spinner.java | 388 +++ .../scene/control/SpinnerValueFactory.java | 392 +++ .../java/javafx/scene/control/SplitPane.java | 410 +++ .../main/java/javafx/scene/control/Tab.java | 433 +++ .../java/javafx/scene/control/TabPane.java | 680 ++++ .../java/javafx/scene/control/TableCell.java | 205 ++ .../javafx/scene/control/TableColumn.java | 366 +++ .../javafx/scene/control/TableColumnBase.java | 399 +++ .../javafx/scene/control/TablePosition.java | 67 + .../scene/control/TablePositionBase.java | 65 + .../java/javafx/scene/control/TableView.java | 1150 +++++++ .../java/javafx/scene/control/TextArea.java | 160 + .../java/javafx/scene/control/TextField.java | 195 ++ .../scene/control/TextInputControl.java | 692 +++++ .../javafx/scene/control/TextInputDialog.java | 72 + .../java/javafx/scene/control/TitledPane.java | 229 ++ .../java/javafx/scene/control/Toggle.java | 59 + .../javafx/scene/control/ToggleButton.java | 206 ++ .../javafx/scene/control/ToggleGroup.java | 150 + .../java/javafx/scene/control/ToolBar.java | 181 ++ .../java/javafx/scene/control/Tooltip.java | 170 + .../java/javafx/scene/control/TreeCell.java | 229 ++ .../java/javafx/scene/control/TreeItem.java | 362 +++ .../java/javafx/scene/control/TreeView.java | 580 ++++ .../scene/control/cell/CheckBoxTableCell.java | 189 ++ .../control/cell/PropertyValueFactory.java | 108 + .../control/cell/TextFieldTableCell.java | 179 ++ .../scene/control/cell/package-info.java | 25 + .../javafx/scene/control/package-info.java | 25 + .../java/javafx/scene/effect/BlurType.java | 35 + .../java/javafx/scene/effect/BoxBlur.java | 111 + .../java/javafx/scene/effect/DropShadow.java | 226 ++ .../main/java/javafx/scene/effect/Effect.java | 47 + .../javafx/scene/effect/GaussianBlur.java | 75 + .../main/java/javafx/scene/effect/Glow.java | 75 + .../java/javafx/scene/effect/InnerShadow.java | 216 ++ .../javafx/scene/effect/package-info.java | 28 + .../main/java/javafx/scene/image/Image.java | 443 +++ .../java/javafx/scene/image/ImageView.java | 315 ++ .../java/javafx/scene/image/PixelReader.java | 36 + .../java/javafx/scene/image/PixelWriter.java | 41 + .../javafx/scene/image/WritableImage.java | 176 ++ .../java/javafx/scene/image/package-info.java | 27 + .../javafx/scene/input/ContextMenuEvent.java | 148 + .../java/javafx/scene/input/GestureEvent.java | 188 ++ .../java/javafx/scene/input/InputEvent.java | 54 + .../main/java/javafx/scene/input/KeyCode.java | 817 +++++ .../scene/input/KeyCodeCombination.java | 89 + .../javafx/scene/input/KeyCombination.java | 369 +++ .../java/javafx/scene/input/KeyEvent.java | 155 + .../java/javafx/scene/input/MouseButton.java | 39 + .../java/javafx/scene/input/MouseEvent.java | 319 ++ .../java/javafx/scene/input/PickResult.java | 64 + .../java/javafx/scene/input/ScrollEvent.java | 296 ++ .../java/javafx/scene/input/SwipeEvent.java | 109 + .../java/javafx/scene/input/TouchEvent.java | 177 ++ .../java/javafx/scene/input/TouchPoint.java | 181 ++ .../java/javafx/scene/input/package-info.java | 25 + .../java/javafx/scene/layout/AnchorPane.java | 237 ++ .../java/javafx/scene/layout/Background.java | 140 + .../javafx/scene/layout/BackgroundFill.java | 78 + .../javafx/scene/layout/BackgroundImage.java | 95 + .../scene/layout/BackgroundPosition.java | 119 + .../javafx/scene/layout/BackgroundRepeat.java | 36 + .../javafx/scene/layout/BackgroundSize.java | 114 + .../main/java/javafx/scene/layout/Border.java | 164 + .../java/javafx/scene/layout/BorderImage.java | 129 + .../java/javafx/scene/layout/BorderPane.java | 415 +++ .../javafx/scene/layout/BorderRepeat.java | 35 + .../javafx/scene/layout/BorderStroke.java | 178 ++ .../scene/layout/BorderStrokeStyle.java | 152 + .../javafx/scene/layout/BorderWidths.java | 142 + .../scene/layout/ColumnConstraints.java | 225 ++ .../javafx/scene/layout/ConstraintsBase.java | 121 + .../java/javafx/scene/layout/CornerRadii.java | 156 + .../java/javafx/scene/layout/FlowPane.java | 473 +++ .../java/javafx/scene/layout/GridPane.java | 1051 +++++++ .../main/java/javafx/scene/layout/HBox.java | 305 ++ .../javafx/scene/layout/LayoutSupport.java | 430 +++ .../main/java/javafx/scene/layout/Pane.java | 65 + .../java/javafx/scene/layout/Priority.java | 54 + .../main/java/javafx/scene/layout/Region.java | 1405 +++++++++ .../javafx/scene/layout/RowConstraints.java | 225 ++ .../java/javafx/scene/layout/StackPane.java | 211 ++ .../java/javafx/scene/layout/StyleImages.java | 596 ++++ .../java/javafx/scene/layout/TilePane.java | 554 ++++ .../main/java/javafx/scene/layout/VBox.java | 293 ++ .../javafx/scene/layout/package-info.java | 24 + .../main/java/javafx/scene/package-info.java | 25 + .../main/java/javafx/scene/paint/Color.java | 925 ++++++ .../java/javafx/scene/paint/CycleMethod.java | 33 + .../javafx/scene/paint/LinearGradient.java | 152 + .../main/java/javafx/scene/paint/Paint.java | 45 + .../javafx/scene/paint/RadialGradient.java | 153 + .../main/java/javafx/scene/paint/Stop.java | 110 + .../java/javafx/scene/paint/package-info.java | 25 + .../src/main/java/javafx/scene/shape/Arc.java | 180 ++ .../main/java/javafx/scene/shape/ArcTo.java | 174 ++ .../main/java/javafx/scene/shape/ArcType.java | 33 + .../main/java/javafx/scene/shape/Circle.java | 130 + .../java/javafx/scene/shape/ClosePath.java | 38 + .../java/javafx/scene/shape/CubicCurve.java | 186 ++ .../java/javafx/scene/shape/CubicCurveTo.java | 150 + .../main/java/javafx/scene/shape/Ellipse.java | 133 + .../java/javafx/scene/shape/FillRule.java | 34 + .../main/java/javafx/scene/shape/HLineTo.java | 62 + .../main/java/javafx/scene/shape/Line.java | 165 + .../main/java/javafx/scene/shape/LineTo.java | 79 + .../main/java/javafx/scene/shape/MoveTo.java | 79 + .../main/java/javafx/scene/shape/Path.java | 145 + .../java/javafx/scene/shape/PathElement.java | 120 + .../main/java/javafx/scene/shape/Polygon.java | 92 + .../java/javafx/scene/shape/Polyline.java | 74 + .../java/javafx/scene/shape/QuadCurve.java | 151 + .../java/javafx/scene/shape/QuadCurveTo.java | 115 + .../java/javafx/scene/shape/Rectangle.java | 180 ++ .../main/java/javafx/scene/shape/SVGPath.java | 92 + .../main/java/javafx/scene/shape/Shape.java | 532 ++++ .../javafx/scene/shape/StrokeLineCap.java | 33 + .../javafx/scene/shape/StrokeLineJoin.java | 33 + .../java/javafx/scene/shape/StrokeType.java | 33 + .../java/javafx/scene/shape/VLineTo.java} | 50 +- .../java/javafx/scene/shape/package-info.java | 24 + .../src/main/java/javafx/scene/text/Font.java | 318 ++ .../java/javafx/scene/text/FontPosture.java | 54 + .../java/javafx/scene/text/FontWeight.java | 97 + .../src/main/java/javafx/scene/text/Text.java | 478 +++ .../java/javafx/scene/text/TextAlignment.java | 35 + .../main/java/javafx/scene/text/TextFlow.java | 499 +++ .../java/javafx/scene/text/package-info.java | 25 + .../java/javafx/scene/transform/Affine.java | 342 +++ .../NonInvertibleTransformException.java | 33 + .../java/javafx/scene/transform/Rotate.java | 136 + .../java/javafx/scene/transform/Scale.java | 132 + .../java/javafx/scene/transform/Shear.java | 133 + .../java/javafx/scene/transform/TDouble.java | 41 + .../javafx/scene/transform/Transform.java | 257 ++ .../javafx/scene/transform/Translate.java | 82 + .../javafx/scene/transform/package-info.java | 26 + .../main/java/javafx/stage/FileChooser.java | 274 ++ .../src/main/java/javafx/stage/Modality.java | 33 + .../main/java/javafx/stage/PopupWindow.java | 208 ++ .../src/main/java/javafx/stage/Screen.java | 116 + .../src/main/java/javafx/stage/Stage.java | 347 +++ .../main/java/javafx/stage/StageStyle.java | 39 + .../src/main/java/javafx/stage/Window.java | 436 +++ .../main/java/javafx/stage/WindowEvent.java | 71 + .../main/java/javafx/stage/package-info.java | 25 + .../src/main/java/javafx/util/Callback.java | 32 + .../src/main/java/javafx/util/Duration.java | 266 ++ .../src/main/java/javafx/util/Pair.java | 76 + .../java/javafx/util/StringConverter.java | 37 + .../converter/BooleanStringConverter.java | 55 + .../converter/DefaultStringConverter.java | 44 + .../util/converter/DoubleStringConverter.java | 55 + .../converter/IntegerStringConverter.java | 55 + .../util/converter/NumberStringConverter.java | 166 + .../javafx/util/converter/package-info.java | 24 + .../main/java/javafx/util/package-info.java | 25 + .../com/codename1/fxcompat/AnimationTest.java | 992 ++++++ .../com/codename1/fxcompat/ApiNamingTest.java | 225 ++ .../fxcompat/ApplicationWindowTest.java | 228 ++ .../com/codename1/fxcompat/BindingsTest.java | 693 +++++ .../fxcompat/ButtonBarAndMnemonicsTest.java | 246 ++ .../com/codename1/fxcompat/CanvasTest.java | 709 +++++ .../com/codename1/fxcompat/ChartTest.java | 933 ++++++ .../fxcompat/CollapsedTransformTest.java | 166 + .../codename1/fxcompat/ConcurrencyTest.java | 786 +++++ .../codename1/fxcompat/ControlLookTest.java | 635 ++++ .../codename1/fxcompat/ControlSizeTest.java | 243 ++ .../codename1/fxcompat/ControlsBasicTest.java | 760 +++++ .../fxcompat/ControlsContainerTest.java | 504 +++ .../codename1/fxcompat/ControlsDataTest.java | 1019 ++++++ .../codename1/fxcompat/DeviceMatrixTest.java | 78 + .../fxcompat/EffectAndRegionShapeTest.java | 231 ++ .../com/codename1/fxcompat/EventTest.java | 225 ++ .../codename1/fxcompat/FileChooserTest.java | 149 + .../codename1/fxcompat/FileUrlImageTest.java | 150 + .../codename1/fxcompat/FontLoadingTest.java | 165 + .../com/codename1/fxcompat/FxInteropTest.java | 335 ++ .../com/codename1/fxcompat/GeometryTest.java | 227 ++ .../fxcompat/GradientAndImageTest.java | 463 +++ .../codename1/fxcompat/GroupBoundsTest.java | 94 + .../codename1/fxcompat/LayoutPanesTest.java | 996 ++++++ .../codename1/fxcompat/NarrowScreenTest.java | 367 +++ .../codename1/fxcompat/NativeChromeTest.java | 133 + .../fxcompat/ObservableListTest.java | 480 +++ .../fxcompat/ObservableMapSetTest.java | 244 ++ .../codename1/fxcompat/PortControlsTest.java | 499 +++ .../com/codename1/fxcompat/PortInputTest.java | 345 +++ .../java/com/codename1/fxcompat/Probe.java | 123 + .../com/codename1/fxcompat/PropertyTest.java | 614 ++++ .../fxcompat/PropertyValueFactoryTest.java | 189 ++ .../codename1/fxcompat/SceneGraphTest.java | 716 +++++ .../fxcompat/ScreenSpaceClipTest.java | 221 ++ .../codename1/fxcompat/ShadowEffectTest.java | 244 ++ .../fxcompat/ShapeTransitionTest.java | 317 ++ .../com/codename1/fxcompat/ShapesTest.java | 600 ++++ .../com/codename1/fxcompat/SvgPathTest.java | 269 ++ .../codename1/fxcompat/TableEditSortTest.java | 417 +++ .../fxcompat/TouchAndTextFlowTest.java | 493 +++ .../com/codename1/fxcompat/TransformTest.java | 254 ++ .../fxcompat/TransformationListTest.java | 309 ++ .../java/com/codename1/fxcompat/UtilTest.java | 287 ++ .../codename1/fxcompat/WritableImageTest.java | 213 ++ .../src/test/resources/cn1-hooks.txt | 87 + .../javafx-compat/src/test/resources/dot.png | Bin 0 -> 73 bytes maven/pom.xml | 5 + maven/swing-compat/pom.xml | 130 + maven/swing-compat/spotbugs-exclude.xml | 740 +++++ maven/swing-compat/src/main/java/.gitkeep | 0 .../codename1/desktopcompat/SwingInterop.java | 115 + .../formdev/flatlaf/FlatClientProperties.java | 183 ++ .../com/formdev/flatlaf/FlatDarculaLaf.java | 58 + .../com/formdev/flatlaf/FlatDarkLaf.java | 63 + .../com/formdev/flatlaf/FlatIntelliJLaf.java | 58 + .../com/formdev/flatlaf/FlatLaf.java | 408 +++ .../com/formdev/flatlaf/FlatLightLaf.java | 63 + .../formdev/flatlaf/FlatSystemProperties.java | 55 + .../flatlaf/extras/FlatAnimatedLafChange.java | 57 + .../formdev/flatlaf/extras/package-info.java | 24 + .../com/formdev/flatlaf/package-info.java | 25 + .../flatlaf/themes/FlatMacDarkLaf.java | 60 + .../flatlaf/themes/FlatMacLightLaf.java | 60 + .../formdev/flatlaf/themes/package-info.java | 24 + .../com/formdev/flatlaf/util/SystemInfo.java | 112 + .../com/formdev/flatlaf/util/UIScale.java | 84 + .../formdev/flatlaf/util/package-info.java | 24 + .../desktopcompat/java/awt/AWTError.java | 31 + .../desktopcompat/java/awt/AWTEvent.java | 113 + .../desktopcompat/java/awt/AWTException.java | 31 + .../desktopcompat/java/awt/AWTKeyStroke.java | 285 ++ .../desktopcompat/java/awt/ActiveEvent.java | 29 + .../desktopcompat/java/awt/Adjustable.java | 65 + .../java/awt/AlphaComposite.java | 110 + .../desktopcompat/java/awt/BasicStroke.java | 177 ++ .../desktopcompat/java/awt/BorderLayout.java | 311 ++ .../desktopcompat/java/awt/Canvas.java | 47 + .../desktopcompat/java/awt/CardLayout.java | 248 ++ .../desktopcompat/java/awt/Color.java | 307 ++ .../desktopcompat/java/awt/Component.java | 1253 ++++++++ .../java/awt/ComponentOrientation.java | 47 + .../desktopcompat/java/awt/Composite.java | 28 + .../desktopcompat/java/awt/Container.java | 573 ++++ .../ContainerOrderFocusTraversalPolicy.java | 115 + .../desktopcompat/java/awt/Cursor.java | 96 + .../java/awt/DefaultFocusTraversalPolicy.java | 30 + .../java/awt/DefaultKeyboardFocusManager.java | 71 + .../desktopcompat/java/awt/Desktop.java | 117 + .../desktopcompat/java/awt/Dialog.java | 201 ++ .../desktopcompat/java/awt/Dimension.java | 101 + .../desktopcompat/java/awt/EventQueue.java | 84 + .../desktopcompat/java/awt/FlowLayout.java | 323 ++ .../java/awt/FocusTraversalPolicy.java | 47 + .../desktopcompat/java/awt/Font.java | 252 ++ .../java/awt/FontFormatException.java | 31 + .../desktopcompat/java/awt/FontMetrics.java | 112 + .../desktopcompat/java/awt/Frame.java | 165 + .../desktopcompat/java/awt/GradientPaint.java | 89 + .../desktopcompat/java/awt/Graphics.java | 204 ++ .../desktopcompat/java/awt/Graphics2D.java | 140 + .../java/awt/GraphicsConfiguration.java | 40 + .../java/awt/GraphicsDevice.java | 65 + .../java/awt/GraphicsEnvironment.java | 91 + .../java/awt/GridBagConstraints.java | 173 ++ .../desktopcompat/java/awt/GridBagLayout.java | 856 ++++++ .../desktopcompat/java/awt/GridLayout.java | 171 ++ .../java/awt/HeadlessException.java | 35 + .../desktopcompat/java/awt/Image.java | 73 + .../desktopcompat/java/awt/Insets.java | 73 + .../java/awt/ItemSelectable.java | 36 + .../java/awt/KeyboardFocusManager.java | 144 + .../desktopcompat/java/awt/LayoutManager.java | 37 + .../java/awt/LayoutManager2.java | 38 + .../desktopcompat/java/awt/Paint.java | 32 + .../desktopcompat/java/awt/Panel.java | 35 + .../desktopcompat/java/awt/Point.java | 106 + .../desktopcompat/java/awt/Polygon.java | 182 ++ .../desktopcompat/java/awt/Rectangle.java | 348 +++ .../java/awt/RenderingHints.java | 283 ++ .../java/awt/ScreenEnvironment.java | 118 + .../desktopcompat/java/awt/Shape.java | 53 + .../desktopcompat/java/awt/Stroke.java | 28 + .../desktopcompat/java/awt/Taskbar.java | 113 + .../desktopcompat/java/awt/Toolkit.java | 142 + .../desktopcompat/java/awt/Transparency.java | 32 + .../desktopcompat/java/awt/Window.java | 693 +++++ .../java/awt/datatransfer/Clipboard.java | 100 + .../java/awt/datatransfer/ClipboardOwner.java | 31 + .../java/awt/datatransfer/DataFlavor.java | 167 + .../awt/datatransfer/StringSelection.java | 60 + .../java/awt/datatransfer/Transferable.java | 43 + .../UnsupportedFlavorException.java | 33 + .../java/awt/datatransfer/package-info.java | 32 + .../java/awt/desktop/AppEvent.java | 34 + .../java/awt/desktop/PreferencesEvent.java | 30 + .../java/awt/desktop/PreferencesHandler.java | 31 + .../java/awt/desktop/package-info.java | 27 + .../java/awt/dnd/DnDConstants.java | 42 + .../java/awt/dnd/DropTarget.java | 164 + .../java/awt/dnd/DropTargetAdapter.java | 47 + .../java/awt/dnd/DropTargetContext.java | 120 + .../java/awt/dnd/DropTargetDragEvent.java | 99 + .../java/awt/dnd/DropTargetDropEvent.java | 114 + .../java/awt/dnd/DropTargetEvent.java | 43 + .../java/awt/dnd/DropTargetListener.java | 46 + .../java/awt/dnd/package-info.java | 25 + .../java/awt/event/AWTEventListener.java | 34 + .../java/awt/event/ActionEvent.java | 91 + .../java/awt/event/ActionListener.java | 31 + .../java/awt/event/AdjustmentEvent.java | 114 + .../java/awt/event/AdjustmentListener.java | 31 + .../java/awt/event/ComponentAdapter.java | 40 + .../java/awt/event/ComponentEvent.java | 76 + .../java/awt/event/ComponentListener.java | 37 + .../java/awt/event/ContainerAdapter.java | 34 + .../java/awt/event/ContainerEvent.java | 70 + .../java/awt/event/ContainerListener.java | 34 + .../java/awt/event/FocusAdapter.java | 34 + .../java/awt/event/FocusEvent.java | 82 + .../java/awt/event/FocusListener.java | 33 + .../java/awt/event/HierarchyEvent.java | 111 + .../java/awt/event/HierarchyListener.java | 31 + .../java/awt/event/InputEvent.java | 249 ++ .../java/awt/event/InvocationEvent.java | 101 + .../java/awt/event/ItemEvent.java | 88 + .../java/awt/event/ItemListener.java | 31 + .../java/awt/event/KeyAdapter.java | 37 + .../java/awt/event/KeyEvent.java | 1013 ++++++ .../java/awt/event/KeyListener.java | 35 + .../java/awt/event/MouseAdapter.java | 52 + .../java/awt/event/MouseEvent.java | 239 ++ .../java/awt/event/MouseListener.java | 39 + .../java/awt/event/MouseMotionAdapter.java | 34 + .../java/awt/event/MouseMotionListener.java | 33 + .../java/awt/event/MouseWheelEvent.java | 98 + .../java/awt/event/MouseWheelListener.java | 31 + .../java/awt/event/TextEvent.java | 44 + .../java/awt/event/TextListener.java | 31 + .../java/awt/event/WindowAdapter.java | 58 + .../java/awt/event/WindowEvent.java | 136 + .../java/awt/event/WindowFocusListener.java | 33 + .../java/awt/event/WindowListener.java | 44 + .../java/awt/event/WindowStateListener.java | 31 + .../java/awt/event/package-info.java | 28 + .../java/awt/geom/AffineTransform.java | 679 ++++ .../desktopcompat/java/awt/geom/Arc2D.java | 588 ++++ .../java/awt/geom/CubicCurve2D.java | 477 +++ .../java/awt/geom/Dimension2D.java | 80 + .../java/awt/geom/Ellipse2D.java | 259 ++ .../java/awt/geom/FlatteningPathIterator.java | 212 ++ .../java/awt/geom/GeneralPath.java | 51 + .../awt/geom/IllegalPathStateException.java | 38 + .../desktopcompat/java/awt/geom/Line2D.java | 397 +++ .../geom/NoninvertibleTransformException.java | 34 + .../desktopcompat/java/awt/geom/Outline.java | 189 ++ .../desktopcompat/java/awt/geom/Path2D.java | 479 +++ .../java/awt/geom/PathIterator.java | 64 + .../desktopcompat/java/awt/geom/Point2D.java | 178 ++ .../java/awt/geom/QuadCurve2D.java | 441 +++ .../java/awt/geom/Rectangle2D.java | 421 +++ .../java/awt/geom/RectangularShape.java | 144 + .../java/awt/geom/RoundRectangle2D.java | 357 +++ .../java/awt/geom/SegmentIterator.java | 177 ++ .../java/awt/geom/package-info.java | 39 + .../java/awt/image/BufferedImage.java | 258 ++ .../java/awt/image/BufferedImageOp.java | 31 + .../java/awt/image/ImageObserver.java | 41 + .../java/awt/image/RenderedImage.java | 37 + .../java/awt/image/package-info.java | 25 + .../desktopcompat/java/awt/package-info.java | 29 + .../java/awt/print/PrinterException.java | 35 + .../java/awt/print/package-info.java | 25 + .../java/beans/BeanDescriptor.java | 50 + .../desktopcompat/java/beans/BeanInfo.java | 36 + .../desktopcompat/java/beans/Beans.java | 56 + .../java/beans/ExceptionListener.java | 29 + .../java/beans/FeatureDescriptor.java | 108 + .../beans/IndexedPropertyChangeEvent.java | 44 + .../java/beans/IntrospectionException.java | 33 + .../java/beans/Introspector.java | 124 + .../java/beans/PropertyChangeEvent.java | 83 + .../java/beans/PropertyChangeListener.java | 31 + .../beans/PropertyChangeListenerProxy.java | 50 + .../java/beans/PropertyChangeSupport.java | 202 ++ .../java/beans/PropertyVetoException.java | 38 + .../java/beans/VetoableChangeListener.java | 31 + .../java/beans/package-info.java | 37 + .../javax/accessibility/Accessible.java | 30 + .../accessibility/AccessibleContext.java | 96 + .../accessibility/AccessibleRelation.java | 107 + .../accessibility/AccessibleRelationSet.java | 121 + .../javax/accessibility/package-info.java | 31 + .../desktopcompat/javax/imageio/ImageIO.java | 180 ++ .../javax/imageio/package-info.java | 27 + .../javax/swing/AbstractAction.java | 119 + .../javax/swing/AbstractButton.java | 858 ++++++ .../javax/swing/AbstractCellEditor.java | 93 + .../javax/swing/AbstractListModel.java | 87 + .../javax/swing/AbstractSpinnerModel.java | 68 + .../desktopcompat/javax/swing/Action.java | 65 + .../desktopcompat/javax/swing/ActionMap.java | 109 + .../javax/swing/BorderFactory.java | 157 + .../javax/swing/BoundedRangeModel.java | 57 + .../desktopcompat/javax/swing/Box.java | 118 + .../desktopcompat/javax/swing/BoxLayout.java | 189 ++ .../javax/swing/ButtonGroup.java | 103 + .../javax/swing/ButtonModel.java | 74 + .../desktopcompat/javax/swing/CellEditor.java | 44 + .../javax/swing/ComboBoxEditor.java | 48 + .../javax/swing/ComboBoxModel.java | 32 + .../javax/swing/ComponentInputMap.java | 51 + .../javax/swing/DefaultBoundedRangeModel.java | 186 ++ .../javax/swing/DefaultButtonModel.java | 263 ++ .../javax/swing/DefaultCellEditor.java | 231 ++ .../javax/swing/DefaultComboBoxModel.java | 138 + .../javax/swing/DefaultListCellRenderer.java | 79 + .../javax/swing/DefaultListModel.java | 202 ++ .../swing/DefaultListSelectionModel.java | 470 +++ .../javax/swing/DefaultRowSorter.java | 453 +++ .../swing/DefaultSingleSelectionModel.java | 92 + .../desktopcompat/javax/swing/DropMode.java | 52 + .../javax/swing/GroupLayout.java | 2580 ++++++++++++++++ .../desktopcompat/javax/swing/Icon.java | 36 + .../desktopcompat/javax/swing/ImageIcon.java | 166 + .../desktopcompat/javax/swing/InputMap.java | 109 + .../javax/swing/InputVerifier.java | 37 + .../desktopcompat/javax/swing/JButton.java | 66 + .../desktopcompat/javax/swing/JCheckBox.java | 69 + .../javax/swing/JCheckBoxMenuItem.java | 79 + .../javax/swing/JColorChooser.java | 197 ++ .../desktopcompat/javax/swing/JComboBox.java | 921 ++++++ .../desktopcompat/javax/swing/JComponent.java | 872 ++++++ .../javax/swing/JDesktopPane.java | 125 + .../desktopcompat/javax/swing/JDialog.java | 236 ++ .../javax/swing/JEditorPane.java | 486 +++ .../javax/swing/JFileChooser.java | 888 ++++++ .../javax/swing/JFormattedTextField.java | 317 ++ .../desktopcompat/javax/swing/JFrame.java | 215 ++ .../javax/swing/JInternalFrame.java | 1020 ++++++ .../desktopcompat/javax/swing/JLabel.java | 576 ++++ .../javax/swing/JLayeredPane.java | 247 ++ .../desktopcompat/javax/swing/JList.java | 1022 ++++++ .../desktopcompat/javax/swing/JMenu.java | 342 +++ .../desktopcompat/javax/swing/JMenuBar.java | 203 ++ .../desktopcompat/javax/swing/JMenuItem.java | 248 ++ .../javax/swing/JOptionPane.java | 673 ++++ .../desktopcompat/javax/swing/JPanel.java | 49 + .../javax/swing/JPasswordField.java | 107 + .../desktopcompat/javax/swing/JPopupMenu.java | 385 +++ .../javax/swing/JProgressBar.java | 392 +++ .../javax/swing/JRadioButton.java | 70 + .../javax/swing/JRadioButtonMenuItem.java | 71 + .../desktopcompat/javax/swing/JRootPane.java | 288 ++ .../desktopcompat/javax/swing/JScrollBar.java | 342 +++ .../javax/swing/JScrollPane.java | 710 +++++ .../desktopcompat/javax/swing/JSeparator.java | 110 + .../desktopcompat/javax/swing/JSlider.java | 785 +++++ .../desktopcompat/javax/swing/JSpinner.java | 808 +++++ .../desktopcompat/javax/swing/JSplitPane.java | 805 +++++ .../javax/swing/JTabbedPane.java | 870 ++++++ .../desktopcompat/javax/swing/JTable.java | 2726 +++++++++++++++++ .../desktopcompat/javax/swing/JTextArea.java | 473 +++ .../desktopcompat/javax/swing/JTextField.java | 212 ++ .../desktopcompat/javax/swing/JTextPane.java | 314 ++ .../javax/swing/JToggleButton.java | 89 + .../desktopcompat/javax/swing/JToolBar.java | 397 +++ .../desktopcompat/javax/swing/JTree.java | 1830 +++++++++++ .../desktopcompat/javax/swing/JViewport.java | 494 +++ .../desktopcompat/javax/swing/JWindow.java | 155 + .../desktopcompat/javax/swing/KeyStroke.java | 76 + .../javax/swing/LayoutStyle.java | 111 + .../javax/swing/ListCellRenderer.java | 36 + .../desktopcompat/javax/swing/ListModel.java | 37 + .../javax/swing/ListSelectionModel.java | 75 + .../javax/swing/LookAndFeel.java | 69 + .../javax/swing/MenuElement.java | 43 + .../javax/swing/MenuSelectionManager.java | 158 + .../javax/swing/MutableComboBoxModel.java | 35 + .../javax/swing/OverlayLayout.java | 141 + .../javax/swing/ProgressMonitor.java | 204 ++ .../javax/swing/RootPaneContainer.java | 45 + .../desktopcompat/javax/swing/RowFilter.java | 232 ++ .../desktopcompat/javax/swing/RowSorter.java | 130 + .../javax/swing/ScrollPaneConstants.java | 49 + .../javax/swing/ScrollPaneLayout.java | 193 ++ .../desktopcompat/javax/swing/Scrollable.java | 42 + .../javax/swing/SingleSelectionModel.java | 41 + .../javax/swing/SizeRequirements.java | 184 ++ .../desktopcompat/javax/swing/SortOrder.java | 30 + .../javax/swing/SpinnerDateModel.java | 159 + .../javax/swing/SpinnerListModel.java | 96 + .../javax/swing/SpinnerModel.java | 43 + .../javax/swing/SpinnerNumberModel.java | 188 ++ .../desktopcompat/javax/swing/Spring.java | 488 +++ .../javax/swing/SpringLayout.java | 773 +++++ .../javax/swing/SwingConstants.java | 65 + .../javax/swing/SwingUtilities.java | 352 +++ .../javax/swing/SwingWorker.java | 365 +++ .../desktopcompat/javax/swing/Timer.java | 217 ++ .../javax/swing/ToolTipManager.java | 150 + .../javax/swing/TransferHandler.java | 501 +++ .../desktopcompat/javax/swing/UIDefaults.java | 112 + .../desktopcompat/javax/swing/UIManager.java | 551 ++++ .../UnsupportedLookAndFeelException.java | 31 + .../javax/swing/WindowConstants.java | 33 + .../javax/swing/border/AbstractBorder.java | 71 + .../javax/swing/border/BevelBorder.java | 151 + .../javax/swing/border/Border.java | 38 + .../javax/swing/border/CompoundBorder.java | 102 + .../javax/swing/border/EmptyBorder.java | 75 + .../javax/swing/border/EtchedBorder.java | 142 + .../javax/swing/border/LineBorder.java | 113 + .../javax/swing/border/MatteBorder.java | 139 + .../javax/swing/border/SoftBevelBorder.java | 88 + .../javax/swing/border/TitledBorder.java | 404 +++ .../javax/swing/border/package-info.java | 33 + .../javax/swing/event/CaretEvent.java | 40 + .../javax/swing/event/CaretListener.java | 31 + .../javax/swing/event/CellEditorListener.java | 33 + .../javax/swing/event/ChangeEvent.java | 33 + .../javax/swing/event/ChangeListener.java | 31 + .../javax/swing/event/DocumentEvent.java | 74 + .../javax/swing/event/DocumentListener.java | 35 + .../javax/swing/event/EventListenerList.java | 156 + .../javax/swing/event/HyperlinkEvent.java | 106 + .../javax/swing/event/HyperlinkListener.java | 31 + .../swing/event/InternalFrameAdapter.java | 56 + .../javax/swing/event/InternalFrameEvent.java | 80 + .../swing/event/InternalFrameListener.java | 44 + .../javax/swing/event/ListDataEvent.java | 64 + .../javax/swing/event/ListDataListener.java | 35 + .../javax/swing/event/ListSelectionEvent.java | 60 + .../swing/event/ListSelectionListener.java | 31 + .../javax/swing/event/MenuEvent.java | 33 + .../javax/swing/event/MenuListener.java | 35 + .../javax/swing/event/PopupMenuEvent.java | 33 + .../javax/swing/event/PopupMenuListener.java | 35 + .../javax/swing/event/RowSorterEvent.java | 77 + .../javax/swing/event/RowSorterListener.java | 31 + .../event/SwingPropertyChangeSupport.java | 47 + .../swing/event/TableColumnModelEvent.java | 47 + .../swing/event/TableColumnModelListener.java | 39 + .../javax/swing/event/TableModelEvent.java | 86 + .../javax/swing/event/TableModelListener.java | 31 + .../javax/swing/event/TreeExpansionEvent.java | 41 + .../swing/event/TreeExpansionListener.java | 33 + .../javax/swing/event/TreeModelEvent.java | 107 + .../javax/swing/event/TreeModelListener.java | 37 + .../javax/swing/event/TreeSelectionEvent.java | 99 + .../swing/event/TreeSelectionListener.java | 31 + .../swing/event/TreeWillExpandListener.java | 35 + .../javax/swing/event/UndoableEditEvent.java | 42 + .../swing/event/UndoableEditListener.java | 31 + .../javax/swing/event/package-info.java | 25 + .../javax/swing/filechooser/FileFilter.java | 37 + .../filechooser/FileNameExtensionFilter.java | 96 + .../javax/swing/filechooser/FileView.java | 51 + .../javax/swing/filechooser/package-info.java | 25 + .../javax/swing/package-info.java | 30 + .../javax/swing/plaf/BorderUIResource.java | 57 + .../javax/swing/plaf/ColorUIResource.java | 45 + .../javax/swing/plaf/ComponentUI.java | 55 + .../javax/swing/plaf/FontUIResource.java | 37 + .../javax/swing/plaf/TreeUI.java | 52 + .../javax/swing/plaf/UIResource.java | 27 + .../javax/swing/plaf/basic/BasicBorders.java | 58 + .../swing/plaf/basic/BasicComboBoxEditor.java | 108 + .../javax/swing/plaf/basic/BasicTreeUI.java | 137 + .../javax/swing/plaf/basic/package-info.java | 26 + .../swing/plaf/metal/DefaultMetalTheme.java | 106 + .../swing/plaf/metal/MetalLookAndFeel.java | 75 + .../javax/swing/plaf/metal/MetalTheme.java | 213 ++ .../javax/swing/plaf/metal/OceanTheme.java | 103 + .../javax/swing/plaf/metal/package-info.java | 31 + .../javax/swing/plaf/package-info.java | 30 + .../javax/swing/table/AbstractTableModel.java | 123 + .../swing/table/DefaultTableCellRenderer.java | 178 ++ .../swing/table/DefaultTableColumnModel.java | 316 ++ .../javax/swing/table/DefaultTableModel.java | 273 ++ .../javax/swing/table/JTableHeader.java | 490 +++ .../javax/swing/table/TableCellEditor.java | 33 + .../javax/swing/table/TableCellRenderer.java | 35 + .../javax/swing/table/TableColumn.java | 267 ++ .../javax/swing/table/TableColumnModel.java | 70 + .../javax/swing/table/TableModel.java | 48 + .../javax/swing/table/TableRowSorter.java | 135 + .../swing/table/TableStringConverter.java | 33 + .../javax/swing/table/package-info.java | 32 + .../javax/swing/text/AbstractDocument.java | 528 ++++ .../javax/swing/text/AttributeSet.java | 53 + .../swing/text/BadLocationException.java | 38 + .../desktopcompat/javax/swing/text/Caret.java | 66 + .../javax/swing/text/DefaultCaret.java | 192 ++ .../javax/swing/text/DefaultEditorKit.java | 172 ++ .../javax/swing/text/DefaultFormatter.java | 120 + .../swing/text/DefaultFormatterFactory.java | 113 + .../swing/text/DefaultStyledDocument.java | 653 ++++ .../javax/swing/text/Document.java | 73 + .../javax/swing/text/DocumentFilter.java | 63 + .../javax/swing/text/Element.java | 48 + .../javax/swing/text/JTextComponent.java | 861 ++++++ .../javax/swing/text/MutableAttributeSet.java | 43 + .../javax/swing/text/NumberFormatter.java | 133 + .../javax/swing/text/PlainDocument.java | 288 ++ .../javax/swing/text/Position.java | 48 + .../javax/swing/text/Segment.java | 101 + .../javax/swing/text/SimpleAttributeSet.java | 272 ++ .../desktopcompat/javax/swing/text/Style.java | 36 + .../javax/swing/text/StyleConstants.java | 186 ++ .../javax/swing/text/StyleContext.java | 275 ++ .../javax/swing/text/StyledDocument.java | 62 + .../javax/swing/text/TextAction.java | 89 + .../javax/swing/text/html/HTMLDocument.java | 133 + .../text/html/HTMLFrameHyperlinkEvent.java | 49 + .../javax/swing/text/html/package-info.java | 30 + .../javax/swing/text/package-info.java | 25 + .../swing/tree/DefaultMutableTreeNode.java | 636 ++++ .../swing/tree/DefaultTreeCellEditor.java | 167 + .../swing/tree/DefaultTreeCellRenderer.java | 251 ++ .../javax/swing/tree/DefaultTreeModel.java | 292 ++ .../swing/tree/DefaultTreeSelectionModel.java | 468 +++ .../javax/swing/tree/ExpandVetoException.java | 40 + .../javax/swing/tree/MutableTreeNode.java | 39 + .../javax/swing/tree/RowMapper.java | 29 + .../javax/swing/tree/TreeCellEditor.java | 35 + .../javax/swing/tree/TreeCellRenderer.java | 35 + .../javax/swing/tree/TreeModel.java | 46 + .../javax/swing/tree/TreeNode.java | 43 + .../javax/swing/tree/TreePath.java | 185 ++ .../javax/swing/tree/TreeSelectionModel.java | 88 + .../javax/swing/tree/package-info.java | 32 + .../swing/undo/AbstractUndoableEdit.java | 114 + .../javax/swing/undo/CannotRedoException.java | 30 + .../javax/swing/undo/CannotUndoException.java | 30 + .../javax/swing/undo/CompoundEdit.java | 137 + .../javax/swing/undo/UndoManager.java | 280 ++ .../javax/swing/undo/UndoableEdit.java | 56 + .../javax/swing/undo/UndoableEditSupport.java | 116 + .../javax/swing/undo/package-info.java | 26 + .../ui/rsyntaxtextarea/DocumentRange.java | 102 + .../org/fife/ui/rsyntaxtextarea/Lexer.java | 310 ++ .../ui/rsyntaxtextarea/RSyntaxTextArea.java | 623 ++++ .../ui/rsyntaxtextarea/SyntaxConstants.java | 88 + .../org/fife/ui/rsyntaxtextarea/Theme.java | 425 +++ .../fife/ui/rsyntaxtextarea/TokenNames.java | 83 + .../fife/ui/rsyntaxtextarea/TokenTypes.java | 69 + .../fife/ui/rsyntaxtextarea/package-info.java | 31 + .../org/fife/ui/rtextarea/Gutter.java | 334 ++ .../org/fife/ui/rtextarea/RTextArea.java | 282 ++ .../org/fife/ui/rtextarea/RTextAreaBase.java | 566 ++++ .../fife/ui/rtextarea/RTextScrollPane.java | 131 + .../org/fife/ui/rtextarea/SearchContext.java | 200 ++ .../org/fife/ui/rtextarea/SearchEngine.java | 233 ++ .../org/fife/ui/rtextarea/SearchResult.java | 132 + .../org/fife/ui/rtextarea/package-info.java | 29 + .../org/jdesktop/beans/AbstractBean.java | 180 ++ .../org/jdesktop/beans/package-info.java | 24 + .../org/jdesktop/swingx/HorizontalLayout.java | 106 + .../org/jdesktop/swingx/JXBusyLabel.java | 326 ++ .../org/jdesktop/swingx/JXButton.java | 231 ++ .../jdesktop/swingx/JXCollapsiblePane.java | 430 +++ .../org/jdesktop/swingx/JXDatePicker.java | 369 +++ .../org/jdesktop/swingx/JXDialog.java | 175 ++ .../org/jdesktop/swingx/JXErrorPane.java | 205 ++ .../jdesktop/swingx/JXFormattedTextField.java | 99 + .../org/jdesktop/swingx/JXFrame.java | 263 ++ .../org/jdesktop/swingx/JXHeader.java | 257 ++ .../org/jdesktop/swingx/JXHyperlink.java | 198 ++ .../org/jdesktop/swingx/JXLabel.java | 489 +++ .../org/jdesktop/swingx/JXList.java | 784 +++++ .../org/jdesktop/swingx/JXMonthView.java | 1176 +++++++ .../org/jdesktop/swingx/JXPanel.java | 296 ++ .../org/jdesktop/swingx/JXSearchField.java | 213 ++ .../org/jdesktop/swingx/JXStatusBar.java | 321 ++ .../org/jdesktop/swingx/JXTable.java | 1256 ++++++++ .../org/jdesktop/swingx/JXTaskPane.java | 359 +++ .../jdesktop/swingx/JXTaskPaneContainer.java | 43 + .../org/jdesktop/swingx/JXTextArea.java | 98 + .../org/jdesktop/swingx/JXTextField.java | 99 + .../org/jdesktop/swingx/JXTitledPanel.java | 263 ++ .../jdesktop/swingx/JXTitledSeparator.java | 188 ++ .../org/jdesktop/swingx/JXTree.java | 392 +++ .../org/jdesktop/swingx/JXTreeTable.java | 1157 +++++++ .../jdesktop/swingx/ScrollableSizeHint.java | 84 + .../org/jdesktop/swingx/VerticalLayout.java | 106 + .../swingx/action/AbstractActionExt.java | 221 ++ .../jdesktop/swingx/action/package-info.java | 26 + .../calendar/AbstractDateSelectionModel.java | 257 ++ .../swingx/calendar/CalendarUtils.java | 189 ++ .../swingx/calendar/DateSelectionModel.java | 112 + .../org/jdesktop/swingx/calendar/DateSet.java | 180 ++ .../swingx/calendar/DaySelectionModel.java | 149 + .../calendar/DefaultDateSelectionModel.java | 149 + .../swingx/calendar/package-info.java | 29 + .../swingx/decorator/AbstractHighlighter.java | 112 + .../decorator/AlignmentHighlighter.java | 92 + .../swingx/decorator/BorderHighlighter.java | 123 + .../swingx/decorator/ColorHighlighter.java | 151 + .../swingx/decorator/ComponentAdapter.java | 185 ++ .../swingx/decorator/CompoundHighlighter.java | 141 + .../swingx/decorator/EnabledHighlighter.java | 66 + .../swingx/decorator/FontHighlighter.java | 72 + .../swingx/decorator/HighlightPredicate.java | 436 +++ .../swingx/decorator/Highlighter.java | 41 + .../swingx/decorator/HighlighterFactory.java | 117 + .../swingx/decorator/IconHighlighter.java | 79 + .../swingx/decorator/PainterHighlighter.java | 80 + .../swingx/decorator/ToolTipHighlighter.java | 80 + .../swingx/decorator/package-info.java | 29 + .../org/jdesktop/swingx/error/ErrorEvent.java | 42 + .../jdesktop/swingx/error/ErrorListener.java | 31 + .../jdesktop/swingx/error/ErrorSupport.java | 84 + .../jdesktop/swingx/error/package-info.java | 28 + .../swingx/event/DateSelectionEvent.java | 83 + .../swingx/event/DateSelectionListener.java | 31 + .../event/TableColumnModelExtListener.java | 34 + .../jdesktop/swingx/event/package-info.java | 24 + .../hyperlink/AbstractHyperlinkAction.java | 87 + .../swingx/hyperlink/HyperlinkAction.java | 82 + .../jdesktop/swingx/hyperlink/UrlOpener.java | 30 + .../swingx/hyperlink/package-info.java | 26 + .../org/jdesktop/swingx/package-info.java | 33 + .../swingx/painter/AbstractAreaPainter.java | 185 ++ .../swingx/painter/AbstractLayoutPainter.java | 146 + .../swingx/painter/AbstractPainter.java | 175 ++ .../jdesktop/swingx/painter/AlphaPainter.java | 72 + .../jdesktop/swingx/painter/BusyPainter.java | 358 +++ .../swingx/painter/CheckerboardPainter.java | 108 + .../swingx/painter/CompoundPainter.java | 207 ++ .../jdesktop/swingx/painter/GlossPainter.java | 100 + .../jdesktop/swingx/painter/ImagePainter.java | 240 ++ .../jdesktop/swingx/painter/MattePainter.java | 60 + .../org/jdesktop/swingx/painter/Painter.java | 35 + .../swingx/painter/PinstripePainter.java | 150 + .../swingx/painter/RectanglePainter.java | 159 + .../jdesktop/swingx/painter/ShapePainter.java | 94 + .../jdesktop/swingx/painter/TextPainter.java | 161 + .../jdesktop/swingx/painter/package-info.java | 26 + .../jdesktop/swingx/prompt/PromptSupport.java | 210 ++ .../jdesktop/swingx/prompt/package-info.java | 25 + .../swingx/renderer/AbstractRenderer.java | 78 + .../swingx/renderer/BooleanValue.java | 29 + .../jdesktop/swingx/renderer/CellContext.java | 190 ++ .../swingx/renderer/CheckBoxProvider.java | 86 + .../swingx/renderer/ComponentProvider.java | 129 + .../swingx/renderer/DefaultListRenderer.java | 72 + .../swingx/renderer/DefaultTableRenderer.java | 71 + .../swingx/renderer/DefaultTreeRenderer.java | 112 + .../swingx/renderer/DefaultVisuals.java | 143 + .../swingx/renderer/FormatStringValue.java | 59 + .../jdesktop/swingx/renderer/IconAware.java | 33 + .../jdesktop/swingx/renderer/IconValue.java | 56 + .../jdesktop/swingx/renderer/IconValues.java | 48 + .../swingx/renderer/JRendererCheckBox.java | 122 + .../swingx/renderer/JRendererLabel.java | 135 + .../swingx/renderer/LabelProvider.java | 75 + .../swingx/renderer/ListCellContext.java | 61 + .../jdesktop/swingx/renderer/MappedValue.java | 60 + .../swingx/renderer/PainterAware.java | 33 + .../jdesktop/swingx/renderer/StringValue.java | 30 + .../swingx/renderer/StringValues.java | 80 + .../swingx/renderer/TableCellContext.java | 77 + .../swingx/renderer/TreeCellContext.java | 118 + .../swingx/renderer/package-info.java | 30 + .../swingx/rollover/RolloverProducer.java | 142 + .../swingx/rollover/RolloverRenderer.java | 34 + .../swingx/rollover/package-info.java | 24 + .../swingx/sort/DefaultSortController.java | 259 ++ .../swingx/sort/ListSortController.java | 75 + .../org/jdesktop/swingx/sort/RowFilters.java | 84 + .../jdesktop/swingx/sort/SortController.java | 71 + .../org/jdesktop/swingx/sort/SortUtils.java | 93 + .../swingx/sort/StringValueProvider.java | 32 + .../swingx/sort/StringValueRegistry.java | 123 + .../swingx/sort/TableSortController.java | 103 + .../jdesktop/swingx/sort/package-info.java | 30 + .../swingx/table/ColumnControlButton.java | 168 + .../jdesktop/swingx/table/ColumnFactory.java | 222 ++ .../table/DefaultTableColumnModelExt.java | 235 ++ .../jdesktop/swingx/table/TableColumnExt.java | 309 ++ .../swingx/table/TableColumnModelExt.java | 52 + .../jdesktop/swingx/table/package-info.java | 27 + .../swingx/tree/TreeModelSupport.java | 161 + .../jdesktop/swingx/tree/package-info.java | 28 + .../AbstractMutableTreeTableNode.java | 197 ++ .../treetable/AbstractTreeTableModel.java | 119 + .../DefaultMutableTreeTableNode.java | 62 + .../treetable/DefaultTreeTableModel.java | 238 ++ .../treetable/MutableTreeTableNode.java | 45 + .../swingx/treetable/TreeTableModel.java | 48 + .../swingx/treetable/TreeTableNode.java | 55 + .../swingx/treetable/package-info.java | 34 + .../codename1/desktopcompat/package-info.java | 29 + .../com/codename1/desktopcompat/rt/Align.java | 84 + .../desktopcompat/rt/AwtListeners.java | 199 ++ .../desktopcompat/rt/ButtonPeer.java | 91 + .../desktopcompat/rt/CanvasPeer.java | 72 + .../desktopcompat/rt/CellPainter.java | 57 + .../codename1/desktopcompat/rt/CellStamp.java | 78 + .../codename1/desktopcompat/rt/CellTheme.java | 206 ++ .../desktopcompat/rt/CheckBoxPeer.java | 164 + .../desktopcompat/rt/ClientProps.java | 275 ++ .../codename1/desktopcompat/rt/ComboPeer.java | 135 + .../desktopcompat/rt/Comparators.java | 71 + .../desktopcompat/rt/ContainerPeer.java | 101 + .../desktopcompat/rt/DatePickerPeer.java | 111 + .../desktopcompat/rt/DesktopLifecycle.java | 193 ++ .../desktopcompat/rt/DesktopToolkit.java | 105 + .../desktopcompat/rt/DialogForm.java | 261 ++ .../desktopcompat/rt/DisplayInputState.java | 92 + .../com/codename1/desktopcompat/rt/Dnd.java | 521 ++++ .../codename1/desktopcompat/rt/EmbedHost.java | 218 ++ .../desktopcompat/rt/EventBridge.java | 621 ++++ .../desktopcompat/rt/FilePicker.java | 95 + .../codename1/desktopcompat/rt/FontFiles.java | 339 ++ .../com/codename1/desktopcompat/rt/Fonts.java | 230 ++ .../codename1/desktopcompat/rt/FrameForm.java | 380 +++ .../com/codename1/desktopcompat/rt/G2D.java | 1439 +++++++++ .../desktopcompat/rt/GradientFill.java | 232 ++ .../com/codename1/desktopcompat/rt/Icons.java | 77 + .../desktopcompat/rt/ImageLoader.java | 134 + .../desktopcompat/rt/Indicators.java | 143 + .../desktopcompat/rt/InputState.java | 46 + .../codename1/desktopcompat/rt/KeyMap.java | 252 ++ .../codename1/desktopcompat/rt/LabelPeer.java | 83 + .../codename1/desktopcompat/rt/LafBorder.java | 67 + .../codename1/desktopcompat/rt/LafTheme.java | 700 +++++ .../codename1/desktopcompat/rt/Launcher.java | 56 + .../desktopcompat/rt/LayoutBridge.java | 71 + .../desktopcompat/rt/ListEnumeration.java | 59 + .../desktopcompat/rt/MenuBridge.java | 192 ++ .../desktopcompat/rt/MenuItemPeer.java | 35 + .../codename1/desktopcompat/rt/MiniHtml.java | 1149 +++++++ .../desktopcompat/rt/NativeImage.java | 61 + .../desktopcompat/rt/NativeWindows.java | 291 ++ .../com/codename1/desktopcompat/rt/Peer.java | 47 + .../desktopcompat/rt/PeerSupport.java | 306 ++ .../desktopcompat/rt/ProgressPeer.java | 34 + .../desktopcompat/rt/RadioButtonPeer.java | 121 + .../codename1/desktopcompat/rt/RootPan.java | 189 ++ .../desktopcompat/rt/ScrollDelegate.java | 142 + .../desktopcompat/rt/SecondaryWindows.java | 41 + .../desktopcompat/rt/SliderPeer.java | 126 + .../desktopcompat/rt/StyledText.java | 441 +++ .../desktopcompat/rt/SystemClipboard.java | 95 + .../desktopcompat/rt/TabbedPanePeer.java | 300 ++ .../desktopcompat/rt/TextAreaPeer.java | 77 + .../desktopcompat/rt/TextFieldPeer.java | 77 + .../codename1/desktopcompat/rt/TreeIcons.java | 105 + .../com/codename1/desktopcompat/rt/Units.java | 115 + .../desktopcompat/rt/ViewportPeer.java | 111 + .../desktopcompat/rt/WindowHost.java | 84 + .../desktopcompat/rt/WindowHosts.java | 256 ++ .../desktopcompat/rt/package-info.java | 31 + .../generated/compat/CompatRegistry.java | 42 + .../generated/compat/package-info.java | 25 + maven/swing-compat/src/test/java/.gitkeep | 0 .../codename1/desktopcompat/ApiShapeTest.java | 328 ++ .../desktopcompat/ApplicationWindowTest.java | 165 + .../desktopcompat/ConstantParityTest.java | 130 + .../DesktopPaneAndFriendsTest.java | 621 ++++ .../desktopcompat/DesktopServicesTest.java | 325 ++ .../codename1/desktopcompat/DialogsTest.java | 537 ++++ .../desktopcompat/DragAndDropTest.java | 358 +++ .../desktopcompat/EdtAndGraphicsTest.java | 354 +++ .../desktopcompat/EventDeliveryTest.java | 345 +++ .../FocusAndKeysFollowUpTest.java | 211 ++ .../desktopcompat/FormattedFieldTest.java | 238 ++ .../desktopcompat/FrameAndLayoutTest.java | 224 ++ .../InputInfrastructureTest.java | 446 +++ .../desktopcompat/KernelTestBase.java | 166 + .../desktopcompat/LayoutParityTest.java | 1235 ++++++++ .../desktopcompat/LibraryLinkageTest.java | 126 + .../desktopcompat/ListAndComboTest.java | 628 ++++ .../codename1/desktopcompat/MenusTest.java | 528 ++++ .../NoJdkDesktopReferenceTest.java | 153 + .../desktopcompat/PaintRoutingTest.java | 163 + .../desktopcompat/PortKeyboardTest.java | 205 ++ .../desktopcompat/ScrollPaneTest.java | 276 ++ .../desktopcompat/SmallDisplayTest.java | 178 ++ .../desktopcompat/SwingInteropTest.java | 415 +++ .../codename1/desktopcompat/TextSyncTest.java | 445 +++ .../ToolkitListenersAndDesktopTest.java | 156 + .../desktopcompat/UndoAndEditorKitTest.java | 335 ++ .../codename1/desktopcompat/WindowsTest.java | 577 ++++ .../desktopcompat/WindowsTestBase.java | 238 ++ .../flatlaf/FlatClientPropertiesTest.java | 173 ++ .../formdev/flatlaf/FlatLafApiShapeTest.java | 338 ++ .../java/awt/CreatedFontTest.java | 226 ++ .../java/awt/ValueParityTest.java | 127 + .../java/awt/geom/GeomParityTest.java | 1447 +++++++++ .../java/beans/PropertyChangeSupportTest.java | 245 ++ .../javax/swing/ButtonLabelGapsTest.java | 683 +++++ .../javax/swing/DefaultButtonModelTest.java | 261 ++ .../javax/swing/EditableComboBoxTest.java | 308 ++ .../javax/swing/EditorPaneHtmlTest.java | 225 ++ .../javax/swing/GroupLayoutParityTest.java | 1523 +++++++++ .../javax/swing/JSplitPaneTest.java | 340 ++ .../javax/swing/JTabbedPaneTest.java | 592 ++++ .../javax/swing/JTableEditingTest.java | 204 ++ .../desktopcompat/javax/swing/JTableTest.java | 652 ++++ .../desktopcompat/javax/swing/JTreeTest.java | 581 ++++ .../javax/swing/ListWrapOrientationTest.java | 215 ++ .../javax/swing/LookAndFeelSwitchTest.java | 381 +++ .../javax/swing/RangeWidgetsTest.java | 496 +++ .../javax/swing/ScrollPaneBarsTest.java | 322 ++ .../swing/ScrollPaneLayoutAndTreeUiTest.java | 209 ++ .../swing/ScrollRevealReentrancyTest.java | 153 + .../javax/swing/SizeFollowsContentTest.java | 222 ++ .../javax/swing/SpinnerTest.java | 497 +++ .../javax/swing/SpringLayoutParityTest.java | 1183 +++++++ .../javax/swing/StyledTextTest.java | 467 +++ .../javax/swing/SwingWorkerTest.java | 335 ++ .../javax/swing/TableTreeScrollPaneTest.java | 129 + .../javax/swing/TextAreaLineWrapTest.java | 126 + .../javax/swing/UIManagerTest.java | 228 ++ .../javax/swing/WidgetLookTest.java | 341 +++ .../javax/swing/border/BorderSidesTest.java | 123 + .../swing/event/ListenerArrayTypesTest.java | 91 + .../tree/DefaultMutableTreeNodeTest.java | 258 ++ .../rsyntaxtextarea/RSyntaxTextAreaTest.java | 479 +++ .../swingx/CollapsibleAndTaskPaneTest.java | 268 ++ .../org/jdesktop/swingx/JXDatePickerTest.java | 221 ++ .../jdesktop/swingx/JXListAndTreeTest.java | 331 ++ .../org/jdesktop/swingx/JXMonthViewTest.java | 337 ++ .../org/jdesktop/swingx/JXTableTest.java | 457 +++ .../org/jdesktop/swingx/JXTreeTableTest.java | 534 ++++ .../jdesktop/swingx/PainterWidgetsTest.java | 500 +++ .../org/jdesktop/swingx/PanelLayoutsTest.java | 134 + .../swingx/TitledPanelAndHeaderTest.java | 160 + .../jdesktop/swingx/WindowsAndErrorsTest.java | 368 +++ .../calendar/DateSelectionModelTest.java | 218 ++ .../HighlighterFactoryParityTest.java} | 46 +- .../swingx/hyperlink/HyperlinkTest.java | 166 + .../jdesktop/swingx/painter/PaintersTest.java | 504 +++ .../swingx/painter/RecordingGraphics.java | 452 +++ .../swingx/prompt/PromptSupportTest.java | 190 ++ .../swingx/table/ColumnControlButtonTest.java | 110 + .../swingx/treetable/TreeTableModelTest.java | 388 +++ .../desktopcompat/rt/GradientFillTest.java | 168 + .../desktopcompat/rt/MiniHtmlTest.java | 266 ++ .../codename1/desktopcompat/rt/UnitsTest.java | 91 + .../com/formdev/flatlaf/flatlaf-absent.txt | 57 + 1524 files changed, 267388 insertions(+), 1470 deletions(-) delete mode 100644 maven/android-compat/src/test/java/com/codename1/androidcompat/jdk/BufferedReaderMarkTest.java delete mode 100644 maven/android-compat/src/test/java/com/codename1/androidcompat/jdk/BufferedStreamsClosedTest.java delete mode 100644 maven/android-compat/src/test/java/com/codename1/androidcompat/jdk/BufferedWriterClosedTest.java delete mode 100644 maven/android-compat/src/test/java/com/codename1/androidcompat/jdk/FileRenameFailedDeleteTest.java delete mode 100644 maven/android-compat/src/test/java/com/codename1/androidcompat/jdk/FileRenameOntoItselfTest.java delete mode 100644 maven/android-compat/src/test/java/com/codename1/androidcompat/jdk/PrintWriterClosedTest.java delete mode 100644 maven/android-compat/src/test/java/com/codename1/androidcompat/testing/HeadlessImplementation.java create mode 100644 maven/compat-testing/pom.xml create mode 100644 maven/compat-testing/src/main/java/com/codename1/compat/testing/HeadlessImplementation.java create mode 100644 maven/compat-testing/src/main/java/com/codename1/compat/testing/JdkResourceSupport.java rename maven/{android-compat/src/test/java/com/codename1/androidcompat => compat-testing/src/main/java/com/codename1/compat}/testing/MainThreadRule.java (93%) create mode 100644 maven/compat-testing/src/main/java/com/codename1/compat/testing/ReferenceJdk.java rename maven/{android-compat/src/test => compat-testing/src/main}/java/com/codename1/impl/ImplementationFactory.java (96%) create mode 100644 maven/desktop-compat-runtime/pom.xml create mode 100644 maven/desktop-compat-runtime/spotbugs-exclude.xml create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/CompatRegistryBase.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/ResourceNames.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/Resources.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/RuntimeExec.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/UncaughtExceptions.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/io/File.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/io/FileFilter.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/io/FileInputStream.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/io/FileOutputStream.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/io/FileReader.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/io/FileWriter.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/io/FilenameFilter.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/io/Host.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/io/package-info.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/net/URL.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/net/package-info.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/AccessDeniedException.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/CopyOption.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/DirectoryIteratorException.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/DirectoryNotEmptyException.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/DirectoryStream.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/FileAlreadyExistsException.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/FileSystemException.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/FileVisitOption.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/FileVisitResult.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/FileVisitor.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/Files.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/Glob.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/InvalidPathException.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/LinkOption.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/NoSuchFileException.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/NotDirectoryException.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/OpenOption.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/Path.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/PathImpl.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/Paths.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/SimpleFileVisitor.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/StandardCopyOption.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/StandardOpenOption.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/attribute/BasicFileAttributes.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/attribute/FileAttribute.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/attribute/FileTime.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/attribute/package-info.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/package-info.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/util/ListResourceBundle.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/util/PropertyResourceBundle.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/util/ResourceBundle.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/util/logging/Level.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/util/logging/Logger.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/util/prefs/BackingStoreException.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/util/prefs/Preferences.java create mode 100644 maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/package-info.java create mode 100644 maven/fxml-compiler/pom.xml create mode 100644 maven/fxml-compiler/spotbugs-exclude.xml create mode 100644 maven/fxml-compiler/src/main/java/com/codename1/fxml/ClassModel.java create mode 100644 maven/fxml-compiler/src/main/java/com/codename1/fxml/CssCompiler.java create mode 100644 maven/fxml-compiler/src/main/java/com/codename1/fxml/DesktopResourceCompiler.java create mode 100644 maven/fxml-compiler/src/main/java/com/codename1/fxml/FxmlClassCompiler.java create mode 100644 maven/fxml-compiler/src/main/java/com/codename1/fxml/FxmlCompiler.java create mode 100644 maven/fxml-compiler/src/main/java/com/codename1/fxml/FxmlDispatchGenerator.java create mode 100644 maven/fxml-compiler/src/main/java/com/codename1/fxml/FxmlDocument.java create mode 100644 maven/fxml-compiler/src/main/java/com/codename1/fxml/Messages.java create mode 100644 maven/fxml-compiler/src/main/java/com/codename1/fxml/css/package-info.java create mode 100644 maven/fxml-compiler/src/main/java/com/codename1/fxml/package-info.java create mode 100644 maven/fxml-compiler/src/test/java/com/codename1/fxml/CssTest.java create mode 100644 maven/fxml-compiler/src/test/java/com/codename1/fxml/FxmlCompilerTest.java create mode 100644 maven/fxml-compiler/src/test/java/com/codename1/fxml/FxmlHarness.java create mode 100644 maven/javafx-compat/pom.xml create mode 100644 maven/javafx-compat/spotbugs-exclude.xml create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/FxInterop.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/package-info.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/ArrayObservableList.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/BidirectionalBinding.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/BindingObserver.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/Changes.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/ConstantObservableList.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/ContentBindings.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/CssEngine.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/CssSheet.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/Diagnostics.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/Dirty.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/Effects.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/EventDispatchChainImpl.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/EventHandlerManager.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/FilePicker.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/FontFiles.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/FontStyles.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/Fonts.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/FrameClock.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/FxBoolean.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/FxDouble.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/FxLifecycle.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/FxObject.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/FxPath.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/FxPeer.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/FxString.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/FxmlContext.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/FxmlDispatch.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/FxmlRegistry.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/GradientRaster.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/HostCore.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/ListChangeBuilder.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/ListenerSet.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/Matrix2D.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/Mnemonics.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/NodePeer.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/ObservableMapWrapper.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/ObservableSetWrapper.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/ParentPeer.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/PeerPaint.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/PropertyAccess.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/PropertyRegistry.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/PropertyText.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/Renderer.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/ResourceUrls.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/SceneEmbed.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/SceneHost.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/SceneInput.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/StageDialog.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/StageForm.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/StageHost.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/StageHosts.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/StagePopup.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/StageWindow.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/StyleEngine.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/StyleList.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/StyleTarget.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/SvgPath.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/Units.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/UnmodifiableViews.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/ValueListeners.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/css/CssDeclaration.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/css/CssDeclarations.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/css/CssProperties.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/css/CssSelector.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/css/CssSheetData.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/css/CssValue.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/css/CssValueParser.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/css/package-info.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/fxcompat/runtime/package-info.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/generated/compat/CompatRegistry.java create mode 100644 maven/javafx-compat/src/main/java/com/codename1/generated/compat/package-info.java create mode 100644 maven/javafx-compat/src/main/java/javafx/animation/Animation.java create mode 100644 maven/javafx-compat/src/main/java/javafx/animation/AnimationTimer.java create mode 100644 maven/javafx-compat/src/main/java/javafx/animation/ChildClips.java create mode 100644 maven/javafx-compat/src/main/java/javafx/animation/FadeTransition.java create mode 100644 maven/javafx-compat/src/main/java/javafx/animation/FillTransition.java create mode 100644 maven/javafx-compat/src/main/java/javafx/animation/Interpolatable.java create mode 100644 maven/javafx-compat/src/main/java/javafx/animation/Interpolator.java create mode 100644 maven/javafx-compat/src/main/java/javafx/animation/KeyFrame.java create mode 100644 maven/javafx-compat/src/main/java/javafx/animation/KeyValue.java create mode 100644 maven/javafx-compat/src/main/java/javafx/animation/ParallelTransition.java create mode 100644 maven/javafx-compat/src/main/java/javafx/animation/PathTransition.java create mode 100644 maven/javafx-compat/src/main/java/javafx/animation/PauseTransition.java create mode 100644 maven/javafx-compat/src/main/java/javafx/animation/RotateTransition.java create mode 100644 maven/javafx-compat/src/main/java/javafx/animation/ScaleTransition.java create mode 100644 maven/javafx-compat/src/main/java/javafx/animation/SequentialTransition.java create mode 100644 maven/javafx-compat/src/main/java/javafx/animation/StrokeTransition.java create mode 100644 maven/javafx-compat/src/main/java/javafx/animation/Timeline.java create mode 100644 maven/javafx-compat/src/main/java/javafx/animation/Transition.java create mode 100644 maven/javafx-compat/src/main/java/javafx/animation/TranslateTransition.java create mode 100644 maven/javafx-compat/src/main/java/javafx/animation/package-info.java create mode 100644 maven/javafx-compat/src/main/java/javafx/application/Application.java create mode 100644 maven/javafx-compat/src/main/java/javafx/application/ConditionalFeature.java create mode 100644 maven/javafx-compat/src/main/java/javafx/application/HostServices.java create mode 100644 maven/javafx-compat/src/main/java/javafx/application/Platform.java create mode 100644 maven/javafx-compat/src/main/java/javafx/application/package-info.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/DefaultProperty.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/InvalidationListener.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/NamedArg.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/Observable.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/WeakInvalidationListener.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/WeakListener.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/binding/Binding.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/binding/Bindings.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/binding/BooleanBinding.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/binding/BooleanExpression.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/binding/Const.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/binding/DoubleBinding.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/binding/DoubleExpression.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/binding/FloatBinding.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/binding/FloatExpression.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/binding/Fn.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/binding/IntegerBinding.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/binding/IntegerExpression.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/binding/ListExpression.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/binding/LongBinding.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/binding/LongExpression.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/binding/NumberBinding.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/binding/NumberExpression.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/binding/NumberExpressionBase.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/binding/ObjectBinding.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/binding/ObjectExpression.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/binding/StringBinding.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/binding/StringExpression.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/binding/When.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/binding/package-info.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/package-info.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/BooleanProperty.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/BooleanPropertyBase.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/DoubleProperty.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/DoublePropertyBase.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/FloatProperty.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/FloatPropertyBase.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/IntegerProperty.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/IntegerPropertyBase.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/ListProperty.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/ListPropertyBase.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/LongProperty.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/LongPropertyBase.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/ObjectProperty.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/ObjectPropertyBase.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/Property.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/ReadOnlyBooleanProperty.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/ReadOnlyBooleanPropertyBase.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/ReadOnlyBooleanWrapper.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/ReadOnlyDoubleProperty.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/ReadOnlyDoublePropertyBase.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/ReadOnlyDoubleWrapper.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/ReadOnlyFloatProperty.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/ReadOnlyFloatPropertyBase.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/ReadOnlyFloatWrapper.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/ReadOnlyIntegerProperty.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/ReadOnlyIntegerPropertyBase.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/ReadOnlyIntegerWrapper.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/ReadOnlyListProperty.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/ReadOnlyLongProperty.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/ReadOnlyLongPropertyBase.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/ReadOnlyLongWrapper.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/ReadOnlyObjectProperty.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/ReadOnlyObjectPropertyBase.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/ReadOnlyObjectWrapper.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/ReadOnlyProperty.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/ReadOnlyStringProperty.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/ReadOnlyStringPropertyBase.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/ReadOnlyStringWrapper.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/SimpleBooleanProperty.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/SimpleDoubleProperty.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/SimpleFloatProperty.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/SimpleIntegerProperty.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/SimpleListProperty.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/SimpleLongProperty.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/SimpleObjectProperty.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/SimpleStringProperty.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/StringProperty.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/StringPropertyBase.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/property/package-info.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/value/ChangeListener.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/value/ObservableBooleanValue.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/value/ObservableDoubleValue.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/value/ObservableFloatValue.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/value/ObservableIntegerValue.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/value/ObservableListValue.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/value/ObservableLongValue.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/value/ObservableNumberValue.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/value/ObservableObjectValue.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/value/ObservableStringValue.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/value/ObservableValue.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/value/ObservableValueBase.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/value/WeakChangeListener.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/value/WritableBooleanValue.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/value/WritableDoubleValue.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/value/WritableFloatValue.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/value/WritableIntegerValue.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/value/WritableListValue.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/value/WritableLongValue.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/value/WritableNumberValue.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/value/WritableObjectValue.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/value/WritableStringValue.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/value/WritableValue.java create mode 100644 maven/javafx-compat/src/main/java/javafx/beans/value/package-info.java create mode 100644 maven/javafx-compat/src/main/java/javafx/collections/FXCollections.java create mode 100644 maven/javafx-compat/src/main/java/javafx/collections/ListChangeListener.java create mode 100644 maven/javafx-compat/src/main/java/javafx/collections/MapChangeListener.java create mode 100644 maven/javafx-compat/src/main/java/javafx/collections/ModifiableObservableListBase.java create mode 100644 maven/javafx-compat/src/main/java/javafx/collections/ObservableList.java create mode 100644 maven/javafx-compat/src/main/java/javafx/collections/ObservableListBase.java create mode 100644 maven/javafx-compat/src/main/java/javafx/collections/ObservableMap.java create mode 100644 maven/javafx-compat/src/main/java/javafx/collections/ObservableSet.java create mode 100644 maven/javafx-compat/src/main/java/javafx/collections/SetChangeListener.java create mode 100644 maven/javafx-compat/src/main/java/javafx/collections/WeakListChangeListener.java create mode 100644 maven/javafx-compat/src/main/java/javafx/collections/WeakMapChangeListener.java create mode 100644 maven/javafx-compat/src/main/java/javafx/collections/WeakSetChangeListener.java create mode 100644 maven/javafx-compat/src/main/java/javafx/collections/package-info.java create mode 100644 maven/javafx-compat/src/main/java/javafx/collections/transformation/FilteredList.java create mode 100644 maven/javafx-compat/src/main/java/javafx/collections/transformation/SortedList.java create mode 100644 maven/javafx-compat/src/main/java/javafx/collections/transformation/TransformationList.java create mode 100644 maven/javafx-compat/src/main/java/javafx/collections/transformation/package-info.java create mode 100644 maven/javafx-compat/src/main/java/javafx/concurrent/Service.java create mode 100644 maven/javafx-compat/src/main/java/javafx/concurrent/Task.java create mode 100644 maven/javafx-compat/src/main/java/javafx/concurrent/Worker.java create mode 100644 maven/javafx-compat/src/main/java/javafx/concurrent/WorkerEvents.java create mode 100644 maven/javafx-compat/src/main/java/javafx/concurrent/WorkerStateEvent.java create mode 100644 maven/javafx-compat/src/main/java/javafx/concurrent/package-info.java create mode 100644 maven/javafx-compat/src/main/java/javafx/css/PseudoClass.java create mode 100644 maven/javafx-compat/src/main/java/javafx/css/Styleable.java create mode 100644 maven/javafx-compat/src/main/java/javafx/css/package-info.java create mode 100644 maven/javafx-compat/src/main/java/javafx/event/ActionEvent.java create mode 100644 maven/javafx-compat/src/main/java/javafx/event/Event.java create mode 100644 maven/javafx-compat/src/main/java/javafx/event/EventDispatchChain.java create mode 100644 maven/javafx-compat/src/main/java/javafx/event/EventDispatcher.java create mode 100644 maven/javafx-compat/src/main/java/javafx/event/EventHandler.java create mode 100644 maven/javafx-compat/src/main/java/javafx/event/EventTarget.java create mode 100644 maven/javafx-compat/src/main/java/javafx/event/EventType.java create mode 100644 maven/javafx-compat/src/main/java/javafx/event/WeakEventHandler.java create mode 100644 maven/javafx-compat/src/main/java/javafx/event/package-info.java create mode 100644 maven/javafx-compat/src/main/java/javafx/fxml/FXML.java create mode 100644 maven/javafx-compat/src/main/java/javafx/fxml/FXMLLoader.java create mode 100644 maven/javafx-compat/src/main/java/javafx/fxml/Initializable.java create mode 100644 maven/javafx-compat/src/main/java/javafx/fxml/LoadException.java create mode 100644 maven/javafx-compat/src/main/java/javafx/fxml/package-info.java create mode 100644 maven/javafx-compat/src/main/java/javafx/geometry/BoundingBox.java create mode 100644 maven/javafx-compat/src/main/java/javafx/geometry/Bounds.java create mode 100644 maven/javafx-compat/src/main/java/javafx/geometry/Dimension2D.java create mode 100644 maven/javafx-compat/src/main/java/javafx/geometry/Geometry.java create mode 100644 maven/javafx-compat/src/main/java/javafx/geometry/HPos.java create mode 100644 maven/javafx-compat/src/main/java/javafx/geometry/HorizontalDirection.java create mode 100644 maven/javafx-compat/src/main/java/javafx/geometry/Insets.java create mode 100644 maven/javafx-compat/src/main/java/javafx/geometry/NodeOrientation.java create mode 100644 maven/javafx-compat/src/main/java/javafx/geometry/Orientation.java create mode 100644 maven/javafx-compat/src/main/java/javafx/geometry/Point2D.java create mode 100644 maven/javafx-compat/src/main/java/javafx/geometry/Pos.java create mode 100644 maven/javafx-compat/src/main/java/javafx/geometry/Rectangle2D.java create mode 100644 maven/javafx-compat/src/main/java/javafx/geometry/Side.java create mode 100644 maven/javafx-compat/src/main/java/javafx/geometry/VPos.java create mode 100644 maven/javafx-compat/src/main/java/javafx/geometry/VerticalDirection.java create mode 100644 maven/javafx-compat/src/main/java/javafx/geometry/package-info.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/Cursor.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/Group.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/Node.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/Parent.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/Scene.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/SnapshotParameters.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/canvas/Canvas.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/canvas/GraphicsContext.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/canvas/package-info.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/chart/AreaChart.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/chart/Axis.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/chart/BarChart.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/chart/CategoryAxis.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/chart/Chart.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/chart/LineChart.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/chart/ListWatch.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/chart/NumberAxis.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/chart/PieChart.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/chart/Scale.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/chart/ScatterChart.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/chart/ValueAxis.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/chart/XYChart.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/chart/package-info.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/Accordion.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/Alert.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/Button.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/ButtonBar.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/ButtonBase.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/ButtonType.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/Cell.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/CheckBox.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/CheckMenuItem.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/ChoiceBox.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/ChoiceCore.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/ChoiceDialog.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/ChoiceModel.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/ColorPicker.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/ComboBox.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/ComboBoxBase.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/ContentDisplay.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/ContextMenu.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/Control.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/DatePicker.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/Dialog.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/DialogEvent.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/DialogField.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/DialogPane.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/DialogRow.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/Hyperlink.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/IndexRange.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/IndexedCell.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/Label.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/Labeled.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/ListCell.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/ListView.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/Menu.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/MenuBar.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/MenuContent.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/MenuItem.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/MenuLink.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/MenuRow.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/MultipleSelectionModel.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/Pagination.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/PasswordField.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/PopupControl.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/ProgressBar.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/ProgressIndicator.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/RadioButton.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/RowFlow.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/RowPress.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/RowSelection.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/ScrollBar.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/ScrollPane.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/SelectionMode.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/SelectionModel.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/Separator.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/SeparatorMenuItem.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/SingleSelectionModel.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/Slider.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/Spinner.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/SpinnerValueFactory.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/SplitPane.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/Tab.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/TabPane.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/TableCell.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/TableColumn.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/TableColumnBase.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/TablePosition.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/TablePositionBase.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/TableView.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/TextArea.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/TextField.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/TextInputControl.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/TextInputDialog.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/TitledPane.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/Toggle.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/ToggleButton.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/ToggleGroup.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/ToolBar.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/Tooltip.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/TreeCell.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/TreeItem.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/TreeView.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/cell/CheckBoxTableCell.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/cell/PropertyValueFactory.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/cell/TextFieldTableCell.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/cell/package-info.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/control/package-info.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/effect/BlurType.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/effect/BoxBlur.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/effect/DropShadow.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/effect/Effect.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/effect/GaussianBlur.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/effect/Glow.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/effect/InnerShadow.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/effect/package-info.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/image/Image.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/image/ImageView.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/image/PixelReader.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/image/PixelWriter.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/image/WritableImage.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/image/package-info.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/input/ContextMenuEvent.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/input/GestureEvent.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/input/InputEvent.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/input/KeyCode.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/input/KeyCodeCombination.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/input/KeyCombination.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/input/KeyEvent.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/input/MouseButton.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/input/MouseEvent.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/input/PickResult.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/input/ScrollEvent.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/input/SwipeEvent.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/input/TouchEvent.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/input/TouchPoint.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/input/package-info.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/layout/AnchorPane.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/layout/Background.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/layout/BackgroundFill.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/layout/BackgroundImage.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/layout/BackgroundPosition.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/layout/BackgroundRepeat.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/layout/BackgroundSize.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/layout/Border.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/layout/BorderImage.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/layout/BorderPane.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/layout/BorderRepeat.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/layout/BorderStroke.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/layout/BorderStrokeStyle.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/layout/BorderWidths.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/layout/ColumnConstraints.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/layout/ConstraintsBase.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/layout/CornerRadii.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/layout/FlowPane.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/layout/GridPane.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/layout/HBox.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/layout/LayoutSupport.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/layout/Pane.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/layout/Priority.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/layout/Region.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/layout/RowConstraints.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/layout/StackPane.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/layout/StyleImages.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/layout/TilePane.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/layout/VBox.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/layout/package-info.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/package-info.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/paint/Color.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/paint/CycleMethod.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/paint/LinearGradient.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/paint/Paint.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/paint/RadialGradient.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/paint/Stop.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/paint/package-info.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/shape/Arc.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/shape/ArcTo.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/shape/ArcType.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/shape/Circle.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/shape/ClosePath.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/shape/CubicCurve.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/shape/CubicCurveTo.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/shape/Ellipse.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/shape/FillRule.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/shape/HLineTo.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/shape/Line.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/shape/LineTo.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/shape/MoveTo.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/shape/Path.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/shape/PathElement.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/shape/Polygon.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/shape/Polyline.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/shape/QuadCurve.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/shape/QuadCurveTo.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/shape/Rectangle.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/shape/SVGPath.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/shape/Shape.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/shape/StrokeLineCap.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/shape/StrokeLineJoin.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/shape/StrokeType.java rename maven/{android-compat/src/test/java/com/codename1/androidcompat/jdk/FileTypeContractTest.java => javafx-compat/src/main/java/javafx/scene/shape/VLineTo.java} (59%) create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/shape/package-info.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/text/Font.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/text/FontPosture.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/text/FontWeight.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/text/Text.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/text/TextAlignment.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/text/TextFlow.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/text/package-info.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/transform/Affine.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/transform/NonInvertibleTransformException.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/transform/Rotate.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/transform/Scale.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/transform/Shear.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/transform/TDouble.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/transform/Transform.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/transform/Translate.java create mode 100644 maven/javafx-compat/src/main/java/javafx/scene/transform/package-info.java create mode 100644 maven/javafx-compat/src/main/java/javafx/stage/FileChooser.java create mode 100644 maven/javafx-compat/src/main/java/javafx/stage/Modality.java create mode 100644 maven/javafx-compat/src/main/java/javafx/stage/PopupWindow.java create mode 100644 maven/javafx-compat/src/main/java/javafx/stage/Screen.java create mode 100644 maven/javafx-compat/src/main/java/javafx/stage/Stage.java create mode 100644 maven/javafx-compat/src/main/java/javafx/stage/StageStyle.java create mode 100644 maven/javafx-compat/src/main/java/javafx/stage/Window.java create mode 100644 maven/javafx-compat/src/main/java/javafx/stage/WindowEvent.java create mode 100644 maven/javafx-compat/src/main/java/javafx/stage/package-info.java create mode 100644 maven/javafx-compat/src/main/java/javafx/util/Callback.java create mode 100644 maven/javafx-compat/src/main/java/javafx/util/Duration.java create mode 100644 maven/javafx-compat/src/main/java/javafx/util/Pair.java create mode 100644 maven/javafx-compat/src/main/java/javafx/util/StringConverter.java create mode 100644 maven/javafx-compat/src/main/java/javafx/util/converter/BooleanStringConverter.java create mode 100644 maven/javafx-compat/src/main/java/javafx/util/converter/DefaultStringConverter.java create mode 100644 maven/javafx-compat/src/main/java/javafx/util/converter/DoubleStringConverter.java create mode 100644 maven/javafx-compat/src/main/java/javafx/util/converter/IntegerStringConverter.java create mode 100644 maven/javafx-compat/src/main/java/javafx/util/converter/NumberStringConverter.java create mode 100644 maven/javafx-compat/src/main/java/javafx/util/converter/package-info.java create mode 100644 maven/javafx-compat/src/main/java/javafx/util/package-info.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/AnimationTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/ApiNamingTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/ApplicationWindowTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/BindingsTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/ButtonBarAndMnemonicsTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/CanvasTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/ChartTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/CollapsedTransformTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/ConcurrencyTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/ControlLookTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/ControlSizeTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/ControlsBasicTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/ControlsContainerTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/ControlsDataTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/DeviceMatrixTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/EffectAndRegionShapeTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/EventTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/FileChooserTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/FileUrlImageTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/FontLoadingTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/FxInteropTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/GeometryTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/GradientAndImageTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/GroupBoundsTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/LayoutPanesTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/NarrowScreenTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/NativeChromeTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/ObservableListTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/ObservableMapSetTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/PortControlsTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/PortInputTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/Probe.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/PropertyTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/PropertyValueFactoryTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/SceneGraphTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/ScreenSpaceClipTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/ShadowEffectTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/ShapeTransitionTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/ShapesTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/SvgPathTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/TableEditSortTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/TouchAndTextFlowTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/TransformTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/TransformationListTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/UtilTest.java create mode 100644 maven/javafx-compat/src/test/java/com/codename1/fxcompat/WritableImageTest.java create mode 100644 maven/javafx-compat/src/test/resources/cn1-hooks.txt create mode 100644 maven/javafx-compat/src/test/resources/dot.png create mode 100644 maven/swing-compat/pom.xml create mode 100644 maven/swing-compat/spotbugs-exclude.xml create mode 100644 maven/swing-compat/src/main/java/.gitkeep create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/SwingInterop.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/com/formdev/flatlaf/FlatClientProperties.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/com/formdev/flatlaf/FlatDarculaLaf.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/com/formdev/flatlaf/FlatDarkLaf.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/com/formdev/flatlaf/FlatIntelliJLaf.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/com/formdev/flatlaf/FlatLaf.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/com/formdev/flatlaf/FlatLightLaf.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/com/formdev/flatlaf/FlatSystemProperties.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/com/formdev/flatlaf/extras/FlatAnimatedLafChange.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/com/formdev/flatlaf/extras/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/com/formdev/flatlaf/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/com/formdev/flatlaf/themes/FlatMacDarkLaf.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/com/formdev/flatlaf/themes/FlatMacLightLaf.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/com/formdev/flatlaf/themes/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/com/formdev/flatlaf/util/SystemInfo.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/com/formdev/flatlaf/util/UIScale.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/com/formdev/flatlaf/util/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/AWTError.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/AWTEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/AWTException.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/AWTKeyStroke.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/ActiveEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/Adjustable.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/AlphaComposite.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/BasicStroke.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/BorderLayout.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/Canvas.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/CardLayout.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/Color.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/Component.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/ComponentOrientation.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/Composite.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/Container.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/ContainerOrderFocusTraversalPolicy.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/Cursor.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/DefaultFocusTraversalPolicy.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/DefaultKeyboardFocusManager.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/Desktop.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/Dialog.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/Dimension.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/EventQueue.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/FlowLayout.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/FocusTraversalPolicy.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/Font.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/FontFormatException.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/FontMetrics.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/Frame.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/GradientPaint.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/Graphics.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/Graphics2D.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/GraphicsConfiguration.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/GraphicsDevice.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/GraphicsEnvironment.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/GridBagConstraints.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/GridBagLayout.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/GridLayout.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/HeadlessException.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/Image.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/Insets.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/ItemSelectable.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/KeyboardFocusManager.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/LayoutManager.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/LayoutManager2.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/Paint.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/Panel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/Point.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/Polygon.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/Rectangle.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/RenderingHints.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/ScreenEnvironment.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/Shape.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/Stroke.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/Taskbar.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/Toolkit.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/Transparency.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/Window.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/datatransfer/Clipboard.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/datatransfer/ClipboardOwner.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/datatransfer/DataFlavor.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/datatransfer/StringSelection.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/datatransfer/Transferable.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/datatransfer/UnsupportedFlavorException.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/datatransfer/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/desktop/AppEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/desktop/PreferencesEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/desktop/PreferencesHandler.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/desktop/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/dnd/DnDConstants.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/dnd/DropTarget.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/dnd/DropTargetAdapter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/dnd/DropTargetContext.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/dnd/DropTargetDragEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/dnd/DropTargetDropEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/dnd/DropTargetEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/dnd/DropTargetListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/dnd/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/event/AWTEventListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/event/ActionEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/event/ActionListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/event/AdjustmentEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/event/AdjustmentListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/event/ComponentAdapter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/event/ComponentEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/event/ComponentListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/event/ContainerAdapter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/event/ContainerEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/event/ContainerListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/event/FocusAdapter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/event/FocusEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/event/FocusListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/event/HierarchyEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/event/HierarchyListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/event/InputEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/event/InvocationEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/event/ItemEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/event/ItemListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/event/KeyAdapter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/event/KeyEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/event/KeyListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/event/MouseAdapter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/event/MouseEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/event/MouseListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/event/MouseMotionAdapter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/event/MouseMotionListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/event/MouseWheelEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/event/MouseWheelListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/event/TextEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/event/TextListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/event/WindowAdapter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/event/WindowEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/event/WindowFocusListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/event/WindowListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/event/WindowStateListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/event/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/geom/AffineTransform.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/geom/Arc2D.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/geom/CubicCurve2D.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/geom/Dimension2D.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/geom/Ellipse2D.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/geom/FlatteningPathIterator.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/geom/GeneralPath.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/geom/IllegalPathStateException.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/geom/Line2D.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/geom/NoninvertibleTransformException.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/geom/Outline.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/geom/Path2D.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/geom/PathIterator.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/geom/Point2D.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/geom/QuadCurve2D.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/geom/Rectangle2D.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/geom/RectangularShape.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/geom/RoundRectangle2D.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/geom/SegmentIterator.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/geom/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/image/BufferedImage.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/image/BufferedImageOp.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/image/ImageObserver.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/image/RenderedImage.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/image/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/print/PrinterException.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/awt/print/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/beans/BeanDescriptor.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/beans/BeanInfo.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/beans/Beans.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/beans/ExceptionListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/beans/FeatureDescriptor.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/beans/IndexedPropertyChangeEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/beans/IntrospectionException.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/beans/Introspector.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/beans/PropertyChangeEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/beans/PropertyChangeListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/beans/PropertyChangeListenerProxy.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/beans/PropertyChangeSupport.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/beans/PropertyVetoException.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/beans/VetoableChangeListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/java/beans/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/accessibility/Accessible.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/accessibility/AccessibleContext.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/accessibility/AccessibleRelation.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/accessibility/AccessibleRelationSet.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/accessibility/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/imageio/ImageIO.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/imageio/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/AbstractAction.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/AbstractButton.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/AbstractCellEditor.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/AbstractListModel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/AbstractSpinnerModel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/Action.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/ActionMap.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/BorderFactory.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/BoundedRangeModel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/Box.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/BoxLayout.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/ButtonGroup.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/ButtonModel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/CellEditor.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/ComboBoxEditor.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/ComboBoxModel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/ComponentInputMap.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/DefaultBoundedRangeModel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/DefaultButtonModel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/DefaultCellEditor.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/DefaultComboBoxModel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/DefaultListCellRenderer.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/DefaultListModel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/DefaultListSelectionModel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/DefaultRowSorter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/DefaultSingleSelectionModel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/DropMode.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/GroupLayout.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/Icon.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/ImageIcon.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/InputMap.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/InputVerifier.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JButton.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JCheckBox.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JCheckBoxMenuItem.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JColorChooser.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JComboBox.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JComponent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JDesktopPane.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JDialog.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JEditorPane.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JFileChooser.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JFormattedTextField.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JFrame.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JInternalFrame.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JLabel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JLayeredPane.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JList.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JMenu.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JMenuBar.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JMenuItem.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JOptionPane.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JPanel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JPasswordField.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JPopupMenu.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JProgressBar.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JRadioButton.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JRadioButtonMenuItem.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JRootPane.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JScrollBar.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JScrollPane.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JSeparator.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JSlider.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JSpinner.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JSplitPane.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JTabbedPane.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JTable.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JTextArea.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JTextField.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JTextPane.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JToggleButton.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JToolBar.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JTree.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JViewport.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/JWindow.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/KeyStroke.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/LayoutStyle.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/ListCellRenderer.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/ListModel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/ListSelectionModel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/LookAndFeel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/MenuElement.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/MenuSelectionManager.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/MutableComboBoxModel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/OverlayLayout.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/ProgressMonitor.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/RootPaneContainer.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/RowFilter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/RowSorter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/ScrollPaneConstants.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/ScrollPaneLayout.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/Scrollable.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/SingleSelectionModel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/SizeRequirements.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/SortOrder.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/SpinnerDateModel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/SpinnerListModel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/SpinnerModel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/SpinnerNumberModel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/Spring.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/SpringLayout.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/SwingConstants.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/SwingUtilities.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/SwingWorker.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/Timer.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/ToolTipManager.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/TransferHandler.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/UIDefaults.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/UIManager.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/UnsupportedLookAndFeelException.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/WindowConstants.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/border/AbstractBorder.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/border/BevelBorder.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/border/Border.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/border/CompoundBorder.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/border/EmptyBorder.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/border/EtchedBorder.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/border/LineBorder.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/border/MatteBorder.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/border/SoftBevelBorder.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/border/TitledBorder.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/border/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/event/CaretEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/event/CaretListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/event/CellEditorListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/event/ChangeEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/event/ChangeListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/event/DocumentEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/event/DocumentListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/event/EventListenerList.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/event/HyperlinkEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/event/HyperlinkListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/event/InternalFrameAdapter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/event/InternalFrameEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/event/InternalFrameListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/event/ListDataEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/event/ListDataListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/event/ListSelectionEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/event/ListSelectionListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/event/MenuEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/event/MenuListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/event/PopupMenuEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/event/PopupMenuListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/event/RowSorterEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/event/RowSorterListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/event/SwingPropertyChangeSupport.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/event/TableColumnModelEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/event/TableColumnModelListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/event/TableModelEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/event/TableModelListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/event/TreeExpansionEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/event/TreeExpansionListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/event/TreeModelEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/event/TreeModelListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/event/TreeSelectionEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/event/TreeSelectionListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/event/TreeWillExpandListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/event/UndoableEditEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/event/UndoableEditListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/event/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/filechooser/FileFilter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/filechooser/FileNameExtensionFilter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/filechooser/FileView.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/filechooser/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/plaf/BorderUIResource.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/plaf/ColorUIResource.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/plaf/ComponentUI.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/plaf/FontUIResource.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/plaf/TreeUI.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/plaf/UIResource.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/plaf/basic/BasicBorders.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/plaf/basic/BasicComboBoxEditor.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/plaf/basic/BasicTreeUI.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/plaf/basic/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/plaf/metal/DefaultMetalTheme.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/plaf/metal/MetalLookAndFeel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/plaf/metal/MetalTheme.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/plaf/metal/OceanTheme.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/plaf/metal/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/plaf/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/table/AbstractTableModel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/table/DefaultTableCellRenderer.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/table/DefaultTableColumnModel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/table/DefaultTableModel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/table/JTableHeader.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/table/TableCellEditor.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/table/TableCellRenderer.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/table/TableColumn.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/table/TableColumnModel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/table/TableModel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/table/TableRowSorter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/table/TableStringConverter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/table/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/text/AbstractDocument.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/text/AttributeSet.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/text/BadLocationException.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/text/Caret.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/text/DefaultCaret.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/text/DefaultEditorKit.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/text/DefaultFormatter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/text/DefaultFormatterFactory.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/text/DefaultStyledDocument.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/text/Document.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/text/DocumentFilter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/text/Element.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/text/JTextComponent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/text/MutableAttributeSet.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/text/NumberFormatter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/text/PlainDocument.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/text/Position.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/text/Segment.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/text/SimpleAttributeSet.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/text/Style.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/text/StyleConstants.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/text/StyleContext.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/text/StyledDocument.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/text/TextAction.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/text/html/HTMLDocument.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/text/html/HTMLFrameHyperlinkEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/text/html/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/text/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/tree/DefaultMutableTreeNode.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/tree/DefaultTreeCellEditor.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/tree/DefaultTreeCellRenderer.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/tree/DefaultTreeModel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/tree/DefaultTreeSelectionModel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/tree/ExpandVetoException.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/tree/MutableTreeNode.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/tree/RowMapper.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/tree/TreeCellEditor.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/tree/TreeCellRenderer.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/tree/TreeModel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/tree/TreeNode.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/tree/TreePath.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/tree/TreeSelectionModel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/tree/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/undo/AbstractUndoableEdit.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/undo/CannotRedoException.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/undo/CannotUndoException.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/undo/CompoundEdit.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/undo/UndoManager.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/undo/UndoableEdit.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/undo/UndoableEditSupport.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/javax/swing/undo/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/fife/ui/rsyntaxtextarea/DocumentRange.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/fife/ui/rsyntaxtextarea/Lexer.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/fife/ui/rsyntaxtextarea/RSyntaxTextArea.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/fife/ui/rsyntaxtextarea/SyntaxConstants.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/fife/ui/rsyntaxtextarea/Theme.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/fife/ui/rsyntaxtextarea/TokenNames.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/fife/ui/rsyntaxtextarea/TokenTypes.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/fife/ui/rsyntaxtextarea/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/fife/ui/rtextarea/Gutter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/fife/ui/rtextarea/RTextArea.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/fife/ui/rtextarea/RTextAreaBase.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/fife/ui/rtextarea/RTextScrollPane.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/fife/ui/rtextarea/SearchContext.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/fife/ui/rtextarea/SearchEngine.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/fife/ui/rtextarea/SearchResult.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/fife/ui/rtextarea/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/beans/AbstractBean.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/beans/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/HorizontalLayout.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/JXBusyLabel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/JXButton.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/JXCollapsiblePane.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/JXDatePicker.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/JXDialog.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/JXErrorPane.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/JXFormattedTextField.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/JXFrame.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/JXHeader.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/JXHyperlink.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/JXLabel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/JXList.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/JXMonthView.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/JXPanel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/JXSearchField.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/JXStatusBar.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/JXTable.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/JXTaskPane.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/JXTaskPaneContainer.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/JXTextArea.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/JXTextField.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/JXTitledPanel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/JXTitledSeparator.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/JXTree.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/JXTreeTable.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/ScrollableSizeHint.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/VerticalLayout.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/action/AbstractActionExt.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/action/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/calendar/AbstractDateSelectionModel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/calendar/CalendarUtils.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/calendar/DateSelectionModel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/calendar/DateSet.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/calendar/DaySelectionModel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/calendar/DefaultDateSelectionModel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/calendar/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/decorator/AbstractHighlighter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/decorator/AlignmentHighlighter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/decorator/BorderHighlighter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/decorator/ColorHighlighter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/decorator/ComponentAdapter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/decorator/CompoundHighlighter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/decorator/EnabledHighlighter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/decorator/FontHighlighter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/decorator/HighlightPredicate.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/decorator/Highlighter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/decorator/HighlighterFactory.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/decorator/IconHighlighter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/decorator/PainterHighlighter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/decorator/ToolTipHighlighter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/decorator/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/error/ErrorEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/error/ErrorListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/error/ErrorSupport.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/error/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/event/DateSelectionEvent.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/event/DateSelectionListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/event/TableColumnModelExtListener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/event/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/hyperlink/AbstractHyperlinkAction.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/hyperlink/HyperlinkAction.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/hyperlink/UrlOpener.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/hyperlink/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/painter/AbstractAreaPainter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/painter/AbstractLayoutPainter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/painter/AbstractPainter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/painter/AlphaPainter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/painter/BusyPainter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/painter/CheckerboardPainter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/painter/CompoundPainter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/painter/GlossPainter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/painter/ImagePainter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/painter/MattePainter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/painter/Painter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/painter/PinstripePainter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/painter/RectanglePainter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/painter/ShapePainter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/painter/TextPainter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/painter/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/prompt/PromptSupport.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/prompt/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/renderer/AbstractRenderer.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/renderer/BooleanValue.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/renderer/CellContext.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/renderer/CheckBoxProvider.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/renderer/ComponentProvider.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/renderer/DefaultListRenderer.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/renderer/DefaultTableRenderer.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/renderer/DefaultTreeRenderer.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/renderer/DefaultVisuals.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/renderer/FormatStringValue.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/renderer/IconAware.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/renderer/IconValue.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/renderer/IconValues.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/renderer/JRendererCheckBox.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/renderer/JRendererLabel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/renderer/LabelProvider.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/renderer/ListCellContext.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/renderer/MappedValue.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/renderer/PainterAware.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/renderer/StringValue.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/renderer/StringValues.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/renderer/TableCellContext.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/renderer/TreeCellContext.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/renderer/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/rollover/RolloverProducer.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/rollover/RolloverRenderer.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/rollover/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/sort/DefaultSortController.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/sort/ListSortController.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/sort/RowFilters.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/sort/SortController.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/sort/SortUtils.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/sort/StringValueProvider.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/sort/StringValueRegistry.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/sort/TableSortController.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/sort/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/table/ColumnControlButton.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/table/ColumnFactory.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/table/DefaultTableColumnModelExt.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/table/TableColumnExt.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/table/TableColumnModelExt.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/table/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/tree/TreeModelSupport.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/tree/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/treetable/AbstractMutableTreeTableNode.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/treetable/AbstractTreeTableModel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/treetable/DefaultMutableTreeTableNode.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/treetable/DefaultTreeTableModel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/treetable/MutableTreeTableNode.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/treetable/TreeTableModel.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/treetable/TreeTableNode.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/org/jdesktop/swingx/treetable/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/Align.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/AwtListeners.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/ButtonPeer.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/CanvasPeer.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/CellPainter.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/CellStamp.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/CellTheme.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/CheckBoxPeer.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/ClientProps.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/ComboPeer.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/Comparators.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/ContainerPeer.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/DatePickerPeer.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/DesktopLifecycle.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/DesktopToolkit.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/DialogForm.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/DisplayInputState.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/Dnd.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/EmbedHost.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/EventBridge.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/FilePicker.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/FontFiles.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/Fonts.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/FrameForm.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/G2D.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/GradientFill.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/Icons.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/ImageLoader.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/Indicators.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/InputState.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/KeyMap.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/LabelPeer.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/LafBorder.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/LafTheme.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/Launcher.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/LayoutBridge.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/ListEnumeration.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/MenuBridge.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/MenuItemPeer.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/MiniHtml.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/NativeImage.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/NativeWindows.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/Peer.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/PeerSupport.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/ProgressPeer.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/RadioButtonPeer.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/RootPan.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/ScrollDelegate.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/SecondaryWindows.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/SliderPeer.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/StyledText.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/SystemClipboard.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/TabbedPanePeer.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/TextAreaPeer.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/TextFieldPeer.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/TreeIcons.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/Units.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/ViewportPeer.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/WindowHost.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/WindowHosts.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/desktopcompat/rt/package-info.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/generated/compat/CompatRegistry.java create mode 100644 maven/swing-compat/src/main/java/com/codename1/generated/compat/package-info.java create mode 100644 maven/swing-compat/src/test/java/.gitkeep create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/ApiShapeTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/ApplicationWindowTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/ConstantParityTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/DesktopPaneAndFriendsTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/DesktopServicesTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/DialogsTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/DragAndDropTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/EdtAndGraphicsTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/EventDeliveryTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/FocusAndKeysFollowUpTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/FormattedFieldTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/FrameAndLayoutTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/InputInfrastructureTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/KernelTestBase.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/LayoutParityTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/LibraryLinkageTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/ListAndComboTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/MenusTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/NoJdkDesktopReferenceTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/PaintRoutingTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/PortKeyboardTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/ScrollPaneTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/SmallDisplayTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/SwingInteropTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/TextSyncTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/ToolkitListenersAndDesktopTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/UndoAndEditorKitTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/WindowsTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/WindowsTestBase.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/com/formdev/flatlaf/FlatClientPropertiesTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/com/formdev/flatlaf/FlatLafApiShapeTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/java/awt/CreatedFontTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/java/awt/ValueParityTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/java/awt/geom/GeomParityTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/java/beans/PropertyChangeSupportTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/javax/swing/ButtonLabelGapsTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/javax/swing/DefaultButtonModelTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/javax/swing/EditableComboBoxTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/javax/swing/EditorPaneHtmlTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/javax/swing/GroupLayoutParityTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/javax/swing/JSplitPaneTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/javax/swing/JTabbedPaneTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/javax/swing/JTableEditingTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/javax/swing/JTableTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/javax/swing/JTreeTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/javax/swing/ListWrapOrientationTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/javax/swing/LookAndFeelSwitchTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/javax/swing/RangeWidgetsTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/javax/swing/ScrollPaneBarsTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/javax/swing/ScrollPaneLayoutAndTreeUiTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/javax/swing/ScrollRevealReentrancyTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/javax/swing/SizeFollowsContentTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/javax/swing/SpinnerTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/javax/swing/SpringLayoutParityTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/javax/swing/StyledTextTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/javax/swing/SwingWorkerTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/javax/swing/TableTreeScrollPaneTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/javax/swing/TextAreaLineWrapTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/javax/swing/UIManagerTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/javax/swing/WidgetLookTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/javax/swing/border/BorderSidesTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/javax/swing/event/ListenerArrayTypesTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/javax/swing/tree/DefaultMutableTreeNodeTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/org/fife/ui/rsyntaxtextarea/RSyntaxTextAreaTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/org/jdesktop/swingx/CollapsibleAndTaskPaneTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/org/jdesktop/swingx/JXDatePickerTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/org/jdesktop/swingx/JXListAndTreeTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/org/jdesktop/swingx/JXMonthViewTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/org/jdesktop/swingx/JXTableTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/org/jdesktop/swingx/JXTreeTableTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/org/jdesktop/swingx/PainterWidgetsTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/org/jdesktop/swingx/PanelLayoutsTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/org/jdesktop/swingx/TitledPanelAndHeaderTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/org/jdesktop/swingx/WindowsAndErrorsTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/org/jdesktop/swingx/calendar/DateSelectionModelTest.java rename maven/{android-compat/src/test/java/com/codename1/androidcompat/jdk/FileCanonicalPathTest.java => swing-compat/src/test/java/com/codename1/desktopcompat/org/jdesktop/swingx/decorator/HighlighterFactoryParityTest.java} (52%) create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/org/jdesktop/swingx/hyperlink/HyperlinkTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/org/jdesktop/swingx/painter/PaintersTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/org/jdesktop/swingx/painter/RecordingGraphics.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/org/jdesktop/swingx/prompt/PromptSupportTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/org/jdesktop/swingx/table/ColumnControlButtonTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/org/jdesktop/swingx/treetable/TreeTableModelTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/rt/GradientFillTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/rt/MiniHtmlTest.java create mode 100644 maven/swing-compat/src/test/java/com/codename1/desktopcompat/rt/UnitsTest.java create mode 100644 maven/swing-compat/src/test/resources/com/codename1/desktopcompat/com/formdev/flatlaf/flatlaf-absent.txt diff --git a/CodenameOne/src/com/codename1/ui/Image.java b/CodenameOne/src/com/codename1/ui/Image.java index e5824e00cf9..6a6f041a264 100644 --- a/CodenameOne/src/com/codename1/ui/Image.java +++ b/CodenameOne/src/com/codename1/ui/Image.java @@ -1469,6 +1469,13 @@ public Image modifyAlpha(byte alpha, int removeColor) { public Graphics getGraphics() { Graphics g = new Graphics(Display.impl.getNativeGraphics(image)); rgbCache = null; // the cache will become invalid + // So do the scaled copies. A port that cannot draw an image at a size + // draws scaled(w, h) instead, which is a copy this image keeps -- at + // the image's own size too. An image drawn on again after it had been + // shown once went on showing the copy made the first time: on the + // native Linux port a picture that was drawn in steps stayed as it was + // after the first one, until the collector happened to drop the copy. + scaleCache = null; return g; } diff --git a/CodenameOne/src/com/codename1/ui/geom/GeneralPath.java b/CodenameOne/src/com/codename1/ui/geom/GeneralPath.java index 8348d54d659..317768e29f9 100644 --- a/CodenameOne/src/com/codename1/ui/geom/GeneralPath.java +++ b/CodenameOne/src/com/codename1/ui/geom/GeneralPath.java @@ -1991,8 +1991,23 @@ private static Shape intersection(Rectangle r, Shape s, GeneralPath out) { maxY = (float) (boundingRect.getY() + boundingRect.getHeight()); } if (rect2D.contains(curve.x(0.5), curve.y(0.5))) { - // It is fully inside, we can just add it to the out path - curve.addToPath(out, false); + // It is fully inside, we can just add it to the out path. + // It CONTINUES the subpath: passing join == false here + // made every curve open a subpath of its own, so a shape + // with a curved corner came back as one sliver per corner + // (the corner and the edge after it, closed across the + // shape) and its inside was no longer inside. Every port + // that narrows a shape clip with clipRect goes through + // this, which is how a gradient filled through a rounded + // rectangle was drawn as a hollow, skewed outline. The + // segment before may have ended outside and been clamped + // to the rectangle, in which case the outline first runs + // from there to where the curve really starts. + if (started && (Math.abs(prevX - origPrevX) > epsilon + || Math.abs(prevY - origPrevY) > epsilon)) { + out.lineTo((float) origPrevX, (float) origPrevY); + } + curve.addToPath(out, started); prevX = (float) curveEnd.getX(); prevY = (float) curveEnd.getY(); origPrevX = curveEnd.getX(); diff --git a/Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java b/Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java index 6b5368286c5..a013b5b5f9e 100644 --- a/Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java +++ b/Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java @@ -12582,7 +12582,60 @@ public void run() { //} checkEDT(); - + + // The editor is a Swing component, so it is built, added and focused on the + // AWT event thread and nowhere else. Built here, on the Codename One EDT, it + // raced the AWT thread that was already painting it: setCaretPosition off the + // AWT thread makes DefaultCaret queue its repaint there, and that repaint read + // the text view while setFont on this thread was replacing it -- an + // intermittent NullPointerException under DefaultCaret.repaintNewCaret. + // + // The hop waits, as flushGraphics and requestFullScreen do, so the editor is + // in place and isEditingText() answers true by the time this method returns, + // exactly as before. Nothing in the body waits for this thread in return: + // what the editor sends back goes through callSerially. + // + // One line of the body is not Swing's: the field's own text is set from the + // editor's when editing starts with a key. That fires the application's + // listeners, so it is handed back and done here, on the thread they expect. + final String[] initialText = new String[1]; + if (EventQueue.isDispatchThread()) { + editStringOnAwtThread(cmp, constraint, text, keyCode, initialText); + } else { + final String fText = text; + try { + EventQueue.invokeAndWait(new Runnable() { + public void run() { + editStringOnAwtThread(cmp, constraint, fText, keyCode, initialText); + } + }); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + return; + } catch (java.lang.reflect.InvocationTargetException ex) { + Throwable cause = ex.getCause(); + if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } + if (cause instanceof Error) { + throw (Error) cause; + } + throw new RuntimeException(cause); + } + } + if (initialText[0] != null) { + ((com.codename1.ui.TextField) cmp).setText(initialText[0]); + } + } + + /** + * The Swing half of editString: builds the native editor over the field and + * starts editing. Runs on the AWT event thread only. + * + * @param initialText receives the text the field itself is to be given, when + * editing starts with a key; the caller sets it on the Codename One EDT + */ + private void editStringOnAwtThread(final Component cmp, final int constraint, String text, final int keyCode, String[] initialText) { class Repainter { JComponent jcmp; javax.swing.border.Border origBorder; @@ -12793,7 +12846,7 @@ public void keyReleased(KeyEvent e) { setText(tf, text); setCaretPosition(tf, text.length()); if(cmp instanceof com.codename1.ui.TextField) { - ((com.codename1.ui.TextField) cmp).setText(getText(tf)); + initialText[0] = getText(tf); } } else { setText(tf, text); diff --git a/Ports/LinuxPort/nativeSources/cn1_linux_desktopwindow.c b/Ports/LinuxPort/nativeSources/cn1_linux_desktopwindow.c index 91bb053105d..c9adff7ada5 100644 --- a/Ports/LinuxPort/nativeSources/cn1_linux_desktopwindow.c +++ b/Ports/LinuxPort/nativeSources/cn1_linux_desktopwindow.c @@ -1191,6 +1191,136 @@ JAVA_BOOLEAN com_codename1_impl_linux_LinuxNative_mainWindowGetBounds___int_1ARR return JAVA_TRUE; } +/* + * Resizing the application's own window. + * + * The main window opens at the size initDisplay is given and, until this existed, + * could only be resized by the user: Display.setWindowSize reached the default + * implementation, which does nothing, so an application that asked for a 350x550 + * window drew into the 800x600 one it was born with. + * + * The size is the OUTER one, frame included, which is what the JavaSE port's + * setWindowSize (JFrame.setSize) and this port's secondary windows both mean. GTK + * resizes the client area, so the chrome has to be subtracted, and the chrome is + * not measurable until the window manager has reparented the window. The request + * is therefore kept pending and applied from configure-event, exactly as + * cn1DesktopApplyPendingOuterSize does for a secondary window. + */ +static int cn1MainOuterPending; +static int cn1MainRequestedOuterW; +static int cn1MainRequestedOuterH; +static int cn1MainConfigureHooked; + +static void cn1MainApplyPendingOuterSize(GtkWidget* main) { + int chromeW; + int chromeH; + int clientW; + int clientH; + if (main == 0 || !cn1MainOuterPending) { + return; + } + if (!cn1DesktopChromeInsets(main, &chromeW, &chromeH)) { + return; + } + clientW = cn1MainRequestedOuterW - chromeW; + clientH = cn1MainRequestedOuterH - chromeH; + if (clientW < 1) { + clientW = 1; + } + if (clientH < 1) { + clientH = 1; + } + /* Cleared before the resize: the resize comes back through configure-event. */ + cn1MainOuterPending = 0; + gtk_window_resize(GTK_WINDOW(main), clientW, clientH); +} + +static gboolean cn1MainOnWindowConfigure(GtkWidget* widget, GdkEventConfigure* e, gpointer data) { + (void) e; + (void) data; + cn1MainApplyPendingOuterSize(widget); + return FALSE; +} + +static void cn1MainWindowSetSizeOnMain(void* arg) { + CN1DesktopOp* op = (CN1DesktopOp*) arg; + GtkWidget* main = cn1LinuxWindowWidget(); + if (main == 0) { + /* Headless: the surface size is the screenshot's, and is not ours to change. */ + return; + } + if (!cn1MainConfigureHooked) { + cn1MainConfigureHooked = 1; + g_signal_connect(main, "configure-event", G_CALLBACK(cn1MainOnWindowConfigure), 0); + } + cn1MainRequestedOuterW = op->a > 0 ? op->a : 1; + cn1MainRequestedOuterH = op->b > 0 ? op->b : 1; + cn1MainOuterPending = 1; + cn1MainApplyPendingOuterSize(main); + if (cn1MainOuterPending) { + /* No frame to measure yet -- or never, with no window manager, where the + * client area is the outer size. Resize to the figure asked for and let + * configure-event correct it once there is chrome. */ + gtk_window_resize(GTK_WINDOW(main), cn1MainRequestedOuterW, cn1MainRequestedOuterH); + } +} + +JAVA_VOID com_codename1_impl_linux_LinuxNative_mainWindowSetSize___int_int( + CODENAME_ONE_THREAD_STATE, JAVA_INT width, JAVA_INT height) { + CN1DesktopOp op; + memset(&op, 0, sizeof(op)); + op.a = width; + op.b = height; + cn1LinuxRunOnMainAndWait(cn1MainWindowSetSizeOnMain, &op); +} + +/* The main window's outer rectangle: what mainWindowSetSize accepts, so that a + * size read back is the size that was set. mainWindowGetBounds answers the client + * area, which is what centring over a Form wants and is smaller by the chrome. */ +static void cn1MainWindowGetFrameOnMain(void* arg) { + CN1DesktopOp* op = (CN1DesktopOp*) arg; + GtkWidget* main = cn1LinuxWindowWidget(); + int chromeW; + int chromeH; + op->a = 0; + if (main == 0) { + return; + } + gtk_window_get_position(GTK_WINDOW(main), &op->out[0], &op->out[1]); + gtk_window_get_size(GTK_WINDOW(main), &op->out[2], &op->out[3]); + if (cn1DesktopChromeInsets(main, &chromeW, &chromeH)) { + op->out[2] += chromeW; + op->out[3] += chromeH; + } else if (cn1MainOuterPending) { + op->out[2] = cn1MainRequestedOuterW; + op->out[3] = cn1MainRequestedOuterH; + } + op->a = 1; +} + +JAVA_BOOLEAN com_codename1_impl_linux_LinuxNative_mainWindowGetFrame___int_1ARRAY_R_boolean( + CODENAME_ONE_THREAD_STATE, JAVA_OBJECT out) { + CN1DesktopOp op; + JAVA_INT* arr; + if (out == JAVA_NULL) { + return JAVA_FALSE; + } + if ((int) (*(JAVA_ARRAY) out).length < 4) { + return JAVA_FALSE; + } + memset(&op, 0, sizeof(op)); + cn1LinuxRunOnMainAndWait(cn1MainWindowGetFrameOnMain, &op); + if (!op.a) { + return JAVA_FALSE; + } + arr = (JAVA_INT*) CN1_ARRAY_DATA(out); + arr[0] = op.out[0]; + arr[1] = op.out[1]; + arr[2] = op.out[2]; + arr[3] = op.out[3]; + return JAVA_TRUE; +} + JAVA_INT com_codename1_impl_linux_LinuxNative_desktopWindowGetWidth___int_R_int( CODENAME_ONE_THREAD_STATE, JAVA_INT slot) { CN1LinuxWindow* w = slotAt(slot); diff --git a/Ports/LinuxPort/nativeSources/cn1_linux_edit.c b/Ports/LinuxPort/nativeSources/cn1_linux_edit.c index 67c03cdda3f..59b5fc0703d 100644 --- a/Ports/LinuxPort/nativeSources/cn1_linux_edit.c +++ b/Ports/LinuxPort/nativeSources/cn1_linux_edit.c @@ -44,6 +44,8 @@ typedef struct { GtkTextBuffer* buffer; /* GtkTextView buffer, or NULL for single-line */ int singleLine; volatile int done; + /* How the edit ended when Tab ended it: 1 forwards, 2 backwards, else 0. */ + volatile int tab; char* text; /* latest committed/changed text (heap) */ /* The window whose overlay hosts this editor, so teardown removes it from the * same place it was added. */ @@ -102,6 +104,28 @@ static gboolean cn1EditFocusOut(GtkWidget* w, GdkEvent* ev, gpointer data) { return FALSE; } +/* + * Tab in a single-line field ends the edit and is reported, so the port hands the + * key to the form and the focus moves on. Left to GTK it moved GTK's own focus: + * the entry is the only focusable widget of the window, so the edit ended on the + * focus-out and the focus of the form stayed where it was -- the next field was + * out of the keyboard's reach. + */ +static gboolean cn1EditKeyPress(GtkWidget* w, GdkEventKey* ev, gpointer data) { + CN1Edit* e = (CN1Edit*) data; + (void) w; + if ((ev->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK)) != 0) { + return FALSE; + } + if (ev->keyval == GDK_KEY_Tab || ev->keyval == GDK_KEY_KP_Tab || ev->keyval == GDK_KEY_ISO_Left_Tab) { + cn1EditCaptureText(e); + e->tab = (ev->keyval == GDK_KEY_ISO_Left_Tab || (ev->state & GDK_SHIFT_MASK) != 0) ? 2 : 1; + e->done = 1; + return TRUE; + } + return FALSE; +} + static void cn1EditApplyStyle(GtkWidget* w, CN1Font* font, int fg, int bg) { if (font && font->desc) { gtk_widget_override_font(w, font->desc); @@ -143,6 +167,7 @@ static void cn1EditCreateOnMain(void* p) { cn1EditApplyStyle(e->entry, req->font, req->fg, req->bg); g_signal_connect(e->entry, "changed", G_CALLBACK(cn1EditChanged), e); g_signal_connect(e->entry, "activate", G_CALLBACK(cn1EditActivate), e); + g_signal_connect(e->entry, "key-press-event", G_CALLBACK(cn1EditKeyPress), e); g_signal_connect(e->entry, "focus-out-event", G_CALLBACK(cn1EditFocusOut), e); } else { /* Use the GtkTextView directly as the overlay widget rather than wrapping it @@ -171,7 +196,15 @@ static void cn1EditCreateOnMain(void* p) { g_object_ref_sink(e->container); cn1LinuxOverlayAdd(req->slot, e->container, req->x, req->y, req->w, req->h); - gtk_widget_grab_focus(e->container); + if (e->entry) { + /* An entry given the focus selects all of its text, so the first key typed + * replaced what the field held. The caret goes after the text instead, as + * it does in the field the editor stands in for. */ + gtk_entry_grab_focus_without_selecting(GTK_ENTRY(e->entry)); + gtk_editable_set_position(GTK_EDITABLE(e->entry), -1); + } else { + gtk_widget_grab_focus(e->container); + } req->result = e; } @@ -202,6 +235,11 @@ JAVA_BOOLEAN com_codename1_impl_linux_LinuxNative_editIsDone___long_R_boolean(CO return (e && e->done) ? JAVA_TRUE : JAVA_FALSE; } +JAVA_INT com_codename1_impl_linux_LinuxNative_editTabbed___long_R_int(CODENAME_ONE_THREAD_STATE, JAVA_LONG peer) { + CN1Edit* e = (CN1Edit*) (intptr_t) peer; + return e ? (JAVA_INT) e->tab : 0; +} + JAVA_OBJECT com_codename1_impl_linux_LinuxNative_editGetText___long_R_java_lang_String(CODENAME_ONE_THREAD_STATE, JAVA_LONG peer) { CN1Edit* e = (CN1Edit*) (intptr_t) peer; JAVA_OBJECT result; diff --git a/Ports/LinuxPort/nativeSources/cn1_linux_image.c b/Ports/LinuxPort/nativeSources/cn1_linux_image.c index 6b8eea1d168..cff7cd2feba 100644 --- a/Ports/LinuxPort/nativeSources/cn1_linux_image.c +++ b/Ports/LinuxPort/nativeSources/cn1_linux_image.c @@ -34,8 +34,15 @@ */ #include "cn1_linux_gfx.h" +#include +#include +#include #include #include +#include +#ifdef __GLIBC__ +#include +#endif extern const char* stringToUTF8(CODENAME_ONE_THREAD_STATE, JAVA_OBJECT str); extern JAVA_OBJECT allocArray(CODENAME_ONE_THREAD_STATE, int length, struct clazz* type, int primitiveSize, int dim); @@ -44,11 +51,61 @@ int cn1LinuxSurfaceToPng(cairo_surface_t* surface, unsigned char** outData, int* #define CN1I(p) ((CN1Image*) (intptr_t) (p)) +/* + * How many images are alive and how many bytes their surfaces hold. An image is + * made on whatever thread asks for one and destroyed on the drawing thread, hence + * atomics. The numbers answer the question a resident-set figure cannot: whether + * memory is in pictures the port still holds, or somewhere else. + * + * CN1_LOG_IMAGES in the environment prints them to stderr, at most every 50 ms, + * as "[IMAGES] live= bytes= made= freed=" -- the same + * kind of switch as the collector's CN1_GC_LOG_CYCLES, and like it one cached + * getenv when it is off. + */ +static _Atomic long cn1ImagesLive = 0; +static _Atomic long cn1ImageBytesLive = 0; +static _Atomic long cn1ImagesMade = 0; +static _Atomic long cn1ImagesFreed = 0; + +static void cn1LogImages(void) { + static _Atomic int logImages = -1; + static _Atomic long long lastLogged = 0; + struct timespec now; + long long millis; + int on = atomic_load_explicit(&logImages, memory_order_relaxed); + if (on < 0) { + on = getenv("CN1_LOG_IMAGES") ? 1 : 0; + atomic_store_explicit(&logImages, on, memory_order_relaxed); + } + if (!on) { + return; + } + clock_gettime(CLOCK_MONOTONIC, &now); + millis = (long long) now.tv_sec * 1000 + now.tv_nsec / 1000000; + if (millis - atomic_load_explicit(&lastLogged, memory_order_relaxed) < 50) { + return; + } + atomic_store_explicit(&lastLogged, millis, memory_order_relaxed); + fprintf(stderr, "[IMAGES] live=%ld bytes=%ld made=%ld freed=%ld\n", + atomic_load_explicit(&cn1ImagesLive, memory_order_relaxed), + atomic_load_explicit(&cn1ImageBytesLive, memory_order_relaxed), + atomic_load_explicit(&cn1ImagesMade, memory_order_relaxed), + atomic_load_explicit(&cn1ImagesFreed, memory_order_relaxed)); +} + +static long cn1SurfaceBytes(cairo_surface_t* surface) { + return (long) cairo_image_surface_get_stride(surface) * cairo_image_surface_get_height(surface); +} + static CN1Image* cn1WrapSurface(cairo_surface_t* surface) { CN1Image* img = (CN1Image*) calloc(1, sizeof(CN1Image)); img->surface = surface; img->width = cairo_image_surface_get_width(surface); img->height = cairo_image_surface_get_height(surface); + atomic_fetch_add_explicit(&cn1ImagesLive, 1, memory_order_relaxed); + atomic_fetch_add_explicit(&cn1ImageBytesLive, cn1SurfaceBytes(surface), memory_order_relaxed); + atomic_fetch_add_explicit(&cn1ImagesMade, 1, memory_order_relaxed); + cn1LogImages(); return img; } @@ -127,6 +184,10 @@ JAVA_LONG com_codename1_impl_linux_LinuxNative_createImageFromBytes___byte_1ARRA if (data == JAVA_NULL || length <= 0) { return 0; } + /* Never read outside the array, whatever the caller passed. */ + if (offset < 0 || length > ((JAVA_ARRAY) data)->length - offset) { + return 0; + } bytes = (unsigned char*) CN1_ARRAY_DATA(data); stream = g_memory_input_stream_new_from_data(bytes + offset, length, 0); pix = gdk_pixbuf_new_from_stream(stream, 0, 0); @@ -235,6 +296,98 @@ JAVA_LONG com_codename1_impl_linux_LinuxNative_getImageGraphics___long_R_long(CO return (JAVA_LONG) (intptr_t) g; } +/* + * Disposal. + * + * An image handle's finalizer runs on the collector's thread while the event + * dispatch thread may be inside cairo with the very surface, so the finalizer only + * queues the pointer (releaseImage) and the drawing thread frees what is queued + * between frames (nextReleasedImage / disposeImage). Until these existed nothing + * in the port destroyed an image surface at all. + */ +/* + * Freeing is not giving back. glibc keeps what free() hands it for the next + * malloc, and a surface is 1.9 MB at 800x600 -- below the size it maps on its + * own once the threshold has adapted, so the pages stay in the process. An + * application that draws a picture per frame freed every one of them and was + * still measured at 340 MB resident with 58 MB in use; malloc_trim(0) took it to + * 188 MB. The pages are returned once this many bytes of surfaces were freed + * since the last time, which is about once per collection. Other C libraries + * have no such call and keep their own counsel. + */ +#define CN1_TRIM_AFTER_BYTES (32L * 1024 * 1024) +static long cn1FreedSinceTrim = 0; + +static pthread_mutex_t cn1ReleasedLock = PTHREAD_MUTEX_INITIALIZER; +static CN1Image** cn1Released = 0; +static int cn1ReleasedCount = 0; +static int cn1ReleasedCapacity = 0; + +JAVA_VOID com_codename1_impl_linux_LinuxNative_releaseImage___long(CODENAME_ONE_THREAD_STATE, JAVA_LONG image) { + CN1Image* img = CN1I(image); + if (!img) { + return; + } + pthread_mutex_lock(&cn1ReleasedLock); + if (cn1ReleasedCount == cn1ReleasedCapacity) { + int capacity = cn1ReleasedCapacity == 0 ? 64 : cn1ReleasedCapacity * 2; + CN1Image** grown = (CN1Image**) realloc(cn1Released, sizeof(CN1Image*) * (size_t) capacity); + if (grown == 0) { + /* Out of memory for the queue itself: the image stays allocated, which + * is what happened to every image before this queue existed. */ + pthread_mutex_unlock(&cn1ReleasedLock); + return; + } + cn1Released = grown; + cn1ReleasedCapacity = capacity; + } + cn1Released[cn1ReleasedCount++] = img; + pthread_mutex_unlock(&cn1ReleasedLock); +} + +JAVA_LONG com_codename1_impl_linux_LinuxNative_nextReleasedImage___R_long(CODENAME_ONE_THREAD_STATE) { + CN1Image* img = 0; + pthread_mutex_lock(&cn1ReleasedLock); + if (cn1ReleasedCount > 0) { + img = cn1Released[--cn1ReleasedCount]; + } + pthread_mutex_unlock(&cn1ReleasedLock); + return (JAVA_LONG) (intptr_t) img; +} + +JAVA_LONG com_codename1_impl_linux_LinuxNative_disposeImage___long_R_long(CODENAME_ONE_THREAD_STATE, JAVA_LONG image) { + CN1Image* img = CN1I(image); + CN1Graphics* g; + if (!img) { + return 0; + } + g = img->mutableGraphics; + if (g != 0) { + if (g->cr != 0) { + cairo_destroy(g->cr); + } + cn1LinuxFreeClipShape(g); + free(g); + } + if (img->surface != 0) { + long bytes = cn1SurfaceBytes(img->surface); + cn1FreedSinceTrim += bytes; + atomic_fetch_sub_explicit(&cn1ImageBytesLive, bytes, memory_order_relaxed); + cairo_surface_destroy(img->surface); + } + free(img); + atomic_fetch_sub_explicit(&cn1ImagesLive, 1, memory_order_relaxed); + atomic_fetch_add_explicit(&cn1ImagesFreed, 1, memory_order_relaxed); + cn1LogImages(); +#ifdef __GLIBC__ + if (cn1FreedSinceTrim >= CN1_TRIM_AFTER_BYTES) { + cn1FreedSinceTrim = 0; + malloc_trim(0); + } +#endif + return (JAVA_LONG) (intptr_t) g; +} + /* drawImage / drawImageScaled live here (image ownership unit). They blit the * CN1Image surface onto the target graphics honouring its clip, affine and * alpha. */ diff --git a/Ports/LinuxPort/nativeSources/cn1_linux_io.c b/Ports/LinuxPort/nativeSources/cn1_linux_io.c index e703ba70f8b..4a0ea775634 100644 --- a/Ports/LinuxPort/nativeSources/cn1_linux_io.c +++ b/Ports/LinuxPort/nativeSources/cn1_linux_io.c @@ -467,6 +467,32 @@ JAVA_OBJECT com_codename1_impl_linux_LinuxNative_storageDir___R_java_lang_String return newStringFromCString(threadStateData, cn1StorageDir()); } +/* + * A moment written the way the user's locale writes a date (what == 0, strftime's %x) + * or a time of day (what == 1, %X), in the local time zone. An empty string when the + * moment cannot be broken down. + * + * The locale is the one GTK selected from the environment when it started + * (setlocale(LC_ALL, "")), so LC_TIME decides: 02/29/2024 for en_US, 29.02.2024 for + * de_DE, 02/29/24 with no locale set at all. + */ +JAVA_OBJECT com_codename1_impl_linux_LinuxNative_formatLocalTime___long_int_R_java_lang_String(CODENAME_ONE_THREAD_STATE, JAVA_LONG millis, JAVA_INT what) { + char buf[256]; + struct tm parts; + time_t seconds = (time_t) (millis / 1000); + if (millis < 0 && millis % 1000 != 0) { + /* Division truncates towards zero; a moment before 1970 belongs to the second below. */ + seconds--; + } + buf[0] = 0; + if (localtime_r(&seconds, &parts) != 0) { + if (strftime(buf, sizeof(buf), what == 1 ? "%X" : "%x", &parts) == 0) { + buf[0] = 0; + } + } + return newStringFromCString(threadStateData, buf); +} + JAVA_OBJECT com_codename1_impl_linux_LinuxNative_executableDir___R_java_lang_String(CODENAME_ONE_THREAD_STATE) { char buf[4096]; ssize_t n = readlink("/proc/self/exe", buf, sizeof(buf) - 1); diff --git a/Ports/LinuxPort/nativeSources/cn1_linux_text.c b/Ports/LinuxPort/nativeSources/cn1_linux_text.c index 3a03bd46039..e27c06acd52 100644 --- a/Ports/LinuxPort/nativeSources/cn1_linux_text.c +++ b/Ports/LinuxPort/nativeSources/cn1_linux_text.c @@ -116,11 +116,66 @@ static CN1Font* cn1MakeFont(const char* family, int weight, int italic, int pixe pango_font_description_set_weight(f->desc, weight); pango_font_description_set_style(f->desc, italic ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL); pango_font_description_set_absolute_size(f->desc, pixelSize * PANGO_SCALE); + /* + * A weight under regular is kept only when the family has a face near it. + * Pango answers with the nearest face it has, and for a family with no Light + * that is its ExtraLight when it has one (DejaVu Sans does): the titles and + * buttons a theme styles with native:MainLight came out as hairlines that read + * as disabled. Regular is what the other ports fall back to. + */ + if (weight < PANGO_WEIGHT_NORMAL) { + PangoFont* loaded = pango_context_load_font(cn1LinuxPangoContext(), f->desc); + int near = 0; + if (loaded != 0) { + PangoFontDescription* actual = pango_font_describe(loaded); + if (actual != 0) { + int delta = (int) pango_font_description_get_weight(actual) - weight; + near = delta > -75 && delta < 75; + pango_font_description_free(actual); + } + g_object_unref(loaded); + } + if (!near) { + pango_font_description_set_weight(f->desc, PANGO_WEIGHT_NORMAL); + } + } f->pixelSize = pixelSize; cn1FontMetrics(f); return f; } +/* + * The weight and the slant a "native:" face asks for. The names are Codename One's: + * Main or Italic, then Thin, Light, Regular, Bold or Black ("native:ItalicBold"). + * + * Every one of them used to load as the regular upright face: the name was reduced + * to the family and the rest of it dropped. A label a theme styles with + * native:MainBold drew like the text around it, and text an application asked for + * in bold or italic -- which the Swing and JavaFX layers ask for under these names + * -- was neither. deriveTrueTypeFont copies the description it is handed, so a font + * derived from one of these keeps what is set here. + */ +static void cn1NativeFace(const char* face, int* weight, int* italic) { + const char* rest = face; + *weight = PANGO_WEIGHT_NORMAL; + *italic = 0; + if (strncmp(face, "Italic", 6) == 0) { + *italic = 1; + rest = face + 6; + } else if (strncmp(face, "Main", 4) == 0) { + rest = face + 4; + } + if (strcmp(rest, "Thin") == 0) { + *weight = PANGO_WEIGHT_THIN; + } else if (strcmp(rest, "Light") == 0) { + *weight = PANGO_WEIGHT_LIGHT; + } else if (strcmp(rest, "Bold") == 0) { + *weight = PANGO_WEIGHT_BOLD; + } else if (strcmp(rest, "Black") == 0) { + *weight = PANGO_WEIGHT_HEAVY; + } +} + JAVA_LONG com_codename1_impl_linux_LinuxNative_createFont___int_int_int_R_long(CODENAME_ONE_THREAD_STATE, JAVA_INT face, JAVA_INT style, JAVA_INT size) { const char* family = "Sans"; int pixelSize = 18; @@ -417,10 +472,12 @@ JAVA_LONG com_codename1_impl_linux_LinuxNative_loadTrueTypeFontFromMemory___java FT_Face face = 0; family[0] = 0; if (data == JAVA_NULL || (len = (int) (*(JAVA_ARRAY) data).length) <= 0) { + int weight = PANGO_WEIGHT_NORMAL; + int italic = 0; if (strncmp(name, "native:", 7) == 0) { return (JAVA_LONG) (intptr_t) cn1LinuxNativeSchemeFont(name); } - return (JAVA_LONG) (intptr_t) cn1MakeFont(name, PANGO_WEIGHT_NORMAL, 0, 18); + return (JAVA_LONG) (intptr_t) cn1MakeFont(name, weight, italic, 18); } bytes = (unsigned char*) CN1_ARRAY_DATA(data); diff --git a/Ports/LinuxPort/nativeSources/cn1_linux_window.c b/Ports/LinuxPort/nativeSources/cn1_linux_window.c index fc62a952ec0..48078776ad6 100644 --- a/Ports/LinuxPort/nativeSources/cn1_linux_window.c +++ b/Ports/LinuxPort/nativeSources/cn1_linux_window.c @@ -715,7 +715,13 @@ static gboolean cn1OnKey(GtkWidget* widget, GdkEventKey* e, gpointer data) { * is torn down GTK clears the toplevel focus to NULL, so CN1 keys resume. */ if (cn1Window != 0) { GtkWidget* focus = gtk_window_get_focus(GTK_WINDOW(cn1Window)); - if (focus != 0 && focus != cn1DrawingArea && focus != cn1TitleCanvas) { + /* The accessibility mirror is not a peer. Its widgets describe Codename One + * components to an assistive technology and have no way to hand text back, + * so a keystroke that arrives while one of them holds the GTK focus is still + * Codename One's. Forwarding it typed into an entry drawn at 1% opacity: + * the text went nowhere the user could see. */ + if (focus != 0 && focus != cn1DrawingArea && focus != cn1TitleCanvas + && !(cn1AccessibilityFixed != 0 && gtk_widget_is_ancestor(focus, cn1AccessibilityFixed))) { return FALSE; } } @@ -1852,6 +1858,47 @@ JAVA_VOID com_codename1_impl_linux_LinuxNative_accessibilityAction___long_java_l cn1A11yQueue(1, action); } +/* Takes a window and everything under it out of pointer delivery. + * + * The mirror is made of real GTK widgets -- entries, toggle buttons, scales -- laid + * exactly over the Codename One components they describe, and each of them owns + * GdkWindows that take pointer input. gtk_overlay_set_overlay_pass_through only + * covers the overlay child's own window: GDK says in so many words that a + * pass-through window "can still have a subwindow without pass through". So a + * click on a text field landed on the invisible GtkEntry above it, which took the + * GTK focus, and everything typed afterwards went into that entry instead of the + * application. An assistive technology drives these widgets through ATK actions, + * never through the pointer, so nothing is lost by letting the pointer through. */ +static void cn1A11yPassPointerThrough(GdkWindow* window) { + GList* item; + if (window == 0) { + return; + } + gdk_window_set_pass_through(window, TRUE); + for (item = gdk_window_peek_children(window); item; item = item->next) { + cn1A11yPassPointerThrough((GdkWindow*) item->data); + } +} + +static void cn1A11yEndMain(void* pointer) { + (void) pointer; + if (cn1AccessibilityFixed) { + gtk_widget_show_all(cn1AccessibilityFixed); + if (gtk_widget_get_realized(cn1AccessibilityFixed)) { + /* A GtkFixed has no window of its own: this is the child window the + * overlay made for it, and the mirror's windows are all below it. */ + GdkWindow* mirror = gtk_widget_get_window(cn1AccessibilityFixed); + /* Never the window the drawing area lives in: passing THAT through + * would take the pointer away from the whole application. */ + if (mirror != 0 && mirror != gtk_widget_get_window(cn1Overlay) + && mirror != gtk_widget_get_window(cn1DrawingArea) + && mirror != gtk_widget_get_window(cn1Window)) { + cn1A11yPassPointerThrough(mirror); + } + } + } +} + /* A complete publication, handed from the publishing thread to the GTK thread. */ typedef struct { CN1A11yOp* ops; @@ -1906,7 +1953,7 @@ static gboolean cn1A11yApplyIdle(gpointer ignored) { * closures they were applied to; only the array is ours. */ free(batch->ops); free(batch); - if (cn1AccessibilityFixed) gtk_widget_show_all(cn1AccessibilityFixed); + cn1A11yEndMain(0); return G_SOURCE_REMOVE; } diff --git a/Ports/LinuxPort/src/com/codename1/impl/linux/LinuxImplementation.java b/Ports/LinuxPort/src/com/codename1/impl/linux/LinuxImplementation.java index e8ecb8f78ea..720d07cf2f4 100644 --- a/Ports/LinuxPort/src/com/codename1/impl/linux/LinuxImplementation.java +++ b/Ports/LinuxPort/src/com/codename1/impl/linux/LinuxImplementation.java @@ -53,6 +53,7 @@ import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; /** * Native Linux (GTK3, desktop) implementation of the Codename One platform @@ -81,6 +82,9 @@ public class LinuxImplementation extends CodenameOneImplementation { /// editor and browser hosts. Matches CN1_MAIN_WINDOW_SLOT in cn1_linux_gfx.h. static final int MAIN_WINDOW_SLOT = -1; + /// The key code of Tab, the one `Form` moves the focus on. + private static final int KEY_CODE_TAB = 9; + @Override public boolean isHighContrastEnabled() { @@ -180,7 +184,109 @@ public static LinuxImplementation getInstance() { /* -------------------------------------------------------------- helpers */ private static long peer(Object o) { - return o == null ? 0L : ((Long) o).longValue(); + if (o instanceof Long) { + return ((Long) o).longValue(); + } + if (o instanceof NativeImage) { + return ((NativeImage) o).peer; + } + if (o instanceof ImageGraphics) { + return ((ImageGraphics) o).peer; + } + return 0L; + } + + /// The native half of an image: a Cairo surface, and the graphics context drawing + /// into it once one was asked for. + /// + /// An image used to be a boxed pointer, and nothing ever gave the surface back: + /// there was no native to do it. An application that made a picture per frame -- + /// 800x600 is 1.9 MB -- grew by that much per frame for as long as it ran, with + /// the collector reclaiming every Java object on schedule. The handle is what + /// tells the port the image is gone. + /// + /// `finalize` runs on the collector's thread, where a surface the event dispatch + /// thread may be part way through blitting must not be destroyed. It only queues + /// the pointer; `#disposeReleasedImages()` frees it between frames. + static final class NativeImage { + final long peer; + + NativeImage(long peer) { + this.peer = peer; + } + + @Override + protected void finalize() { + if (peer != 0) { + LinuxNative.releaseImage(peer); + } + } + } + + /// The graphics of a mutable image. It keeps the image: the context draws into + /// the image's surface and is freed with it, so the image must outlive every + /// graphics object still drawing there. + static final class ImageGraphics { + final long peer; + final Object image; + + ImageGraphics(long peer, Object image) { + this.peer = peer; + this.image = image; + } + } + + /// Surface bytes handed out since a collection was last asked for. A surface is + /// invisible to the collector -- the Java side of a 2 MB picture is 16 bytes -- + /// so a program can allocate images for a long time without the heap ever + /// reaching the size that starts a cycle. Plain, not atomic: a lost update only + /// postpones the request by one image. + private static long imageBytesSinceGc; + + private static final long IMAGE_BYTES_PER_GC = 32L * 1024 * 1024; + + /// When an image was last made, and how long none must have been made before + /// an idle event dispatch thread asks for the collection that the count above + /// did not reach. An application that draws a picture per frame and then stops + /// leaves the images of its last frames behind, short of the count for good: + /// nothing allocates any more, so nothing ever starts a cycle. While images + /// are still being made the count is what asks. + private static long lastImageMillis; + + private static final long IMAGE_QUIET_MILLIS = 500; + + static Object wrapImage(long peer, int width, int height) { + if (peer != 0) { + if (width <= 0 || height <= 0) { + width = LinuxNative.imageWidth(peer); + height = LinuxNative.imageHeight(peer); + } + imageBytesSinceGc += 4L * width * height; + lastImageMillis = System.currentTimeMillis(); + if (imageBytesSinceGc > IMAGE_BYTES_PER_GC) { + imageBytesSinceGc = 0; + System.gc(); + } + } + return new NativeImage(peer); + } + + /// Frees the images whose handles the collector finalized. Event dispatch thread + /// only, between frames: it is the one moment no native draw can be holding one. + /// The graphics context freed with an image takes its entries in the transform + /// and clip-stack tables with it; a context allocated later at the same address + /// would otherwise start life under a dead image's transform. + private void disposeReleasedImages() { + long image = LinuxNative.nextReleasedImage(); + while (image != 0) { + long graphics = LinuxNative.disposeImage(image); + if (graphics != 0) { + Long key = Long.valueOf(graphics); + graphicsTransforms.remove(key); + clipStacks.remove(key); + } + image = LinuxNative.nextReleasedImage(); + } } private String storagePath(String name) { @@ -681,8 +787,64 @@ public int getDisplayHeight() { return LinuxNative.getDisplayHeight(); } + /// Resizes the application window. The size is the outer one, frame included, + /// as on the JavaSE port; the display follows through the size-changed event + /// the window's new allocation produces, so nothing is laid out here. + /// + /// The window still OPENS at 800x600: it exists before any application code + /// runs, and that default is what every screenshot baseline was taken at. + @Override + public void setWindowSize(int width, int height) { + if (width > 0 && height > 0) { + LinuxNative.mainWindowSetSize(width, height); + } + } + + @Override + public com.codename1.ui.geom.Rectangle getWindowBounds() { + int[] frame = new int[4]; + if (!LinuxNative.mainWindowGetFrame(frame)) { + return super.getWindowBounds(); + } + return new com.codename1.ui.geom.Rectangle(frame[0], frame[1], frame[2], frame[3]); + } + + /// The monitor the application window is on, which is the desktop a + /// percentage of it is taken from. + @Override + public com.codename1.ui.geom.Dimension getDesktopSize() { + int[] bounds = new int[4]; + if (!LinuxNative.mainWindowGetFrame(bounds)) { + return super.getDesktopSize(); + } + LinuxNative.monitorBounds(LinuxNative.monitorForMainWindow(), false, bounds); + if (bounds[2] <= 0 || bounds[3] <= 0) { + return super.getDesktopSize(); + } + return new com.codename1.ui.geom.Dimension(bounds[2], bounds[3]); + } + + /// The hint is a size for the window the first form opens in. Here that window + /// already exists, so the hint is applied as it arrives and consumed, the way + /// the JavaSE port consumes it when it builds its frame. + @Override + public void setInitialWindowSizeHintPercent(com.codename1.ui.geom.Dimension hint) { + super.setInitialWindowSizeHintPercent(hint); + com.codename1.ui.geom.Dimension desktop = hint == null ? null : getDesktopSize(); + if (desktop == null) { + return; + } + int width = Math.min(desktop.getWidth(), + Math.max(1, Math.round(desktop.getWidth() * (hint.getWidth() / 100f)))); + int height = Math.min(desktop.getHeight(), + Math.max(1, Math.round(desktop.getHeight() * (hint.getHeight() / 100f)))); + super.setInitialWindowSizeHintPercent(null); + setWindowSize(width, height); + } + @Override public void flushGraphics() { + disposeReleasedImages(); LinuxNative.flushGraphics(windowGraphicsPeer, 0, 0, getDisplayWidth(), getDisplayHeight()); } @@ -757,6 +919,8 @@ public void run() { @Override public void flushGraphics(int x, int y, int width, int height) { + // Most frames repaint a part of the window and come through here. + disposeReleasedImages(); LinuxNative.flushGraphics(windowGraphicsPeer, x, y, width, height); } @@ -800,11 +964,85 @@ public void screenshot(com.codename1.util.SuccessCallback callback) { @Override public void edtIdle(boolean enter) { - // Intentionally empty: the Win32 message pump and input dispatch run on the + // No pumping here: the message pump and input dispatch run on the // main thread (see runMainEventLoop), and the EDT is woken by the Display // lock notifications those dispatches trigger. The EDT therefore idles by // sleeping on the lock like every other Codename One platform -- it must // not pump or drain on its own (it is not the window's owning thread). + // + // What it does do on its way to sleep is free the images the collector + // let go of. A frame frees them too, but an application that computed a + // picture and then sat still painted no further frame: the images of + // its last collection stayed queued, 70 to 200 MB of them in one + // measured run, for as long as nothing was repainted. + if (enter) { + disposeReleasedImages(); + if (imageBytesSinceGc > 0) { + long quiet = System.currentTimeMillis() - lastImageMillis; + if (quiet > IMAGE_QUIET_MILLIS) { + // See lastImageMillis. Nothing is counted until an image is made. + imageBytesSinceGc = 0; + System.gc(); + releaseWakes = 2; + wakeEdtIn(IMAGE_RELEASE_MILLIS); + } else { + // Too soon to call it quiet, and this thread may not wake again + // on its own: an application that stops moving stops for good. + wakeEdtIn(IMAGE_QUIET_MILLIS - quiet + 1); + } + } else if (releaseWakes > 0) { + releaseWakes--; + if (releaseWakes > 0) { + wakeEdtIn(4 * IMAGE_RELEASE_MILLIS); + } + } + } + } + + /// How long after asking for the quiet collection this thread comes back to free + /// what it let go of, and how many times it still will: once soon, once later for + /// a cycle that took its time. + private static final long IMAGE_RELEASE_MILLIS = 250; + + private int releaseWakes; + + private static final AtomicBoolean WAKE_PENDING = new AtomicBoolean(); + + private static final Runnable NOTHING = new Runnable() { + @Override + public void run() { + } + }; + + /// Has the event dispatch thread pass through `edtIdle` once more, some time + /// from now. The quiet collection above is asked for by a thread on its way to + /// sleep, and the collector answers on its own thread a moment later: the images + /// it released sat in the queue until the next frame, which a still application + /// never paints. A Mandelbrot picture that had finished rendering held 115 + /// surfaces, 220 MB, for as long as nobody touched the window; and when the + /// thread went to sleep less than `IMAGE_QUIET_MILLIS` after the last image, + /// the collection was never asked for at all. + /// + /// One wake-up is pending at a time, hence the atomic: the timer thread clears + /// it and this thread sets it. + private static void wakeEdtIn(final long millis) { + if (!WAKE_PENDING.compareAndSet(false, true)) { + return; + } + Thread t = new Thread(new Runnable() { + @Override + public void run() { + try { + Thread.sleep(millis); + } catch (InterruptedException ex) { + // Woken early is still woken: the pass it asks for is harmless. + } + WAKE_PENDING.set(false); + Display.getInstance().callSerially(NOTHING); + } + }, "cn1-linux-image-release"); + t.setDaemon(true); + t.start(); } // High bits in the native key flag touch, pen, or eraser input (see cn1_linux.h); @@ -919,10 +1157,10 @@ private void drainInput() { dispatchPointerHover(windowId, x, y, key); break; case EVENT_KEY_PRESSED: - windowKeyPressed(windowId, key); + windowKeyPressed(windowId, keyCodeOf(key)); break; case EVENT_KEY_RELEASED: - windowKeyReleased(windowId, key); + windowKeyReleased(windowId, keyCodeOf(key)); break; case EVENT_SIZE_CHANGED: if (windowId == 0) { @@ -1222,7 +1460,7 @@ public Object getNativeGraphics() { @Override public Object getNativeGraphics(Object image) { - return Long.valueOf(LinuxNative.getImageGraphics(peer(image))); + return new ImageGraphics(LinuxNative.getImageGraphics(peer(image)), image); } /* ------------------------------------------------------------- transforms @@ -1977,6 +2215,15 @@ public void drawImage(Object graphics, Object img, int x, int y) { /// again. On every paint, since nothing kept the result: the transpiled /// Flutter gallery ran 13 PNG encodes before its first frame, all from /// scaled draws of its cards, icons and logo. + /// + /// The same answer also stops a memory leak. Without it the core scales for + /// the port: `Image.scaled` makes a second surface of the size + /// asked for and keeps it in the image's scale cache for as long as the image + /// lives, one per size. An application that zooms a picture draws it at a new + /// size every frame, so every frame left a surface behind, each larger than the + /// last: a JavaFX canvas zooming an 800x600 snapshot was measured holding 156 + /// surfaces, 1.8 GB, by the end of a one second animation, none of them garbage + /// until the snapshot itself was replaced. @Override public boolean isScaledImageDrawingSupported() { return true; @@ -2105,28 +2352,34 @@ public Object deriveTrueTypeFont(Object font, float size, int weight) { @Override public Object createImage(int[] rgb, int width, int height) { - return Long.valueOf(LinuxNative.createImageFromARGB(rgb, width, height)); + return wrapImage(LinuxNative.createImageFromARGB(rgb, width, height), width, height); } @Override public Object createImage(String path) throws IOException { - return Long.valueOf(LinuxNative.createImageFromFile(stripFileUrl(path))); + return wrapImage(LinuxNative.createImageFromFile(stripFileUrl(path)), 0, 0); } @Override public Object createImage(InputStream i) throws IOException { byte[] data = readFully(i); - return Long.valueOf(LinuxNative.createImageFromBytes(data, 0, data.length)); + return wrapImage(LinuxNative.createImageFromBytes(data, 0, data.length), 0, 0); } @Override public Object createImage(byte[] bytes, int offset, int len) { - return Long.valueOf(LinuxNative.createImageFromBytes(bytes, offset, len)); + // The native decoder reads the range as it is given; a range past + // the array is refused here, as Java would, before it is read. + if (bytes != null && (offset < 0 || len < 0 || offset > bytes.length - len)) { + throw new ArrayIndexOutOfBoundsException("offset " + offset + ", length " + len + + ", array " + bytes.length); + } + return wrapImage(LinuxNative.createImageFromBytes(bytes, offset, len), 0, 0); } @Override public Object createMutableImage(int width, int height, int fillColor) { - return Long.valueOf(LinuxNative.createMutableImage(width, height, fillColor)); + return wrapImage(LinuxNative.createMutableImage(width, height, fillColor), width, height); } @Override @@ -2141,7 +2394,7 @@ public int getImageHeight(Object i) { @Override public Object scale(Object nativeImage, int width, int height) { - return Long.valueOf(LinuxNative.scaleImage(peer(nativeImage), width, height)); + return wrapImage(LinuxNative.scaleImage(peer(nativeImage), width, height), width, height); } @Override @@ -2246,6 +2499,14 @@ public void editString(final Component cmp, int maxSize, int constraint, String // The editor goes into the overlay of the window the field lives in; without // the slot it appeared over the main window while the window being typed into // showed nothing. + // The key that started the edit is part of what was typed. The editor + // opens after it was pressed and never sees it, so it is added here; + // without it the first character typed into a field was lost. + if (initiatingKeycode >= ' ' && initiatingKeycode != 127 && initiatingKeycode <= 0xffff + && getGameAction(initiatingKeycode) == 0 + && (maxSize <= 0 || text == null || text.length() < maxSize)) { + text = (text == null ? "" : text) + (char) initiatingKeycode; + } long peer = LinuxNative.editStringAt(x, y, w, h, text == null ? "" : text, singleLine, maxSize, fontPeer, s.getFgColor(), s.getBgColor(), 0, LinuxWindowManager.slotForComponent(cmp)); @@ -2269,7 +2530,21 @@ public void run() { return; } if (LinuxNative.editIsDone(editPeer)) { + // Tab ended it: the editor kept the key from the form, so + // the form is given it now and moves its focus on. In the + // main window only; a second window keeps what it did. + int tabbed = LinuxNative.editTabbed(editPeer); + boolean tab = tabbed != 0 + && LinuxWindowManager.slotForComponent(cmp) == MAIN_WINDOW_SLOT; hideTextEditor(); + if (tab) { + // The editor consumed the key, so the direction travels + // with it: 2 is Shift-Tab. See #isShiftKeyDown. + tabDirection = tabbed; + tabDirectionUntil = System.currentTimeMillis() + 1000; + keyPressed(KEY_CODE_TAB); + keyReleased(KEY_CODE_TAB); + } return; } // Mirror the native control's text into the field as the user @@ -2646,12 +2921,71 @@ public int getBackKeyCode() { @Override public int getGameAction(int keyCode) { - return 0; + switch (keyCode) { + case GAME_KEY_CODE_UP: + return Display.GAME_UP; + case GAME_KEY_CODE_DOWN: + return Display.GAME_DOWN; + case GAME_KEY_CODE_LEFT: + return Display.GAME_LEFT; + case GAME_KEY_CODE_RIGHT: + return Display.GAME_RIGHT; + default: + return 0; + } } @Override public int getKeyCode(int gameAction) { - return 0; + switch (gameAction) { + case Display.GAME_UP: + return GAME_KEY_CODE_UP; + case Display.GAME_DOWN: + return GAME_KEY_CODE_DOWN; + case Display.GAME_LEFT: + return GAME_KEY_CODE_LEFT; + case Display.GAME_RIGHT: + return GAME_KEY_CODE_RIGHT; + default: + return 0; + } + } + + // The codes the arrow keys are handed to Codename One as. They are + // negative, as on the JavaSE port: a positive code is a character, and + // a form, a list and everything else that moves on an arrow asks + // getGameAction what a negative one means. + private static final int GAME_KEY_CODE_UP = -91; + private static final int GAME_KEY_CODE_DOWN = -92; + private static final int GAME_KEY_CODE_LEFT = -93; + private static final int GAME_KEY_CODE_RIGHT = -94; + + /// The Codename One code of a key the window reported. + /// + /// The window hands over the character a key types, and for a key + /// that types none its GDK key value. The arrows are such keys, and + /// their values (0xff51 to 0xff54, and the keypad's 0xff96 to 0xff99) + /// were passed on as they came: getGameAction answered 0 for + /// everything, so no arrow key did anything in any application -- a + /// form did not move its focus, and a game played with the arrows did + /// not play -- while a text component read the value as a character. + static int keyCodeOf(int key) { + switch (key) { + case 0xff51: + case 0xff96: + return GAME_KEY_CODE_LEFT; + case 0xff52: + case 0xff97: + return GAME_KEY_CODE_UP; + case 0xff53: + case 0xff98: + return GAME_KEY_CODE_RIGHT; + case 0xff54: + case 0xff99: + return GAME_KEY_CODE_DOWN; + default: + return key; + } } /* ------------------------------------------------------------ network */ @@ -3424,6 +3758,17 @@ public boolean isDesktop() { /// JavaSEPort and the macOS port reported modifier state before this. @Override public boolean isShiftKeyDown() { + if (tabDirection != 0) { + // Answered once, for the Tab a native editor ended its edit with: the + // form asks this when it handles that key, and by then the modifiers + // the window recorded belong to whatever key came last -- a Tab + // released after Shift reads as a plain Tab and moved focus forwards. + int direction = tabDirection; + tabDirection = 0; + if (System.currentTimeMillis() <= tabDirectionUntil) { + return direction == 2; + } + } return (LinuxNative.currentModifiers() & 1) != 0; } @@ -3443,6 +3788,12 @@ public boolean isAltKeyDown() { return (LinuxNative.currentModifiers() & 4) != 0; } + /// The direction of the Tab that ended a native edit -- 1 forward, 2 back + /// (Shift-Tab), 0 none -- for the one [#isShiftKeyDown] that asks about it, + /// and the time past which it is stale. Both on the EDT only. + private int tabDirection; + private long tabDirectionUntil; + @Override public int[] getNativeWindowTitleBarInsets(com.codename1.ui.Component toolbar) { if (!(toolbar.getTopLevelContainer() instanceof com.codename1.ui.Form) @@ -3849,6 +4200,27 @@ public String getLongMonthName(Date date) { } return LONG_MONTHS[m]; } + + /* + * The short date and the time of day are the locale's own. The + * base class answers Date.toString() for both, which is neither + * short nor a date alone: a date Picker -- and so a JavaFX + * DatePicker or anything else that shows a day -- read + * "February 29, 2024 12:00:00 PM" where every other port shows + * 2/29/2024. An empty answer means the C library could not + * write the moment, and the old text is better than none. + */ + @Override + public String formatDateShortStyle(Date d) { + String s = LinuxNative.formatLocalTime(d.getTime(), 0); + return s == null || s.length() == 0 ? super.formatDateShortStyle(d) : s; + } + + @Override + public String formatTime(Date d) { + String s = LinuxNative.formatLocalTime(d.getTime(), 1); + return s == null || s.length() == 0 ? super.formatTime(d) : s; + } }; } return l10n; diff --git a/Ports/LinuxPort/src/com/codename1/impl/linux/LinuxNative.java b/Ports/LinuxPort/src/com/codename1/impl/linux/LinuxNative.java index 5dc0610f6ef..59ec78fd6ea 100644 --- a/Ports/LinuxPort/src/com/codename1/impl/linux/LinuxNative.java +++ b/Ports/LinuxPort/src/com/codename1/impl/linux/LinuxNative.java @@ -241,6 +241,15 @@ public static native int desktopWindowCreate(int windowId, String title, int x, public static native boolean mainWindowGetBounds(int[] out); + /// Resizes the application's MAIN window to an outer size, frame included. + /// A no-op when headless, where the surface is the size of the screenshot. + public static native void mainWindowSetSize(int width, int height); + + /// Fills `out` with the main window's outer rectangle, the one + /// `#mainWindowSetSize(int, int)` sets; `#mainWindowGetBounds(int[])` answers + /// the client area. False when there is no window. + public static native boolean mainWindowGetFrame(int[] out); + public static native int desktopWindowGetWidth(int slot); public static native int desktopWindowGetHeight(int slot); @@ -488,6 +497,18 @@ public static native void accessibilityNode(long id, long parentId, String role, public static native long getImageGraphics(long image); + /// Queues an image for disposal. Safe from any thread: this is what an image + /// handle's finalizer calls, on the collector's thread. Nothing is freed here. + public static native void releaseImage(long image); + + /// The next queued image, or 0 when the queue is empty. + public static native long nextReleasedImage(); + + /// Destroys an image's surface and the graphics context cached for it, and + /// returns that context's pointer (0 if it never had one) so the caller can + /// forget what it kept under it. Drawing thread only. + public static native long disposeImage(long image); + /* ------------------------------------------------------- filesystem */ /** @@ -517,6 +538,9 @@ public static native long editStringAt(int x, int y, int w, int h, String text, /** True once the user has committed the native edit (Enter / focus loss). */ public static native boolean editIsDone(long peer); + /** How Tab ended the native edit: 1 forwards, 2 backwards, 0 when it did not. */ + public static native int editTabbed(long peer); + /** The native EDIT control's current text. */ public static native String editGetText(long peer); @@ -530,6 +554,13 @@ public static native long editStringAt(int x, int y, int w, int h, String text, /** Why the most recent open failed, for the exception the port raises. */ public static native String lastIoError(); + /** + * A moment as the user's locale writes a date ({@code what == 0}) or a time + * of day ({@code what == 1}), in the local time zone; empty when it cannot + * be written. + */ + public static native String formatLocalTime(long millis, int what); + /* ---------------------------------------------------------- crypto */ /** Fills {@code out} with fresh entropy; false when the platform RNG failed. */ diff --git a/Ports/LinuxPort/src/com/codename1/impl/linux/LinuxWindowManager.java b/Ports/LinuxPort/src/com/codename1/impl/linux/LinuxWindowManager.java index 14468e52ca2..8864a9477d2 100644 --- a/Ports/LinuxPort/src/com/codename1/impl/linux/LinuxWindowManager.java +++ b/Ports/LinuxPort/src/com/codename1/impl/linux/LinuxWindowManager.java @@ -463,7 +463,7 @@ public Object capture(Object peer) { if (img == 0) { return null; } - return Long.valueOf(img); + return LinuxImplementation.wrapImage(img, 0, 0); } @Override diff --git a/Ports/WindowsPort/nativeSources/cn1_windows.h b/Ports/WindowsPort/nativeSources/cn1_windows.h index 90216898f93..1050d7a39d8 100644 --- a/Ports/WindowsPort/nativeSources/cn1_windows.h +++ b/Ports/WindowsPort/nativeSources/cn1_windows.h @@ -179,6 +179,13 @@ typedef struct CN1Graphics { JAVA_BOOLEAN clipIsRect; void* clipGeom; /* ID2D1PathGeometry* when clip is a shape, else NULL */ void* clipLayer; /* ID2D1Layer* pushed for a shape clip, else NULL */ + JAVA_BOOLEAN clipGeomHasRect; /* a clipRect narrowed the shape clip: clipX/Y/W/H is + * then a rectangle the shape is ALSO cut to. False + * right after setClipShape, when clipX/Y/W/H is still + * the rectangle that was the clip before the shape + * and is not part of the clip at all */ + JAVA_BOOLEAN clipRectPushed; /* that rectangle was pushed under the shape's layer + * and cn1WinPopClip has to pop it as well */ D2D1_MATRIX_3X2_F clipMaskTransform; /* world transform captured at setClipShape time; * applied to the (raw) clip geometry as the layer * maskTransform so a shape clip lands exactly where diff --git a/Ports/WindowsPort/nativeSources/cn1_windows_graphics.cpp b/Ports/WindowsPort/nativeSources/cn1_windows_graphics.cpp index 3c610c22797..b9569326b8f 100644 --- a/Ports/WindowsPort/nativeSources/cn1_windows_graphics.cpp +++ b/Ports/WindowsPort/nativeSources/cn1_windows_graphics.cpp @@ -84,6 +84,8 @@ CN1Graphics* cn1WinCreateGraphics(ID2D1RenderTarget* target) { g->clipIsRect = JAVA_TRUE; g->clipGeom = NULL; g->clipLayer = NULL; + g->clipGeomHasRect = JAVA_FALSE; + g->clipRectPushed = JAVA_FALSE; g->clipMaskTransform = D2D1::Matrix3x2F::Identity(); g->font = cn1Win.defaultFont; g->inFrame = JAVA_FALSE; @@ -179,6 +181,22 @@ extern "C" void cn1WinPushClip(CN1Graphics* g) { * the fill) -- so the clip stays fixed while the fill transforms within it. */ if (!g->clipIsRect && g->clipGeom != NULL) { ID2D1Layer* layer = NULL; + /* A clipRect that came after the shape: the clip is the shape AND that + * rectangle (see clipRect below). The rectangle goes on first, under + * identity for the reason given at the end of this function, and the + * shape's layer inside it; cn1WinPopClip takes them off in reverse. */ + if (g->clipGeomHasRect) { + D2D1_RECT_F rc; + D2D1_MATRIX_3X2_F idr = D2D1::Matrix3x2F::Identity(); + rc.left = (FLOAT)g->clipX; + rc.top = (FLOAT)g->clipY; + rc.right = (FLOAT)(g->clipX + g->clipW); + rc.bottom = (FLOAT)(g->clipY + g->clipH); + ID2D1RenderTarget_SetTransform(g->target, &idr); + ID2D1RenderTarget_PushAxisAlignedClip(g->target, &rc, D2D1_ANTIALIAS_MODE_PER_PRIMITIVE); + ID2D1RenderTarget_SetTransform(g->target, &g->transform); + g->clipRectPushed = JAVA_TRUE; + } if (SUCCEEDED(ID2D1RenderTarget_CreateLayer(g->target, NULL, &layer)) && layer != NULL) { D2D1_LAYER_PARAMETERS lp; lp.contentBounds = D2D1::InfiniteRect(); @@ -253,6 +271,20 @@ extern "C" void cn1WinPopClip(CN1Graphics* g) { ID2D1RenderTarget_PopLayer(g->target); ID2D1Layer_Release((ID2D1Layer*) g->clipLayer); g->clipLayer = NULL; + if (g->clipRectPushed) { + ID2D1RenderTarget_PopAxisAlignedClip(g->target); + g->clipRectPushed = JAVA_FALSE; + } + return; + } + if (g->clipRectPushed) { + /* The shape's layer could not be made; its rectangle was still pushed. */ + ID2D1RenderTarget_PopAxisAlignedClip(g->target); + g->clipRectPushed = JAVA_FALSE; + return; + } + if (!g->clipIsRect && g->clipGeom != NULL) { + /* A shape clip whose layer could not be made pushed nothing at all. */ return; } ID2D1RenderTarget_PopAxisAlignedClip(g->target); @@ -335,6 +367,7 @@ JAVA_VOID com_codename1_impl_windows_WindowsNative_setClip___long_int_int_int_in g->clipW = __cn1Arg4; g->clipH = __cn1Arg5; g->clipIsRect = JAVA_TRUE; + g->clipGeomHasRect = JAVA_FALSE; cn1WinReleaseClipGeom(g); } @@ -375,12 +408,33 @@ JAVA_VOID com_codename1_impl_windows_WindowsNative_setClipShape___long_float_1AR g->clipGeom = cn1WinBuildPathGeometry(coords, types, __cn1Arg4, __cn1Arg5); g->clipMaskTransform = g->transform; /* freeze the world transform at setClip time */ g->clipIsRect = (g->clipGeom == NULL) ? JAVA_TRUE : JAVA_FALSE; - /* Keep clipX/Y/W/H as the geometry's bounding box so getClip* stays sane. */ + /* clipX/Y/W/H is left as it was: the rectangle that was the clip BEFORE the + * shape replaced it. It is what getClip* goes on answering, and it is not + * part of the clip until a clipRect says so. */ + g->clipGeomHasRect = JAVA_FALSE; } JAVA_VOID com_codename1_impl_windows_WindowsNative_clipRect___long_int_int_int_int(CODENAME_ONE_THREAD_STATE, JAVA_LONG __cn1Arg1, JAVA_INT __cn1Arg2, JAVA_INT __cn1Arg3, JAVA_INT __cn1Arg4, JAVA_INT __cn1Arg5) { /* Intersect the requested rectangle with the current clip. */ CN1Graphics* g = (CN1Graphics*)__cn1Arg1; + /* A shape clip is narrowed, not thrown away. This used to end in + * clipIsRect = JAVA_TRUE and a released geometry whatever the clip was, so + * "setClip(shape); clipRect(the clip that was there)" -- which is how + * Graphics.fillShape confines a Paint to the shape it fills, and how any + * caller narrows the clip to a shape without widening it -- left only the + * rectangle that had been the clip BEFORE the shape: a gradient meant for a + * small rounded rectangle covered everything from the panel's corner to the + * end of the gradient's band. The first clipRect after the shape supplies + * the rectangle outright, since clipX/Y/W/H still holds the replaced clip + * then; later ones intersect with it as they always did. */ + if (!g->clipIsRect && g->clipGeom != NULL && !g->clipGeomHasRect) { + g->clipX = __cn1Arg2; + g->clipY = __cn1Arg3; + g->clipW = (__cn1Arg4 > 0) ? __cn1Arg4 : 0; + g->clipH = (__cn1Arg5 > 0) ? __cn1Arg5 : 0; + g->clipGeomHasRect = JAVA_TRUE; + return; + } JAVA_INT curRight = g->clipX + g->clipW; JAVA_INT curBottom = g->clipY + g->clipH; JAVA_INT reqRight = __cn1Arg2 + __cn1Arg4; @@ -395,6 +449,9 @@ JAVA_VOID com_codename1_impl_windows_WindowsNative_clipRect___long_int_int_int_i g->clipY = newY; g->clipW = (newRight > newX) ? (newRight - newX) : 0; g->clipH = (newBottom > newY) ? (newBottom - newY) : 0; + if (!g->clipIsRect && g->clipGeom != NULL) { + return; /* the shape stays, cut to the narrower rectangle */ + } g->clipIsRect = JAVA_TRUE; cn1WinReleaseClipGeom(g); } diff --git a/Ports/WindowsPort/src/com/codename1/impl/windows/WindowsImplementation.java b/Ports/WindowsPort/src/com/codename1/impl/windows/WindowsImplementation.java index edff35220d4..c4fd30a64f1 100644 --- a/Ports/WindowsPort/src/com/codename1/impl/windows/WindowsImplementation.java +++ b/Ports/WindowsPort/src/com/codename1/impl/windows/WindowsImplementation.java @@ -189,9 +189,18 @@ private String storagePath(String name) { return WindowsNative.storageDir() + getFileSystemSeparator() + name; } + /// The native path of a `file://` URL. This port writes `file://C:/x`, + /// but the standard form of the same URL is `file:///C:/x` -- which is + /// what `java.io.File` hands over for an absolute path -- and its leading + /// slash makes `/C:/x`, a name Windows refuses (error 123). private static String stripFileUrl(String path) { if (path.startsWith("file://")) { - return path.substring("file://".length()); + String p = path.substring("file://".length()); + if (p.length() > 2 && p.charAt(0) == '/' && p.charAt(2) == ':' + && Character.isLetter(p.charAt(1))) { + return p.substring(1); + } + return p; } return path; } diff --git a/maven/android-compat/pom.xml b/maven/android-compat/pom.xml index 39bd17dddff..3a5e1d4b807 100644 --- a/maven/android-compat/pom.xml +++ b/maven/android-compat/pom.xml @@ -167,6 +167,13 @@ junit test + + + com.codenameone + codenameone-compat-testing + ${project.version} + test + diff --git a/maven/android-compat/src/main/java/android/content/Context.java b/maven/android-compat/src/main/java/android/content/Context.java index 670b4dee220..3cdb6585396 100644 --- a/maven/android-compat/src/main/java/android/content/Context.java +++ b/maven/android-compat/src/main/java/android/content/Context.java @@ -180,14 +180,24 @@ public int checkCallingOrSelfPermission(String permission) { // ------------------------------------------------------------ files /// The application's private directories live under the Codename One - /// application home, laid out like Android's data directory. `java.io.File` - /// here is the compatibility runtime's file class once the build has - /// relocated the code. + /// application home, laid out like Android's data directory. private static java.io.File appDir(String name) { - String home = com.codename1.io.FileSystemStorage.getInstance().getAppHomePath(); + String home = appHome(); return ensureDir(new java.io.File(home.endsWith("/") ? home + name : home + "/" + name)); } + /// The application home as a native path. `FileSystemStorage` answers a + /// `file:` URL, and `java.io.File` is the platform's own class: on + /// Android and the simulator that is the JDK's, which takes the URL as a + /// relative file name and fails to open anything under it. + private static String appHome() { + return nativePath(com.codename1.io.FileSystemStorage.getInstance().getAppHomePath()); + } + + private static String nativePath(String path) { + return com.codename1.io.FileSystemStorage.getInstance().toNativePath(path); + } + /// Creates `dir` when missing. Like Android's, the directory getters /// still answer it when that fails, and report the failure in the log. private static java.io.File ensureDir(java.io.File dir) { @@ -198,7 +208,7 @@ private static java.io.File ensureDir(java.io.File dir) { } public java.io.File getDataDir() { - return new java.io.File(com.codename1.io.FileSystemStorage.getInstance().getAppHomePath()); + return new java.io.File(appHome()); } public java.io.File getFilesDir() { @@ -218,7 +228,7 @@ public java.io.File getCacheDir() { if (fs.hasCachesDir()) { String caches = fs.getCachesDir(); if (caches != null) { - return ensureDir(new java.io.File(caches)); + return ensureDir(new java.io.File(nativePath(caches))); } } return appDir("cache"); @@ -286,14 +296,17 @@ private static void checkFileName(String name) { /// Where a named database lives: the platform's database path when it /// exposes one, otherwise the `databases` directory Android would use. public java.io.File getDatabasePath(String name) { - if (name.startsWith("/") || name.startsWith("file:")) { + if (name.startsWith("/")) { return new java.io.File(name); } + if (name.startsWith("file:")) { + return new java.io.File(nativePath(name)); + } try { if (com.codename1.db.Database.isCustomPathSupported()) { String p = com.codename1.db.Database.getDatabasePath(name); if (p != null) { - return new java.io.File(p); + return new java.io.File(nativePath(p)); } } } catch (IllegalArgumentException e) { diff --git a/maven/android-compat/src/main/java/android/net/Uri.java b/maven/android-compat/src/main/java/android/net/Uri.java index a331daf7bdb..49f1bc8983f 100644 --- a/maven/android-compat/src/main/java/android/net/Uri.java +++ b/maven/android-compat/src/main/java/android/net/Uri.java @@ -53,10 +53,11 @@ public static Uri withAppendedPath(Uri baseUri, String pathSegment) { } public static Uri fromFile(Object file) { - // A shim directory (`getFilesDir()`) is already a `file:` path; - // prefixing another scheme gave `file://file:///...`. - String s = file instanceof com.codename1.androidcompat.jdk.File - ? ((com.codename1.androidcompat.jdk.File) file).storagePath() : String.valueOf(file); + // A device's java.io.File keeps the storage path it was made from + // (`getFilesDir()` is a `file:` path there), and a JDK's is a plain + // absolute path; both are handled below. Prefixing another scheme + // to the first gave `file://file:///...`. + String s = String.valueOf(file); // The path is percent-encoded (everything but `/`) as Android's // fromFile does: a raw `#` or `?` in a file name started a fragment // or query, so `getPath()` named another file. The authority a diff --git a/maven/android-compat/src/main/java/com/codename1/androidcompat/jdk/BufferedReader.java b/maven/android-compat/src/main/java/com/codename1/androidcompat/jdk/BufferedReader.java index dd5b62fdb4c..88fe282b665 100644 --- a/maven/android-compat/src/main/java/com/codename1/androidcompat/jdk/BufferedReader.java +++ b/maven/android-compat/src/main/java/com/codename1/androidcompat/jdk/BufferedReader.java @@ -221,4 +221,59 @@ public void close() throws IOException { buf = null; in.close(); } + + /// The remaining lines, read as the stream asks for them. A read that + /// fails surfaces as an UncheckedIOException; closing the stream does + /// not close this reader, as in the JDK. + public java.util.stream.Stream lines() { + return java.util.stream.StreamSupport.stream( + java.util.Spliterators.spliteratorUnknownSize(new LineIterator(this), + java.util.Spliterator.ORDERED | java.util.Spliterator.NONNULL), false); + } + + /// Reads one line ahead, so `hasNext` can answer without losing one. + private static final class LineIterator implements java.util.Iterator { + private final BufferedReader reader; + private String next; + private boolean done; + + LineIterator(BufferedReader reader) { + this.reader = reader; + } + + @Override + public boolean hasNext() { + if (next != null) { + return true; + } + if (done) { + return false; + } + try { + next = reader.readLine(); + } catch (IOException e) { + throw new java.io.UncheckedIOException(e); + } + if (next == null) { + done = true; + return false; + } + return true; + } + + @Override + public String next() { + if (!hasNext()) { + throw new java.util.NoSuchElementException(); + } + String line = next; + next = null; + return line; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } } diff --git a/maven/android-compat/src/test/java/android/animation/AnimatorMathTest.java b/maven/android-compat/src/test/java/android/animation/AnimatorMathTest.java index 6b1103c4478..ce74d0d802f 100644 --- a/maven/android-compat/src/test/java/android/animation/AnimatorMathTest.java +++ b/maven/android-compat/src/test/java/android/animation/AnimatorMathTest.java @@ -29,7 +29,7 @@ import android.view.animation.AccelerateInterpolator; import android.view.animation.LinearInterpolator; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/animation/AutoCancelReleaseTest.java b/maven/android-compat/src/test/java/android/animation/AutoCancelReleaseTest.java index 6d84c520e98..9e84e00c16a 100644 --- a/maven/android-compat/src/test/java/android/animation/AutoCancelReleaseTest.java +++ b/maven/android-compat/src/test/java/android/animation/AutoCancelReleaseTest.java @@ -22,7 +22,7 @@ */ package android.animation; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/animation/ObjectAnimatorSetPropertyTest.java b/maven/android-compat/src/test/java/android/animation/ObjectAnimatorSetPropertyTest.java index 70662c4d5db..5113ff1165f 100644 --- a/maven/android-compat/src/test/java/android/animation/ObjectAnimatorSetPropertyTest.java +++ b/maven/android-compat/src/test/java/android/animation/ObjectAnimatorSetPropertyTest.java @@ -24,7 +24,7 @@ import android.view.animation.LinearInterpolator; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/animation/ReverseRepeatedAnimatorTest.java b/maven/android-compat/src/test/java/android/animation/ReverseRepeatedAnimatorTest.java index 6d91b01f83b..f47c5e0c6ab 100644 --- a/maven/android-compat/src/test/java/android/animation/ReverseRepeatedAnimatorTest.java +++ b/maven/android-compat/src/test/java/android/animation/ReverseRepeatedAnimatorTest.java @@ -25,7 +25,7 @@ import android.view.animation.AnimationUtils; import android.view.animation.LinearInterpolator; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/app/ActivityBoundaryTest.java b/maven/android-compat/src/test/java/android/app/ActivityBoundaryTest.java index 6a5cc08c9d1..5c475ba12bc 100644 --- a/maven/android-compat/src/test/java/android/app/ActivityBoundaryTest.java +++ b/maven/android-compat/src/test/java/android/app/ActivityBoundaryTest.java @@ -26,7 +26,7 @@ import android.content.Intent; import android.os.Bundle; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.*; import static org.junit.Assert.*; diff --git a/maven/android-compat/src/test/java/android/app/DialogDefaultTouchTest.java b/maven/android-compat/src/test/java/android/app/DialogDefaultTouchTest.java index 13506dd3641..ff55d7420be 100644 --- a/maven/android-compat/src/test/java/android/app/DialogDefaultTouchTest.java +++ b/maven/android-compat/src/test/java/android/app/DialogDefaultTouchTest.java @@ -22,7 +22,7 @@ */ package android.app; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; import static org.junit.Assert.*; diff --git a/maven/android-compat/src/test/java/android/app/FragmentLauncherRecreationTest.java b/maven/android-compat/src/test/java/android/app/FragmentLauncherRecreationTest.java index 9d707687c4e..7d4558d1d84 100644 --- a/maven/android-compat/src/test/java/android/app/FragmentLauncherRecreationTest.java +++ b/maven/android-compat/src/test/java/android/app/FragmentLauncherRecreationTest.java @@ -37,7 +37,7 @@ import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentFactory; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/app/FragmentManagerTest.java b/maven/android-compat/src/test/java/android/app/FragmentManagerTest.java index 41e5060bdfe..05fa932117d 100644 --- a/maven/android-compat/src/test/java/android/app/FragmentManagerTest.java +++ b/maven/android-compat/src/test/java/android/app/FragmentManagerTest.java @@ -29,7 +29,7 @@ import com.codename1.androidcompat.runtime.MenuImpl; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/app/FragmentViewStateTest.java b/maven/android-compat/src/test/java/android/app/FragmentViewStateTest.java index cec65c164d1..cfde8aa55eb 100644 --- a/maven/android-compat/src/test/java/android/app/FragmentViewStateTest.java +++ b/maven/android-compat/src/test/java/android/app/FragmentViewStateTest.java @@ -31,7 +31,7 @@ import android.widget.FrameLayout; import android.widget.LinearLayout; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; import static org.junit.Assert.*; diff --git a/maven/android-compat/src/test/java/android/app/PauseSelfFinishTest.java b/maven/android-compat/src/test/java/android/app/PauseSelfFinishTest.java index 8b5c196f2c2..a64f0f1fa13 100644 --- a/maven/android-compat/src/test/java/android/app/PauseSelfFinishTest.java +++ b/maven/android-compat/src/test/java/android/app/PauseSelfFinishTest.java @@ -24,7 +24,7 @@ import android.content.Context; import android.content.Intent; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; import static org.junit.Assert.*; diff --git a/maven/android-compat/src/test/java/android/app/ResultAfterRelaunchTest.java b/maven/android-compat/src/test/java/android/app/ResultAfterRelaunchTest.java index 678ce50dd6e..68920e10d7c 100644 --- a/maven/android-compat/src/test/java/android/app/ResultAfterRelaunchTest.java +++ b/maven/android-compat/src/test/java/android/app/ResultAfterRelaunchTest.java @@ -27,7 +27,7 @@ import com.codename1.androidcompat.runtime.ResourceManager; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.HeadlessImplementation; +import com.codename1.compat.testing.HeadlessImplementation; import com.codename1.ui.Display; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/app/ResumeConfigurationTest.java b/maven/android-compat/src/test/java/android/app/ResumeConfigurationTest.java index 5cdae4a9955..2b71d4c582f 100644 --- a/maven/android-compat/src/test/java/android/app/ResumeConfigurationTest.java +++ b/maven/android-compat/src/test/java/android/app/ResumeConfigurationTest.java @@ -26,7 +26,7 @@ import android.content.Intent; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.HeadlessImplementation; +import com.codename1.compat.testing.HeadlessImplementation; import com.codename1.ui.Display; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/app/SupportFragmentsTest.java b/maven/android-compat/src/test/java/android/app/SupportFragmentsTest.java index e19bd8f29ab..823bbbba545 100644 --- a/maven/android-compat/src/test/java/android/app/SupportFragmentsTest.java +++ b/maven/android-compat/src/test/java/android/app/SupportFragmentsTest.java @@ -36,7 +36,7 @@ import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModelProvider; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/app/WindowKeepScreenOnTest.java b/maven/android-compat/src/test/java/android/app/WindowKeepScreenOnTest.java index b46827ee87e..ee29af997b6 100644 --- a/maven/android-compat/src/test/java/android/app/WindowKeepScreenOnTest.java +++ b/maven/android-compat/src/test/java/android/app/WindowKeepScreenOnTest.java @@ -25,8 +25,8 @@ import android.content.Intent; import android.view.WindowManager; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.HeadlessImplementation; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.HeadlessImplementation; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; import static org.junit.Assert.*; diff --git a/maven/android-compat/src/test/java/android/content/BroadcastFilterMatchTest.java b/maven/android-compat/src/test/java/android/content/BroadcastFilterMatchTest.java index 956640fd142..2722df75f35 100644 --- a/maven/android-compat/src/test/java/android/content/BroadcastFilterMatchTest.java +++ b/maven/android-compat/src/test/java/android/content/BroadcastFilterMatchTest.java @@ -26,7 +26,7 @@ import android.os.Bundle; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/content/ContentResolverFileUriTest.java b/maven/android-compat/src/test/java/android/content/ContentResolverFileUriTest.java index a163eb2bf10..7d3f6ecb72b 100644 --- a/maven/android-compat/src/test/java/android/content/ContentResolverFileUriTest.java +++ b/maven/android-compat/src/test/java/android/content/ContentResolverFileUriTest.java @@ -25,8 +25,8 @@ import android.net.Uri; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.HeadlessImplementation; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.HeadlessImplementation; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/content/ContentValuesTest.java b/maven/android-compat/src/test/java/android/content/ContentValuesTest.java index 333ccdec458..0b788b61023 100644 --- a/maven/android-compat/src/test/java/android/content/ContentValuesTest.java +++ b/maven/android-compat/src/test/java/android/content/ContentValuesTest.java @@ -31,7 +31,7 @@ import java.util.ArrayList; import java.util.List; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/content/ContextFileNameTest.java b/maven/android-compat/src/test/java/android/content/ContextFileNameTest.java index edec7b76d71..1e0ab7cc6d6 100644 --- a/maven/android-compat/src/test/java/android/content/ContextFileNameTest.java +++ b/maven/android-compat/src/test/java/android/content/ContextFileNameTest.java @@ -23,7 +23,7 @@ package android.content; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/content/IntentCloneFilterTest.java b/maven/android-compat/src/test/java/android/content/IntentCloneFilterTest.java index b1575de4746..95f2e4b0e23 100644 --- a/maven/android-compat/src/test/java/android/content/IntentCloneFilterTest.java +++ b/maven/android-compat/src/test/java/android/content/IntentCloneFilterTest.java @@ -22,7 +22,7 @@ */ package android.content; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/content/IntentResolveActivityTest.java b/maven/android-compat/src/test/java/android/content/IntentResolveActivityTest.java index a42c0b56bd9..1d1c2a4c721 100644 --- a/maven/android-compat/src/test/java/android/content/IntentResolveActivityTest.java +++ b/maven/android-compat/src/test/java/android/content/IntentResolveActivityTest.java @@ -26,7 +26,7 @@ import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/database/CursorsTest.java b/maven/android-compat/src/test/java/android/database/CursorsTest.java index 5447872cd79..fcdd9c0ee36 100644 --- a/maven/android-compat/src/test/java/android/database/CursorsTest.java +++ b/maven/android-compat/src/test/java/android/database/CursorsTest.java @@ -30,7 +30,7 @@ import java.util.Arrays; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/database/sqlite/SQLiteDatabaseTest.java b/maven/android-compat/src/test/java/android/database/sqlite/SQLiteDatabaseTest.java index 986eb66130c..7ece2c8fefb 100644 --- a/maven/android-compat/src/test/java/android/database/sqlite/SQLiteDatabaseTest.java +++ b/maven/android-compat/src/test/java/android/database/sqlite/SQLiteDatabaseTest.java @@ -39,7 +39,7 @@ import java.util.ArrayList; import java.util.List; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.After; import org.junit.Before; import org.junit.Rule; diff --git a/maven/android-compat/src/test/java/android/database/sqlite/SQLiteQueryBuilderTest.java b/maven/android-compat/src/test/java/android/database/sqlite/SQLiteQueryBuilderTest.java index ee8cc23eae4..15a2d099ba1 100644 --- a/maven/android-compat/src/test/java/android/database/sqlite/SQLiteQueryBuilderTest.java +++ b/maven/android-compat/src/test/java/android/database/sqlite/SQLiteQueryBuilderTest.java @@ -28,7 +28,7 @@ import java.util.LinkedHashMap; import java.util.Map; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/graphics/BitmapCompressFormatTest.java b/maven/android-compat/src/test/java/android/graphics/BitmapCompressFormatTest.java index 68e955fd7a7..aeba30331f0 100644 --- a/maven/android-compat/src/test/java/android/graphics/BitmapCompressFormatTest.java +++ b/maven/android-compat/src/test/java/android/graphics/BitmapCompressFormatTest.java @@ -23,8 +23,8 @@ package android.graphics; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.HeadlessImplementation; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.HeadlessImplementation; +import com.codename1.compat.testing.MainThreadRule; import com.codename1.ui.Image; import com.codename1.ui.util.ImageIO; diff --git a/maven/android-compat/src/test/java/android/graphics/BitmapDerivedDensityTest.java b/maven/android-compat/src/test/java/android/graphics/BitmapDerivedDensityTest.java index c220192f44d..7cf76dafcdf 100644 --- a/maven/android-compat/src/test/java/android/graphics/BitmapDerivedDensityTest.java +++ b/maven/android-compat/src/test/java/android/graphics/BitmapDerivedDensityTest.java @@ -23,8 +23,8 @@ package android.graphics; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.HeadlessImplementation; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.HeadlessImplementation; +import com.codename1.compat.testing.MainThreadRule; import org.junit.After; import org.junit.Before; diff --git a/maven/android-compat/src/test/java/android/graphics/BitmapDisplayMetricsDensityTest.java b/maven/android-compat/src/test/java/android/graphics/BitmapDisplayMetricsDensityTest.java index 6d8fe12ff37..3cf77a10665 100644 --- a/maven/android-compat/src/test/java/android/graphics/BitmapDisplayMetricsDensityTest.java +++ b/maven/android-compat/src/test/java/android/graphics/BitmapDisplayMetricsDensityTest.java @@ -25,7 +25,7 @@ import android.util.DisplayMetrics; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/graphics/BitmapFactoryBoundsTest.java b/maven/android-compat/src/test/java/android/graphics/BitmapFactoryBoundsTest.java index 052df841c43..1b2c1af4307 100644 --- a/maven/android-compat/src/test/java/android/graphics/BitmapFactoryBoundsTest.java +++ b/maven/android-compat/src/test/java/android/graphics/BitmapFactoryBoundsTest.java @@ -23,8 +23,8 @@ package android.graphics; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.HeadlessImplementation; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.HeadlessImplementation; +import com.codename1.compat.testing.MainThreadRule; import org.junit.After; import org.junit.Before; diff --git a/maven/android-compat/src/test/java/android/graphics/BitmapFactoryHeaderBoundsTest.java b/maven/android-compat/src/test/java/android/graphics/BitmapFactoryHeaderBoundsTest.java index eead128d5cf..eeea2455638 100644 --- a/maven/android-compat/src/test/java/android/graphics/BitmapFactoryHeaderBoundsTest.java +++ b/maven/android-compat/src/test/java/android/graphics/BitmapFactoryHeaderBoundsTest.java @@ -23,8 +23,8 @@ package android.graphics; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.HeadlessImplementation; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.HeadlessImplementation; +import com.codename1.compat.testing.MainThreadRule; import org.junit.After; import org.junit.Before; diff --git a/maven/android-compat/src/test/java/android/graphics/BitmapFactoryMimeTypeTest.java b/maven/android-compat/src/test/java/android/graphics/BitmapFactoryMimeTypeTest.java index f500b92cc79..c8f19cbaa02 100644 --- a/maven/android-compat/src/test/java/android/graphics/BitmapFactoryMimeTypeTest.java +++ b/maven/android-compat/src/test/java/android/graphics/BitmapFactoryMimeTypeTest.java @@ -23,7 +23,7 @@ package android.graphics; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Before; import org.junit.Rule; diff --git a/maven/android-compat/src/test/java/android/graphics/BitmapFactoryUnscaledDensityTest.java b/maven/android-compat/src/test/java/android/graphics/BitmapFactoryUnscaledDensityTest.java index d8d830e2971..e13ec64adb6 100644 --- a/maven/android-compat/src/test/java/android/graphics/BitmapFactoryUnscaledDensityTest.java +++ b/maven/android-compat/src/test/java/android/graphics/BitmapFactoryUnscaledDensityTest.java @@ -23,8 +23,8 @@ package android.graphics; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.HeadlessImplementation; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.HeadlessImplementation; +import com.codename1.compat.testing.MainThreadRule; import org.junit.After; import org.junit.Before; diff --git a/maven/android-compat/src/test/java/android/graphics/BitmapImmutableWriteTest.java b/maven/android-compat/src/test/java/android/graphics/BitmapImmutableWriteTest.java index 7d14e71f210..824f8f3fb60 100644 --- a/maven/android-compat/src/test/java/android/graphics/BitmapImmutableWriteTest.java +++ b/maven/android-compat/src/test/java/android/graphics/BitmapImmutableWriteTest.java @@ -23,8 +23,8 @@ package android.graphics; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.HeadlessImplementation; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.HeadlessImplementation; +import com.codename1.compat.testing.MainThreadRule; import com.codename1.ui.Image; import org.junit.After; diff --git a/maven/android-compat/src/test/java/android/graphics/BitmapMatrixTest.java b/maven/android-compat/src/test/java/android/graphics/BitmapMatrixTest.java index 2ef074d80b9..78483b0e4fa 100644 --- a/maven/android-compat/src/test/java/android/graphics/BitmapMatrixTest.java +++ b/maven/android-compat/src/test/java/android/graphics/BitmapMatrixTest.java @@ -23,8 +23,8 @@ package android.graphics; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.HeadlessImplementation; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.HeadlessImplementation; +import com.codename1.compat.testing.MainThreadRule; import org.junit.After; import org.junit.Rule; diff --git a/maven/android-compat/src/test/java/android/graphics/BitmapMutableSnapshotTest.java b/maven/android-compat/src/test/java/android/graphics/BitmapMutableSnapshotTest.java index b9bb3828141..65640cd7c29 100644 --- a/maven/android-compat/src/test/java/android/graphics/BitmapMutableSnapshotTest.java +++ b/maven/android-compat/src/test/java/android/graphics/BitmapMutableSnapshotTest.java @@ -23,8 +23,8 @@ package android.graphics; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.HeadlessImplementation; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.HeadlessImplementation; +import com.codename1.compat.testing.MainThreadRule; import com.codename1.ui.Image; import org.junit.After; diff --git a/maven/android-compat/src/test/java/android/graphics/BitmapPixelBoundsTest.java b/maven/android-compat/src/test/java/android/graphics/BitmapPixelBoundsTest.java index eea0ac80ae6..f260fab4ece 100644 --- a/maven/android-compat/src/test/java/android/graphics/BitmapPixelBoundsTest.java +++ b/maven/android-compat/src/test/java/android/graphics/BitmapPixelBoundsTest.java @@ -23,8 +23,8 @@ package android.graphics; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.HeadlessImplementation; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.HeadlessImplementation; +import com.codename1.compat.testing.MainThreadRule; import com.codename1.ui.Image; import org.junit.After; diff --git a/maven/android-compat/src/test/java/android/graphics/CanvasBitmapPixelCacheTest.java b/maven/android-compat/src/test/java/android/graphics/CanvasBitmapPixelCacheTest.java index 75f6c6c614b..33f65fa6546 100644 --- a/maven/android-compat/src/test/java/android/graphics/CanvasBitmapPixelCacheTest.java +++ b/maven/android-compat/src/test/java/android/graphics/CanvasBitmapPixelCacheTest.java @@ -23,8 +23,8 @@ package android.graphics; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.HeadlessImplementation; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.HeadlessImplementation; +import com.codename1.compat.testing.MainThreadRule; import com.codename1.ui.Image; import org.junit.After; diff --git a/maven/android-compat/src/test/java/android/graphics/CanvasClipPathTest.java b/maven/android-compat/src/test/java/android/graphics/CanvasClipPathTest.java index 3627afe5504..473ae646703 100644 --- a/maven/android-compat/src/test/java/android/graphics/CanvasClipPathTest.java +++ b/maven/android-compat/src/test/java/android/graphics/CanvasClipPathTest.java @@ -23,8 +23,8 @@ package android.graphics; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.HeadlessImplementation; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.HeadlessImplementation; +import com.codename1.compat.testing.MainThreadRule; import com.codename1.ui.Graphics; import com.codename1.ui.Image; import com.codename1.ui.geom.Rectangle; diff --git a/maven/android-compat/src/test/java/android/graphics/CanvasClipRestoreAndClearTest.java b/maven/android-compat/src/test/java/android/graphics/CanvasClipRestoreAndClearTest.java index 120b15654dd..9dff20fc1af 100644 --- a/maven/android-compat/src/test/java/android/graphics/CanvasClipRestoreAndClearTest.java +++ b/maven/android-compat/src/test/java/android/graphics/CanvasClipRestoreAndClearTest.java @@ -23,8 +23,8 @@ package android.graphics; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.HeadlessImplementation; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.HeadlessImplementation; +import com.codename1.compat.testing.MainThreadRule; import com.codename1.ui.Graphics; import com.codename1.ui.Image; diff --git a/maven/android-compat/src/test/java/android/graphics/CanvasDrawPaintShaderTest.java b/maven/android-compat/src/test/java/android/graphics/CanvasDrawPaintShaderTest.java index 83a14a4809b..b9ac446b9cb 100644 --- a/maven/android-compat/src/test/java/android/graphics/CanvasDrawPaintShaderTest.java +++ b/maven/android-compat/src/test/java/android/graphics/CanvasDrawPaintShaderTest.java @@ -23,8 +23,8 @@ package android.graphics; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.HeadlessImplementation; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.HeadlessImplementation; +import com.codename1.compat.testing.MainThreadRule; import com.codename1.ui.Graphics; import com.codename1.ui.Image; diff --git a/maven/android-compat/src/test/java/android/graphics/CanvasImmutableBitmapTest.java b/maven/android-compat/src/test/java/android/graphics/CanvasImmutableBitmapTest.java index 14303275337..201587bca27 100644 --- a/maven/android-compat/src/test/java/android/graphics/CanvasImmutableBitmapTest.java +++ b/maven/android-compat/src/test/java/android/graphics/CanvasImmutableBitmapTest.java @@ -23,7 +23,7 @@ package android.graphics; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import com.codename1.ui.Image; import org.junit.Rule; diff --git a/maven/android-compat/src/test/java/android/graphics/CanvasRecycledBitmapTest.java b/maven/android-compat/src/test/java/android/graphics/CanvasRecycledBitmapTest.java index 713eede7a93..2433e520485 100644 --- a/maven/android-compat/src/test/java/android/graphics/CanvasRecycledBitmapTest.java +++ b/maven/android-compat/src/test/java/android/graphics/CanvasRecycledBitmapTest.java @@ -23,7 +23,7 @@ package android.graphics; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Before; import org.junit.Rule; diff --git a/maven/android-compat/src/test/java/android/graphics/CanvasRotatedClipRectTest.java b/maven/android-compat/src/test/java/android/graphics/CanvasRotatedClipRectTest.java index fa774c77ba1..d3b1f76008a 100644 --- a/maven/android-compat/src/test/java/android/graphics/CanvasRotatedClipRectTest.java +++ b/maven/android-compat/src/test/java/android/graphics/CanvasRotatedClipRectTest.java @@ -23,8 +23,8 @@ package android.graphics; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.HeadlessImplementation; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.HeadlessImplementation; +import com.codename1.compat.testing.MainThreadRule; import com.codename1.ui.Graphics; import com.codename1.ui.Image; diff --git a/maven/android-compat/src/test/java/android/graphics/CanvasSaveLayerTest.java b/maven/android-compat/src/test/java/android/graphics/CanvasSaveLayerTest.java index fec47a1c2b7..e46b658b8bc 100644 --- a/maven/android-compat/src/test/java/android/graphics/CanvasSaveLayerTest.java +++ b/maven/android-compat/src/test/java/android/graphics/CanvasSaveLayerTest.java @@ -23,8 +23,8 @@ package android.graphics; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.HeadlessImplementation; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.HeadlessImplementation; +import com.codename1.compat.testing.MainThreadRule; import com.codename1.ui.Graphics; import com.codename1.ui.Image; diff --git a/maven/android-compat/src/test/java/android/graphics/CanvasSpacedSurrogateTest.java b/maven/android-compat/src/test/java/android/graphics/CanvasSpacedSurrogateTest.java index b089d7f646e..fea418263a0 100644 --- a/maven/android-compat/src/test/java/android/graphics/CanvasSpacedSurrogateTest.java +++ b/maven/android-compat/src/test/java/android/graphics/CanvasSpacedSurrogateTest.java @@ -23,8 +23,8 @@ package android.graphics; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.HeadlessImplementation; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.HeadlessImplementation; +import com.codename1.compat.testing.MainThreadRule; import com.codename1.ui.Graphics; import com.codename1.ui.Image; diff --git a/maven/android-compat/src/test/java/android/graphics/CanvasTextScaleXTest.java b/maven/android-compat/src/test/java/android/graphics/CanvasTextScaleXTest.java index fbca64f1e07..5e8b13e33ed 100644 --- a/maven/android-compat/src/test/java/android/graphics/CanvasTextScaleXTest.java +++ b/maven/android-compat/src/test/java/android/graphics/CanvasTextScaleXTest.java @@ -23,8 +23,8 @@ package android.graphics; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.HeadlessImplementation; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.HeadlessImplementation; +import com.codename1.compat.testing.MainThreadRule; import com.codename1.ui.Graphics; import com.codename1.ui.Image; diff --git a/maven/android-compat/src/test/java/android/graphics/LetterSpacingMeasureTest.java b/maven/android-compat/src/test/java/android/graphics/LetterSpacingMeasureTest.java index 30fef53d5f2..5d2aea4a718 100644 --- a/maven/android-compat/src/test/java/android/graphics/LetterSpacingMeasureTest.java +++ b/maven/android-compat/src/test/java/android/graphics/LetterSpacingMeasureTest.java @@ -23,8 +23,8 @@ package android.graphics; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.HeadlessImplementation; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.HeadlessImplementation; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/graphics/LetterSpacingWidthsTest.java b/maven/android-compat/src/test/java/android/graphics/LetterSpacingWidthsTest.java index 59c8c9b8594..8cf87a0486e 100644 --- a/maven/android-compat/src/test/java/android/graphics/LetterSpacingWidthsTest.java +++ b/maven/android-compat/src/test/java/android/graphics/LetterSpacingWidthsTest.java @@ -23,7 +23,7 @@ package android.graphics; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/graphics/drawable/BitmapDrawableTargetDensityTest.java b/maven/android-compat/src/test/java/android/graphics/drawable/BitmapDrawableTargetDensityTest.java index 38b8affe473..ef3673a0520 100644 --- a/maven/android-compat/src/test/java/android/graphics/drawable/BitmapDrawableTargetDensityTest.java +++ b/maven/android-compat/src/test/java/android/graphics/drawable/BitmapDrawableTargetDensityTest.java @@ -26,8 +26,8 @@ import android.graphics.Bitmap; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.HeadlessImplementation; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.HeadlessImplementation; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/graphics/drawable/BitmapDrawableTileModeTest.java b/maven/android-compat/src/test/java/android/graphics/drawable/BitmapDrawableTileModeTest.java index c9bd951fdd4..d93ec2d7a3a 100644 --- a/maven/android-compat/src/test/java/android/graphics/drawable/BitmapDrawableTileModeTest.java +++ b/maven/android-compat/src/test/java/android/graphics/drawable/BitmapDrawableTileModeTest.java @@ -30,8 +30,8 @@ import android.graphics.Shader; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.HeadlessImplementation; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.HeadlessImplementation; +import com.codename1.compat.testing.MainThreadRule; import com.codename1.ui.Image; import java.util.ArrayList; diff --git a/maven/android-compat/src/test/java/android/graphics/drawable/GradientDrawableOrientationTest.java b/maven/android-compat/src/test/java/android/graphics/drawable/GradientDrawableOrientationTest.java index d8316b803e1..2fa89708efd 100644 --- a/maven/android-compat/src/test/java/android/graphics/drawable/GradientDrawableOrientationTest.java +++ b/maven/android-compat/src/test/java/android/graphics/drawable/GradientDrawableOrientationTest.java @@ -25,8 +25,8 @@ import android.graphics.Canvas; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.HeadlessImplementation; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.HeadlessImplementation; +import com.codename1.compat.testing.MainThreadRule; import com.codename1.ui.Image; import com.codename1.ui.LinearGradient; diff --git a/maven/android-compat/src/test/java/android/graphics/drawable/StateListTintModeTest.java b/maven/android-compat/src/test/java/android/graphics/drawable/StateListTintModeTest.java index d3537db90f5..9581c195278 100644 --- a/maven/android-compat/src/test/java/android/graphics/drawable/StateListTintModeTest.java +++ b/maven/android-compat/src/test/java/android/graphics/drawable/StateListTintModeTest.java @@ -26,7 +26,7 @@ import android.graphics.PorterDuff; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Before; import org.junit.Rule; diff --git a/maven/android-compat/src/test/java/android/graphics/drawable/WrapperChildStateTest.java b/maven/android-compat/src/test/java/android/graphics/drawable/WrapperChildStateTest.java index ff0b99b893d..09e47c251ca 100644 --- a/maven/android-compat/src/test/java/android/graphics/drawable/WrapperChildStateTest.java +++ b/maven/android-compat/src/test/java/android/graphics/drawable/WrapperChildStateTest.java @@ -29,7 +29,7 @@ import android.graphics.Rect; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import com.codename1.ui.Image; import org.junit.Before; diff --git a/maven/android-compat/src/test/java/android/net/UriFromFileTest.java b/maven/android-compat/src/test/java/android/net/UriFromFileTest.java index 5007efeab78..8174d42b76b 100644 --- a/maven/android-compat/src/test/java/android/net/UriFromFileTest.java +++ b/maven/android-compat/src/test/java/android/net/UriFromFileTest.java @@ -22,8 +22,7 @@ */ package android.net; -import com.codename1.androidcompat.jdk.File; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; @@ -32,21 +31,35 @@ /// A context directory is already a `file:` path; `fromFile` prefixed a /// second scheme to it (`file://file:///...`). +/// +/// A device's `java.io.File` keeps the storage path it was made from, which +/// the JDK's running these tests would rewrite (`file:///a` becomes +/// `file:/a`), so the files here are stand-ins that print their path as the +/// device's does: `fromFile` reads nothing of a file but that. public class UriFromFileTest { + private static Object file(final String path) { + return new Object() { + @Override + public String toString() { + return path; + } + }; + } + @Rule public final MainThreadRule mainThread = new MainThreadRule(); @Test public void keepsAnExistingFileScheme() { - Uri u = Uri.fromFile(new File("file:///data/files/report.pdf")); + Uri u = Uri.fromFile(file("file:///data/files/report.pdf")); assertEquals("file:///data/files/report.pdf", u.toString()); assertEquals("/data/files/report.pdf", u.getPath()); } @Test public void anAbsolutePathGainsTheScheme() { - assertEquals("file:///data/report.pdf", Uri.fromFile(new File("/data/report.pdf")).toString()); + assertEquals("file:///data/report.pdf", Uri.fromFile(file("/data/report.pdf")).toString()); } /// Reserved characters in a file name are percent-encoded, so the whole @@ -54,12 +67,12 @@ public void anAbsolutePathGainsTheScheme() { /// `getPath()` answered `/data/a`. @Test public void encodesReservedCharactersInThePath() { - Uri u = Uri.fromFile(new File("/data/a#b?c d.txt")); + Uri u = Uri.fromFile(file("/data/a#b?c d.txt")); assertEquals("file:///data/a%23b%3Fc%20d.txt", u.toString()); assertEquals("/data/a#b?c d.txt", u.getPath()); assertEquals(null, u.getFragment()); - Uri home = Uri.fromFile(new File("file://home/x#1.txt")); + Uri home = Uri.fromFile(file("file://home/x#1.txt")); assertEquals("file://home/x%231.txt", home.toString()); assertEquals("home", home.getAuthority()); assertEquals("/x#1.txt", home.getPath()); diff --git a/maven/android-compat/src/test/java/android/net/UriNormalizeSchemeTest.java b/maven/android-compat/src/test/java/android/net/UriNormalizeSchemeTest.java index b6144d8b5f2..bb8225464bf 100644 --- a/maven/android-compat/src/test/java/android/net/UriNormalizeSchemeTest.java +++ b/maven/android-compat/src/test/java/android/net/UriNormalizeSchemeTest.java @@ -22,7 +22,7 @@ */ package android.net; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/os/BundleDeepCopyTest.java b/maven/android-compat/src/test/java/android/os/BundleDeepCopyTest.java index 77a29ef9d69..aa334c90126 100644 --- a/maven/android-compat/src/test/java/android/os/BundleDeepCopyTest.java +++ b/maven/android-compat/src/test/java/android/os/BundleDeepCopyTest.java @@ -24,7 +24,7 @@ import android.util.SparseArray; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import java.util.ArrayList; diff --git a/maven/android-compat/src/test/java/android/os/BundleEmptyImmutableTest.java b/maven/android-compat/src/test/java/android/os/BundleEmptyImmutableTest.java index 74f80640063..3fe553ed892 100644 --- a/maven/android-compat/src/test/java/android/os/BundleEmptyImmutableTest.java +++ b/maven/android-compat/src/test/java/android/os/BundleEmptyImmutableTest.java @@ -22,7 +22,7 @@ */ package android.os; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/os/HandlerFrontOfQueueTest.java b/maven/android-compat/src/test/java/android/os/HandlerFrontOfQueueTest.java index b3c06ca1ad3..5829b007fe1 100644 --- a/maven/android-compat/src/test/java/android/os/HandlerFrontOfQueueTest.java +++ b/maven/android-compat/src/test/java/android/os/HandlerFrontOfQueueTest.java @@ -23,7 +23,7 @@ package android.os; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/text/AllCapsSpansTest.java b/maven/android-compat/src/test/java/android/text/AllCapsSpansTest.java index 4940fb4d5a2..4258445935d 100644 --- a/maven/android-compat/src/test/java/android/text/AllCapsSpansTest.java +++ b/maven/android-compat/src/test/java/android/text/AllCapsSpansTest.java @@ -22,7 +22,7 @@ */ package android.text; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/text/LengthFilterSurrogateTest.java b/maven/android-compat/src/test/java/android/text/LengthFilterSurrogateTest.java index 4ddfa134989..99b4d08ceef 100644 --- a/maven/android-compat/src/test/java/android/text/LengthFilterSurrogateTest.java +++ b/maven/android-compat/src/test/java/android/text/LengthFilterSurrogateTest.java @@ -22,7 +22,7 @@ */ package android.text; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/text/TextUtilsCommaEllipsizeTest.java b/maven/android-compat/src/test/java/android/text/TextUtilsCommaEllipsizeTest.java index 3262ee9d567..11a2423106b 100644 --- a/maven/android-compat/src/test/java/android/text/TextUtilsCommaEllipsizeTest.java +++ b/maven/android-compat/src/test/java/android/text/TextUtilsCommaEllipsizeTest.java @@ -23,7 +23,7 @@ package android.text; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/text/TextUtilsEllipsizeSpansTest.java b/maven/android-compat/src/test/java/android/text/TextUtilsEllipsizeSpansTest.java index c1725b37a0d..641a7dc43df 100644 --- a/maven/android-compat/src/test/java/android/text/TextUtilsEllipsizeSpansTest.java +++ b/maven/android-compat/src/test/java/android/text/TextUtilsEllipsizeSpansTest.java @@ -23,7 +23,7 @@ package android.text; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; import static org.junit.Assert.*; diff --git a/maven/android-compat/src/test/java/android/text/TextUtilsSplitRegexTest.java b/maven/android-compat/src/test/java/android/text/TextUtilsSplitRegexTest.java index 93bec284971..c7ed5d3f22b 100644 --- a/maven/android-compat/src/test/java/android/text/TextUtilsSplitRegexTest.java +++ b/maven/android-compat/src/test/java/android/text/TextUtilsSplitRegexTest.java @@ -22,7 +22,7 @@ */ package android.text; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import java.util.Arrays; diff --git a/maven/android-compat/src/test/java/android/view/AncestorChangeDispatchTest.java b/maven/android-compat/src/test/java/android/view/AncestorChangeDispatchTest.java index 9cc1fd29164..613b1b9c997 100644 --- a/maven/android-compat/src/test/java/android/view/AncestorChangeDispatchTest.java +++ b/maven/android-compat/src/test/java/android/view/AncestorChangeDispatchTest.java @@ -25,7 +25,7 @@ import android.widget.FrameLayout; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/view/BackgroundTintModeTest.java b/maven/android-compat/src/test/java/android/view/BackgroundTintModeTest.java index edda59e4650..c946dd02a5a 100644 --- a/maven/android-compat/src/test/java/android/view/BackgroundTintModeTest.java +++ b/maven/android-compat/src/test/java/android/view/BackgroundTintModeTest.java @@ -27,7 +27,7 @@ import android.graphics.drawable.ColorDrawable; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/view/DrawableLayoutDirectionTest.java b/maven/android-compat/src/test/java/android/view/DrawableLayoutDirectionTest.java index e0014988756..0794ff31bcf 100644 --- a/maven/android-compat/src/test/java/android/view/DrawableLayoutDirectionTest.java +++ b/maven/android-compat/src/test/java/android/view/DrawableLayoutDirectionTest.java @@ -26,7 +26,7 @@ import android.widget.FrameLayout; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/view/HiddenViewLosesFocusTest.java b/maven/android-compat/src/test/java/android/view/HiddenViewLosesFocusTest.java index 93e81aeb78d..448047cc662 100644 --- a/maven/android-compat/src/test/java/android/view/HiddenViewLosesFocusTest.java +++ b/maven/android-compat/src/test/java/android/view/HiddenViewLosesFocusTest.java @@ -25,7 +25,7 @@ import android.widget.FrameLayout; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/view/IncludeThemeTest.java b/maven/android-compat/src/test/java/android/view/IncludeThemeTest.java index 9e333583934..52c5903b242 100644 --- a/maven/android-compat/src/test/java/android/view/IncludeThemeTest.java +++ b/maven/android-compat/src/test/java/android/view/IncludeThemeTest.java @@ -27,7 +27,7 @@ import com.codename1.androidcompat.runtime.ResValue; import com.codename1.androidcompat.runtime.XmlNode; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/view/MergedIncludeFinishInflateTest.java b/maven/android-compat/src/test/java/android/view/MergedIncludeFinishInflateTest.java index 20aff7b491f..9deba5b9f49 100644 --- a/maven/android-compat/src/test/java/android/view/MergedIncludeFinishInflateTest.java +++ b/maven/android-compat/src/test/java/android/view/MergedIncludeFinishInflateTest.java @@ -31,7 +31,7 @@ import com.codename1.androidcompat.runtime.ResValue; import com.codename1.androidcompat.runtime.XmlNode; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/view/RemovedTouchTargetTest.java b/maven/android-compat/src/test/java/android/view/RemovedTouchTargetTest.java index b8bfc05e767..0f3fbb09c40 100644 --- a/maven/android-compat/src/test/java/android/view/RemovedTouchTargetTest.java +++ b/maven/android-compat/src/test/java/android/view/RemovedTouchTargetTest.java @@ -26,7 +26,7 @@ import android.widget.FrameLayout; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import java.util.ArrayList; import java.util.List; diff --git a/maven/android-compat/src/test/java/android/view/TransformedChildTouchTest.java b/maven/android-compat/src/test/java/android/view/TransformedChildTouchTest.java index c2276483cf5..f402a2acab4 100644 --- a/maven/android-compat/src/test/java/android/view/TransformedChildTouchTest.java +++ b/maven/android-compat/src/test/java/android/view/TransformedChildTouchTest.java @@ -26,7 +26,7 @@ import android.widget.FrameLayout; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/view/ViewCallbacksTest.java b/maven/android-compat/src/test/java/android/view/ViewCallbacksTest.java index 5cb6d4948db..1c0b9f6b0c2 100644 --- a/maven/android-compat/src/test/java/android/view/ViewCallbacksTest.java +++ b/maven/android-compat/src/test/java/android/view/ViewCallbacksTest.java @@ -33,7 +33,7 @@ import com.codename1.androidcompat.runtime.ResValue; import com.codename1.androidcompat.runtime.XmlNode; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import com.codename1.ui.Image; import org.junit.Rule; diff --git a/maven/android-compat/src/test/java/android/view/ViewStubReferenceTest.java b/maven/android-compat/src/test/java/android/view/ViewStubReferenceTest.java index 3f66e8167bd..dfbc02b1011 100644 --- a/maven/android-compat/src/test/java/android/view/ViewStubReferenceTest.java +++ b/maven/android-compat/src/test/java/android/view/ViewStubReferenceTest.java @@ -24,7 +24,7 @@ import android.content.Context; import android.widget.FrameLayout; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; import static org.junit.Assert.*; diff --git a/maven/android-compat/src/test/java/android/view/ViewTreeScrollTest.java b/maven/android-compat/src/test/java/android/view/ViewTreeScrollTest.java index b505ee3f95f..32016b44d53 100644 --- a/maven/android-compat/src/test/java/android/view/ViewTreeScrollTest.java +++ b/maven/android-compat/src/test/java/android/view/ViewTreeScrollTest.java @@ -24,7 +24,7 @@ import android.widget.FrameLayout; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; import static org.junit.Assert.*; diff --git a/maven/android-compat/src/test/java/android/view/VisibleRectAndKeepScreenOnTest.java b/maven/android-compat/src/test/java/android/view/VisibleRectAndKeepScreenOnTest.java index 71a2b8c5402..96c78ce69be 100644 --- a/maven/android-compat/src/test/java/android/view/VisibleRectAndKeepScreenOnTest.java +++ b/maven/android-compat/src/test/java/android/view/VisibleRectAndKeepScreenOnTest.java @@ -26,8 +26,8 @@ import android.widget.FrameLayout; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.HeadlessImplementation; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.HeadlessImplementation; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/view/animation/AnimationCloneTest.java b/maven/android-compat/src/test/java/android/view/animation/AnimationCloneTest.java index d79f0b3d46e..0d5eae6a73a 100644 --- a/maven/android-compat/src/test/java/android/view/animation/AnimationCloneTest.java +++ b/maven/android-compat/src/test/java/android/view/animation/AnimationCloneTest.java @@ -31,7 +31,7 @@ import android.animation.TimeAnimator; import android.animation.ValueAnimator; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/view/animation/AnimationTimingTest.java b/maven/android-compat/src/test/java/android/view/animation/AnimationTimingTest.java index a0ddab1878e..cb9287d85bb 100644 --- a/maven/android-compat/src/test/java/android/view/animation/AnimationTimingTest.java +++ b/maven/android-compat/src/test/java/android/view/animation/AnimationTimingTest.java @@ -28,7 +28,7 @@ import android.graphics.Matrix; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/webkit/WebViewHistoryDistanceTest.java b/maven/android-compat/src/test/java/android/webkit/WebViewHistoryDistanceTest.java index 8a18d872564..044c8da936a 100644 --- a/maven/android-compat/src/test/java/android/webkit/WebViewHistoryDistanceTest.java +++ b/maven/android-compat/src/test/java/android/webkit/WebViewHistoryDistanceTest.java @@ -25,7 +25,7 @@ import android.content.Context; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/webkit/WebViewHistoryUrlTest.java b/maven/android-compat/src/test/java/android/webkit/WebViewHistoryUrlTest.java index 64bd42d6839..8744080d191 100644 --- a/maven/android-compat/src/test/java/android/webkit/WebViewHistoryUrlTest.java +++ b/maven/android-compat/src/test/java/android/webkit/WebViewHistoryUrlTest.java @@ -23,7 +23,7 @@ package android.webkit; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import com.codename1.ui.BrowserComponent; import com.codename1.ui.events.ActionEvent; diff --git a/maven/android-compat/src/test/java/android/widget/AbsListViewSelectionAndStackTest.java b/maven/android-compat/src/test/java/android/widget/AbsListViewSelectionAndStackTest.java index 83110ec9734..eec691d1790 100644 --- a/maven/android-compat/src/test/java/android/widget/AbsListViewSelectionAndStackTest.java +++ b/maven/android-compat/src/test/java/android/widget/AbsListViewSelectionAndStackTest.java @@ -27,7 +27,7 @@ import android.view.View; import android.view.ViewGroup; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; import java.lang.reflect.Field; diff --git a/maven/android-compat/src/test/java/android/widget/DatePickerRangeClampNotifiesTest.java b/maven/android-compat/src/test/java/android/widget/DatePickerRangeClampNotifiesTest.java index 2cf1e43e3c8..b6cf77f6feb 100644 --- a/maven/android-compat/src/test/java/android/widget/DatePickerRangeClampNotifiesTest.java +++ b/maven/android-compat/src/test/java/android/widget/DatePickerRangeClampNotifiesTest.java @@ -23,7 +23,7 @@ package android.widget; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/widget/DatePickerSavedStateTest.java b/maven/android-compat/src/test/java/android/widget/DatePickerSavedStateTest.java index 003f32de146..dfe338d87cb 100644 --- a/maven/android-compat/src/test/java/android/widget/DatePickerSavedStateTest.java +++ b/maven/android-compat/src/test/java/android/widget/DatePickerSavedStateTest.java @@ -24,7 +24,7 @@ import android.os.Parcelable; import android.util.SparseArray; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; import static org.junit.Assert.*; diff --git a/maven/android-compat/src/test/java/android/widget/EditTextEditableWatcherTest.java b/maven/android-compat/src/test/java/android/widget/EditTextEditableWatcherTest.java index c7b6ca6a6da..38a884aaabc 100644 --- a/maven/android-compat/src/test/java/android/widget/EditTextEditableWatcherTest.java +++ b/maven/android-compat/src/test/java/android/widget/EditTextEditableWatcherTest.java @@ -26,7 +26,7 @@ import android.text.TextWatcher; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Before; import org.junit.Rule; diff --git a/maven/android-compat/src/test/java/android/widget/EditTextSelectionTest.java b/maven/android-compat/src/test/java/android/widget/EditTextSelectionTest.java index 8a8109aab77..ed0f131fb11 100644 --- a/maven/android-compat/src/test/java/android/widget/EditTextSelectionTest.java +++ b/maven/android-compat/src/test/java/android/widget/EditTextSelectionTest.java @@ -23,7 +23,7 @@ package android.widget; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/widget/ForegroundGravityTest.java b/maven/android-compat/src/test/java/android/widget/ForegroundGravityTest.java index d00dfccaffe..68abba59b85 100644 --- a/maven/android-compat/src/test/java/android/widget/ForegroundGravityTest.java +++ b/maven/android-compat/src/test/java/android/widget/ForegroundGravityTest.java @@ -27,7 +27,7 @@ import android.view.View; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/widget/FormWidgetSavedStateTest.java b/maven/android-compat/src/test/java/android/widget/FormWidgetSavedStateTest.java index fbd0e3c8d21..4bfc88abf87 100644 --- a/maven/android-compat/src/test/java/android/widget/FormWidgetSavedStateTest.java +++ b/maven/android-compat/src/test/java/android/widget/FormWidgetSavedStateTest.java @@ -25,7 +25,7 @@ import android.util.SparseArray; import android.view.View; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; import static org.junit.Assert.*; diff --git a/maven/android-compat/src/test/java/android/widget/GridViewUniformSpacingTest.java b/maven/android-compat/src/test/java/android/widget/GridViewUniformSpacingTest.java index 938fd356c4d..c51dd74c112 100644 --- a/maven/android-compat/src/test/java/android/widget/GridViewUniformSpacingTest.java +++ b/maven/android-compat/src/test/java/android/widget/GridViewUniformSpacingTest.java @@ -25,7 +25,7 @@ import android.view.View; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/widget/ImageViewClearColorModTest.java b/maven/android-compat/src/test/java/android/widget/ImageViewClearColorModTest.java index f6372455b8b..9c4abb319f6 100644 --- a/maven/android-compat/src/test/java/android/widget/ImageViewClearColorModTest.java +++ b/maven/android-compat/src/test/java/android/widget/ImageViewClearColorModTest.java @@ -31,7 +31,7 @@ import android.graphics.drawable.Drawable; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Before; import org.junit.Rule; diff --git a/maven/android-compat/src/test/java/android/widget/ImageViewMeasureInputsRelayoutTest.java b/maven/android-compat/src/test/java/android/widget/ImageViewMeasureInputsRelayoutTest.java index 1cafce39123..eafdf7f49c3 100644 --- a/maven/android-compat/src/test/java/android/widget/ImageViewMeasureInputsRelayoutTest.java +++ b/maven/android-compat/src/test/java/android/widget/ImageViewMeasureInputsRelayoutTest.java @@ -25,7 +25,7 @@ import android.view.View; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Before; import org.junit.Rule; diff --git a/maven/android-compat/src/test/java/android/widget/LinearLayoutDividersTest.java b/maven/android-compat/src/test/java/android/widget/LinearLayoutDividersTest.java index 72071b046e4..73a3237cc24 100644 --- a/maven/android-compat/src/test/java/android/widget/LinearLayoutDividersTest.java +++ b/maven/android-compat/src/test/java/android/widget/LinearLayoutDividersTest.java @@ -26,7 +26,7 @@ import android.view.View; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/widget/ListCheckedRowsAfterDataChangeTest.java b/maven/android-compat/src/test/java/android/widget/ListCheckedRowsAfterDataChangeTest.java index 4997b0eb282..b756c613ea5 100644 --- a/maven/android-compat/src/test/java/android/widget/ListCheckedRowsAfterDataChangeTest.java +++ b/maven/android-compat/src/test/java/android/widget/ListCheckedRowsAfterDataChangeTest.java @@ -26,7 +26,7 @@ import android.view.ViewGroup; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/widget/ListViewDragOffClickTest.java b/maven/android-compat/src/test/java/android/widget/ListViewDragOffClickTest.java index b1186cfce76..733f3e52a25 100644 --- a/maven/android-compat/src/test/java/android/widget/ListViewDragOffClickTest.java +++ b/maven/android-compat/src/test/java/android/widget/ListViewDragOffClickTest.java @@ -27,7 +27,7 @@ import android.view.ViewGroup; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/widget/ProgressBarIndeterminateDrawableTest.java b/maven/android-compat/src/test/java/android/widget/ProgressBarIndeterminateDrawableTest.java index 381bcb0c029..611b6db643c 100644 --- a/maven/android-compat/src/test/java/android/widget/ProgressBarIndeterminateDrawableTest.java +++ b/maven/android-compat/src/test/java/android/widget/ProgressBarIndeterminateDrawableTest.java @@ -29,7 +29,7 @@ import android.graphics.drawable.Drawable; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import com.codename1.ui.Image; import org.junit.Before; diff --git a/maven/android-compat/src/test/java/android/widget/ProgressBarRangeClampTest.java b/maven/android-compat/src/test/java/android/widget/ProgressBarRangeClampTest.java index fc6d8fad659..c4b2bce89e8 100644 --- a/maven/android-compat/src/test/java/android/widget/ProgressBarRangeClampTest.java +++ b/maven/android-compat/src/test/java/android/widget/ProgressBarRangeClampTest.java @@ -23,7 +23,7 @@ package android.widget; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/widget/ProgressDrawableOwnershipTest.java b/maven/android-compat/src/test/java/android/widget/ProgressDrawableOwnershipTest.java index edd4aebd750..01f108c3ed5 100644 --- a/maven/android-compat/src/test/java/android/widget/ProgressDrawableOwnershipTest.java +++ b/maven/android-compat/src/test/java/android/widget/ProgressDrawableOwnershipTest.java @@ -25,7 +25,7 @@ import android.util.SparseArray; import android.view.View; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; import static org.junit.Assert.*; diff --git a/maven/android-compat/src/test/java/android/widget/RadioGroupAndFilteredAdapterTest.java b/maven/android-compat/src/test/java/android/widget/RadioGroupAndFilteredAdapterTest.java index 03291c5189a..5239c64a603 100644 --- a/maven/android-compat/src/test/java/android/widget/RadioGroupAndFilteredAdapterTest.java +++ b/maven/android-compat/src/test/java/android/widget/RadioGroupAndFilteredAdapterTest.java @@ -26,7 +26,7 @@ import android.view.View; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/widget/RowStateNotSavedTest.java b/maven/android-compat/src/test/java/android/widget/RowStateNotSavedTest.java index 9cfaeaae8aa..9daf60f95da 100644 --- a/maven/android-compat/src/test/java/android/widget/RowStateNotSavedTest.java +++ b/maven/android-compat/src/test/java/android/widget/RowStateNotSavedTest.java @@ -28,7 +28,7 @@ import android.view.ViewGroup; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/widget/ScrollViewChildMarginsTest.java b/maven/android-compat/src/test/java/android/widget/ScrollViewChildMarginsTest.java index e9dcc07e29a..d1fb853fdf7 100644 --- a/maven/android-compat/src/test/java/android/widget/ScrollViewChildMarginsTest.java +++ b/maven/android-compat/src/test/java/android/widget/ScrollViewChildMarginsTest.java @@ -25,7 +25,7 @@ import android.view.View; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/widget/SeekBarInScrollingContainerTest.java b/maven/android-compat/src/test/java/android/widget/SeekBarInScrollingContainerTest.java index 2872b0e92ee..c15394abe5e 100644 --- a/maven/android-compat/src/test/java/android/widget/SeekBarInScrollingContainerTest.java +++ b/maven/android-compat/src/test/java/android/widget/SeekBarInScrollingContainerTest.java @@ -27,7 +27,7 @@ import android.view.ViewGroup; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/widget/SimpleAdapterBindingHooksTest.java b/maven/android-compat/src/test/java/android/widget/SimpleAdapterBindingHooksTest.java index 8fb88ecd390..a3d5c4f06df 100644 --- a/maven/android-compat/src/test/java/android/widget/SimpleAdapterBindingHooksTest.java +++ b/maven/android-compat/src/test/java/android/widget/SimpleAdapterBindingHooksTest.java @@ -26,7 +26,7 @@ import android.view.View; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/widget/SimpleAdapterFilterTest.java b/maven/android-compat/src/test/java/android/widget/SimpleAdapterFilterTest.java index 607ff60ac7d..b39b3438dbc 100644 --- a/maven/android-compat/src/test/java/android/widget/SimpleAdapterFilterTest.java +++ b/maven/android-compat/src/test/java/android/widget/SimpleAdapterFilterTest.java @@ -23,7 +23,7 @@ package android.widget; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.BeforeClass; import org.junit.Rule; diff --git a/maven/android-compat/src/test/java/android/widget/SpinnerSelectedRowIdTest.java b/maven/android-compat/src/test/java/android/widget/SpinnerSelectedRowIdTest.java index d24b6cc0ff5..941328aea4e 100644 --- a/maven/android-compat/src/test/java/android/widget/SpinnerSelectedRowIdTest.java +++ b/maven/android-compat/src/test/java/android/widget/SpinnerSelectedRowIdTest.java @@ -26,7 +26,7 @@ import android.view.ViewGroup; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/widget/TextViewAppendSpansTest.java b/maven/android-compat/src/test/java/android/widget/TextViewAppendSpansTest.java index 1a9d8f94cc0..bf142d9511c 100644 --- a/maven/android-compat/src/test/java/android/widget/TextViewAppendSpansTest.java +++ b/maven/android-compat/src/test/java/android/widget/TextViewAppendSpansTest.java @@ -24,7 +24,7 @@ import android.text.SpannableString; import android.text.Spanned; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; import static org.junit.Assert.*; diff --git a/maven/android-compat/src/test/java/android/widget/TextViewDefaultAppearanceTest.java b/maven/android-compat/src/test/java/android/widget/TextViewDefaultAppearanceTest.java index 1aabbd84ed6..54c1323080a 100644 --- a/maven/android-compat/src/test/java/android/widget/TextViewDefaultAppearanceTest.java +++ b/maven/android-compat/src/test/java/android/widget/TextViewDefaultAppearanceTest.java @@ -28,7 +28,7 @@ import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/widget/TextViewEllipsizeModeTest.java b/maven/android-compat/src/test/java/android/widget/TextViewEllipsizeModeTest.java index c8a3e804664..28bb6628491 100644 --- a/maven/android-compat/src/test/java/android/widget/TextViewEllipsizeModeTest.java +++ b/maven/android-compat/src/test/java/android/widget/TextViewEllipsizeModeTest.java @@ -25,7 +25,7 @@ import android.text.TextUtils; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/widget/TextViewFilterAndWatcherTest.java b/maven/android-compat/src/test/java/android/widget/TextViewFilterAndWatcherTest.java index a5b62cdd415..b5b4aedb07e 100644 --- a/maven/android-compat/src/test/java/android/widget/TextViewFilterAndWatcherTest.java +++ b/maven/android-compat/src/test/java/android/widget/TextViewFilterAndWatcherTest.java @@ -27,7 +27,7 @@ import android.text.TextWatcher; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Before; import org.junit.Rule; diff --git a/maven/android-compat/src/test/java/android/widget/TextViewNormalSnapshotTest.java b/maven/android-compat/src/test/java/android/widget/TextViewNormalSnapshotTest.java index 2bc1c87ca39..41598d12959 100644 --- a/maven/android-compat/src/test/java/android/widget/TextViewNormalSnapshotTest.java +++ b/maven/android-compat/src/test/java/android/widget/TextViewNormalSnapshotTest.java @@ -28,7 +28,7 @@ import android.text.style.ForegroundColorSpan; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/widget/TextViewRelativeDrawablesTest.java b/maven/android-compat/src/test/java/android/widget/TextViewRelativeDrawablesTest.java index 402c147a697..f25b2f2abb2 100644 --- a/maven/android-compat/src/test/java/android/widget/TextViewRelativeDrawablesTest.java +++ b/maven/android-compat/src/test/java/android/widget/TextViewRelativeDrawablesTest.java @@ -27,7 +27,7 @@ import android.view.View; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/widget/TextViewSpannableBufferTest.java b/maven/android-compat/src/test/java/android/widget/TextViewSpannableBufferTest.java index 2fc10d10635..4baea4ced6a 100644 --- a/maven/android-compat/src/test/java/android/widget/TextViewSpannableBufferTest.java +++ b/maven/android-compat/src/test/java/android/widget/TextViewSpannableBufferTest.java @@ -27,7 +27,7 @@ import android.text.style.ForegroundColorSpan; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/widget/TextViewTransitionTest.java b/maven/android-compat/src/test/java/android/widget/TextViewTransitionTest.java index 7c54e844bfa..63ba1cc2ba8 100644 --- a/maven/android-compat/src/test/java/android/widget/TextViewTransitionTest.java +++ b/maven/android-compat/src/test/java/android/widget/TextViewTransitionTest.java @@ -25,7 +25,7 @@ import android.graphics.drawable.ColorDrawable; import android.view.View; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; import static org.junit.Assert.*; diff --git a/maven/android-compat/src/test/java/android/widget/TimePickerNullLegacyTimeTest.java b/maven/android-compat/src/test/java/android/widget/TimePickerNullLegacyTimeTest.java index 1f77a6ba0ec..c701cd6e410 100644 --- a/maven/android-compat/src/test/java/android/widget/TimePickerNullLegacyTimeTest.java +++ b/maven/android-compat/src/test/java/android/widget/TimePickerNullLegacyTimeTest.java @@ -23,7 +23,7 @@ package android.widget; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/android/widget/ViewAnimatorRemovalTest.java b/maven/android-compat/src/test/java/android/widget/ViewAnimatorRemovalTest.java index c9af48d36c8..adec8146aa4 100644 --- a/maven/android-compat/src/test/java/android/widget/ViewAnimatorRemovalTest.java +++ b/maven/android-compat/src/test/java/android/widget/ViewAnimatorRemovalTest.java @@ -25,7 +25,7 @@ import android.util.SparseArray; import android.view.View; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; import static org.junit.Assert.*; diff --git a/maven/android-compat/src/test/java/androidx/activity/ComponentActivitySaveLifecycleTest.java b/maven/android-compat/src/test/java/androidx/activity/ComponentActivitySaveLifecycleTest.java index 12c325a50ec..669ed1ebb72 100644 --- a/maven/android-compat/src/test/java/androidx/activity/ComponentActivitySaveLifecycleTest.java +++ b/maven/android-compat/src/test/java/androidx/activity/ComponentActivitySaveLifecycleTest.java @@ -26,7 +26,7 @@ import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleRegistry; import androidx.savedstate.SavedStateRegistry; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/androidx/activity/GalleryLauncherUnregisterTest.java b/maven/android-compat/src/test/java/androidx/activity/GalleryLauncherUnregisterTest.java index bda78424041..e99fb353e08 100644 --- a/maven/android-compat/src/test/java/androidx/activity/GalleryLauncherUnregisterTest.java +++ b/maven/android-compat/src/test/java/androidx/activity/GalleryLauncherUnregisterTest.java @@ -31,8 +31,8 @@ import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; -import com.codename1.androidcompat.testing.HeadlessImplementation; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.HeadlessImplementation; +import com.codename1.compat.testing.MainThreadRule; import com.codename1.ui.events.ActionEvent; import com.codename1.ui.events.ActionListener; diff --git a/maven/android-compat/src/test/java/androidx/activity/GalleryResultAfterRecreateTest.java b/maven/android-compat/src/test/java/androidx/activity/GalleryResultAfterRecreateTest.java index c69284c6e55..bbee6318884 100644 --- a/maven/android-compat/src/test/java/androidx/activity/GalleryResultAfterRecreateTest.java +++ b/maven/android-compat/src/test/java/androidx/activity/GalleryResultAfterRecreateTest.java @@ -32,7 +32,7 @@ import android.content.Intent; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.HeadlessImplementation; +import com.codename1.compat.testing.HeadlessImplementation; import com.codename1.ui.Display; import com.codename1.ui.events.ActionEvent; import com.codename1.ui.events.ActionListener; diff --git a/maven/android-compat/src/test/java/androidx/activity/OnBackPressedDispatcherTest.java b/maven/android-compat/src/test/java/androidx/activity/OnBackPressedDispatcherTest.java index 0b38cce84a3..29902c060fd 100644 --- a/maven/android-compat/src/test/java/androidx/activity/OnBackPressedDispatcherTest.java +++ b/maven/android-compat/src/test/java/androidx/activity/OnBackPressedDispatcherTest.java @@ -29,7 +29,7 @@ import java.util.ArrayList; import java.util.List; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/androidx/activity/SynchronousResultPostedTest.java b/maven/android-compat/src/test/java/androidx/activity/SynchronousResultPostedTest.java index 7e7c3a566a0..0290750c1cb 100644 --- a/maven/android-compat/src/test/java/androidx/activity/SynchronousResultPostedTest.java +++ b/maven/android-compat/src/test/java/androidx/activity/SynchronousResultPostedTest.java @@ -34,7 +34,7 @@ import androidx.activity.result.contract.ActivityResultContract; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.BeforeClass; import org.junit.Rule; diff --git a/maven/android-compat/src/test/java/androidx/constraintlayout/widget/ConstraintLayoutTest.java b/maven/android-compat/src/test/java/androidx/constraintlayout/widget/ConstraintLayoutTest.java index 88ad6e15c7a..cfce97e534f 100644 --- a/maven/android-compat/src/test/java/androidx/constraintlayout/widget/ConstraintLayoutTest.java +++ b/maven/android-compat/src/test/java/androidx/constraintlayout/widget/ConstraintLayoutTest.java @@ -29,7 +29,7 @@ import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Before; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/androidx/lifecycle/LifecycleRegistryTest.java b/maven/android-compat/src/test/java/androidx/lifecycle/LifecycleRegistryTest.java index 6a9ba8fddd5..899a7922145 100644 --- a/maven/android-compat/src/test/java/androidx/lifecycle/LifecycleRegistryTest.java +++ b/maven/android-compat/src/test/java/androidx/lifecycle/LifecycleRegistryTest.java @@ -27,7 +27,7 @@ import java.util.ArrayList; import java.util.List; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/androidx/lifecycle/LiveDataTest.java b/maven/android-compat/src/test/java/androidx/lifecycle/LiveDataTest.java index 42a1be26b3b..61dfb9b2c41 100644 --- a/maven/android-compat/src/test/java/androidx/lifecycle/LiveDataTest.java +++ b/maven/android-compat/src/test/java/androidx/lifecycle/LiveDataTest.java @@ -26,7 +26,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/androidx/lifecycle/MediatorReactivationTest.java b/maven/android-compat/src/test/java/androidx/lifecycle/MediatorReactivationTest.java index af7572e5535..abed612750a 100644 --- a/maven/android-compat/src/test/java/androidx/lifecycle/MediatorReactivationTest.java +++ b/maven/android-compat/src/test/java/androidx/lifecycle/MediatorReactivationTest.java @@ -21,7 +21,7 @@ * need additional information or have any questions. */ package androidx.lifecycle; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import androidx.arch.core.util.Function; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/androidx/recyclerview/widget/DiffUtilTest.java b/maven/android-compat/src/test/java/androidx/recyclerview/widget/DiffUtilTest.java index 7ccb16d5321..bef48d00a33 100644 --- a/maven/android-compat/src/test/java/androidx/recyclerview/widget/DiffUtilTest.java +++ b/maven/android-compat/src/test/java/androidx/recyclerview/widget/DiffUtilTest.java @@ -25,7 +25,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/androidx/recyclerview/widget/GridRowSizeTest.java b/maven/android-compat/src/test/java/androidx/recyclerview/widget/GridRowSizeTest.java index 4069effb9cc..ca8b5b26b2e 100644 --- a/maven/android-compat/src/test/java/androidx/recyclerview/widget/GridRowSizeTest.java +++ b/maven/android-compat/src/test/java/androidx/recyclerview/widget/GridRowSizeTest.java @@ -24,7 +24,7 @@ import android.view.View; import android.view.ViewGroup; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; import static org.junit.Assert.*; diff --git a/maven/android-compat/src/test/java/com/codename1/androidcompat/jdk/BufferedReaderMarkTest.java b/maven/android-compat/src/test/java/com/codename1/androidcompat/jdk/BufferedReaderMarkTest.java deleted file mode 100644 index 8dad31566a7..00000000000 --- a/maven/android-compat/src/test/java/com/codename1/androidcompat/jdk/BufferedReaderMarkTest.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Codename One designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Codename One through http://www.codenameone.com/ if you - * need additional information or have any questions. - */ -package com.codename1.androidcompat.jdk; - -import org.junit.Test; - -import java.io.IOException; -import java.io.StringReader; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -/// Code compiled against `java.io.BufferedReader` marks ahead before choosing -/// how to parse; the remapped reader must honour `mark`/`reset` across -/// buffer refills, as the JDK class does. -public class BufferedReaderMarkTest { - - @Test - public void resetReturnsToTheMarkAcrossRefills() throws IOException { - BufferedReader r = new BufferedReader(new StringReader("header\nabcdefghijklmnop\nrest"), 4); - assertTrue(r.markSupported()); - assertEquals("header", r.readLine()); - r.mark(64); - assertEquals("abcdefghijklmnop", r.readLine()); - r.reset(); - assertEquals('a', r.read()); - assertEquals("bcdefghijklmnop", r.readLine()); - assertEquals("rest", r.readLine()); - } - - @Test - public void markAfterCarriageReturnKeepsTheLineFeedSkip() throws IOException { - BufferedReader r = new BufferedReader(new StringReader("a\r\nb"), 2); - assertEquals("a", r.readLine()); - r.mark(8); - assertEquals('b', r.read()); - r.reset(); - assertEquals('b', r.read()); - assertEquals(-1, r.read()); - } - - @Test - public void readingPastTheLimitInvalidatesTheMark() throws IOException { - BufferedReader r = new BufferedReader(new StringReader("0123456789abcdef"), 4); - r.mark(2); - char[] c = new char[16]; - int n = 0; - while (n < 10) { - n += r.read(c, n, 10 - n); - } - try { - r.reset(); - fail("reset after reading past the read-ahead limit"); - } catch (IOException expected) { - assertEquals("Mark invalid", expected.getMessage()); - } - } -} diff --git a/maven/android-compat/src/test/java/com/codename1/androidcompat/jdk/BufferedStreamsClosedTest.java b/maven/android-compat/src/test/java/com/codename1/androidcompat/jdk/BufferedStreamsClosedTest.java deleted file mode 100644 index f93466f1bc6..00000000000 --- a/maven/android-compat/src/test/java/com/codename1/androidcompat/jdk/BufferedStreamsClosedTest.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Codename One designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Codename One through http://www.codenameone.com/ if you - * need additional information or have any questions. - */ -package com.codename1.androidcompat.jdk; - -import org.junit.Test; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.StringReader; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - -/// A closed `BufferedReader` or `BufferedInputStream` throws rather than -/// handing out the characters or bytes it had already buffered, and a closed -/// `BufferedOutputStream` refuses writes, as the JDK classes do. -public class BufferedStreamsClosedTest { - - private interface Op { - void run() throws IOException; - } - - private static void assertClosed(Op op) { - try { - op.run(); - fail("expected IOException after close"); - } catch (IOException e) { - assertEquals("Stream closed", e.getMessage()); - } - } - - @Test - public void closedReaderRejectsBufferedReads() throws IOException { - final BufferedReader r = new BufferedReader(new StringReader("first\nsecond\nthird")); - r.mark(100); - assertEquals("first", r.readLine()); - r.close(); - r.close(); - assertClosed(new Op() { public void run() throws IOException { r.read(); } }); - assertClosed(new Op() { public void run() throws IOException { r.read(new char[4], 0, 4); } }); - assertClosed(new Op() { public void run() throws IOException { r.readLine(); } }); - assertClosed(new Op() { public void run() throws IOException { r.ready(); } }); - assertClosed(new Op() { public void run() throws IOException { r.skip(1); } }); - assertClosed(new Op() { public void run() throws IOException { r.reset(); } }); - } - - @Test - public void closedStreamRejectsBufferedReads() throws IOException { - final BufferedInputStream in = new BufferedInputStream(new ByteArrayInputStream(new byte[] {1, 2, 3, 4})); - in.mark(10); - assertEquals(1, in.read()); - in.close(); - in.close(); - assertClosed(new Op() { public void run() throws IOException { in.read(); } }); - assertClosed(new Op() { public void run() throws IOException { in.read(new byte[4], 0, 4); } }); - assertClosed(new Op() { public void run() throws IOException { in.skip(1); } }); - assertClosed(new Op() { public void run() throws IOException { in.available(); } }); - assertClosed(new Op() { public void run() throws IOException { in.reset(); } }); - } - - @Test - public void closedOutputStreamRejectsWrites() throws IOException { - // ByteArrayOutputStream's close is a no-op, so a buffered write after - // close would reach it on the next flush or close. - final ByteArrayOutputStream sink = new ByteArrayOutputStream(); - final BufferedOutputStream out = new BufferedOutputStream(sink); - out.write(1); - out.close(); - out.close(); - assertEquals(1, sink.size()); - assertClosed(new Op() { public void run() throws IOException { out.write(2); } }); - assertClosed(new Op() { public void run() throws IOException { out.write(new byte[] {3, 4}, 0, 2); } }); - assertClosed(new Op() { public void run() throws IOException { out.flush(); } }); - out.close(); - assertEquals(1, sink.size()); - } -} diff --git a/maven/android-compat/src/test/java/com/codename1/androidcompat/jdk/BufferedWriterClosedTest.java b/maven/android-compat/src/test/java/com/codename1/androidcompat/jdk/BufferedWriterClosedTest.java deleted file mode 100644 index 8f0be466350..00000000000 --- a/maven/android-compat/src/test/java/com/codename1/androidcompat/jdk/BufferedWriterClosedTest.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Codename One designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Codename One through http://www.codenameone.com/ if you - * need additional information or have any questions. - */ -package com.codename1.androidcompat.jdk; - -import java.io.IOException; -import java.io.Writer; - -import org.junit.Test; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - -/// A closed `BufferedWriter` used to accept writes into its buffer, and a -/// second close handed them to the underlying writer. Writing or flushing -/// after close now throws, and a second close does nothing. -public class BufferedWriterClosedTest { - - /// Records what reaches it; its close does nothing, like a writer over a - /// shared sink. - private static final class Sink extends Writer { - final StringBuilder text = new StringBuilder(); - int closes; - - @Override - public void write(char[] cbuf, int off, int len) { - text.append(cbuf, off, len); - } - - @Override - public void flush() { - } - - @Override - public void close() { - closes++; - } - } - - @Test - public void writesAfterCloseAreRejected() throws IOException { - Sink sink = new Sink(); - BufferedWriter w = new BufferedWriter(sink); - w.write("ab"); - w.close(); - try { - w.write('c'); - fail("write after close"); - } catch (IOException expected) { - assertEquals("Stream closed", expected.getMessage()); - } - try { - w.write("cd"); - fail("write after close"); - } catch (IOException expected) { - assertEquals("Stream closed", expected.getMessage()); - } - try { - w.flush(); - fail("flush after close"); - } catch (IOException expected) { - assertEquals("Stream closed", expected.getMessage()); - } - w.close(); - assertEquals("ab", sink.text.toString()); - assertEquals(1, sink.closes); - } -} diff --git a/maven/android-compat/src/test/java/com/codename1/androidcompat/jdk/FileRenameFailedDeleteTest.java b/maven/android-compat/src/test/java/com/codename1/androidcompat/jdk/FileRenameFailedDeleteTest.java deleted file mode 100644 index cc3daffd3c7..00000000000 --- a/maven/android-compat/src/test/java/com/codename1/androidcompat/jdk/FileRenameFailedDeleteTest.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Codename One designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Codename One through http://www.codenameone.com/ if you - * need additional information or have any questions. - */ -package com.codename1.androidcompat.jdk; - -import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.HeadlessImplementation; -import com.codename1.androidcompat.testing.MainThreadRule; - -import org.junit.After; -import org.junit.Rule; -import org.junit.Test; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -/// A move between directories copies and deletes. When the source cannot be -/// deleted the rename used to report success with the file at both paths; -/// it now fails and takes back the copy it made. -public class FileRenameFailedDeleteTest { - - private static final String SRC = "file:///data/a/src.txt"; - private static final String DST = "file:///data/b/dst.txt"; - - @Rule - public final MainThreadRule mainThread = new MainThreadRule(); - - @After - public void reset() { - HeadlessImplementation.fileSystem = false; - HeadlessImplementation.UNDELETABLE.clear(); - HeadlessImplementation.FILES.remove(SRC); - HeadlessImplementation.FILES.remove(DST); - } - - private static void prepare() { - AndroidTestSupport.context(); - HeadlessImplementation.fileSystem = true; - HeadlessImplementation.FILES.put(SRC, new byte[]{1, 2, 3}); - } - - @Test - public void aSourceThatCannotBeDeletedFailsTheRename() { - prepare(); - HeadlessImplementation.UNDELETABLE.add(SRC); - assertFalse(new File(SRC).renameTo(new File(DST))); - assertTrue(HeadlessImplementation.FILES.containsKey(SRC)); - assertFalse("the copy is taken back", HeadlessImplementation.FILES.containsKey(DST)); - } - - @Test - public void aMoveBetweenDirectoriesSucceeds() { - prepare(); - assertTrue(new File(SRC).renameTo(new File(DST))); - assertFalse(HeadlessImplementation.FILES.containsKey(SRC)); - assertArrayEquals(new byte[]{1, 2, 3}, HeadlessImplementation.FILES.get(DST)); - } -} diff --git a/maven/android-compat/src/test/java/com/codename1/androidcompat/jdk/FileRenameOntoItselfTest.java b/maven/android-compat/src/test/java/com/codename1/androidcompat/jdk/FileRenameOntoItselfTest.java deleted file mode 100644 index 3916d6599b0..00000000000 --- a/maven/android-compat/src/test/java/com/codename1/androidcompat/jdk/FileRenameOntoItselfTest.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Codename One designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Codename One through http://www.codenameone.com/ if you - * need additional information or have any questions. - */ -package com.codename1.androidcompat.jdk; - -import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.HeadlessImplementation; -import com.codename1.androidcompat.testing.MainThreadRule; - -import org.junit.After; -import org.junit.Rule; -import org.junit.Test; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertTrue; - -/// Renaming a file onto itself used to take the copy path, which opened the -/// same storage path for reading and writing -- truncating it -- and then -/// deleted it. It now succeeds and leaves the file alone. -public class FileRenameOntoItselfTest { - - private static final String SRC = "file:///data/a/src.txt"; - - @Rule - public final MainThreadRule mainThread = new MainThreadRule(); - - @After - public void reset() { - HeadlessImplementation.fileSystem = false; - HeadlessImplementation.FILES.remove(SRC); - } - - private static void prepare() { - AndroidTestSupport.context(); - HeadlessImplementation.fileSystem = true; - HeadlessImplementation.FILES.put(SRC, new byte[]{1, 2, 3}); - } - - @Test - public void theSamePathKeepsTheFile() { - prepare(); - assertTrue(new File(SRC).renameTo(new File(SRC))); - assertArrayEquals(new byte[]{1, 2, 3}, HeadlessImplementation.FILES.get(SRC)); - } - - @Test - public void aDifferentSpellingOfTheSamePathKeepsTheFile() { - prepare(); - assertTrue(new File(SRC).renameTo(new File("file:///data/b/../a/src.txt"))); - assertArrayEquals(new byte[]{1, 2, 3}, HeadlessImplementation.FILES.get(SRC)); - } -} diff --git a/maven/android-compat/src/test/java/com/codename1/androidcompat/jdk/PrintWriterClosedTest.java b/maven/android-compat/src/test/java/com/codename1/androidcompat/jdk/PrintWriterClosedTest.java deleted file mode 100644 index 89dfc59adad..00000000000 --- a/maven/android-compat/src/test/java/com/codename1/androidcompat/jdk/PrintWriterClosedTest.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Codename One designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Codename One through http://www.codenameone.com/ if you - * need additional information or have any questions. - */ -package com.codename1.androidcompat.jdk; - -import java.io.Writer; - -import org.junit.Test; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -/// A closed `PrintWriter` kept its delegate, so over a writer whose close is -/// a no-op every later print still landed in it and `checkError` reported -/// nothing. Like the JDK it now suppresses those writes and reports them. -public class PrintWriterClosedTest { - - /// Records what reaches it; its close does nothing, like a `StringWriter`. - private static final class Sink extends Writer { - final StringBuilder text = new StringBuilder(); - int closes; - - @Override - public void write(char[] cbuf, int off, int len) { - text.append(cbuf, off, len); - } - - @Override - public void flush() { - } - - @Override - public void close() { - closes++; - } - } - - @Test - public void writesAfterCloseAreSuppressedAndReported() { - Sink sink = new Sink(); - PrintWriter w = new PrintWriter(sink); - w.print("ab"); - assertFalse(w.checkError()); - w.close(); - w.print("cd"); - w.write('e'); - w.write(new char[] {'f'}); - w.println("g"); - w.flush(); - w.close(); - assertEquals("ab", sink.text.toString()); - assertEquals(1, sink.closes); - assertTrue(w.checkError()); - } - - @Test - public void closeAloneIsNotAnError() { - PrintWriter w = new PrintWriter(new Sink()); - w.close(); - assertFalse(w.checkError()); - } -} diff --git a/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/DrawableForDensityTest.java b/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/DrawableForDensityTest.java index b3dbf9f06a3..92c961845d6 100644 --- a/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/DrawableForDensityTest.java +++ b/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/DrawableForDensityTest.java @@ -27,8 +27,8 @@ import com.codename1.android.rescompiler.ResourceCompiler; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.HeadlessImplementation; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.HeadlessImplementation; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/FontCacheTest.java b/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/FontCacheTest.java index fcabdee781e..bb1891176c5 100644 --- a/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/FontCacheTest.java +++ b/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/FontCacheTest.java @@ -24,7 +24,7 @@ import android.graphics.Typeface; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/LocaleQualifierTest.java b/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/LocaleQualifierTest.java index 2a247a9968a..54b7549f4f3 100644 --- a/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/LocaleQualifierTest.java +++ b/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/LocaleQualifierTest.java @@ -22,7 +22,7 @@ */ package com.codename1.androidcompat.runtime; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/MenuXmlOnClickTest.java b/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/MenuXmlOnClickTest.java index 6703130cfdf..3e31dbd416b 100644 --- a/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/MenuXmlOnClickTest.java +++ b/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/MenuXmlOnClickTest.java @@ -26,7 +26,7 @@ import android.view.MenuItem; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/ResTableRoundTripTest.java b/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/ResTableRoundTripTest.java index d758d5342c1..127106b52a3 100644 --- a/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/ResTableRoundTripTest.java +++ b/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/ResTableRoundTripTest.java @@ -23,7 +23,7 @@ package com.codename1.androidcompat.runtime; import com.codename1.android.rescompiler.ResourceCompiler; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; diff --git a/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/ResourceSelectionRegressionTest.java b/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/ResourceSelectionRegressionTest.java index e6c67d3e42d..5c0fdff3fb2 100644 --- a/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/ResourceSelectionRegressionTest.java +++ b/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/ResourceSelectionRegressionTest.java @@ -23,7 +23,7 @@ package com.codename1.androidcompat.runtime; import android.content.res.Resources; import android.util.TypedValue; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; import static org.junit.Assert.*; diff --git a/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/ScriptQualifierTest.java b/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/ScriptQualifierTest.java index 21152d7a5ef..59084ec7086 100644 --- a/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/ScriptQualifierTest.java +++ b/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/ScriptQualifierTest.java @@ -22,7 +22,7 @@ */ package com.codename1.androidcompat.runtime; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/ShareStreamTest.java b/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/ShareStreamTest.java index c9a9001285b..d47a3278fdc 100644 --- a/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/ShareStreamTest.java +++ b/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/ShareStreamTest.java @@ -27,7 +27,7 @@ import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/SharedPreferencesApplyDeferredTest.java b/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/SharedPreferencesApplyDeferredTest.java index 0dea6c78e23..2c1e7069791 100644 --- a/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/SharedPreferencesApplyDeferredTest.java +++ b/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/SharedPreferencesApplyDeferredTest.java @@ -26,7 +26,7 @@ import android.content.SharedPreferences; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import com.codename1.io.Storage; import org.junit.Rule; diff --git a/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/SharedPreferencesClearListenerTest.java b/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/SharedPreferencesClearListenerTest.java index b9b2438837b..cb78a4f3d15 100644 --- a/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/SharedPreferencesClearListenerTest.java +++ b/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/SharedPreferencesClearListenerTest.java @@ -26,7 +26,7 @@ import android.content.SharedPreferences; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import java.util.ArrayList; import java.util.Collections; diff --git a/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/SharedPreferencesCommitFailureTest.java b/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/SharedPreferencesCommitFailureTest.java index 322b2b04f71..890747c2db6 100644 --- a/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/SharedPreferencesCommitFailureTest.java +++ b/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/SharedPreferencesCommitFailureTest.java @@ -26,7 +26,7 @@ import android.content.SharedPreferences; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import com.codename1.io.Storage; import org.junit.Rule; diff --git a/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/SharedPreferencesEditOrderTest.java b/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/SharedPreferencesEditOrderTest.java index e0b7569aa6d..f6cb9fe33ec 100644 --- a/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/SharedPreferencesEditOrderTest.java +++ b/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/SharedPreferencesEditOrderTest.java @@ -27,7 +27,7 @@ import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/SharedPreferencesEditorReuseTest.java b/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/SharedPreferencesEditorReuseTest.java index bde5737819d..fe237cfd531 100644 --- a/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/SharedPreferencesEditorReuseTest.java +++ b/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/SharedPreferencesEditorReuseTest.java @@ -26,7 +26,7 @@ import android.content.SharedPreferences; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/SharedPreferencesStringTypeTest.java b/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/SharedPreferencesStringTypeTest.java index ac94a949922..767da845f47 100644 --- a/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/SharedPreferencesStringTypeTest.java +++ b/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/SharedPreferencesStringTypeTest.java @@ -26,7 +26,7 @@ import android.content.SharedPreferences; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/SharedPreferencesUnchangedValueTest.java b/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/SharedPreferencesUnchangedValueTest.java index c99130b29b2..89dcc828f03 100644 --- a/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/SharedPreferencesUnchangedValueTest.java +++ b/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/SharedPreferencesUnchangedValueTest.java @@ -26,7 +26,7 @@ import android.content.SharedPreferences; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import java.util.ArrayList; import java.util.Arrays; diff --git a/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/SharedPreferencesWeakListenerTest.java b/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/SharedPreferencesWeakListenerTest.java index e75298e0e3c..c2de3a4fc7b 100644 --- a/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/SharedPreferencesWeakListenerTest.java +++ b/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/SharedPreferencesWeakListenerTest.java @@ -26,7 +26,7 @@ import android.content.SharedPreferences; import com.codename1.androidcompat.testing.AndroidTestSupport; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import java.lang.ref.WeakReference; import java.util.ArrayList; diff --git a/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/XmlPullParserTest.java b/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/XmlPullParserTest.java index 7a65f7e296a..5abd15a6c9d 100644 --- a/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/XmlPullParserTest.java +++ b/maven/android-compat/src/test/java/com/codename1/androidcompat/runtime/XmlPullParserTest.java @@ -31,7 +31,7 @@ import android.util.TypedValue; import android.util.Xml; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; import org.xmlpull.v1.XmlPullParser; diff --git a/maven/android-compat/src/test/java/com/codename1/androidcompat/testing/AndroidTestSupport.java b/maven/android-compat/src/test/java/com/codename1/androidcompat/testing/AndroidTestSupport.java index 72a1550b639..1461e0484bc 100644 --- a/maven/android-compat/src/test/java/com/codename1/androidcompat/testing/AndroidTestSupport.java +++ b/maven/android-compat/src/test/java/com/codename1/androidcompat/testing/AndroidTestSupport.java @@ -30,6 +30,7 @@ import com.codename1.androidcompat.runtime.AndroidApp; import com.codename1.androidcompat.runtime.AndroidRuntime; +import com.codename1.compat.testing.HeadlessImplementation; /// The Android runtime started on [HeadlessImplementation] with an /// application that has no resources of its own, so tests can construct, diff --git a/maven/android-compat/src/test/java/com/codename1/androidcompat/testing/HeadlessImplementation.java b/maven/android-compat/src/test/java/com/codename1/androidcompat/testing/HeadlessImplementation.java deleted file mode 100644 index 80b53e49d78..00000000000 --- a/maven/android-compat/src/test/java/com/codename1/androidcompat/testing/HeadlessImplementation.java +++ /dev/null @@ -1,724 +0,0 @@ -/* - * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Codename One designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Codename One through http://www.codenameone.com/ if you - * need additional information or have any questions. - */ -package com.codename1.androidcompat.testing; - -import com.codename1.impl.CodenameOneImplementation; -import com.codename1.l10n.L10NManager; -import com.codename1.ui.Display; - -import java.io.InputStream; - -/// A Codename One implementation with no screen, for tests that build and -/// lay out Android views: a fixed 1080x1920 display at 320 dpi (density 2), -/// resources from the class path, and monospaced font metrics. Drawing and -/// everything else answer neutral values. -public class HeadlessImplementation extends CodenameOneImplementation { - - public static final int WIDTH = 1080; - public static final int HEIGHT = 1920; - public static final int DPI = 320; - static final int CHAR_WIDTH = 16; - static final int FONT_HEIGHT = 32; - private static final Object FONT = new Object(); - private static final L10NManager L10N = new L10NManager("en", "US") { - }; - - /// Starts Codename One on this implementation once per test JVM (the - /// test ImplementationFactory creates it). - public static synchronized void install() { - if (!Display.isInitialized()) { - Display.init(null); - } - } - - /// The listener of the last `openGallery` call, which the headless - /// implementation never answers itself. A test that reads it must reset - /// it. - public static com.codename1.ui.events.ActionListener gallery; - - @Override - public void openGallery(com.codename1.ui.events.ActionListener response, int type) { - gallery = response; - } - - /// The image encoder `ImageIO.getImageIO()` answers; none by default. A - /// test that sets it must reset it. - public static com.codename1.ui.util.ImageIO imageIO; - - @Override - public com.codename1.ui.util.ImageIO getImageIO() { - return imageIO; - } - - @Override - public InputStream getResourceAsStream(Class cls, String resource) { - Class c = cls == null ? HeadlessImplementation.class : cls; - return c.getResourceAsStream(resource.startsWith("/") ? resource : "/" + resource); - } - - @Override - public int convertToPixels(int dipCount, boolean horizontal) { - return Math.round(dipCount * DPI / 25.4f); - } - - @Override - public boolean isTrueTypeSupported() { - return true; - } - - @Override - public boolean isNativeFontSchemeSupported() { - return true; - } - - @Override - public Object loadTrueTypeFont(String fontName, String fileName) { - return FONT; - } - - @Override - public Object deriveTrueTypeFont(Object font, float size, int weight) { - return FONT; - } - - /// What [#isDarkMode()] answers; tests flip it to change the system theme. - public static boolean darkMode; - - @Override - public Boolean isDarkMode() { - return Boolean.valueOf(darkMode); - } - - /// Whether the last screen saver call kept the screen on. - public static boolean screenLocked; - - @Override - public void lockScreen() { - screenLocked = true; - } - - @Override - public void unlockScreen() { - screenLocked = false; - } - - @Override - public void init(java.lang.Object a0) { - // Nothing to set up: there is no screen. - } - - @Override - public int getDisplayWidth() { - return WIDTH; - } - - @Override - public int getDisplayHeight() { - return HEIGHT; - } - - @Override - public void editString(com.codename1.ui.Component a0, int a1, int a2, java.lang.String a3, int a4) { - } - - @Override - public void flushGraphics(int a0, int a1, int a2, int a3) { - } - - @Override - public void flushGraphics() { - } - - @Override - public void getRGB(java.lang.Object a0, int[] a1, int a2, int a3, int a4, int a5, int a6) { - if (a0 instanceof int[][]) { - int[][] rows = (int[][]) a0; - for (int y = 0; y < a6; y++) { - System.arraycopy(rows[a4 + y], a3, a1, a2 + y * a5, a5); - } - } - } - - /// When set, an image made from ARGB pixels keeps them (as an `int[][]` - /// of rows) and answers them to `getRGB`. Off by default; tests that - /// inspect pixels flip it. - public static boolean pixelImages; - - @Override - public java.lang.Object createImage(int[] a0, int a1, int a2) { - if (pixelImages && a1 > 0 && a2 > 0) { - int[][] rows = new int[a2][a1]; - for (int y = 0; y < a2; y++) { - System.arraycopy(a0, y * a1, rows[y], 0, a1); - } - return rows; - } - return new Object(); - } - - @Override - public java.lang.Object createImage(java.lang.String a0) throws java.io.IOException { - return new Object(); - } - - /// With [#pixelImages] set, an encoded image (a PNG resource) is decoded - /// to its pixels, so tests can check the size a drawable comes out at. - @Override - public java.lang.Object createImage(java.io.InputStream a0) throws java.io.IOException { - if (pixelImages) { - java.awt.image.BufferedImage img = javax.imageio.ImageIO.read(a0); - if (img != null) { - int w = img.getWidth(); - int h = img.getHeight(); - return createImage(img.getRGB(0, 0, w, h, null, 0, w), w, h); - } - } - return new Object(); - } - - @Override - public java.lang.Object createMutableImage(int a0, int a1, int a2) { - return new Object(); - } - - /// Counts encoded images handed to [#createImage(byte[], int, int)], so a - /// test can tell a header-only bounds probe from a full decode. - public static int encodedDecodes; - - @Override - public java.lang.Object createImage(byte[] a0, int a1, int a2) { - encodedDecodes++; - if (pixelImages) { - try { - return createImage(new java.io.ByteArrayInputStream(a0, a1, a2)); - } catch (java.io.IOException e) { - return new Object(); - } - } - return new Object(); - } - - @Override - public int getImageWidth(java.lang.Object image) { - return image instanceof int[][] ? ((int[][]) image)[0].length : 0; - } - - @Override - public int getImageHeight(java.lang.Object image) { - return image instanceof int[][] ? ((int[][]) image).length : 0; - } - - @Override - public java.lang.Object scale(java.lang.Object a0, int a1, int a2) { - if (a0 instanceof int[][] && a1 > 0 && a2 > 0) { - // Nearest-neighbour, for the pixel images of pixelImages. - int[][] src = (int[][]) a0; - int[][] out = new int[a2][a1]; - for (int y = 0; y < a2; y++) { - for (int x = 0; x < a1; x++) { - out[y][x] = src[y * src.length / a2][x * src[0].length / a1]; - } - } - return out; - } - return new Object(); - } - - @Override - public int getSoftkeyCount() { - return 0; - } - - @Override - public int[] getSoftkeyCode(int a0) { - return null; - } - - @Override - public int getClearKeyCode() { - return 0; - } - - @Override - public int getBackspaceKeyCode() { - return 0; - } - - @Override - public int getBackKeyCode() { - return 0; - } - - @Override - public int getGameAction(int a0) { - return 0; - } - - @Override - public int getKeyCode(int a0) { - return 0; - } - - @Override - public boolean isTouchDevice() { - return true; - } - - @Override - public int getColor(java.lang.Object a0) { - return 0; - } - - @Override - public void setColor(java.lang.Object a0, int a1) { - } - - /// When set, each graphics context keeps the alpha it was given, and - /// every `fillRect`, `drawImage` and `fillLinearGradient` is added to - /// [#draws] as {operation, native graphics, alpha}. A test that sets it - /// must reset it and clear [#draws]. - public static boolean recordDraws; - public static final java.util.List draws = new java.util.ArrayList(); - private static final java.util.Map ALPHAS = new java.util.IdentityHashMap(); - - private static void recordDraw(String op, Object graphics) { - if (recordDraws) { - draws.add(new Object[]{op, graphics, Integer.valueOf(alphaOf(graphics))}); - } - } - - private static int alphaOf(Object graphics) { - Integer a = ALPHAS.get(graphics); - return a == null ? 255 : a.intValue(); - } - - @Override - public void setAlpha(java.lang.Object a0, int a1) { - if (recordDraws) { - ALPHAS.put(a0, Integer.valueOf(a1)); - } - } - - @Override - public int getAlpha(java.lang.Object a0) { - return recordDraws ? alphaOf(a0) : 0; - } - - /// With [#recordDraws], the arguments of every gradient fill: - /// {"fillLinearGradient", start, end, horizontal} or - /// {"fillGradient", the gradient}. A test that records clears it too. - public static final java.util.List gradients = new java.util.ArrayList(); - - @Override - public void fillGradient(Object graphics, com.codename1.ui.Gradient gradient, int x, int y, int width, - int height) { - if (recordDraws) { - recordDraw("fillGradient", graphics); - gradients.add(new Object[]{"fillGradient", gradient}); - } else { - super.fillGradient(graphics, gradient, x, y, width, height); - } - } - - @Override - public void fillLinearGradient(Object graphics, int startColor, int endColor, int x, int y, int width, - int height, boolean horizontal) { - if (recordDraws) { - recordDraw("fillLinearGradient", graphics); - gradients.add(new Object[]{"fillLinearGradient", Integer.valueOf(startColor), - Integer.valueOf(endColor), Boolean.valueOf(horizontal)}); - } else { - super.fillLinearGradient(graphics, startColor, endColor, x, y, width, height, horizontal); - } - } - - @Override - public void setNativeFont(java.lang.Object a0, java.lang.Object a1) { - } - - /// When set, every graphics context shares one tracked clip rectangle - /// and shape clips are supported: `setClip(Shape)` records the shape in - /// [#shapeClip] and clips to its bounds. Off by default, when the clip is - /// always the whole display; tests that check clipping flip it. - public static boolean trackClip; - /// The last shape passed to `setClip(Shape)` while [#trackClip] is set, - /// null once a rectangle replaces it. - public static com.codename1.ui.geom.Shape shapeClip; - private static int clipX; - private static int clipY; - private static int clipW = WIDTH; - private static int clipH = HEIGHT; - - @Override - public int getClipX(java.lang.Object a0) { - return trackClip ? clipX : 0; - } - - @Override - public int getClipY(java.lang.Object a0) { - return trackClip ? clipY : 0; - } - - @Override - public int getClipWidth(java.lang.Object a0) { - return trackClip ? clipW : WIDTH; - } - - @Override - public int getClipHeight(java.lang.Object a0) { - return trackClip ? clipH : HEIGHT; - } - - @Override - public void setClip(java.lang.Object a0, int a1, int a2, int a3, int a4) { - clipX = a1; - clipY = a2; - clipW = a3; - clipH = a4; - shapeClip = null; - } - - @Override - public void clipRect(java.lang.Object a0, int a1, int a2, int a3, int a4) { - int x2 = Math.min(clipX + clipW, a1 + a3); - int y2 = Math.min(clipY + clipH, a2 + a4); - clipX = Math.max(clipX, a1); - clipY = Math.max(clipY, a2); - clipW = Math.max(0, x2 - clipX); - clipH = Math.max(0, y2 - clipY); - shapeClip = null; - } - - @Override - public boolean isShapeClipSupported(java.lang.Object graphics) { - return trackClip; - } - - @Override - public void setClip(java.lang.Object graphics, com.codename1.ui.geom.Shape shape) { - if (trackClip) { - com.codename1.ui.geom.Rectangle b = shape.getBounds(); - setClip(graphics, b.getX(), b.getY(), b.getWidth(), b.getHeight()); - shapeClip = shape; - } - } - - @Override - public void drawLine(java.lang.Object a0, int a1, int a2, int a3, int a4) { - } - - @Override - public void fillRect(java.lang.Object a0, int a1, int a2, int a3, int a4) { - recordDraw("fillRect", a0); - } - - /// The rectangles `clearRect` was asked to erase, as `{x, y, w, h}`. - /// Tests that check erasing clear it first. - public static final java.util.List clearedRects = new java.util.ArrayList(); - - @Override - public void clearRect(java.lang.Object graphics, int x, int y, int width, int height) { - clearedRects.add(new int[]{x, y, width, height}); - if (graphics instanceof int[][]) { - // A pixel image's graphics (see getNativeGraphics) really erases. - int[][] rows = (int[][]) graphics; - for (int row = Math.max(0, y); row < Math.min(rows.length, y + height); row++) { - for (int col = Math.max(0, x); col < Math.min(rows[row].length, x + width); col++) { - rows[row][col] = 0; - } - } - } - } - - @Override - public void drawRect(java.lang.Object a0, int a1, int a2, int a3, int a4) { - } - - @Override - public void drawRoundRect(java.lang.Object a0, int a1, int a2, int a3, int a4, int a5, int a6) { - } - - @Override - public void fillRoundRect(java.lang.Object a0, int a1, int a2, int a3, int a4, int a5, int a6) { - } - - @Override - public void fillArc(java.lang.Object a0, int a1, int a2, int a3, int a4, int a5, int a6) { - } - - @Override - public void drawArc(java.lang.Object a0, int a1, int a2, int a3, int a4, int a5, int a6) { - } - - @Override - public void drawString(java.lang.Object a0, java.lang.String a1, int a2, int a3) { - if (recordText) { - drawnText.add(new Object[]{a1, Integer.valueOf(a2), Integer.valueOf(a3)}); - } - } - - /// When set, every `drawString` (a `drawChar` arrives as one) is added - /// to [#drawnText] as {text, x, y}. A test that sets it must reset both. - public static boolean recordText; - public static final java.util.List drawnText = new java.util.ArrayList(); - - @Override - public void drawImage(java.lang.Object a0, java.lang.Object a1, int a2, int a3) { - recordDraw("drawImage", a0); - } - - @Override - public void drawRGB(java.lang.Object a0, int[] a1, int a2, int a3, int a4, int a5, int a6, boolean a7) { - } - - @Override - public java.lang.Object getNativeGraphics() { - return new Object(); - } - - @Override - public java.lang.Object getNativeGraphics(java.lang.Object a0) { - // With pixelImages, drawing into a pixel image draws into its rows; - // only clearRect writes pixels so far. - if (pixelImages && a0 instanceof int[][]) { - return a0; - } - return new Object(); - } - - /// When set, a run of characters measures 1px narrower per adjacent pair - /// than its characters one by one, as a kerning font (a browser canvas) - /// does. Off by default; tests that measure text flip it. - public static boolean kerning; - - private static int kerned(int width, int len) { - return kerning && len > 1 ? width - (len - 1) : width; - } - - @Override - public int charsWidth(java.lang.Object nativeFont, char[] ch, int offset, int len) { - return kerned(len * CHAR_WIDTH, len); - } - - @Override - public int stringWidth(java.lang.Object nativeFont, java.lang.String text) { - return text == null ? 0 : kerned(text.length() * CHAR_WIDTH, text.length()); - } - - @Override - public int charWidth(java.lang.Object a0, char a1) { - return CHAR_WIDTH; - } - - @Override - public int getHeight(java.lang.Object a0) { - return FONT_HEIGHT; - } - - @Override - public java.lang.Object getDefaultFont() { - return FONT; - } - - @Override - public java.lang.Object createFont(int a0, int a1, int a2) { - return FONT; - } - - @Override - public java.lang.Object connect(java.lang.String a0, boolean a1, boolean a2) throws java.io.IOException { - return new Object(); - } - - @Override - public void setHeader(java.lang.Object a0, java.lang.String a1, java.lang.String a2) { - } - - @Override - public int getContentLength(java.lang.Object a0) { - return 0; - } - - /// In-memory file system: what was written through `openOutputStream` - /// with a String path, readable back through `openInputStream` under the - /// exact same path, so tests can see which path the runtime addressed. - public static final java.util.Map FILES = new java.util.HashMap(); - - @Override - public java.io.OutputStream openOutputStream(final java.lang.Object a0) throws java.io.IOException { - if (!(a0 instanceof String)) { - return null; - } - return new java.io.ByteArrayOutputStream() { - @Override - public void close() throws java.io.IOException { - super.close(); - FILES.put((String) a0, toByteArray()); - } - }; - } - - @Override - public java.io.OutputStream openOutputStream(java.lang.Object a0, int a1) throws java.io.IOException { - return null; - } - - @Override - public java.io.InputStream openInputStream(java.lang.Object a0) throws java.io.IOException { - byte[] b = a0 instanceof String ? FILES.get(a0) : null; - return b == null ? null : new java.io.ByteArrayInputStream(b); - } - - @Override - public void setPostRequest(java.lang.Object a0, boolean a1) { - } - - @Override - public int getResponseCode(java.lang.Object a0) throws java.io.IOException { - return 0; - } - - @Override - public java.lang.String getResponseMessage(java.lang.Object a0) throws java.io.IOException { - return null; - } - - @Override - public java.lang.String getHeaderField(java.lang.String a0, java.lang.Object a1) throws java.io.IOException { - return null; - } - - @Override - public java.lang.String[] getHeaderFieldNames(java.lang.Object a0) throws java.io.IOException { - return null; - } - - @Override - public java.lang.String[] getHeaderFields(java.lang.String a0, java.lang.Object a1) throws java.io.IOException { - return null; - } - - @Override - public void deleteStorageFile(java.lang.String a0) { - } - - @Override - public java.io.OutputStream createStorageOutputStream(java.lang.String a0) throws java.io.IOException { - return null; - } - - @Override - public java.io.InputStream createStorageInputStream(java.lang.String a0) throws java.io.IOException { - return null; - } - - @Override - public boolean storageFileExists(java.lang.String a0) { - return false; - } - - @Override - public java.lang.String[] listStorageEntries() { - return null; - } - - @Override - public java.lang.String[] listFilesystemRoots() { - return null; - } - - @Override - public java.lang.String[] listFiles(java.lang.String a0) throws java.io.IOException { - return null; - } - - @Override - public long getRootSizeBytes(java.lang.String a0) { - return 0; - } - - @Override - public long getRootAvailableSpace(java.lang.String a0) { - return 0; - } - - @Override - public void mkdir(java.lang.String a0) { - } - - @Override - public void deleteFile(java.lang.String a0) { - if (fileSystem && !UNDELETABLE.contains(a0)) { - FILES.remove(a0); - } - } - - /// When set, [#FILES] answers `exists` and `deleteFile` removes from it, - /// except a path in [#UNDELETABLE], which a refused unlink leaves in - /// place. A test that sets it must reset it and clear both. - public static boolean fileSystem; - public static final java.util.Set UNDELETABLE = new java.util.HashSet(); - - @Override - public boolean isHidden(java.lang.String a0) { - return false; - } - - @Override - public void setHidden(java.lang.String a0, boolean a1) { - } - - @Override - public long getFileLength(java.lang.String a0) { - return 0; - } - - @Override - public boolean isDirectory(java.lang.String a0) { - return false; - } - - @Override - public boolean exists(java.lang.String a0) { - return fileSystem && FILES.containsKey(a0); - } - - @Override - public void rename(java.lang.String a0, java.lang.String a1) { - } - - @Override - public char getFileSystemSeparator() { - return '/'; - } - - @Override - public java.lang.String getPlatformName() { - return "headless"; - } - - @Override - public com.codename1.l10n.L10NManager getLocalizationManager() { - return L10N; - } -} diff --git a/maven/android-compat/src/test/java/com/google/android/material/MaterialComponentsTest.java b/maven/android-compat/src/test/java/com/google/android/material/MaterialComponentsTest.java index 861278c55e6..3a9d7277830 100644 --- a/maven/android-compat/src/test/java/com/google/android/material/MaterialComponentsTest.java +++ b/maven/android-compat/src/test/java/com/google/android/material/MaterialComponentsTest.java @@ -53,7 +53,7 @@ import com.google.android.material.textfield.TextInputLayout; import com.google.android.material.theme.MaterialComponentsViewInflater; -import com.codename1.androidcompat.testing.MainThreadRule; +import com.codename1.compat.testing.MainThreadRule; import org.junit.Rule; import org.junit.Test; diff --git a/maven/compat-testing/pom.xml b/maven/compat-testing/pom.xml new file mode 100644 index 00000000000..7cddabd8923 --- /dev/null +++ b/maven/compat-testing/pom.xml @@ -0,0 +1,102 @@ + + + + + com.codenameone + codenameone + 8.0-SNAPSHOT + + 4.0.0 + + codenameone-compat-testing + 8.0-SNAPSHOT + jar + codenameone-compat-testing + + Test support shared by the compatibility layers: a headless Codename + One implementation with fixed metrics, the factory that selects it, and + a JUnit rule that runs a test on the EDT. A test-scope dependency only; + an ordinary module rather than a test-jar so a build that skips test + compilation still resolves it. + + + + UTF-8 + 1.8 + 1.8 + + + + + + maven-compiler-plugin + + 1.8 + 1.8 + + + + + maven-resources-plugin + + + copy-jdk-host + generate-sources + + copy-resources + + + ${project.build.directory}/generated-sources/jdk-host/com/codename1/impl + + + ${project.basedir}/../../Ports/JavaSE/src/com/codename1/impl + + JdkHost.java + + + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-jdk-host + generate-sources + + add-source + + + + ${project.build.directory}/generated-sources/jdk-host + + + + + + + + + + + com.codenameone + codenameone-core + provided + + + junit + junit + + compile + + + diff --git a/maven/compat-testing/src/main/java/com/codename1/compat/testing/HeadlessImplementation.java b/maven/compat-testing/src/main/java/com/codename1/compat/testing/HeadlessImplementation.java new file mode 100644 index 00000000000..632734cd736 --- /dev/null +++ b/maven/compat-testing/src/main/java/com/codename1/compat/testing/HeadlessImplementation.java @@ -0,0 +1,1522 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.compat.testing; + +import com.codename1.impl.CodenameOneImplementation; +import com.codename1.l10n.L10NManager; +import com.codename1.ui.Display; + +import java.io.InputStream; + +/// A Codename One implementation with no screen, for tests that build and +/// lay out Android views: a fixed 1080x1920 display at 320 dpi (density 2), +/// resources from the class path, and monospaced font metrics. Drawing and +/// everything else answer neutral values. +public class HeadlessImplementation extends CodenameOneImplementation { + + public static final int WIDTH = 1080; + public static final int HEIGHT = 1920; + public static final int DPI = 320; + public static final int CHAR_WIDTH = 16; + public static final int FONT_HEIGHT = 32; + private static final Object FONT = new Object(); + private static final L10NManager L10N = new L10NManager("en", "US") { + }; + + /// Starts Codename One on this implementation once per test JVM. + /// + /// The factory is set here and not left to the class path. A module whose + /// tests also carry a port, as the build engine's carry the JavaSE port, + /// has two classes named `ImplementationFactory`, and which of them the + /// loader finds first is the order of the class path: with the port's + /// first, a test asking for no screen opened a window, and failed where + /// there is no display to open it on. + public static synchronized void install() { + if (!Display.isInitialized()) { + com.codename1.impl.ImplementationFactory.setInstance(new HeadlessFactory()); + Display.init(null); + } + } + + private static final class HeadlessFactory extends com.codename1.impl.ImplementationFactory { + @Override + public Object createImplementation() { + return new HeadlessImplementation(); + } + } + + /// The listener of the last `openGallery` call, which the headless + /// implementation never answers itself. A test that reads it must reset + /// it. + public static com.codename1.ui.events.ActionListener gallery; + + @Override + public void openGallery(com.codename1.ui.events.ActionListener response, int type) { + gallery = response; + } + + /// The image encoder `ImageIO.getImageIO()` answers; none by default. A + /// test that sets it must reset it. + public static com.codename1.ui.util.ImageIO imageIO; + + @Override + public com.codename1.ui.util.ImageIO getImageIO() { + return imageIO; + } + + @Override + public InputStream getResourceAsStream(Class cls, String resource) { + Class c = cls == null ? HeadlessImplementation.class : cls; + return c.getResourceAsStream(resource.startsWith("/") ? resource : "/" + resource); + } + + @Override + public int convertToPixels(int dipCount, boolean horizontal) { + return Math.round(dipCount * DPI / 25.4f); + } + + @Override + public boolean isTrueTypeSupported() { + return true; + } + + @Override + public boolean isNativeFontSchemeSupported() { + return true; + } + + @Override + public Object loadTrueTypeFont(String fontName, String fileName) { + return FONT; + } + + @Override + public Object deriveTrueTypeFont(Object font, float size, int weight) { + return FONT; + } + + /// What [#isDarkMode()] answers; tests flip it to change the system theme. + public static boolean darkMode; + + @Override + public Boolean isDarkMode() { + return Boolean.valueOf(darkMode); + } + + /// Whether the last screen saver call kept the screen on. + public static boolean screenLocked; + + @Override + public void lockScreen() { + screenLocked = true; + } + + @Override + public void unlockScreen() { + screenLocked = false; + } + + @Override + public void init(java.lang.Object a0) { + // No screen to set up; what the real ports install for the JDK's + // resource lookups is installed the same way. + JdkResourceSupport.install(); + } + + @Override + public int getDisplayWidth() { + return WIDTH; + } + + @Override + public int getDisplayHeight() { + return HEIGHT; + } + + @Override + public void editString(com.codename1.ui.Component a0, int a1, int a2, java.lang.String a3, int a4) { + } + + @Override + public void flushGraphics(int a0, int a1, int a2, int a3) { + } + + @Override + public void flushGraphics() { + } + + @Override + public void getRGB(java.lang.Object a0, int[] a1, int a2, int a3, int a4, int a5, int a6) { + if (a0 instanceof int[][]) { + int[][] rows = (int[][]) a0; + for (int y = 0; y < a6; y++) { + System.arraycopy(rows[a4 + y], a3, a1, a2 + y * a5, a5); + } + } + } + + /// When set, an image made from ARGB pixels keeps them (as an `int[][]` + /// of rows) and answers them to `getRGB`. Off by default; tests that + /// inspect pixels flip it. + public static boolean pixelImages; + + /// How many images were made from pixels or made to be drawn on: the + /// ones that are memory of the platform on a device. A test that + /// counts sets it to zero first. + public static int imagesMade; + + /// How many of those were made to be drawn on. + public static int mutableImagesMade; + + @Override + public java.lang.Object createImage(int[] a0, int a1, int a2) { + imagesMade++; + if (pixelImages && a1 > 0 && a2 > 0) { + int[][] rows = new int[a2][a1]; + for (int y = 0; y < a2; y++) { + System.arraycopy(a0, y * a1, rows[y], 0, a1); + } + return rows; + } + return new Object(); + } + + @Override + public java.lang.Object createImage(java.lang.String a0) throws java.io.IOException { + return new Object(); + } + + /// With [#pixelImages] set, an encoded image (a PNG resource) is decoded + /// to its pixels, so tests can check the size a drawable comes out at. + @Override + public java.lang.Object createImage(java.io.InputStream a0) throws java.io.IOException { + if (pixelImages) { + java.awt.image.BufferedImage img = javax.imageio.ImageIO.read(a0); + if (img != null) { + int w = img.getWidth(); + int h = img.getHeight(); + return createImage(img.getRGB(0, 0, w, h, null, 0, w), w, h); + } + } + return new Object(); + } + + @Override + public java.lang.Object createMutableImage(int a0, int a1, int a2) { + imagesMade++; + mutableImagesMade++; + if (rasterImages && a0 > 0 && a1 > 0) { + int[][] rows = new int[a1][a0]; + for (int y = 0; y < a1; y++) { + java.util.Arrays.fill(rows[y], a2); + } + return rows; + } + return new Object(); + } + + // ------------------------------------------------------------ raster + + /// When set, a mutable image keeps its pixels (as an `int[][]` of + /// rows) and what is drawn into it is rasterized: rectangles, lines, + /// polygons and ellipses in the current color and alpha, inside the + /// clip, and pixel images copied. A rounded rectangle is drawn square, + /// an arc as its whole ellipse, and a string as a solid box of the size + /// it measures -- enough to assert where something was painted and in + /// which color, which is all it is for. Sets nothing else: a test that + /// flips it resets it, and wants [#trackClip] for clipping to count. + public static boolean rasterImages; + private static final java.util.Map COLORS = new java.util.IdentityHashMap(); + private static final java.util.Map RASTER_ALPHAS = + new java.util.IdentityHashMap(); + + /// Forgets the color and alpha kept for every raster graphics. + public static void resetRaster() { + COLORS.clear(); + RASTER_ALPHAS.clear(); + } + + private static boolean raster(Object graphics) { + return rasterImages && graphics instanceof int[][]; + } + + private static int blend(int under, int rgb, int alpha) { + if (alpha >= 255) { + return 0xff000000 | rgb; + } + if (alpha <= 0) { + return under; + } + int ua = under >>> 24; + int r = (((rgb >> 16) & 0xff) * alpha + ((under >> 16) & 0xff) * (255 - alpha)) / 255; + int g = (((rgb >> 8) & 0xff) * alpha + ((under >> 8) & 0xff) * (255 - alpha)) / 255; + int b = ((rgb & 0xff) * alpha + (under & 0xff) * (255 - alpha)) / 255; + return (Math.max(ua, alpha) << 24) | (r << 16) | (g << 8) | b; + } + + private static void span(Object graphics, int x, int y, int w, int h) { + int[][] rows = (int[][]) graphics; + Integer c = COLORS.get(graphics); + Integer a = RASTER_ALPHAS.get(graphics); + int rgb = c == null ? 0 : c.intValue() & 0xffffff; + int alpha = a == null ? 255 : a.intValue(); + int x1 = Math.max(0, x); + int y1 = Math.max(0, y); + int x2 = Math.min(rows[0].length, x + w); + int y2 = Math.min(rows.length, y + h); + if (trackClip) { + x1 = Math.max(x1, clipX); + y1 = Math.max(y1, clipY); + x2 = Math.min(x2, clipX + clipW); + y2 = Math.min(y2, clipY + clipH); + } + for (int row = y1; row < y2; row++) { + int[] line = rows[row]; + for (int col = x1; col < x2; col++) { + line[col] = blend(line[col], rgb, alpha); + } + } + } + + private static void line(Object graphics, int x1, int y1, int x2, int y2) { + int dx = Math.abs(x2 - x1); + int dy = Math.abs(y2 - y1); + int steps = Math.max(dx, dy); + if (steps == 0) { + span(graphics, x1, y1, 1, 1); + return; + } + if (dy == 0) { + span(graphics, Math.min(x1, x2), y1, dx + 1, 1); + return; + } + if (dx == 0) { + span(graphics, x1, Math.min(y1, y2), 1, dy + 1); + return; + } + for (int i = 0; i <= steps; i++) { + span(graphics, x1 + (x2 - x1) * i / steps, y1 + (y2 - y1) * i / steps, 1, 1); + } + } + + private static void ellipse(Object graphics, int x, int y, int w, int h) { + if (w <= 0 || h <= 0) { + return; + } + double rx = w / 2.0; + double ry = h / 2.0; + for (int row = 0; row < h; row++) { + double t = (row + 0.5 - ry) / ry; + int half = (int) Math.round(rx * Math.sqrt(Math.max(0, 1 - t * t))); + if (half > 0) { + span(graphics, (int) Math.round(x + rx - half), y + row, half * 2, 1); + } + } + } + + @Override + public void fillPolygon(Object graphics, int[] xPoints, int[] yPoints, int nPoints) { + if (!raster(graphics) || nPoints < 3) { + return; + } + int top = Integer.MAX_VALUE; + int bottom = Integer.MIN_VALUE; + for (int i = 0; i < nPoints; i++) { + top = Math.min(top, yPoints[i]); + bottom = Math.max(bottom, yPoints[i]); + } + for (int row = top; row <= bottom; row++) { + int left = Integer.MAX_VALUE; + int right = Integer.MIN_VALUE; + for (int i = 0; i < nPoints; i++) { + int j = (i + 1) % nPoints; + int ya = yPoints[i]; + int yb = yPoints[j]; + if (ya == yb) { + if (ya == row) { + left = Math.min(left, Math.min(xPoints[i], xPoints[j])); + right = Math.max(right, Math.max(xPoints[i], xPoints[j])); + } + continue; + } + if (row < Math.min(ya, yb) || row > Math.max(ya, yb)) { + continue; + } + int at = xPoints[i] + (xPoints[j] - xPoints[i]) * (row - ya) / (yb - ya); + left = Math.min(left, at); + right = Math.max(right, at); + } + if (left <= right) { + span(graphics, left, row, right - left + 1, 1); + } + } + } + + @Override + public void drawPolygon(Object graphics, int[] xPoints, int[] yPoints, int nPoints) { + if (!raster(graphics)) { + return; + } + for (int i = 0; i < nPoints; i++) { + int j = (i + 1) % nPoints; + line(graphics, xPoints[i], yPoints[i], xPoints[j], yPoints[j]); + } + } + + // ------------------------------------------------------------ shapes + + /// When set, the graphics of a raster image (see [#rasterImages]) draws + /// shapes: `isShapeSupported` answers true for it, `fillShape` colours + /// every pixel whose middle is inside the path by its winding rule, and + /// `drawShape` draws the outline one pixel wide. Curves are cut into + /// straight pieces. Off by default, when no graphics has shapes and code + /// under test takes the path of a port without them. + /// + /// What it deliberately does NOT do is clip to a shape. `setClip(Shape)` + /// stays what it was -- the bounds of the shape with [#trackClip] and + /// nothing at all without -- because a rectangle put on top of a shape + /// clip with `clipRect` is exactly what real ports have got wrong: one + /// forgot the shape and kept the rectangle that was the clip before it, + /// two more rebuilt a shape with a curved corner as slivers along its + /// edges. Code that confines a fill to a shape that way -- which is what + /// `Graphics.fillShape` does for a `Paint` -- therefore paints outside + /// the shape here, and a test that looks at a pixel between a rounded + /// corner and the corner of its bounds sees it. + public static boolean shapes; + + @Override + public boolean isShapeSupported(Object graphics) { + return shapes && raster(graphics); + } + + /// The path as closed or open runs of points, `{x0, y0, x1, y1, ...}` + /// each, its curves cut into [#CURVE_PIECES] straight pieces. + private static java.util.List flatten(com.codename1.ui.geom.Shape shape, boolean close) { + java.util.List out = new java.util.ArrayList(); + java.util.List run = new java.util.ArrayList(); + com.codename1.ui.geom.PathIterator it = shape.getPathIterator(); + float[] k = new float[6]; + float startX = 0; + float startY = 0; + float lastX = 0; + float lastY = 0; + while (!it.isDone()) { + int type = it.currentSegment(k); + if (type == com.codename1.ui.geom.PathIterator.SEG_MOVETO) { + endRun(out, run, close, startX, startY); + startX = k[0]; + startY = k[1]; + lastX = k[0]; + lastY = k[1]; + run.add(Float.valueOf(lastX)); + run.add(Float.valueOf(lastY)); + } else if (type == com.codename1.ui.geom.PathIterator.SEG_LINETO) { + lastX = k[0]; + lastY = k[1]; + run.add(Float.valueOf(lastX)); + run.add(Float.valueOf(lastY)); + } else if (type == com.codename1.ui.geom.PathIterator.SEG_QUADTO) { + for (int i = 1; i <= CURVE_PIECES; i++) { + float t = i / (float) CURVE_PIECES; + float u = 1 - t; + run.add(Float.valueOf(u * u * lastX + 2 * u * t * k[0] + t * t * k[2])); + run.add(Float.valueOf(u * u * lastY + 2 * u * t * k[1] + t * t * k[3])); + } + lastX = k[2]; + lastY = k[3]; + } else if (type == com.codename1.ui.geom.PathIterator.SEG_CUBICTO) { + for (int i = 1; i <= CURVE_PIECES; i++) { + float t = i / (float) CURVE_PIECES; + float u = 1 - t; + run.add(Float.valueOf(u * u * u * lastX + 3 * u * u * t * k[0] + 3 * u * t * t * k[2] + + t * t * t * k[4])); + run.add(Float.valueOf(u * u * u * lastY + 3 * u * u * t * k[1] + 3 * u * t * t * k[3] + + t * t * t * k[5])); + } + lastX = k[4]; + lastY = k[5]; + } else { + run.add(Float.valueOf(startX)); + run.add(Float.valueOf(startY)); + lastX = startX; + lastY = startY; + } + it.next(); + } + endRun(out, run, close, startX, startY); + return out; + } + + private static final int CURVE_PIECES = 16; + + private static void endRun(java.util.List out, java.util.List run, boolean close, float startX, + float startY) { + if (run.size() >= 4) { + if (close) { + // A fill closes every run, whether the path said so or not. + run.add(Float.valueOf(startX)); + run.add(Float.valueOf(startY)); + } + float[] pts = new float[run.size()]; + for (int i = 0; i < pts.length; i++) { + pts[i] = run.get(i).floatValue(); + } + out.add(pts); + } + run.clear(); + } + + @Override + public void fillShape(Object graphics, com.codename1.ui.geom.Shape shape) { + if (!isShapeSupported(graphics) || shape == null) { + return; + } + int[][] rows = (int[][]) graphics; + java.util.List runs = flatten(shape, true); + boolean evenOdd = shape.getPathIterator().getWindingRule() + == com.codename1.ui.geom.PathIterator.WIND_EVEN_ODD; + for (int row = 0; row < rows.length; row++) { + float y = row + 0.5f; + // Where the outline crosses the middle of this row of pixels, + // and which way it was going. + java.util.List crossings = new java.util.ArrayList(); + for (float[] pts : runs) { + for (int i = 0; i + 3 < pts.length; i += 2) { + float ya = pts[i + 1]; + float yb = pts[i + 3]; + if ((ya <= y) == (yb <= y)) { + continue; + } + float x = pts[i] + (pts[i + 2] - pts[i]) * (y - ya) / (yb - ya); + crossings.add(new float[] {x, yb > ya ? 1 : -1}); + } + } + java.util.Collections.sort(crossings, new java.util.Comparator() { + @Override + public int compare(float[] a, float[] b) { + return Float.compare(a[0], b[0]); + } + }); + int winding = 0; + for (int i = 0; i + 1 < crossings.size(); i++) { + winding += (int) crossings.get(i)[1]; + boolean inside = evenOdd ? (i % 2) == 0 : winding != 0; + if (inside) { + int from = (int) Math.ceil(crossings.get(i)[0] - 0.5f); + int to = (int) Math.ceil(crossings.get(i + 1)[0] - 0.5f); + if (to > from) { + span(graphics, from, row, to - from, 1); + } + } + } + } + } + + @Override + public void drawShape(Object graphics, com.codename1.ui.geom.Shape shape, com.codename1.ui.Stroke stroke) { + if (!isShapeSupported(graphics) || shape == null) { + return; + } + for (float[] pts : flatten(shape, false)) { + for (int i = 0; i + 3 < pts.length; i += 2) { + line(graphics, (int) Math.floor(pts[i]), (int) Math.floor(pts[i + 1]), (int) Math.floor(pts[i + 2]), + (int) Math.floor(pts[i + 3])); + } + } + } + + /// Counts encoded images handed to [#createImage(byte[], int, int)], so a + /// test can tell a header-only bounds probe from a full decode. + public static int encodedDecodes; + + @Override + public java.lang.Object createImage(byte[] a0, int a1, int a2) { + encodedDecodes++; + if (pixelImages) { + try { + return createImage(new java.io.ByteArrayInputStream(a0, a1, a2)); + } catch (java.io.IOException e) { + return new Object(); + } + } + return new Object(); + } + + @Override + public int getImageWidth(java.lang.Object image) { + return image instanceof int[][] ? ((int[][]) image)[0].length : 0; + } + + @Override + public int getImageHeight(java.lang.Object image) { + return image instanceof int[][] ? ((int[][]) image).length : 0; + } + + @Override + public java.lang.Object scale(java.lang.Object a0, int a1, int a2) { + if (a0 instanceof int[][] && a1 > 0 && a2 > 0) { + // Nearest-neighbour, for the pixel images of pixelImages. + int[][] src = (int[][]) a0; + int[][] out = new int[a2][a1]; + for (int y = 0; y < a2; y++) { + for (int x = 0; x < a1; x++) { + out[y][x] = src[y * src.length / a2][x * src[0].length / a1]; + } + } + return out; + } + return new Object(); + } + + @Override + public int getSoftkeyCount() { + return 0; + } + + @Override + public int[] getSoftkeyCode(int a0) { + return null; + } + + @Override + public int getClearKeyCode() { + return 0; + } + + @Override + public int getBackspaceKeyCode() { + return 0; + } + + @Override + public int getBackKeyCode() { + return 0; + } + + /// The key codes of the arrows and of fire, which are those of the + /// desktop port. With no code for them a test could not press an + /// arrow at all: every game action answered the same key, zero, and + /// zero is no key. + private static final int KEY_FIRE = -90; + private static final int KEY_UP = -91; + private static final int KEY_DOWN = -92; + private static final int KEY_LEFT = -93; + private static final int KEY_RIGHT = -94; + + @Override + public int getGameAction(int a0) { + switch (a0) { + case KEY_FIRE: + return Display.GAME_FIRE; + case KEY_UP: + return Display.GAME_UP; + case KEY_DOWN: + return Display.GAME_DOWN; + case KEY_LEFT: + return Display.GAME_LEFT; + case KEY_RIGHT: + return Display.GAME_RIGHT; + default: + return 0; + } + } + + @Override + public int getKeyCode(int a0) { + switch (a0) { + case Display.GAME_FIRE: + return KEY_FIRE; + case Display.GAME_UP: + return KEY_UP; + case Display.GAME_DOWN: + return KEY_DOWN; + case Display.GAME_LEFT: + return KEY_LEFT; + case Display.GAME_RIGHT: + return KEY_RIGHT; + default: + return 0; + } + } + + private static boolean desktop; + + /// Makes the implementation answer that it is a desktop, as the port + /// an application runs on at a desk does, or not. A form of a desktop + /// moves the focus itself on Tab and acts on Escape, which a test of + /// the keyboard has to run with; a test that asked for it turns it + /// off again when it is done. + public static void setDesktop(boolean value) { + desktop = value; + } + + @Override + public boolean isDesktop() { + return desktop; + } + + /// Whether the title of the form showing belongs on the window of the + /// operating system, as it does on a desktop port in its `native` + /// title bar mode. A test that sets it must reset it. + public static boolean nativeTitle; + + /// The title last pushed to the window of the operating system, or + /// null. Only [#refreshNativeTitle] writes it, as on a real port: a + /// title that nothing asked to push stays where it was. + public static String windowTitle; + + /// The size last asked of the application's window as `{width, + /// height}`, or null. + public static int[] windowSize; + + @Override + public boolean isNativeTitle() { + return desktop && nativeTitle; + } + + @Override + public void refreshNativeTitle() { + com.codename1.ui.Form f = getCurrentForm(); + if (isNativeTitle() && f != null && !(f instanceof com.codename1.ui.Dialog)) { + windowTitle = f.getTitle() == null ? "" : f.getTitle(); + } + } + + /// The size of the desktop the window is on as `{width, height}`, or + /// null for a port that has none to report, which is every port that + /// is not at a desk. A test that sets it must reset it. + public static int[] desktopSize; + + @Override + public com.codename1.ui.geom.Dimension getDesktopSize() { + return desktop && desktopSize != null + ? new com.codename1.ui.geom.Dimension(desktopSize[0], desktopSize[1]) : null; + } + + /// What the frame of the window adds to the display as `{width, + /// height}`, or null: the window of a desktop is larger than what is + /// drawn in it by its borders and its title bar. A test that sets it + /// must reset it. + public static int[] windowFrame; + + @Override + public com.codename1.ui.geom.Rectangle getWindowBounds() { + if (!desktop || windowFrame == null) { + return null; + } + return new com.codename1.ui.geom.Rectangle(0, 0, WIDTH + windowFrame[0], HEIGHT + windowFrame[1]); + } + + @Override + public void setWindowSize(int width, int height) { + if (desktop) { + windowSize = new int[] {width, height}; + } + } + + @Override + public boolean isTouchDevice() { + return true; + } + + @Override + public int getColor(java.lang.Object a0) { + Integer c = rasterImages ? COLORS.get(a0) : null; + return c == null ? 0 : c.intValue(); + } + + @Override + public void setColor(java.lang.Object a0, int a1) { + if (raster(a0)) { + COLORS.put(a0, Integer.valueOf(a1)); + } + } + + /// When set, each graphics context keeps the alpha it was given, and + /// every `fillRect`, `drawImage` and `fillLinearGradient` is added to + /// [#draws] as {operation, native graphics, alpha}. A test that sets it + /// must reset it and clear [#draws]. + public static boolean recordDraws; + public static final java.util.List draws = new java.util.ArrayList(); + private static final java.util.Map ALPHAS = new java.util.IdentityHashMap(); + + private static void recordDraw(String op, Object graphics) { + if (recordDraws) { + draws.add(new Object[]{op, graphics, Integer.valueOf(alphaOf(graphics))}); + } + } + + private static int alphaOf(Object graphics) { + Integer a = ALPHAS.get(graphics); + return a == null ? 255 : a.intValue(); + } + + @Override + public void setAlpha(java.lang.Object a0, int a1) { + if (raster(a0)) { + RASTER_ALPHAS.put(a0, Integer.valueOf(a1)); + } + if (recordDraws) { + ALPHAS.put(a0, Integer.valueOf(a1)); + } + } + + @Override + public int getAlpha(java.lang.Object a0) { + if (raster(a0)) { + Integer a = RASTER_ALPHAS.get(a0); + return a == null ? 255 : a.intValue(); + } + return recordDraws ? alphaOf(a0) : 0; + } + + /// With [#recordDraws], the arguments of every gradient fill: + /// {"fillLinearGradient", start, end, horizontal} or + /// {"fillGradient", the gradient}. A test that records clears it too. + public static final java.util.List gradients = new java.util.ArrayList(); + + @Override + public void fillGradient(Object graphics, com.codename1.ui.Gradient gradient, int x, int y, int width, + int height) { + if (recordDraws) { + recordDraw("fillGradient", graphics); + gradients.add(new Object[]{"fillGradient", gradient}); + } else { + super.fillGradient(graphics, gradient, x, y, width, height); + } + } + + @Override + public void fillLinearGradient(Object graphics, int startColor, int endColor, int x, int y, int width, + int height, boolean horizontal) { + if (recordDraws) { + recordDraw("fillLinearGradient", graphics); + gradients.add(new Object[]{"fillLinearGradient", Integer.valueOf(startColor), + Integer.valueOf(endColor), Boolean.valueOf(horizontal)}); + } else { + super.fillLinearGradient(graphics, startColor, endColor, x, y, width, height, horizontal); + } + } + + @Override + public void setNativeFont(java.lang.Object a0, java.lang.Object a1) { + } + + /// When set, every graphics context shares one tracked clip rectangle + /// and shape clips are supported: `setClip(Shape)` records the shape in + /// [#shapeClip] and clips to its bounds. Off by default, when the clip is + /// always the whole display; tests that check clipping flip it. + public static boolean trackClip; + /// The last shape passed to `setClip(Shape)` while [#trackClip] is set, + /// null once a rectangle replaces it. + public static com.codename1.ui.geom.Shape shapeClip; + private static int clipX; + private static int clipY; + private static int clipW = WIDTH; + private static int clipH = HEIGHT; + + @Override + public int getClipX(java.lang.Object a0) { + own(a0); + return trackClip ? clipX : 0; + } + + @Override + public int getClipY(java.lang.Object a0) { + own(a0); + return trackClip ? clipY : 0; + } + + @Override + public int getClipWidth(java.lang.Object a0) { + own(a0); + return trackClip ? clipW : WIDTH; + } + + @Override + public int getClipHeight(java.lang.Object a0) { + own(a0); + return trackClip ? clipH : HEIGHT; + } + + @Override + public void setClip(java.lang.Object a0, int a1, int a2, int a3, int a4) { + own(a0); + clipX = a1; + clipY = a2; + clipW = a3; + clipH = a4; + shapeClip = null; + } + + /// When set, the implementation is a port with affine transforms whose + /// clip stays in the coordinates of the screen, which is how the native + /// Linux port behaves: `setClip` and the clip getters carry the same + /// numbers whatever matrix is installed, and `clipRect` under a matrix + /// REPLACES the clip with the bounds of the transformed rectangle. Wants + /// [#trackClip]. What is drawn is clipped but not moved by the matrix: + /// the mode is for testing what gets painted, not where it lands. + public static boolean screenSpaceClip; + private static double[] matrix = {1, 0, 0, 1, 0, 0}; + + private static double[] lastMatrix = matrix; + + /// Where a point handed to the port lands under the matrix installed + /// last, on whichever graphics that was, in [#screenSpaceClip] mode. + public static double[] onScreen(double x, double y) { + double[] m = lastMatrix; + return new double[] {m[0] * x + m[2] * y + m[4], m[1] * x + m[3] * y + m[5]}; + } + + private static double[] affine(Object nativeTransform) { + return (double[]) nativeTransform; + } + + private static boolean plain(double[] m) { + return m[0] == 1 && m[1] == 0 && m[2] == 0 && m[3] == 1 && m[4] == 0 && m[5] == 0; + } + + @Override + public boolean isTransformSupported() { + return screenSpaceClip; + } + + @Override + public boolean isTransformSupported(Object graphics) { + return screenSpaceClip; + } + + @Override + public Object makeTransformIdentity() { + return new double[] {1, 0, 0, 1, 0, 0}; + } + + @Override + public void setTransformIdentity(Object t) { + setTransformAffine(t, 1, 0, 0, 1, 0, 0); + } + + @Override + public Object makeTransformAffine(double m00, double m10, double m01, double m11, double m02, double m12) { + return new double[] {m00, m10, m01, m11, m02, m12}; + } + + @Override + public void setTransformAffine(Object t, double m00, double m10, double m01, double m11, double m02, + double m12) { + double[] m = affine(t); + m[0] = m00; + m[1] = m10; + m[2] = m01; + m[3] = m11; + m[4] = m02; + m[5] = m12; + } + + @Override + public Object makeTransformTranslation(float x, float y, float z) { + return new double[] {1, 0, 0, 1, x, y}; + } + + @Override + public void setTransformTranslation(Object t, float x, float y, float z) { + setTransformAffine(t, 1, 0, 0, 1, x, y); + } + + @Override + public Object makeTransformScale(float x, float y, float z) { + return new double[] {x, 0, 0, y, 0, 0}; + } + + @Override + public void setTransformScale(Object t, float x, float y, float z) { + setTransformAffine(t, x, 0, 0, y, 0, 0); + } + + @Override + public void transformTranslate(Object t, float x, float y, float z) { + concatenateTransform(t, new double[] {1, 0, 0, 1, x, y}); + } + + @Override + public void transformScale(Object t, float x, float y, float z) { + concatenateTransform(t, new double[] {x, 0, 0, y, 0, 0}); + } + + @Override + public Object makeTransformInverse(Object t) { + double[] m = affine(t); + double det = m[0] * m[3] - m[1] * m[2]; + if (det == 0) { + return null; + } + return new double[] {m[3] / det, -m[1] / det, -m[2] / det, m[0] / det, + (m[2] * m[5] - m[3] * m[4]) / det, (m[1] * m[4] - m[0] * m[5]) / det}; + } + + @Override + public void setTransformInverse(Object t) throws com.codename1.ui.Transform.NotInvertibleException { + Object inverse = makeTransformInverse(t); + if (inverse == null) { + throw new com.codename1.ui.Transform.NotInvertibleException(); + } + copyTransform(inverse, t); + } + + @Override + public void copyTransform(Object src, Object dest) { + System.arraycopy(affine(src), 0, affine(dest), 0, 6); + } + + @Override + public void concatenateTransform(Object t1, Object t2) { + double[] a = affine(t1); + double[] b = affine(t2); + setTransformAffine(t1, a[0] * b[0] + a[2] * b[1], a[1] * b[0] + a[3] * b[1], + a[0] * b[2] + a[2] * b[3], a[1] * b[2] + a[3] * b[3], + a[0] * b[4] + a[2] * b[5] + a[4], a[1] * b[4] + a[3] * b[5] + a[5]); + } + + @Override + public boolean transformNativeEqualsImpl(Object t1, Object t2) { + return t1 != null && t2 != null && java.util.Arrays.equals(affine(t1), affine(t2)); + } + + @Override + public void transformPoint(Object t, float[] in, float[] out) { + double[] m = affine(t); + float x = in[0]; + float y = in[1]; + out[0] = (float) (m[0] * x + m[2] * y + m[4]); + out[1] = (float) (m[1] * x + m[3] * y + m[5]); + } + + @Override + public void setTransform(Object graphics, com.codename1.ui.Transform transform) { + if (graphics instanceof Offscreen) { + ((Offscreen) graphics).matrix = transform == null ? new double[] {1, 0, 0, 1, 0, 0} + : affine(transform.getNativeTransform()).clone(); + lastMatrix = ((Offscreen) graphics).matrix; + return; + } + if (transform == null) { + matrix = new double[] {1, 0, 0, 1, 0, 0}; + } else { + matrix = affine(transform.getNativeTransform()).clone(); + } + lastMatrix = matrix; + } + + @Override + public com.codename1.ui.Transform getTransform(Object graphics) { + double[] m = screenSpaceClip ? matrixOf(graphics) : new double[] {1, 0, 0, 1, 0, 0}; + return com.codename1.ui.Transform.makeAffine(m[0], m[1], m[2], m[3], m[4], m[5]); + } + + @Override + public void getTransform(Object graphics, com.codename1.ui.Transform t) { + t.setTransform(getTransform(graphics)); + } + + @Override + public void clipRect(java.lang.Object a0, int a1, int a2, int a3, int a4) { + own(a0); + if (screenSpaceClip && !plain(matrixOf(a0))) { + double[] m = matrixOf(a0); + double minX = Double.MAX_VALUE; + double minY = Double.MAX_VALUE; + double maxX = -Double.MAX_VALUE; + double maxY = -Double.MAX_VALUE; + for (int i = 0; i < 4; i++) { + double x = a1 + ((i & 1) == 0 ? 0 : a3); + double y = a2 + ((i & 2) == 0 ? 0 : a4); + double sx = m[0] * x + m[2] * y + m[4]; + double sy = m[1] * x + m[3] * y + m[5]; + minX = Math.min(minX, sx); + minY = Math.min(minY, sy); + maxX = Math.max(maxX, sx); + maxY = Math.max(maxY, sy); + } + clipX = (int) Math.floor(minX); + clipY = (int) Math.floor(minY); + clipW = (int) Math.ceil(maxX) - clipX; + clipH = (int) Math.ceil(maxY) - clipY; + shapeClip = null; + return; + } + int x2 = Math.min(clipX + clipW, a1 + a3); + int y2 = Math.min(clipY + clipH, a2 + a4); + clipX = Math.max(clipX, a1); + clipY = Math.max(clipY, a2); + clipW = Math.max(0, x2 - clipX); + clipH = Math.max(0, y2 - clipY); + shapeClip = null; + } + + @Override + public boolean isShapeClipSupported(java.lang.Object graphics) { + return trackClip; + } + + @Override + public void setClip(java.lang.Object graphics, com.codename1.ui.geom.Shape shape) { + if (trackClip) { + com.codename1.ui.geom.Rectangle b = shape.getBounds(); + if (screenSpaceClip && !plain(matrixOf(graphics))) { + // The native Linux port: the shape goes through the matrix + // and its bounds on the screen become the clip. + clipRect(graphics, b.getX(), b.getY(), b.getWidth(), b.getHeight()); + } else { + setClip(graphics, b.getX(), b.getY(), b.getWidth(), b.getHeight()); + } + shapeClip = shape; + } + } + + @Override + public void drawLine(java.lang.Object a0, int a1, int a2, int a3, int a4) { + if (raster(a0)) { + line(a0, a1, a2, a3, a4); + } + } + + @Override + public void fillRect(java.lang.Object a0, int a1, int a2, int a3, int a4) { + if (raster(a0)) { + span(a0, a1, a2, a3, a4); + } + recordDraw("fillRect", a0); + } + + /// The rectangles `clearRect` was asked to erase, as `{x, y, w, h}`. + /// Tests that check erasing clear it first. + public static final java.util.List clearedRects = new java.util.ArrayList(); + + @Override + public void clearRect(java.lang.Object graphics, int x, int y, int width, int height) { + clearedRects.add(new int[]{x, y, width, height}); + if (graphics instanceof int[][]) { + // A pixel image's graphics (see getNativeGraphics) really erases. + int[][] rows = (int[][]) graphics; + for (int row = Math.max(0, y); row < Math.min(rows.length, y + height); row++) { + for (int col = Math.max(0, x); col < Math.min(rows[row].length, x + width); col++) { + rows[row][col] = 0; + } + } + } + } + + @Override + public void drawRect(java.lang.Object a0, int a1, int a2, int a3, int a4) { + if (raster(a0)) { + line(a0, a1, a2, a1 + a3, a2); + line(a0, a1, a2 + a4, a1 + a3, a2 + a4); + line(a0, a1, a2, a1, a2 + a4); + line(a0, a1 + a3, a2, a1 + a3, a2 + a4); + } + } + + @Override + public void drawRoundRect(java.lang.Object a0, int a1, int a2, int a3, int a4, int a5, int a6) { + drawRect(a0, a1, a2, a3, a4); + } + + @Override + public void fillRoundRect(java.lang.Object a0, int a1, int a2, int a3, int a4, int a5, int a6) { + if (raster(a0)) { + span(a0, a1, a2, a3, a4); + } + } + + @Override + public void fillArc(java.lang.Object a0, int a1, int a2, int a3, int a4, int a5, int a6) { + if (raster(a0)) { + ellipse(a0, a1, a2, a3, a4); + } + } + + @Override + public void drawArc(java.lang.Object a0, int a1, int a2, int a3, int a4, int a5, int a6) { + } + + @Override + public void drawString(java.lang.Object a0, java.lang.String a1, int a2, int a3) { + if (raster(a0) && a1 != null) { + span(a0, a2, a3, a1.length() * CHAR_WIDTH + textOverhang, FONT_HEIGHT); + } + if (recordText) { + own(a0); + // Whether the place the text starts at is inside the clip, which + // is in the coordinates the graphics draws to once its matrix + // has been applied. + double[] m = screenSpaceClip ? matrixOf(a0) : new double[] {1, 0, 0, 1, 0, 0}; + double sx = m[0] * (a2 + 1) + m[2] * (a3 + 1) + m[4]; + double sy = m[1] * (a2 + 1) + m[3] * (a3 + 1) + m[5]; + boolean seen = sx >= clipX && sx < clipX + clipW && sy >= clipY && sy < clipY + clipH; + drawnText.add(new Object[]{a1, Integer.valueOf(a2), Integer.valueOf(a3), Boolean.valueOf(seen)}); + } + } + + /// How many pixels the box a string is rasterized as reaches past the + /// width the string measures, as the last letter of an italic does on + /// a real port, where a text clipped to its measured width loses that + /// much of it. 0 by default; a test that sets it puts it back. + public static int textOverhang; + + /// When set, every `drawString` (a `drawChar` arrives as one) is added + /// to [#drawnText] as {text, x, y, inside}, the last being whether the + /// text starts inside the clip in force, which only means something + /// with [#trackClip]. A test that sets it must reset both. + public static boolean recordText; + public static final java.util.List drawnText = new java.util.ArrayList(); + + @Override + public void drawImage(java.lang.Object a0, java.lang.Object a1, int a2, int a3) { + if (raster(a0) && a1 instanceof int[][]) { + int[][] src = (int[][]) a1; + int[][] rows = (int[][]) a0; + for (int y = 0; y < src.length; y++) { + int row = a3 + y; + if (row < 0 || row >= rows.length || (trackClip && (row < clipY || row >= clipY + clipH))) { + continue; + } + for (int x = 0; x < src[y].length; x++) { + int col = a2 + x; + if (col < 0 || col >= rows[row].length || (trackClip && (col < clipX || col >= clipX + clipW))) { + continue; + } + rows[row][col] = blend(rows[row][col], src[y][x] & 0xffffff, src[y][x] >>> 24); + } + } + } + recordDraw("drawImage", a0); + } + + @Override + public void drawRGB(java.lang.Object a0, int[] a1, int a2, int a3, int a4, int a5, int a6, boolean a7) { + } + + @Override + public java.lang.Object getNativeGraphics() { + return new Object(); + } + + @Override + public java.lang.Object getNativeGraphics(java.lang.Object a0) { + // With pixelImages, drawing into a pixel image draws into its rows; + // only clearRect writes pixels so far. + if ((pixelImages || rasterImages) && a0 instanceof int[][]) { + return a0; + } + return new Offscreen(); + } + + /// The graphics of an image without pixels. It keeps the matrix + /// installed on it, as a port does: what is on the graphics of one + /// image, or on the screen's, is not on that of another. + private static final class Offscreen { + private double[] matrix = {1, 0, 0, 1, 0, 0}; + private int[] clip = {0, 0, 1 << 20, 1 << 20}; + } + + private static Offscreen clipOwner; + + /// In [#screenSpaceClip] mode the clip is that of the graphics it was + /// set on, as it is on a port: a picture painted in the middle of a + /// frame starts with the whole of itself, not with what the screen was + /// clipped to. The one tracked rectangle is handed from graphics to + /// graphics as each is used. + private static void own(Object graphics) { + if (!screenSpaceClip || !(graphics instanceof Offscreen) || graphics == clipOwner) { + return; + } + if (clipOwner != null) { + clipOwner.clip = new int[] {clipX, clipY, clipW, clipH}; + } + clipOwner = (Offscreen) graphics; + clipX = clipOwner.clip[0]; + clipY = clipOwner.clip[1]; + clipW = clipOwner.clip[2]; + clipH = clipOwner.clip[3]; + shapeClip = null; + } + + /// The matrix in force on a graphics, in [#screenSpaceClip] mode. + private static double[] matrixOf(Object graphics) { + if (graphics instanceof Offscreen) { + return ((Offscreen) graphics).matrix; + } + return matrix; + } + + /// When set, a run of characters measures 1px narrower per adjacent pair + /// than its characters one by one, as a kerning font (a browser canvas) + /// does. Off by default; tests that measure text flip it. + public static boolean kerning; + + private static int kerned(int width, int len) { + return kerning && len > 1 ? width - (len - 1) : width; + } + + @Override + public int charsWidth(java.lang.Object nativeFont, char[] ch, int offset, int len) { + return kerned(len * CHAR_WIDTH, len); + } + + @Override + public int stringWidth(java.lang.Object nativeFont, java.lang.String text) { + return text == null ? 0 : kerned(text.length() * CHAR_WIDTH, text.length()); + } + + @Override + public int charWidth(java.lang.Object a0, char a1) { + return CHAR_WIDTH; + } + + @Override + public int getHeight(java.lang.Object a0) { + return FONT_HEIGHT; + } + + @Override + public java.lang.Object getDefaultFont() { + return FONT; + } + + /// The face of the system font asked for last, -1 before any was: + /// every font here is the same one, so this is all a test can tell a + /// fixed width font by. + public static int lastFontFace = -1; + + @Override + public java.lang.Object createFont(int a0, int a1, int a2) { + lastFontFace = a0; + return FONT; + } + + @Override + public java.lang.Object connect(java.lang.String a0, boolean a1, boolean a2) throws java.io.IOException { + return new Object(); + } + + @Override + public void setHeader(java.lang.Object a0, java.lang.String a1, java.lang.String a2) { + } + + @Override + public int getContentLength(java.lang.Object a0) { + return 0; + } + + /// In-memory file system: what was written through `openOutputStream` + /// with a String path, readable back through `openInputStream` under the + /// exact same path, so tests can see which path the runtime addressed. + public static final java.util.Map FILES = new java.util.HashMap(); + + /// Refuses what a real port's file system refuses: a path that is not + /// an absolute `file:/` one. The simulator throws exactly this for a + /// relative path or a bare `file:name`, so code that hands one over + /// must fail here too rather than pass a test and die on a device. + private static void checkPath(java.lang.Object path) { + if (path instanceof String && !((String) path).startsWith("file:/")) { + throw new IllegalArgumentException(path + " is not a valid path, use " + + "FileSystemStorage.getInstance().getAppHomePath() to get a valid dir path to read/write files"); + } + } + + @Override + public java.io.OutputStream openOutputStream(final java.lang.Object a0) throws java.io.IOException { + checkPath(a0); + if (!(a0 instanceof String)) { + return null; + } + return new java.io.ByteArrayOutputStream() { + @Override + public void close() throws java.io.IOException { + super.close(); + FILES.put((String) a0, toByteArray()); + } + }; + } + + @Override + public java.io.OutputStream openOutputStream(java.lang.Object a0, int a1) throws java.io.IOException { + return null; + } + + @Override + public java.io.InputStream openInputStream(java.lang.Object a0) throws java.io.IOException { + checkPath(a0); + byte[] b = a0 instanceof String ? FILES.get(a0) : null; + return b == null ? null : new java.io.ByteArrayInputStream(b); + } + + @Override + public void setPostRequest(java.lang.Object a0, boolean a1) { + } + + @Override + public int getResponseCode(java.lang.Object a0) throws java.io.IOException { + return 0; + } + + @Override + public java.lang.String getResponseMessage(java.lang.Object a0) throws java.io.IOException { + return null; + } + + @Override + public java.lang.String getHeaderField(java.lang.String a0, java.lang.Object a1) throws java.io.IOException { + return null; + } + + @Override + public java.lang.String[] getHeaderFieldNames(java.lang.Object a0) throws java.io.IOException { + return null; + } + + @Override + public java.lang.String[] getHeaderFields(java.lang.String a0, java.lang.Object a1) throws java.io.IOException { + return null; + } + + @Override + public void deleteStorageFile(java.lang.String a0) { + } + + @Override + public java.io.OutputStream createStorageOutputStream(java.lang.String a0) throws java.io.IOException { + return null; + } + + @Override + public java.io.InputStream createStorageInputStream(java.lang.String a0) throws java.io.IOException { + return null; + } + + @Override + public boolean storageFileExists(java.lang.String a0) { + return false; + } + + @Override + public java.lang.String[] listStorageEntries() { + return null; + } + + @Override + public java.lang.String[] listFilesystemRoots() { + return new java.lang.String[] {"file:///"}; + } + + /// A fixed home under the one root, so a relative path an application + /// opens resolves instead of failing on a root list that is not there. + /// Nothing exists in it until a test writes it: see [#FILES]. + @Override + public java.lang.String getAppHomePath() { + return "file:///home/"; + } + + @Override + public java.lang.String[] listFiles(java.lang.String a0) throws java.io.IOException { + checkPath(a0); + return null; + } + + @Override + public long getRootSizeBytes(java.lang.String a0) { + return 0; + } + + @Override + public long getRootAvailableSpace(java.lang.String a0) { + return 0; + } + + @Override + public void mkdir(java.lang.String a0) { + checkPath(a0); + } + + @Override + public void deleteFile(java.lang.String a0) { + checkPath(a0); + if (fileSystem && !UNDELETABLE.contains(a0)) { + FILES.remove(a0); + } + } + + /// When set, [#FILES] answers `exists` and `deleteFile` removes from it, + /// except a path in [#UNDELETABLE], which a refused unlink leaves in + /// place. A test that sets it must reset it and clear both. + public static boolean fileSystem; + public static final java.util.Set UNDELETABLE = new java.util.HashSet(); + + @Override + public boolean isHidden(java.lang.String a0) { + checkPath(a0); + return false; + } + + @Override + public void setHidden(java.lang.String a0, boolean a1) { + } + + @Override + public long getFileLength(java.lang.String a0) { + checkPath(a0); + return 0; + } + + @Override + public boolean isDirectory(java.lang.String a0) { + checkPath(a0); + return false; + } + + @Override + public boolean exists(java.lang.String a0) { + checkPath(a0); + return fileSystem && FILES.containsKey(a0); + } + + @Override + public void rename(java.lang.String a0, java.lang.String a1) { + checkPath(a0); + } + + @Override + public char getFileSystemSeparator() { + return '/'; + } + + @Override + public java.lang.String getPlatformName() { + return "headless"; + } + + @Override + public com.codename1.l10n.L10NManager getLocalizationManager() { + return L10N; + } +} diff --git a/maven/compat-testing/src/main/java/com/codename1/compat/testing/JdkResourceSupport.java b/maven/compat-testing/src/main/java/com/codename1/compat/testing/JdkResourceSupport.java new file mode 100644 index 00000000000..8bbd0ecba61 --- /dev/null +++ b/maven/compat-testing/src/main/java/com/codename1/compat/testing/JdkResourceSupport.java @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.compat.testing; + +import com.codename1.ui.Display; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; +import java.net.URLStreamHandlerFactory; + +/// Teaches the Java runtime the headless implementation runs on the `cn1res:` URL scheme, for +/// tests that run the desktop runtime as compiled rather than relocated. +/// A built application's `Class.getResource` answers the desktop runtime's +/// own `java.net.URL` shim, which reads the scheme itself; the unrelocated +/// runtime answers a JDK `URL`, which without this does not know it. +/// +/// A `cn1res:` URL's path is the resource's original absolute path. The +/// build ships a nested resource at the root of the application under a flat +/// name -- each `_` written `_u` and each `/` written `__` -- and the +/// handler reads that name, then the path as it was, through the display, +/// as the shim does. It knows nothing else of the desktop runtime. +/// +/// A JVM takes one stream handler factory, and every test in it shares this +/// one. A test that reads resources from somewhere else -- a `Resources` +/// class another loader defined, a fake bundle -- points the scheme there +/// with [#setReader(Reader)] and restores it with `setReader(null)`. +public final class JdkResourceSupport { + static final String SCHEME = "cn1res"; + + private static boolean installed; + + private static Reader reader; + + /// Reads the resource a `cn1res:` URL names, given the URL's path as it + /// is written (percent-encoded). + public interface Reader { + /// The resource's bytes, or null when there is none. + InputStream open(String encodedPath) throws IOException; + } + + /// Replaces how a `cn1res:` URL is read; null restores the port's way. + /// Installs the scheme if it is not installed yet. + public static synchronized void setReader(Reader r) { + install(); + reader = r; + } + + private JdkResourceSupport() { + } + + /// Registers the scheme once. A JVM takes a single stream handler + /// factory; when another is in place already the scheme stays unknown, + /// and `Class.getResource` answers null for a flattened resource. + public static synchronized void install() { + if (installed) { + return; + } + installed = true; + try { + URL.setURLStreamHandlerFactory(new Factory()); + } catch (Error alreadySet) { + // Another factory owns URL; see above. + } + } + + static String flatName(String path) { + if (path.indexOf('/') < 0) { + return path; + } + StringBuilder out = new StringBuilder(path.length() + 16); + for (int i = 0; i < path.length(); i++) { + char c = path.charAt(i); + if (c == '/') { + out.append("__"); + } else if (c == '_') { + out.append("_u"); + } else { + out.append(c); + } + } + return out.toString(); + } + + static InputStream open(String path) throws IOException { + String p = path.startsWith("/") ? path.substring(1) : path; + if (p.length() == 0) { + return null; + } + InputStream in = read("/" + flatName(p)); + return in != null ? in : read("/" + p); + } + + private static InputStream read(String name) throws IOException { + if (Display.isInitialized()) { + return Display.getInstance().getResourceAsStream(JdkResourceSupport.class, name); + } + return JdkResourceSupport.class.getResourceAsStream(name); + } + + private static final class Factory implements URLStreamHandlerFactory { + @Override + public URLStreamHandler createURLStreamHandler(String protocol) { + return SCHEME.equals(protocol) ? new Handler() : null; + } + } + + private static final class Handler extends URLStreamHandler { + @Override + protected URLConnection openConnection(URL u) { + return new URLConnection(u) { + @Override + public void connect() { + connected = true; + } + + @Override + public InputStream getInputStream() throws IOException { + Reader custom = reader; + if (custom != null) { + InputStream in = custom.open(getURL().getPath()); + if (in == null) { + throw new FileNotFoundException(getURL().toExternalForm()); + } + return in; + } + String path; + try { + path = new URI(getURL().toExternalForm()).getPath(); + } catch (URISyntaxException e) { + path = null; + } + if (path == null) { + path = getURL().getPath(); + } + InputStream in = open(path); + if (in == null) { + throw new FileNotFoundException(getURL().toExternalForm()); + } + return in; + } + }; + } + } +} diff --git a/maven/android-compat/src/test/java/com/codename1/androidcompat/testing/MainThreadRule.java b/maven/compat-testing/src/main/java/com/codename1/compat/testing/MainThreadRule.java similarity index 93% rename from maven/android-compat/src/test/java/com/codename1/androidcompat/testing/MainThreadRule.java rename to maven/compat-testing/src/main/java/com/codename1/compat/testing/MainThreadRule.java index 7cba00f8c82..bdf39fdcda4 100644 --- a/maven/android-compat/src/test/java/com/codename1/androidcompat/testing/MainThreadRule.java +++ b/maven/compat-testing/src/main/java/com/codename1/compat/testing/MainThreadRule.java @@ -20,7 +20,7 @@ * Please contact Codename One through http://www.codenameone.com/ if you * need additional information or have any questions. */ -package com.codename1.androidcompat.testing; +package com.codename1.compat.testing; import com.codename1.ui.Display; @@ -28,7 +28,9 @@ import org.junit.runner.Description; import org.junit.runners.model.Statement; -/// Runs each test on the Android main thread, which is Codename One's EDT. +/// Runs each test on the UI thread every compatibility layer maps onto -- +/// Android's main thread, Swing's event dispatch thread, the JavaFX +/// application thread -- which is Codename One's EDT. /// /// Until a display exists every thread counts as the main one, so a test /// that never starts one passes on the JUnit thread. Tests that run diff --git a/maven/compat-testing/src/main/java/com/codename1/compat/testing/ReferenceJdk.java b/maven/compat-testing/src/main/java/com/codename1/compat/testing/ReferenceJdk.java new file mode 100644 index 00000000000..ac342a5334f --- /dev/null +++ b/maven/compat-testing/src/main/java/com/codename1/compat/testing/ReferenceJdk.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.compat.testing; + +import org.junit.Assume; + +/// The JDK the compatibility layers' parity tests compare with. +/// +/// A parity test runs the same input through a layer class and through the +/// class of the JDK the test itself runs on, and requires the same answer. +/// The layers were written to behave as Java 8 does, and the JDK has changed +/// some of those answers since: where it has, the comparison only means +/// something on Java 8, which is what the workflows that run these tests +/// use. A test that depends on such an answer calls [#assume(String)] +/// first, so that on a later JDK it is reported as skipped, with the reason, +/// rather than as a difference nobody introduced. +public final class ReferenceJdk { + + private ReferenceJdk() { + } + + /// Whether the tests are running on Java 8. + public static boolean isRunning() { + return "1.8".equals(System.getProperty("java.specification.version")); + } + + /// Skips the calling test unless it runs on Java 8. `changed` completes + /// the sentence "After Java 8 the JDK ...". + public static void assume(String changed) { + Assume.assumeTrue("After Java 8 the JDK " + changed + "; the layer keeps the Java 8 behaviour, and this " + + "comparison runs on Java 8 only (this is Java " + System.getProperty("java.specification.version") + + ")", isRunning()); + } +} diff --git a/maven/android-compat/src/test/java/com/codename1/impl/ImplementationFactory.java b/maven/compat-testing/src/main/java/com/codename1/impl/ImplementationFactory.java similarity index 96% rename from maven/android-compat/src/test/java/com/codename1/impl/ImplementationFactory.java rename to maven/compat-testing/src/main/java/com/codename1/impl/ImplementationFactory.java index fc98e18f555..09d44e4eb21 100644 --- a/maven/android-compat/src/test/java/com/codename1/impl/ImplementationFactory.java +++ b/maven/compat-testing/src/main/java/com/codename1/impl/ImplementationFactory.java @@ -22,7 +22,7 @@ */ package com.codename1.impl; -import com.codename1.androidcompat.testing.HeadlessImplementation; +import com.codename1.compat.testing.HeadlessImplementation; /// The port hook Codename One's Display uses to create its implementation. /// Every port ships its own; the runtime's tests run on no port, so this one diff --git a/maven/desktop-compat-runtime/pom.xml b/maven/desktop-compat-runtime/pom.xml new file mode 100644 index 00000000000..9668aabfde0 --- /dev/null +++ b/maven/desktop-compat-runtime/pom.xml @@ -0,0 +1,127 @@ + + + + + com.codenameone + codenameone + 8.0-SNAPSHOT + + 4.0.0 + + codenameone-desktop-compat-runtime + 8.0-SNAPSHOT + jar + codenameone-desktop-compat-runtime + + The runtime both desktop layers share: classpath resources and + resource bundles over the flat names the build ships, the uncaught + exception handler, Runtime.exec and the registry base the build + generates for each application. The remap step copies it into an + application that has desktop sources; nothing in the framework or a + port depends on it. + + + + UTF-8 + 1.8 + 1.8 + + + + + + com.github.spotbugs + spotbugs-maven-plugin + 4.7.3.4 + + Max + Low + false + true + ${project.build.directory} + ${project.basedir}/spotbugs-exclude.xml + + + + spotbugs + verify + + spotbugs + + + + + + maven-compiler-plugin + + 1.8 + 1.8 + + + + + maven-resources-plugin + + + copy-jdk-host + generate-sources + + copy-resources + + + ${project.build.directory}/generated-sources/jdk-host/com/codename1/impl + + + ${project.basedir}/../../Ports/JavaSE/src/com/codename1/impl + + JdkHost.java + + + + + + + + + maven-jar-plugin + + + com/codename1/impl/** + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-jdk-host + generate-sources + + add-source + + + + ${project.build.directory}/generated-sources/jdk-host + + + + + + + + + + + com.codenameone + codenameone-core + provided + + + diff --git a/maven/desktop-compat-runtime/spotbugs-exclude.xml b/maven/desktop-compat-runtime/spotbugs-exclude.xml new file mode 100644 index 00000000000..8bae30e5136 --- /dev/null +++ b/maven/desktop-compat-runtime/spotbugs-exclude.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/CompatRegistryBase.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/CompatRegistryBase.java new file mode 100644 index 00000000000..a71a6b25099 --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/CompatRegistryBase.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared; + +import java.util.HashMap; +import java.util.Map; + +/// What the build knows about one application's classes: the superclass of +/// the registry it generates for every application built from desktop +/// sources, `com.codename1.generated.compat.CompatRegistry`. +/// +/// A device creates no class from its name, so the desktop runtime's +/// `java.util.ResourceBundle` asks the installed registry instead. The +/// generated subclass records, while it installs: +/// +/// - every resource the application ships, in [Resources]'s index; +/// - every resource bundle class of the application, under a number its +/// [#cn1Create(int)] turns into a `new` and its [#cn1Type(int)] into a +/// class constant. +/// +/// It then makes itself the installed registry. The generated `cn1Install()` +/// does all of that once, however often it is called. +public abstract class CompatRegistryBase { + + private static CompatRegistryBase installed; + + private final Map classes = new HashMap(); + + /// For the generated subclass. + protected CompatRegistryBase() { + } + + /// Whether a generated registry is installed already. + public static boolean cn1Installed() { + return installed != null; + } + + /// Forgets the installed registry, so that the next `cn1Install()` + /// installs again. For tests, which define several applications in one + /// process. + public static void cn1ClearInstalled() { + installed = null; + } + + /// A new instance of the application class `className`, as + /// `Class.getName()` answers it, or null when the installed registry has + /// no such class or none is installed. + public static Object cn1NewInstance(String className) { + CompatRegistryBase r = installed; + return r == null ? null : r.newInstance(className); + } + + /// The application class `className`, or null; see [#cn1NewInstance(String)]. + public static Class cn1TypeOf(String className) { + CompatRegistryBase r = installed; + return r == null ? null : r.type(className); + } + + /// Records that the application has the class `className`, as + /// `Class.getName()` answers it, created and typed under `id`. + public final void cn1Class(String className, int id) { + classes.put(className, Integer.valueOf(id)); + } + + /// Makes this the installed registry. + public final void cn1Activate() { + installed = this; + } + + /// A new instance of the class recorded under `id`, or null. + protected abstract Object cn1Create(int id); + + /// The class recorded under `id`, or null. + protected abstract Class cn1Type(int id); + + private Object newInstance(String className) { + Integer id = classes.get(className); + return id == null ? null : cn1Create(id.intValue()); + } + + private Class type(String className) { + Integer id = classes.get(className); + return id == null ? null : cn1Type(id.intValue()); + } +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/ResourceNames.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/ResourceNames.java new file mode 100644 index 00000000000..11b6e04f580 --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/ResourceNames.java @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared; + +/// How a classpath resource is named once it ships: the one definition the +/// build and the runtime share. +/// +/// A desktop application keeps its resources in package directories +/// (`com/example/img/logo.png`) and asks for them by that path. A Codename +/// One application bundle is flat: every resource sits at its root. The build +/// therefore ships each nested resource under a flat name, and the runtime +/// turns the path an application asks for into that same name. Both call +/// [#flatName(String)], so they cannot disagree; the build engine reads it +/// from the core for that reason. +/// +/// #### The rule +/// +/// - A path with no directory -- `logo.png` -- keeps its name. +/// - In any other path every `_` becomes `_u` and every `/` becomes `__`: +/// `com/example/img/my_logo.png` ships as +/// `com__example__img__my_ulogo.png`. +/// +/// `_` is an escape character there, so two different nested paths never +/// share a flat name, and the original path can be read back from it. The +/// file extension is the end of the last segment and is never touched, which +/// matters because a Codename One build treats some extensions specially. A +/// nested path can still produce the name of a file that is at the root on +/// its own account; the build reports that as an error rather than letting +/// one replace the other. +/// +/// This class depends on nothing but `java.lang`, and must stay that way. +public final class ResourceNames { + + private ResourceNames() { + } + + /// The resource path, absolute and without its leading slash, that + /// `Class.getResource(name)` means for a class called `className`: a name + /// starting with `/` is absolute, any other is relative to the class's + /// package. `.` and `..` segments are resolved. Answers null for a path + /// that climbs above the root or names nothing. + /// + /// #### Parameters + /// + /// - `className`: the class's name in either form, `com.example.Main` or + /// `com/example/Main`; null for the absolute lookup a class loader does + /// + /// - `name`: the name as the application passed it + public static String resolve(String className, String name) { + if (name == null) { + return null; + } + if (name.startsWith("/")) { + return normalize(name); + } + if (className == null) { + return normalize(name); + } + String cls = className.replace('.', '/'); + int slash = cls.lastIndexOf('/'); + return normalize(slash < 0 ? name : cls.substring(0, slash + 1) + name); + } + + /// `path` with leading slashes dropped, empty and `.` segments removed + /// and `..` segments resolved; null when it climbs above the root or is + /// empty once resolved. + public static String normalize(String path) { + if (path == null) { + return null; + } + int n = path.length(); + StringBuilder out = new StringBuilder(n); + int i = 0; + while (i < n) { + int end = path.indexOf('/', i); + if (end < 0) { + end = n; + } + int len = end - i; + if (len == 0 || (len == 1 && path.charAt(i) == '.')) { + // An empty segment or the directory itself: nothing to add. + i = end + 1; + continue; + } + if (len == 2 && path.charAt(i) == '.' && path.charAt(i + 1) == '.') { + if (out.length() == 0) { + return null; + } + int cut = out.length() - 1; + while (cut >= 0 && out.charAt(cut) != '/') { + cut--; + } + out.setLength(cut < 0 ? 0 : cut); + i = end + 1; + continue; + } + if (out.length() > 0) { + out.append('/'); + } + out.append(path.substring(i, end)); + i = end + 1; + } + return out.length() == 0 ? null : out.toString(); + } + + /// The name `path` ships under at the root of the application bundle. + /// `path` is a normalized resource path ([#normalize(String)]); see the + /// class description for the rule. + public static String flatName(String path) { + if (path.indexOf('/') < 0) { + return path; + } + int n = path.length(); + StringBuilder out = new StringBuilder(n + 16); + for (int i = 0; i < n; i++) { + char c = path.charAt(i); + if (c == '/') { + out.append("__"); + } else if (c == '_') { + out.append("_u"); + } else { + out.append(c); + } + } + return out.toString(); + } + + /// The nested path a flat name was made from, the inverse of + /// [#flatName(String)]; null when `flat` is not the flat name of any + /// nested path, which includes every name of a file that was at the root + /// all along. + public static String nestedPath(String flat) { + int n = flat.length(); + StringBuilder out = new StringBuilder(n); + boolean nested = false; + for (int i = 0; i < n; i++) { + char c = flat.charAt(i); + if (c != '_') { + out.append(c); + continue; + } + if (i + 1 >= n) { + return null; + } + char next = flat.charAt(++i); + if (next == '_') { + out.append('/'); + nested = true; + } else if (next == 'u') { + out.append('_'); + } else { + return null; + } + } + return nested ? out.toString() : null; + } +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/Resources.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/Resources.java new file mode 100644 index 00000000000..7e2d793278a --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/Resources.java @@ -0,0 +1,350 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared; + +import com.codename1.ui.Display; + +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.HashSet; +import java.util.Set; + +/// Classpath resources for an application built from desktop sources: what +/// `Class.getResource` and `Class.getResourceAsStream` do on a desktop, and +/// the properties files `ResourceBundle.getBundle` reads. +/// +/// Part of the desktop compatibility runtime: the build copies it into the +/// application with the layer that needs it. Nothing in the framework or a +/// port names it. +/// +/// Nothing calls these methods by name. The build's remap step rewrites an +/// application's calls so that they arrive here: `getClass().getResource(n)` +/// becomes `Resources.getResource(getClass(), n)`, and so on for each method +/// below that takes the original receiver as its first parameter. +/// +/// #### How a resource is found +/// +/// A device has no classpath and no directories: every resource of the +/// application sits at the root of its bundle. The build ships each nested +/// resource under the flat name [ResourceNames#flatName(String)] gives its +/// path, and records the paths in an index (the registry generated with the +/// application, `com.codename1.generated.compat.CompatRegistry`, which calls +/// [#cn1BeginIndex()] and [#cn1AddResource(String)]). A lookup resolves +/// the name the way the JDK does -- relative to the class's package unless it +/// starts with `/`, with `.` and `..` resolved -- answers null when the index +/// has no such path, and otherwise reads the flat name. Nothing is opened to +/// find out whether a nested resource exists. +/// +/// A name with no directory is not flattened, so the build's index may not +/// know it (a file from the ordinary Codename One resource directory is one +/// the desktop sources never listed). Such a name is looked for in the +/// bundle. +/// +/// Where no build step ran -- this module's own tests, a layer's tests -- no +/// index is installed, and a resource is read under the path that was asked +/// for, then under its flat name. +/// +/// #### URLs +/// +/// `getResource` answers a [URL] in the `cn1res:` scheme whose path is the +/// resource's ORIGINAL absolute path: `cn1res:/com/example/img/logo.png`. +/// `openStream()` reads it through this class, and `toString()`, +/// `toExternalForm()`, `getPath()` and `getFile()` are the same on every +/// platform and every run, so code that passes `url.toExternalForm()` to an +/// image or stylesheet loader keeps working: the loader opens the string as a +/// URL again, or hands its path to [#open(String)]. +/// +/// The device's own `java.net.URL` knows the scheme. A port that runs on a +/// real JDK registers a handler for it that reads the flat name, so the same +/// URL opens there too; where none is registered, [#getResource(Class, String)] +/// answers null. +/// +/// #### Class names +/// +/// A relative name is resolved against `Class.getName()`. A build that +/// renames classes after the remap step (an obfuscator) changes that name, +/// so relative lookups need the application's classes kept under their +/// names; absolute names are unaffected. +public final class Resources { + + /// The paths the build shipped, or null when no build step recorded any. + private static Set index; + + private static Provider provider; + + private Resources() { + } + + /// Where the bytes of a flat resource name come from. The default reads + /// the application bundle; a test installs its own with + /// [#cn1SetProvider(Provider)]. + public interface Provider { + /// The resource shipped as `flatName` (no leading slash), or null + /// when the bundle has none. + InputStream open(String flatName) throws IOException; + } + + /// Replaces the source of resource bytes; null restores the default. + public static void cn1SetProvider(Provider p) { + provider = p; + } + + /// Opens the resource a `cn1res:` URL names by `encodedPath`, its + /// percent-encoded path: what such a URL's stream reads. + public static InputStream openUrlPath(String encodedPath) { + String path = decode(encodedPath); + return path == null ? null : open(path); + } + + /// Starts an index: from here on a nested path exists only if + /// [#cn1AddResource(String)] named it. The generated registry calls + /// this once, then adds every path. + public static void cn1BeginIndex() { + index = new HashSet(); + } + + /// Records that the build shipped the resource at `path`, an absolute + /// path without its leading slash. Only after [#cn1BeginIndex()]: a path + /// added to no index would be one forgotten in silence. + public static void cn1AddResource(String path) { + Set current = index; + if (current == null) { + throw new IllegalStateException("cn1BeginIndex() has not been called"); + } + current.add(path); + } + + /// Every path the build recorded, in no particular order; empty when + /// no build step recorded any. A loader that has to find a resource by + /// what is IN it -- a font by the name it gives itself -- reads these. + public static String[] cn1ResourcePaths() { + Set current = index; + if (current == null) { + return new String[0]; + } + String[] out = new String[current.size()]; + int i = 0; + for (String path : current) { + out[i++] = path; + } + return out; + } + + /// Forgets the index, as if no build step had run. For tests. + public static void cn1ClearIndex() { + index = null; + } + + /// `Class.getResourceAsStream(name)` for `cls`. + public static InputStream getResourceAsStream(Class cls, String name) { + return open(ResourceNames.resolve(cls.getName(), requireName(name))); + } + + /// `Class.getResource(name)` for `cls`. + public static URL getResource(Class cls, String name) { + return url(ResourceNames.resolve(cls.getName(), requireName(name))); + } + + + /// Whether the application ships a resource at `path`, an absolute path + /// with or without its leading slash. + public static boolean exists(String path) { + String p = ResourceNames.normalize(path); + if (p == null) { + return false; + } + if (index != null && (index.contains(p) || p.indexOf('/') >= 0)) { + return index.contains(p); + } + // Closed just below; PMD does not follow a close inside a catch. + InputStream in = read(p); //NOPMD CloseResource + if (in == null) { + return false; + } + try { + in.close(); + } catch (IOException e) { + // It was there to be opened, which is all that was asked. + } + return true; + } + + /// Opens the resource at `path`, an absolute path with or without its + /// leading slash, as the application named it on a desktop + /// (`/com/example/img/logo.png`). Answers null when there is none. This + /// is what a `cn1res:` URL reads, and what a loader that was handed such + /// a URL's path should call. + public static InputStream open(String path) { + String p = ResourceNames.normalize(path); + if (p == null) { + return null; + } + if (index != null && p.indexOf('/') >= 0 && !index.contains(p)) { + return null; + } + return read(p); + } + + private static InputStream read(String p) { + boolean nested = p.indexOf('/') >= 0; + if (index == null && nested && provider == null) { + // Nothing was flattened by a build: the resource may still be + // where the application's own build put it. + InputStream direct = Resources.class.getResourceAsStream("/" + p); + if (direct != null) { + return direct; + } + } + String flat = ResourceNames.flatName(p); + if (provider != null) { + try { + return provider.open(flat); + } catch (IOException e) { + // A resource that cannot be read is one that is not there. + return null; + } + } + if (Display.isInitialized()) { + return Display.getInstance().getResourceAsStream(Resources.class, "/" + flat); + } + return Resources.class.getResourceAsStream("/" + flat); + } + + private static URL url(String p) { + if (p == null || !exists(p)) { + return null; + } + String encoded = "/" + encode(p); + try { + return new URL("cn1res", null, encoded); + } catch (MalformedURLException e) { + // The device's URL knows cn1res, and a port that runs on a real + // JDK registers a handler for it; a URL nobody can make is no + // resource. + return null; + } + } + + /// The inverse of [#encode(String)]: UTF-8 percent escapes decoded, + /// anything else as it stands. Null for a malformed escape. + static String decode(String encoded) { + if (encoded == null || encoded.indexOf('%') < 0) { + return encoded; + } + int n = encoded.length(); + byte[] bytes = new byte[n]; + StringBuilder out = new StringBuilder(n); + int i = 0; + while (i < n) { + char c = encoded.charAt(i); + if (c != '%') { + out.append(c); + i++; + continue; + } + int count = 0; + while (i < n && encoded.charAt(i) == '%') { + if (i + 2 >= n) { + return null; + } + int high = Character.digit(encoded.charAt(i + 1), 16); + int low = Character.digit(encoded.charAt(i + 2), 16); + if (high < 0 || low < 0) { + return null; + } + bytes[count++] = (byte) ((high << 4) | low); + i += 3; + } + try { + out.append(new String(bytes, 0, count, "UTF-8")); + } catch (java.io.UnsupportedEncodingException e) { + return null; + } + } + return out.toString(); + } + + private static String requireName(String name) { + if (name == null) { + throw new NullPointerException(); + } + return name; + } + + + private static final String HEX = "0123456789ABCDEF"; + + /// Percent-encodes what would otherwise end a URL's path or be read as + /// an escape: everything but letters, digits and `/ - . _ ~ $ + ! ' ( ) + /// , @ = & ;`, as UTF-8. + static String encode(String path) { + int n = path.length(); + StringBuilder out = null; + for (int i = 0; i < n; i++) { + char c = path.charAt(i); + boolean plain = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') + || "/-._~$+!'(),@=&;".indexOf(c) >= 0; + if (plain) { + if (out != null) { + out.append(c); + } + continue; + } + if (out == null) { + out = new StringBuilder(n + 16); + out.append(path.substring(0, i)); + } + int code = c; + if (c >= 0xd800 && c <= 0xdbff && i + 1 < n) { + char low = path.charAt(i + 1); + if (low >= 0xdc00 && low <= 0xdfff) { + code = 0x10000 + ((c - 0xd800) << 10) + (low - 0xdc00); + i++; + } + } + if (code < 0x80) { + hex(out, code); + } else if (code < 0x800) { + hex(out, 0xc0 | (code >> 6)); + hex(out, 0x80 | (code & 0x3f)); + } else if (code < 0x10000) { + hex(out, 0xe0 | (code >> 12)); + hex(out, 0x80 | ((code >> 6) & 0x3f)); + hex(out, 0x80 | (code & 0x3f)); + } else { + hex(out, 0xf0 | (code >> 18)); + hex(out, 0x80 | ((code >> 12) & 0x3f)); + hex(out, 0x80 | ((code >> 6) & 0x3f)); + hex(out, 0x80 | (code & 0x3f)); + } + } + return out == null ? path : out.toString(); + } + + private static void hex(StringBuilder out, int b) { + out.append('%').append(HEX.charAt((b >> 4) & 0xf)).append(HEX.charAt(b & 0xf)); + } + +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/RuntimeExec.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/RuntimeExec.java new file mode 100644 index 00000000000..87d8d83b086 --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/RuntimeExec.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared; + +import com.codename1.io.Process; +import com.codename1.io.ProcessBuilder; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/// `Runtime.exec`, for desktop code relocated onto [ProcessBuilder]. +/// +/// The device runtime's `java.lang.Runtime` has no `exec`, so the build +/// rewrites `runtime.exec(...)` into a call here that takes the receiver +/// first, the way it rewrites every instance member the device lacks. Each +/// method does what the JavaSE overload of the same parameters does: a +/// command string is split at whitespace with no quoting (a shell is never +/// implied), and an environment array, when not null, replaces the inherited +/// environment entirely, and a null working directory inherits the parent's. +/// @hidden +public final class RuntimeExec { + private RuntimeExec() { + } + + /// `Runtime.exec(String)`. + public static Process exec(Runtime runtime, String command) throws IOException { + return exec(runtime, command, null); + } + + /// `Runtime.exec(String, String[])`. + public static Process exec(Runtime runtime, String command, String[] envp) throws IOException { + return exec(runtime, command, envp, null); + } + + /// `Runtime.exec(String, String[], File)`. + public static Process exec(Runtime runtime, String command, String[] envp, File dir) throws IOException { + if (command == null) { + throw new NullPointerException("command"); + } + if (command.length() == 0) { + throw new IllegalArgumentException("Empty command"); + } + return exec(runtime, split(command), envp, dir); + } + + /// `Runtime.exec(String[])`. + public static Process exec(Runtime runtime, String[] cmdarray) throws IOException { + return exec(runtime, cmdarray, null); + } + + /// `Runtime.exec(String[], String[])`. + public static Process exec(Runtime runtime, String[] cmdarray, String[] envp) throws IOException { + return exec(runtime, cmdarray, envp, null); + } + + /// `Runtime.exec(String[], String[], File)`. + public static Process exec(Runtime runtime, String[] cmdarray, String[] envp, File dir) throws IOException { + if (runtime == null) { + // What calling exec on a null Runtime throws. + throw new NullPointerException("runtime"); + } + if (cmdarray == null) { + throw new NullPointerException("cmdarray"); + } + if (cmdarray.length == 0) { + // JavaSE reads cmdarray[0] unguarded. + throw new IndexOutOfBoundsException("Empty command"); + } + ProcessBuilder builder = new ProcessBuilder(cmdarray); + if (envp != null) { + Map env = builder.environment(); + env.clear(); + for (String raw : envp) { + // As JavaSE: anything after a NUL is dropped and an entry with + // no '=' after its first character is skipped, not rejected. + int nul = raw.indexOf('\0'); + String entry = nul >= 0 ? raw.substring(0, nul) : raw; + int split = entry.indexOf('=', 1); + if (split > 0) { + env.put(entry.substring(0, split), entry.substring(split + 1)); + } + } + } + if (dir != null) { + builder.directory(dir.getPath()); + } + return builder.start(); + } + + /// The words of `command`, as `java.util.StringTokenizer` splits it with + /// its default delimiters: space, tab, newline, carriage return, form feed. + static String[] split(String command) { + List words = new ArrayList(); + int start = -1; + int n = command.length(); + for (int i = 0; i < n; i++) { + if (isDelimiter(command.charAt(i))) { + if (start >= 0) { + words.add(command.substring(start, i)); + start = -1; + } + } else if (start < 0) { + start = i; + } + } + if (start >= 0) { + words.add(command.substring(start)); + } + return words.toArray(new String[words.size()]); + } + + private static boolean isDelimiter(char c) { + return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f'; + } +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/UncaughtExceptions.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/UncaughtExceptions.java new file mode 100644 index 00000000000..88679e7ba5e --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/UncaughtExceptions.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared; + +import com.codename1.ui.Display; +import com.codename1.ui.events.ActionEvent; +import com.codename1.ui.events.ActionListener; + +/// Makes the default uncaught exception handler an application sets see what +/// its event dispatch thread tasks throw, as a desktop's does. +/// +/// An exception escaping a task on the Codename One EDT never reaches a +/// thread's uncaught handler: the platform's EDT error handling catches it +/// first. So the first time a handler is installed after the display is +/// up, an EDT error handler is registered that hands the throwable to the +/// default handler as it stands when the error happens, and consumes it. +/// +/// The build sends an application's `Thread.setDefaultUncaughtExceptionHandler` +/// calls to [#setDefaultUncaughtExceptionHandler], so this happens on every +/// platform, including the ones whose `Thread` is the JDK's. Only the +/// application's own call registers the forwarder: a handler a platform +/// installed for itself (Android has one that ends the process) does not. +/// +/// Internal: the build rewrites the application's calls to arrive here; an +/// application does not name it. +public final class UncaughtExceptions { + + private static boolean watched; + + private UncaughtExceptions() { + } + + /// `Thread.setDefaultUncaughtExceptionHandler(handler)`, and then + /// [#watch()] when `handler` is not null. + public static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler handler) { + Thread.setDefaultUncaughtExceptionHandler(handler); + if (handler != null) { + watch(); + } + } + + /// Registers the forwarder, once, when the display is initialized. A + /// call before then does nothing, and a later one registers it. + public static void watch() { + if (watched || !Display.isInitialized()) { + return; + } + watched = true; + Display.getInstance().addEdtErrorHandler(new EdtErrorForwarder()); + } + + /// Forwards an event dispatch thread error to the default uncaught + /// exception handler, as it stands when the error happens. + private static final class EdtErrorForwarder implements ActionListener { + @Override + public void actionPerformed(ActionEvent evt) { + Thread.UncaughtExceptionHandler handler = Thread.getDefaultUncaughtExceptionHandler(); + Object source = evt.getSource(); + if (handler != null && source instanceof Throwable) { + evt.consume(); + handler.uncaughtException(Thread.currentThread(), (Throwable) source); + } + } + } +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/io/File.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/io/File.java new file mode 100644 index 00000000000..8fefe4b9b82 --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/io/File.java @@ -0,0 +1,767 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.io; + +import java.io.*; +import java.net.URI; +import java.net.URISyntaxException; +import com.codename1.desktopcompat.shared.jdk.java.net.URL; +import com.codename1.desktopcompat.shared.jdk.java.nio.file.Path; +import com.codename1.desktopcompat.shared.jdk.java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +/// A path in the file system, and the operations on the file or directory at +/// it. +/// +/// Every operation goes to the platform's file system through +/// `FileSystemStorage`, on every port. +/// +/// #### Paths +/// +/// A path is kept with duplicate separators collapsed and no trailing one, +/// and a backslash is read as a separator. It is absolute when it starts with +/// `/`, with a drive (`C:/`), or with the `file:` scheme Codename One names its +/// files with. A relative path is resolved against the application's home +/// directory, which in a program run from a shell is the working directory. +/// +/// #### What differs from a desktop file system +/// +/// - Where the file system has no permissions, every file that exists may be +/// read and written, a directory may be entered and nothing may be run; the +/// permission setters answer whether the file already is as asked. +/// - [#setLastModified(long)] answers false: no platform lets it be set. +/// - [#deleteOnExit()] does nothing. +/// - Where the file system cannot resolve symbolic links, +/// [#getCanonicalPath()] answers the absolute path with `.` and `..` +/// resolved. +public class File implements Serializable, Comparable { + + private static final long serialVersionUID = 1L; + + /// The separator of the names in a path. + public static final char separatorChar = '/'; + /// [#separatorChar] as a string. + public static final String separator = "/"; + /// The separator of the paths in a list of them. + public static final char pathSeparatorChar = ':'; + /// [#pathSeparatorChar] as a string. + public static final String pathSeparator = ":"; + + private static final String SCHEME = "file:"; + + private final String path; + + /// The file at `pathname`. + /// + /// #### Throws + /// + /// - `NullPointerException`: when `pathname` is null + public File(String pathname) { + if (pathname == null) { + throw new NullPointerException(); + } + this.path = normalize(pathname); + } + + /// The file `child` in the directory `parent`. A null `parent` makes it + /// `new File(child)`, an empty one the root. + /// + /// #### Throws + /// + /// - `NullPointerException`: when `child` is null + public File(String parent, String child) { + if (child == null) { + throw new NullPointerException(); + } + if (parent == null) { + this.path = normalize(child); + } else if (parent.length() == 0) { + this.path = join(separator, child); + } else { + this.path = join(normalize(parent), child); + } + } + + /// The file `child` in the directory `parent`. A null `parent` makes it + /// `new File(child)`, an empty one the root. + /// + /// #### Throws + /// + /// - `NullPointerException`: when `child` is null + public File(File parent, String child) { + if (child == null) { + throw new NullPointerException(); + } + if (parent == null) { + this.path = normalize(child); + } else if (parent.path.length() == 0) { + this.path = join(separator, child); + } else { + this.path = join(parent.path, child); + } + } + + /// The file a `file:` URI names. + /// + /// #### Throws + /// + /// - `IllegalArgumentException`: when `uri` is not an absolute, + /// hierarchical `file:` URI with a path and without an authority, a query + /// or a fragment + public File(URI uri) { + if (!uri.isAbsolute()) { + throw new IllegalArgumentException("URI is not absolute"); + } + if (uri.isOpaque()) { + throw new IllegalArgumentException("URI is not hierarchical"); + } + if (!"file".equalsIgnoreCase(uri.getScheme())) { + throw new IllegalArgumentException("URI scheme is not \"file\""); + } + if (present(uri.getRawAuthority())) { + throw new IllegalArgumentException("URI has an authority component"); + } + if (present(uri.getRawFragment())) { + throw new IllegalArgumentException("URI has a fragment component"); + } + if (present(uri.getRawQuery())) { + throw new IllegalArgumentException("URI has a query component"); + } + String p = uri.getPath(); + if (p == null || p.length() == 0) { + throw new IllegalArgumentException("URI path component is empty"); + } + this.path = normalize(p); + } + + /// Whether a URI has the component. An empty one counts as none: the + /// runtime's `URI` answers an empty authority for `file:///x`, where the + /// JDK's answers null. + private static boolean present(String component) { + return component != null && component.length() > 0; + } + + /// How many leading characters of `p` are its root: the scheme and its + /// slashes, `/`, a drive with its separator, or none. + private static int rootLength(String p) { + if (p.startsWith(SCHEME)) { + int i = SCHEME.length(); + while (i < p.length() && p.charAt(i) == '/') { + i++; + } + return i; + } + if (p.startsWith("/")) { + return 1; + } + if (p.length() >= 3 && p.charAt(1) == ':' && p.charAt(2) == '/' && isLetter(p.charAt(0))) { + return 3; + } + return 0; + } + + private static boolean isLetter(char c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); + } + + /// Reads a backslash as a separator, collapses duplicate separators + /// outside the scheme and drops a trailing one, as `com.codename1.desktopcompat.shared.jdk.java.io.File` does. + private static String normalize(String p) { + StringBuilder sb = new StringBuilder(p.length()); + int start = 0; + if (p.startsWith("file://")) { + sb.append("file://"); + start = 7; + } + char prev = 0; + for (int i = start; i < p.length(); i++) { + char c = p.charAt(i); + if (c == '\\') { + c = '/'; + } + if (c == '/' && prev == '/') { + continue; + } + sb.append(c); + prev = c; + } + int min = start + 1; + while (sb.length() > min && sb.charAt(sb.length() - 1) == '/') { + sb.setLength(sb.length() - 1); + } + return sb.toString(); + } + + private static String join(String parent, String child) { + String c = normalize(child); + if (parent.length() == 0) { + return c; + } + if (c.startsWith("/")) { + c = c.substring(1); + } + if (c.length() == 0) { + return parent; + } + return parent.endsWith("/") ? parent + c : parent + "/" + c; + } + + /// The path the host is asked about: this one when it is absolute, else + /// resolved against the application's home directory. + String hostPath() { + if (path.startsWith("file:/") || (rootLength(path) > 0 && !path.startsWith(SCHEME))) { + return path; + } + // A relative path, a "file:" with no slash after it included: a + // device's file system throws for anything but an absolute path. + String rel = path.startsWith(SCHEME) ? path.substring(SCHEME.length()) : path; + String home = Host.appHome(); + if (rel.length() == 0) { + return home; + } + return home.endsWith("/") ? home + rel : home + "/" + rel; + } + + /// The last name of the path, or the whole path when it has one name. + public String getName() { + int i = path.lastIndexOf('/'); + return i < 0 ? path : path.substring(i + 1); + } + + /// The path as it was given, normalized. + public String getPath() { + return path; + } + + /// The path resolved against the application's home directory when it is + /// relative. + public String getAbsolutePath() { + return hostPath(); + } + + /// The file at [#getAbsolutePath()]. + public File getAbsoluteFile() { + return new File(getAbsolutePath()); + } + + /// The absolute path with symbolic links, `.` and `..` resolved. Code + /// checks a canonical path against a directory to reject a name like + /// `root/../outside`, so where the file system cannot resolve links the + /// dots are still resolved; a `..` at the root stays at the root. + public String getCanonicalPath() throws IOException { + String abs = hostPath(); + String canonical = Host.fileCanonicalPath(abs); + return canonical != null ? canonical : resolveDots(abs); + } + + /// The file at [#getCanonicalPath()]. + public File getCanonicalFile() throws IOException { + return new File(getCanonicalPath()); + } + + private static String resolveDots(String p) { + int start = rootLength(p); + ArrayList segs = new ArrayList(); + int s = start; + for (int i = start; i <= p.length(); i++) { + if (i == p.length() || p.charAt(i) == '/') { + String seg = p.substring(s, i); + if (seg.equals("..")) { + if (!segs.isEmpty()) { + segs.remove(segs.size() - 1); + } + } else if (seg.length() > 0 && !seg.equals(".")) { + segs.add(seg); + } + s = i + 1; + } + } + StringBuilder sb = new StringBuilder(p.length()); + sb.append(p.substring(0, start)); + for (int i = 0; i < segs.size(); i++) { + if (i > 0) { + sb.append('/'); + } + sb.append(segs.get(i)); + } + return sb.toString(); + } + + /// The path without its last name, or null when it has no parent. + public String getParent() { + int i = path.lastIndexOf('/'); + if (i < 0) { + return null; + } + int root = rootLength(path); + if (i < root) { + return path.length() > root ? path.substring(0, root) : null; + } + if (i == root - 1) { + return path.length() > root ? path.substring(0, root) : null; + } + return path.substring(0, i); + } + + /// The file at [#getParent()], or null. + public File getParentFile() { + String p = getParent(); + return p == null ? null : new File(p); + } + + /// Whether the path is absolute. + public boolean isAbsolute() { + return rootLength(path) > 0; + } + + /// Whether something is at the path. + public boolean exists() { + return Host.fileTest(hostPath(), Host.FILE_EXISTS); + } + + /// Whether the path is a directory. + public boolean isDirectory() { + return Host.fileTest(hostPath(), Host.FILE_DIRECTORY); + } + + /// Whether the path is a file that is not a directory. + public boolean isFile() { + return Host.fileTest(hostPath(), Host.FILE_REGULAR); + } + + /// Whether the file is hidden; on most file systems, whether its name + /// starts with a dot. + public boolean isHidden() { + return Host.fileTest(hostPath(), Host.FILE_HIDDEN); + } + + /// Whether the file may be read. + public boolean canRead() { + return Host.fileTest(hostPath(), Host.FILE_READABLE); + } + + /// Whether the file may be written. + public boolean canWrite() { + return Host.fileTest(hostPath(), Host.FILE_WRITABLE); + } + + /// Whether the file may be run, or the directory entered. + public boolean canExecute() { + return Host.fileTest(hostPath(), Host.FILE_EXECUTABLE); + } + + /// Lets the file be read, or not; false when it is not left as asked. + public boolean setReadable(boolean readable, boolean ownerOnly) { + return Host.fileSet(hostPath(), Host.FILE_READABLE, readable); + } + + /// Lets the file be read, or not; false when it is not left as asked. + public boolean setReadable(boolean readable) { + return setReadable(readable, true); + } + + /// Lets the file be written, or not; false when it is not left as asked. + public boolean setWritable(boolean writable, boolean ownerOnly) { + return Host.fileSet(hostPath(), Host.FILE_WRITABLE, writable); + } + + /// Lets the file be written, or not; false when it is not left as asked. + public boolean setWritable(boolean writable) { + return setWritable(writable, true); + } + + /// Lets the file be run, or not; false when it is not left as asked. + public boolean setExecutable(boolean executable, boolean ownerOnly) { + return Host.fileSet(hostPath(), Host.FILE_EXECUTABLE, executable); + } + + /// Lets the file be run, or not; false when it is not left as asked. + public boolean setExecutable(boolean executable) { + return setExecutable(executable, true); + } + + /// Stops the file being written; false when it still can be. + public boolean setReadOnly() { + return Host.fileSet(hostPath(), Host.FILE_WRITABLE, false); + } + + /// Answers false: no platform lets a file's time be set. + /// + /// #### Throws + /// + /// - `IllegalArgumentException`: when `time` is negative + public boolean setLastModified(long time) { + if (time < 0) { + throw new IllegalArgumentException("Negative time"); + } + return false; + } + + /// The length of the file in bytes; 0 for a directory or when nothing is + /// there. + public long length() { + return Host.fileLength(hostPath()); + } + + /// When the file was last modified, in milliseconds since the epoch; 0 + /// when nothing is there. + public long lastModified() { + return Host.fileLastModified(hostPath()); + } + + /// The size of the volume holding the file, or 0 when unknown. + public long getTotalSpace() { + return Host.fileTotalSpace(hostPath()); + } + + /// The bytes free on the volume holding the file, or 0 when unknown. + public long getFreeSpace() { + return Host.fileFreeSpace(hostPath()); + } + + /// The bytes this program may still write on the volume holding the + /// file, or 0 when unknown. + public long getUsableSpace() { + return Host.fileUsableSpace(hostPath()); + } + + /// Creates the file, empty, when nothing is at the path. + /// + /// #### Returns + /// + /// true when it was created, false when something was there + /// + /// #### Throws + /// + /// - `IOException`: when the directory it would be in does not exist, or + /// the file could not be created + public boolean createNewFile() throws IOException { + File parent = getParentFile(); + if (parent != null && !parent.isDirectory()) { + throw new IOException("No such file or directory: " + getParent()); + } + return Host.fileCreate(hostPath()); + } + + /// Deletes the file, or the directory when it is empty. + /// + /// #### Returns + /// + /// whether it was deleted + public boolean delete() { + return Host.fileDelete(hostPath()); + } + + /// Does nothing: nothing is deleted when the program ends. + public void deleteOnExit() { + } + + /// Creates the directory, in a parent that must exist. + /// + /// #### Returns + /// + /// whether it was created + public boolean mkdir() { + return Host.fileMkdir(hostPath()); + } + + /// Creates the directory and the parents it needs. + /// + /// #### Returns + /// + /// whether it was created; false also when it already existed + public boolean mkdirs() { + if (exists()) { + return false; + } + if (mkdir()) { + return true; + } + File p = getParentFile(); + return p != null && (p.mkdirs() || p.exists()) && mkdir(); + } + + /// Moves the file or directory to `dest`. A file replaces a file already + /// there. Where the platform cannot move a file in place, it is copied and + /// the original deleted; when the original will not go, the copy is taken + /// back unless it replaced something. + /// + /// #### Returns + /// + /// whether it was moved + /// + /// #### Throws + /// + /// - `NullPointerException`: when `dest` is null + public boolean renameTo(File dest) { + if (dest == null) { + throw new NullPointerException(); + } + String from = hostPath(); + String to = dest.hostPath(); + if (!Host.fileTest(from, Host.FILE_EXISTS)) { + return false; + } + // A file renamed onto itself is already where it was asked to be. + // The copy below would open the same file for reading and writing, + // truncating it, and then delete it. + if (resolveDots(from).equals(resolveDots(to))) { + return true; + } + boolean replacing = Host.fileTest(to, Host.FILE_EXISTS); + if (replacing ? Host.fileReplace(from, to) : Host.fileRename(from, to)) { + return true; + } + if (Host.fileTest(from, Host.FILE_DIRECTORY) || (replacing && Host.fileTest(to, Host.FILE_DIRECTORY))) { + return false; + } + try { + InputStream in = Host.fileRead(from); + try { + OutputStream out = Host.fileWrite(to, false); + try { + byte[] buffer = new byte[8192]; + int n; + while ((n = in.read(buffer, 0, buffer.length)) >= 0) { + out.write(buffer, 0, n); + } + } finally { + out.close(); + } + } finally { + in.close(); + } + } catch (IOException e) { + return false; + } + // The move only happened if the source is gone. When it cannot be + // removed the rename fails, and a copy that created the destination + // is taken back so the file is left where it was; one that replaced + // an existing destination stays, as deleting it would lose both. + Host.fileDelete(from); + if (Host.fileTest(from, Host.FILE_EXISTS)) { + if (!replacing) { + Host.fileDelete(to); + } + return false; + } + return true; + } + + /// The names of the entries of the directory, or null when this is not a + /// directory. + public String[] list() { + String[] names; + try { + names = Host.fileList(hostPath()); + } catch (IOException e) { + return null; + } + if (names == null) { + return null; + } + String[] out = new String[names.length]; + for (int i = 0; i < names.length; i++) { + String n = names[i]; + while (n.endsWith("/")) { + n = n.substring(0, n.length() - 1); + } + int slash = n.lastIndexOf('/'); + out[i] = slash < 0 ? n : n.substring(slash + 1); + } + return out; + } + + /// The names of the entries of the directory that `filter` accepts, or + /// null when this is not a directory. A null `filter` accepts all. + public String[] list(FilenameFilter filter) { + String[] names = list(); + if (names == null || filter == null) { + return names; + } + List out = new ArrayList(); + for (int i = 0; i < names.length; i++) { + if (filter.accept(this, names[i])) { + out.add(names[i]); + } + } + return out.toArray(new String[out.size()]); + } + + /// The entries of the directory, or null when this is not a directory. + public File[] listFiles() { + return files(list()); + } + + /// The entries of the directory whose names `filter` accepts, or null when + /// this is not a directory. A null `filter` accepts all. + public File[] listFiles(FilenameFilter filter) { + return files(list(filter)); + } + + /// The entries of the directory that `filter` accepts, or null when this + /// is not a directory. A null `filter` accepts all. + public File[] listFiles(FileFilter filter) { + File[] all = listFiles(); + if (all == null || filter == null) { + return all; + } + List out = new ArrayList(); + for (int i = 0; i < all.length; i++) { + if (filter.accept(all[i])) { + out.add(all[i]); + } + } + return out.toArray(new File[out.size()]); + } + + private File[] files(String[] names) { + if (names == null) { + return null; + } + File[] out = new File[names.length]; + for (int i = 0; i < names.length; i++) { + out[i] = new File(this, names[i]); + } + return out; + } + + /// Creates an empty file named `prefix`, a number and `suffix` in the + /// directory for disposable files. + public static File createTempFile(String prefix, String suffix) throws IOException { + return createTempFile(prefix, suffix, null); + } + + /// Creates an empty file named `prefix`, a number and `suffix` (`.tmp` + /// when null) in `directory`, or in the directory for disposable files + /// when that is null. Nothing deletes it. + /// + /// #### Throws + /// + /// - `IllegalArgumentException`: when `prefix` is shorter than three + /// characters + /// - `IOException`: when the file could not be created + public static File createTempFile(String prefix, String suffix, File directory) throws IOException { + if (prefix.length() < 3) { + throw new IllegalArgumentException("Prefix string too short"); + } + File dir = directory; + if (dir == null) { + String caches = Host.cachesDir(); + dir = new File(caches != null ? caches : Host.appHome()); + } + String s = suffix == null ? ".tmp" : suffix; + long n = System.currentTimeMillis(); + while (true) { + File f = new File(dir, prefix + n + s); + if (f.createNewFile()) { + return f; + } + if (!f.exists()) { + throw new IOException("Unable to create temporary file " + f.getPath()); + } + n++; + } + } + + /// The roots of the file system. + public static File[] listRoots() { + String[] roots = Host.fileRoots(); + File[] out = new File[roots == null ? 0 : roots.length]; + for (int i = 0; i < out.length; i++) { + out[i] = new File(roots[i]); + } + return out; + } + + /// Compares the paths. + @Override + public int compareTo(File other) { + return path.compareTo(other.path); + } + + /// Whether `o` is a file with the same path. + @Override + public boolean equals(Object o) { + return o instanceof File && ((File) o).path.equals(path); + } + + @Override + public int hashCode() { + return path.hashCode() ^ 1234321; + } + + /// The path. + @Override + public String toString() { + return path; + } + + /// This file as a `com.codename1.desktopcompat.shared.jdk.java.nio.file.Path`. + public Path toPath() { + return Paths.get(path); + } + + /// The absolute path, without the scheme and with a single leading + /// separator, as a URI's path. + private String uriPath() { + String abs = getAbsolutePath(); + if (abs.startsWith(SCHEME)) { + int at = SCHEME.length(); + while (at + 1 < abs.length() && abs.charAt(at) == '/' && abs.charAt(at + 1) == '/') { + at++; + } + abs = abs.substring(at); + } + if (!abs.startsWith("/")) { + abs = "/" + abs; + } + return abs; + } + + /// The `file:` URI of the absolute path, ending in a separator when the + /// file is a directory. + public URI toURI() { + String p = uriPath(); + if (!p.endsWith("/") && isDirectory()) { + p = p + "/"; + } + try { + return new URI("file", null, p, null, null); + } catch (URISyntaxException e) { + throw new IllegalArgumentException(e.getMessage()); + } + } + + /// The `file:` URL of the absolute path. + /// + /// #### Deprecated + /// + /// The path is not escaped; use `toURI().toURL()` where it may hold + /// characters a URL does not allow. + @Deprecated + public URL toURL() throws java.net.MalformedURLException { + return new URL("file", "", uriPath()); + } +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/io/FileFilter.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/io/FileFilter.java new file mode 100644 index 00000000000..f79baa3effc --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/io/FileFilter.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.io; + +import java.io.*; + +/// Decides which files `File.listFiles(FileFilter)` answers. +public interface FileFilter { + + /// Whether `pathname` is wanted. + boolean accept(File pathname); +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/io/FileInputStream.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/io/FileInputStream.java new file mode 100644 index 00000000000..056fc9a3989 --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/io/FileInputStream.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.io; + +import java.io.*; + + +/// Reads the bytes of a file, from the platform's file system through +/// `FileSystemStorage`. +/// +/// Like most streams, it is for one thread at a time; closing it twice, or +/// from another thread, is safe. +public class FileInputStream extends InputStream { + + private final InputStream in; + private boolean closed; + + /// Opens the file at `name`. + /// + /// #### Throws + /// + /// - `FileNotFoundException`: when it is not there, is a directory or + /// cannot be read + public FileInputStream(String name) throws FileNotFoundException { + this(new File(name)); + } + + /// Opens `file`. + /// + /// #### Throws + /// + /// - `FileNotFoundException`: when it is not there, is a directory or + /// cannot be read + public FileInputStream(File file) throws FileNotFoundException { + if (file == null) { + throw new NullPointerException(); + } + try { + in = Host.fileRead(file.hostPath()); + } catch (FileNotFoundException e) { + throw e; + } catch (IOException e) { + throw new FileNotFoundException(file.getPath() + " (" + e.getMessage() + ")"); + } + if (in == null) { + throw new FileNotFoundException(file.getPath()); + } + } + + private void checkOpen() throws IOException { + if (closed) { + throw new IOException("Stream closed"); + } + } + + @Override + public int read() throws IOException { + checkOpen(); + return in.read(); + } + + @Override + public int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (off < 0 || len < 0 || len > b.length - off) { + throw new IndexOutOfBoundsException(); + } + checkOpen(); + if (len == 0) { + return 0; + } + return in.read(b, off, len); + } + + @Override + public long skip(long n) throws IOException { + checkOpen(); + return n <= 0 ? 0 : in.skip(n); + } + + @Override + public int available() throws IOException { + checkOpen(); + return in.available(); + } + + @Override + public void close() throws IOException { + if (closed) { + return; + } + closed = true; + in.close(); + } +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/io/FileOutputStream.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/io/FileOutputStream.java new file mode 100644 index 00000000000..489fe61307e --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/io/FileOutputStream.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.io; + +import java.io.*; + + +/// Writes the bytes of a file, to the platform's file system through +/// `FileSystemStorage`. The file is created when it is not there. +/// +/// Like most streams, it is for one thread at a time; closing it twice, or +/// from another thread, is safe. +public class FileOutputStream extends OutputStream { + + private final OutputStream out; + private boolean closed; + + /// Opens the file at `name`, emptying it. + /// + /// #### Throws + /// + /// - `FileNotFoundException`: when it is a directory or cannot be written + public FileOutputStream(String name) throws FileNotFoundException { + this(new File(name), false); + } + + /// Opens the file at `name`, at its end when `append` is true and emptied + /// otherwise. + /// + /// #### Throws + /// + /// - `FileNotFoundException`: when it is a directory or cannot be written + public FileOutputStream(String name, boolean append) throws FileNotFoundException { + this(new File(name), append); + } + + /// Opens `file`, emptying it. + /// + /// #### Throws + /// + /// - `FileNotFoundException`: when it is a directory or cannot be written + public FileOutputStream(File file) throws FileNotFoundException { + this(file, false); + } + + /// Opens `file`, at its end when `append` is true and emptied otherwise. + /// + /// #### Throws + /// + /// - `FileNotFoundException`: when it is a directory or cannot be written + public FileOutputStream(File file, boolean append) throws FileNotFoundException { + if (file == null) { + throw new NullPointerException(); + } + try { + out = Host.fileWrite(file.hostPath(), append); + } catch (FileNotFoundException e) { + throw e; + } catch (IOException e) { + throw new FileNotFoundException(file.getPath() + " (" + e.getMessage() + ")"); + } + if (out == null) { + throw new FileNotFoundException(file.getPath()); + } + } + + private void checkOpen() throws IOException { + if (closed) { + throw new IOException("Stream closed"); + } + } + + @Override + public void write(int b) throws IOException { + checkOpen(); + out.write(b); + } + + @Override + public void write(byte[] b) throws IOException { + write(b, 0, b.length); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + if (off < 0 || len < 0 || len > b.length - off) { + throw new IndexOutOfBoundsException(); + } + checkOpen(); + if (len == 0) { + return; + } + out.write(b, off, len); + } + + @Override + public void flush() throws IOException { + checkOpen(); + out.flush(); + } + + @Override + public void close() throws IOException { + if (closed) { + return; + } + closed = true; + out.close(); + } +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/io/FileReader.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/io/FileReader.java new file mode 100644 index 00000000000..474d131b064 --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/io/FileReader.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.io; + +import java.io.*; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +/// Reads the text of a file, in UTF-8 unless a charset is given. +public class FileReader extends InputStreamReader { + + /// Opens the file at `fileName`. + /// + /// #### Throws + /// + /// - `FileNotFoundException`: when it is not there, is a directory or + /// cannot be read + public FileReader(String fileName) throws FileNotFoundException { + super(new FileInputStream(fileName), StandardCharsets.UTF_8); + } + + /// Opens `file`. + /// + /// #### Throws + /// + /// - `FileNotFoundException`: when it is not there, is a directory or + /// cannot be read + public FileReader(File file) throws FileNotFoundException { + super(new FileInputStream(file), StandardCharsets.UTF_8); + } + + /// Opens the file at `fileName`, reading `charset`. + public FileReader(String fileName, Charset charset) throws IOException { + super(new FileInputStream(fileName), charset); + } + + /// Opens `file`, reading `charset`. + public FileReader(File file, Charset charset) throws IOException { + super(new FileInputStream(file), charset); + } +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/io/FileWriter.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/io/FileWriter.java new file mode 100644 index 00000000000..cd6b4904de0 --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/io/FileWriter.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.io; + +import java.io.*; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +/// Writes text to a file, in UTF-8 unless a charset is given. +public class FileWriter extends OutputStreamWriter { + + /// Opens the file at `fileName`, emptying it. + public FileWriter(String fileName) throws IOException { + super(new FileOutputStream(fileName), StandardCharsets.UTF_8); + } + + /// Opens the file at `fileName`, at its end when `append` is true. + public FileWriter(String fileName, boolean append) throws IOException { + super(new FileOutputStream(fileName, append), StandardCharsets.UTF_8); + } + + /// Opens `file`, emptying it. + public FileWriter(File file) throws IOException { + super(new FileOutputStream(file), StandardCharsets.UTF_8); + } + + /// Opens `file`, at its end when `append` is true. + public FileWriter(File file, boolean append) throws IOException { + super(new FileOutputStream(file, append), StandardCharsets.UTF_8); + } + + /// Opens the file at `fileName`, emptying it, writing `charset`. + public FileWriter(String fileName, Charset charset) throws IOException { + super(new FileOutputStream(fileName), charset); + } + + /// Opens the file at `fileName`, at its end when `append` is true, + /// writing `charset`. + public FileWriter(String fileName, Charset charset, boolean append) throws IOException { + super(new FileOutputStream(fileName, append), charset); + } + + /// Opens `file`, emptying it, writing `charset`. + public FileWriter(File file, Charset charset) throws IOException { + super(new FileOutputStream(file), charset); + } + + /// Opens `file`, at its end when `append` is true, writing `charset`. + public FileWriter(File file, Charset charset, boolean append) throws IOException { + super(new FileOutputStream(file, append), charset); + } +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/io/FilenameFilter.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/io/FilenameFilter.java new file mode 100644 index 00000000000..9eaa5caecdc --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/io/FilenameFilter.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.io; + +import java.io.*; + +/// Decides which names `File.list(FilenameFilter)` answers. +public interface FilenameFilter { + + /// Whether the entry `name` of the directory `dir` is wanted. + boolean accept(File dir, String name); +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/io/Host.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/io/Host.java new file mode 100644 index 00000000000..26f3cc276a3 --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/io/Host.java @@ -0,0 +1,290 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.io; + +import com.codename1.io.FileSystemStorage; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/// The file system of the `java.io` shims: `FileSystemStorage` paths, or +/// plain absolute paths, which are taken as `file://` paths. +final class Host { + /// [#fileTest]: something is at the path. + static final int FILE_EXISTS = 1; + /// [#fileTest]: the path is a directory. + static final int FILE_DIRECTORY = 2; + /// [#fileTest]: the path is a file that is not a directory. + static final int FILE_REGULAR = 3; + /// [#fileTest] and [#fileSet]: the path may be read. + static final int FILE_READABLE = 4; + /// [#fileTest] and [#fileSet]: the path may be written. + static final int FILE_WRITABLE = 5; + /// [#fileTest] and [#fileSet]: the path may be executed or entered. + static final int FILE_EXECUTABLE = 6; + /// [#fileTest]: the path is hidden. + static final int FILE_HIDDEN = 7; + + private Host() { + } + + private static FileSystemStorage fs() { + return FileSystemStorage.getInstance(); + } + + /// `path` as a `FileSystemStorage` path: `/x` and a drive path `C:/x` + /// gain the `file://` scheme; anything with a scheme is left alone. + static String storage(String path) { + if (path.startsWith("/")) { + return "file://" + path; + } + if (path.length() >= 3 && path.charAt(1) == ':' && path.charAt(2) == '/' + && ((path.charAt(0) >= 'A' && path.charAt(0) <= 'Z') + || (path.charAt(0) >= 'a' && path.charAt(0) <= 'z'))) { + return "file:///" + path; + } + return path; + } + + /// Whether `path` has the property `what`, one of the `FILE_` constants. + /// A file system without permissions lets everything that exists be read + /// and written, a directory be entered and nothing be run. + static boolean fileTest(String path, int what) { + String p = storage(path); + FileSystemStorage fs = fs(); + if (!fs.exists(p)) { + return false; + } + if (what == FILE_HIDDEN) { + return fs.isHidden(p); + } + if (what == FILE_DIRECTORY || what == FILE_EXECUTABLE) { + return fs.isDirectory(p); + } + if (what == FILE_REGULAR) { + return !fs.isDirectory(p); + } + return true; + } + + /// Nothing can be changed, so this answers whether the path already is + /// as asked, by the rules of [#fileTest]. + static boolean fileSet(String path, int what, boolean on) { + String p = storage(path); + FileSystemStorage fs = fs(); + if (!fs.exists(p)) { + return false; + } + if (what == FILE_EXECUTABLE) { + return on == fs.isDirectory(p); + } + return on; + } + + static long fileLength(String path) { + String p = storage(path); + FileSystemStorage fs = fs(); + return !fs.exists(p) || fs.isDirectory(p) ? 0L : fs.getLength(p); + } + + static long fileLastModified(String path) { + String p = storage(path); + FileSystemStorage fs = fs(); + return fs.exists(p) ? fs.getLastModified(p) : 0L; + } + + /// Check-then-create; not atomic. + static boolean fileCreate(String path) throws IOException { + if (fs().exists(storage(path))) { + return false; + } + fileWrite(path, false).close(); + return true; + } + + /// Always false; the caller copies. + static boolean fileReplace(String from, String to) { + return false; + } + + /// Null: storage cannot resolve links, the caller resolves the dots. + static String fileCanonicalPath(String path) { + return null; + } + + static String[] fileList(String path) throws IOException { + path = storage(path); + FileSystemStorage fs = fs(); + if (!fs.exists(path) || !fs.isDirectory(path)) { + return null; + } + return fs.listFiles(path); + } + + static InputStream fileRead(String path) throws IOException { + path = storage(path); + FileSystemStorage fs = fs(); + if (!fs.exists(path) || fs.isDirectory(path)) { + throw new FileNotFoundException(path); + } + return fs.openInputStream(path); + } + + static OutputStream fileWrite(String path, boolean append) throws IOException { + path = storage(path); + FileSystemStorage fs = fs(); + if (fs.exists(path) && fs.isDirectory(path)) { + throw new FileNotFoundException(path + " (Is a directory)"); + } + if (append && fs.exists(path)) { + long length = fs.getLength(path); + if (length > Integer.MAX_VALUE) { + // The storage API takes the offset as an int. Narrowed, it + // wraps, and the write would land inside the file. + throw new IOException(path + " (Too large to append to)"); + } + return fs.openOutputStream(path, (int) length); + } + return fs.openOutputStream(path); + } + + static boolean fileMkdir(String path) { + path = storage(path); + FileSystemStorage fs = fs(); + if (fs.exists(path)) { + return false; + } + // mkdir(2) creates one level; a storage that creates the missing + // parents too would make File.mkdir answer true where the JDK does not. + String parent = parentOf(path); + if (parent != null && !(fs.exists(parent) && fs.isDirectory(parent))) { + return false; + } + fs.mkdir(path); + return fs.exists(path) && fs.isDirectory(path); + } + + static boolean fileDelete(String path) { + path = storage(path); + FileSystemStorage fs = fs(); + if (!fs.exists(path)) { + return false; + } + if (fs.isDirectory(path)) { + // The JDK deletes only an empty directory; some storages delete + // the whole tree. + String[] entries; + try { + entries = fs.listFiles(path); + } catch (IOException e) { + return false; + } + if (entries != null && entries.length > 0) { + return false; + } + } + fs.delete(path); + return !fs.exists(path); + } + + /// Renames within one directory, which is all `FileSystemStorage` can do; + /// a move to another directory answers false and the caller copies. + static boolean fileRename(String from, String to) { + from = storage(from); + to = storage(to); + FileSystemStorage fs = fs(); + int fromSlash = lastSeparator(from); + int toSlash = lastSeparator(to); + if (fromSlash < 0 || toSlash < 0 || fs.exists(to) || !fs.exists(from) + || !from.substring(0, fromSlash).equals(to.substring(0, toSlash))) { + return false; + } + fs.rename(from, to.substring(toSlash + 1)); + return fs.exists(to) && !fs.exists(from); + } + + /// The parent of the storage path `path`, or null at a root. + private static String parentOf(String path) { + String p = path; + while (p.length() > 1 && p.endsWith("/")) { + p = p.substring(0, p.length() - 1); + } + int slash = lastSeparator(p); + if (slash <= 0) { + return null; + } + String parent = p.substring(0, slash); + // "file://" itself is no directory: the parent of /x is the root. + return parent.endsWith(":/") || parent.endsWith(":") ? parent + "/" : parent; + } + + private static int lastSeparator(String path) { + int slash = path.lastIndexOf('/'); + char sep = fs().getFileSystemSeparator(); + return sep == '/' ? slash : Math.max(slash, path.lastIndexOf(sep)); + } + + static String appHome() { + return fs().getAppHomePath(); + } + + static String cachesDir() { + FileSystemStorage fs = fs(); + return fs.hasCachesDir() ? fs.getCachesDir() : null; + } + + static String[] fileRoots() { + return fs().getRoots(); + } + + static long fileTotalSpace(String path) { + String root = rootOf(storage(path)); + return root == null ? 0L : fs().getRootSizeBytes(root); + } + + static long fileUsableSpace(String path) { + String root = rootOf(storage(path)); + return root == null ? 0L : fs().getRootAvailableSpace(root); + } + + static long fileFreeSpace(String path) { + return fileUsableSpace(path); + } + + /// The longest root `path` lies under, else the first root, else null. + private static String rootOf(String path) { + String[] roots = fs().getRoots(); + if (roots == null || roots.length == 0) { + return null; + } + String best = null; + for (String r : roots) { + if (r != null && path.startsWith(r) && (best == null || r.length() > best.length())) { + best = r; + } + } + return best != null ? best : roots[0]; + } +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/io/package-info.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/io/package-info.java new file mode 100644 index 00000000000..647d26d22b2 --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/io/package-info.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ + +/// `java.io` file classes for relocated desktop applications: `File`, its +/// filters and the file streams, over `FileSystemStorage`. +package com.codename1.desktopcompat.shared.jdk.java.io; diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/net/URL.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/net/URL.java new file mode 100644 index 00000000000..85460da4783 --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/net/URL.java @@ -0,0 +1,762 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.net; + +import java.io.ByteArrayInputStream; +import java.net.*; + + +import java.io.ByteArrayOutputStream; +import com.codename1.desktopcompat.shared.jdk.java.io.File; +import com.codename1.desktopcompat.shared.jdk.java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; + +/// A parsed URL, and a way to read what it points at. +/// +/// Parsing follows the JDK class, including the resolution of a relative +/// reference against a context URL. The protocols accepted are `http`, +/// `https`, `file`, `ftp`, `jar`, `mailto` and `cn1res`; any other throws +/// `MalformedURLException`, as an unknown protocol does on the JDK. +/// +/// #### Reading +/// +/// [#openStream()] reads: +/// +/// - `file:` through `com.codename1.desktopcompat.shared.jdk.java.io.FileInputStream`. +/// - `http:` and `https:` through the platform's network, which +/// `NetworkManager` reaches (a Codename One `ConnectionRequest` +/// in an application), run to completion before the stream is returned: +/// the stream is over the whole downloaded body, so it suits configuration +/// files and images, not endless or very large responses. +/// - `cn1res:` from the application's packaged resources, through +/// `Resources`. +/// +/// #### The `cn1res` scheme +/// +/// `cn1res:/images/logo.png` names the resource `/images/logo.png` of the +/// application, the one `Class.getResourceAsStream("/images/logo.png")` +/// opens. The scheme exists because the device has no `Class.getResource`: +/// a later step of the build rewrites calls to `Class.getResource(String)` so +/// that they return a URL of this scheme, and code that goes on to call +/// `openStream()`, `toString()` or `toExternalForm()` on the result keeps +/// working. A `cn1res` URL has no host; its path is the absolute resource +/// name. +/// +/// #### Differences from the JDK +/// +/// - No `openConnection`, `getContent` or stream handler factories: the +/// device has no `URLConnection`. +/// - `equals` and `hashCode` compare host names as text, ignoring case, and +/// never resolve them. +public final class URL implements java.io.Serializable { + + private static final long serialVersionUID = 1L; + + private final String protocol; + private final String host; + private final int port; + private final String authority; + private final String userInfo; + private final String path; + private final String query; + private final String ref; + + public URL(String spec) throws MalformedURLException { + this(null, spec); + } + + public URL(String protocol, String host, String file) throws MalformedURLException { + this(protocol, host, -1, file); + } + + public URL(String protocol, String host, int port, String file) throws MalformedURLException { + String scheme = asciiLower(protocol); + if (!isKnownProtocol(scheme)) { + throw new MalformedURLException("unknown protocol: " + protocol); + } + this.protocol = scheme; + if (host != null) { + String h = host; + if (h.indexOf(':') >= 0 && !h.startsWith("[")) { + h = "[" + h + "]"; + } + if (port < -1) { + throw new MalformedURLException("Invalid port number :" + port); + } + this.host = h; + this.port = port; + this.authority = port == -1 ? h : h + ":" + port; + } else { + this.host = null; + this.port = -1; + this.authority = null; + } + this.userInfo = null; + String rest = file; + int hash = rest.indexOf('#'); + this.ref = hash < 0 ? null : rest.substring(hash + 1); + if (hash >= 0) { + rest = rest.substring(0, hash); + } + int question = rest.lastIndexOf('?'); + if (question >= 0) { + this.query = rest.substring(question + 1); + this.path = rest.substring(0, question); + } else { + this.query = null; + this.path = rest; + } + } + + /// Parses `spec`, resolving it against `context` when it is a relative + /// reference; `context` may be `null`. + public URL(URL context, String spec) throws MalformedURLException { + if (spec == null) { + throw new MalformedURLException("no protocol: null"); + } + int limit = spec.length(); + while (limit > 0 && spec.charAt(limit - 1) <= ' ') { + limit--; + } + int start = 0; + while (start < limit && spec.charAt(start) <= ' ') { + start++; + } + if (spec.regionMatches(true, start, "url:", 0, 4)) { + start += 4; + } + boolean isRef = start < spec.length() && spec.charAt(start) == '#'; + String newProtocol = null; + for (int i = start; !isRef && i < limit; i++) { + char c = spec.charAt(i); + if (c == '/') { + break; + } + if (c == ':') { + String s = asciiLower(spec.substring(start, i)); + if (isValidProtocol(s)) { + newProtocol = s; + start = i + 1; + } + break; + } + } + + String p = newProtocol; + String h = null; + int prt = -1; + String auth = null; + String user = null; + String pth = null; + String qry = null; + String rf = null; + boolean isRelative = false; + if (context != null && (newProtocol == null || newProtocol.equals(context.protocol))) { + // A scheme repeated in front of a relative reference to a + // hierarchical URL is ignored. + if (context.path != null && context.path.startsWith("/")) { + newProtocol = null; + } + if (newProtocol == null) { + p = context.protocol; + auth = context.authority; + user = context.userInfo; + h = context.host; + prt = context.port; + pth = context.path; + isRelative = true; + } + } + if (p == null) { + throw new MalformedURLException("no protocol: " + spec); + } + if (!isKnownProtocol(p)) { + throw new MalformedURLException("unknown protocol: " + p); + } + int hash = spec.indexOf('#', start); + if (hash >= 0) { + rf = spec.substring(hash + 1, limit); + limit = hash; + } + if (isRelative && start == limit) { + // An empty reference is the context itself. + qry = context.query; + if (rf == null) { + rf = context.ref; + } + } + + if ("jar".equals(p) || "mailto".equals(p)) { + // Not hierarchical: everything after the scheme is the file. + String file; + if ("mailto".equals(p)) { + file = spec.substring(start, limit); + if (file.trim().length() == 0) { + throw new MalformedURLException("No email address"); + } + } else if (hash == start && isRelative) { + file = context.getFile(); + } else { + file = jarFile(isRelative ? context.getFile() : null, spec.substring(start, limit), + newProtocol != null); + } + if ("mailto".equals(p)) { + // As in the JDK, an address has no fragment. + rf = null; + } + int question = file.lastIndexOf('?'); + this.protocol = p; + this.host = ""; + this.port = -1; + this.authority = null; + this.userInfo = null; + this.path = question < 0 ? file : file.substring(0, question); + this.query = question < 0 ? null : file.substring(question + 1); + this.ref = rf; + return; + } + + // From here on: the authority, then the path and the query. + String s = spec; + boolean isRelPath = false; + boolean queryOnly = false; + if (start < limit) { + int queryStart = s.indexOf('?'); + queryOnly = queryStart == start; + if (queryStart != -1 && queryStart < limit) { + qry = s.substring(queryStart + 1, limit); + limit = queryStart; + s = s.substring(0, queryStart); + } + } + boolean fourSlashes = start <= limit - 4 && s.charAt(start) == '/' && s.charAt(start + 1) == '/' + && s.charAt(start + 2) == '/' && s.charAt(start + 3) == '/'; + if (!fourSlashes && start <= limit - 2 && s.charAt(start) == '/' && s.charAt(start + 1) == '/') { + start += 2; + int end = s.indexOf('/', start); + if (end < 0 || end > limit) { + end = s.indexOf('?', start); + if (end < 0 || end > limit) { + end = limit; + } + } + auth = s.substring(start, end); + h = auth; + int at = auth.indexOf('@'); + if (at != -1) { + if (at != auth.lastIndexOf('@')) { + user = null; + h = null; + } else { + user = auth.substring(0, at); + h = auth.substring(at + 1); + } + } else { + user = null; + } + prt = -1; + if (h != null) { + String portText = null; + if (h.length() > 0 && h.charAt(0) == '[') { + int close = h.indexOf(']'); + if (close <= 2) { + throw new MalformedURLException("Invalid authority field: " + auth); + } + if (h.length() > close + 1) { + if (h.charAt(close + 1) != ':') { + throw new MalformedURLException("Invalid authority field: " + auth); + } + portText = h.substring(close + 2); + } + h = h.substring(0, close + 1); + } else { + int colon = h.indexOf(':'); + if (colon >= 0) { + portText = h.substring(colon + 1); + h = h.substring(0, colon); + } + } + if (portText != null && portText.length() > 0) { + prt = parsePort(portText); + } + } else { + h = ""; + } + start = end; + if (auth.length() > 0) { + // With an authority, the path comes from the reference alone. + pth = ""; + } + } + if (h == null) { + h = ""; + } + if (start < limit) { + if (s.charAt(start) == '/') { + pth = s.substring(start, limit); + } else if (pth != null && pth.length() > 0) { + isRelPath = true; + int slash = pth.lastIndexOf('/'); + String separator = slash == -1 && auth != null ? "/" : ""; + pth = pth.substring(0, slash + 1) + separator + s.substring(start, limit); + } else { + String separator = auth != null ? "/" : ""; + pth = separator + s.substring(start, limit); + } + } else if (queryOnly && pth != null) { + int slash = pth.lastIndexOf('/'); + if (slash < 0) { + slash = 0; + } + pth = pth.substring(0, slash) + "/"; + } + if (pth == null) { + pth = ""; + } + if (isRelPath) { + pth = removeDots(pth); + } + this.protocol = p; + this.host = h; + this.port = prt; + this.authority = auth; + this.userInfo = user; + this.path = pth; + this.query = qry; + this.ref = rf; + } + + /// The index of the `/` of the last `!/` in a `jar:` file, or -1. + private static int indexOfBangSlash(String file) { + int bang = file.lastIndexOf('!'); + while (bang >= 0) { + if (bang != file.length() - 1 && file.charAt(bang + 1) == '/') { + return bang + 1; + } + bang = bang == 0 ? -1 : file.lastIndexOf('!', bang - 1); + } + return -1; + } + + /// The file of a `jar:` URL: an archive's own URL, `!/`, and the entry. + /// A relative reference is resolved inside the archive, never out of it. + private static String jarFile(String contextFile, String part, boolean absolute) + throws MalformedURLException { + if (absolute) { + int bangSlash = indexOfBangSlash(part); + if (bangSlash < 0) { + throw new MalformedURLException("no !/ in spec"); + } + try { + new URL(part.substring(0, bangSlash - 1)); + } catch (MalformedURLException e) { + throw new MalformedURLException("invalid url: " + part + " (" + e.getMessage() + ")"); + } + return part; + } + if (contextFile == null) { + throw new MalformedURLException("no !/ in spec"); + } + String file = contextFile; + if (part.startsWith("/")) { + int bangSlash = indexOfBangSlash(file); + if (bangSlash < 0) { + throw new MalformedURLException("malformed context url: no !/"); + } + file = file.substring(0, bangSlash); + } else if (!file.endsWith("/")) { + int lastSlash = file.lastIndexOf('/'); + if (lastSlash < 0) { + throw new MalformedURLException("malformed context url"); + } + file = file.substring(0, lastSlash + 1); + } + file = file + part; + int bangSlash = indexOfBangSlash(file); + if (bangSlash < 0) { + throw new MalformedURLException("no !/ in spec"); + } + return file.substring(0, bangSlash) + canonizeEntry(file.substring(bangSlash)); + } + + /// Resolves `.` and `..` in the entry part of a `jar:` file. + private static String canonizeEntry(String entry) { + String file = entry; + int i = file.indexOf("/../"); + while (i >= 0) { + int lim = i > 0 ? file.lastIndexOf('/', i - 1) : -1; + file = lim >= 0 ? file.substring(0, lim) + file.substring(i + 3) : file.substring(i + 3); + i = file.indexOf("/../"); + } + i = file.indexOf("/./"); + while (i >= 0) { + file = file.substring(0, i) + file.substring(i + 2); + i = file.indexOf("/./"); + } + while (file.endsWith("/..")) { + i = file.indexOf("/.."); + int lim = i > 0 ? file.lastIndexOf('/', i - 1) : -1; + file = lim >= 0 ? file.substring(0, lim + 1) : file.substring(0, i); + } + if (file.endsWith("/.")) { + file = file.substring(0, file.length() - 1); + } + return file; + } + + /// A port as the JDK reads one: a decimal `int` with an optional sign, + /// of which -1, "no port", is the only negative accepted. + private static int parsePort(String text) throws MalformedURLException { + int from = 0; + boolean negative = false; + if (text.charAt(0) == '-' || text.charAt(0) == '+') { + negative = text.charAt(0) == '-'; + from = 1; + } + long value = 0; + if (text.length() == from || text.length() - from > 10) { + throw new MalformedURLException("For input string: \"" + text + "\""); + } + for (int i = from; i < text.length(); i++) { + char c = text.charAt(i); + if (c < '0' || c > '9') { + throw new MalformedURLException("For input string: \"" + text + "\""); + } + value = value * 10 + (c - '0'); + } + if (negative) { + value = -value; + } + if (value > Integer.MAX_VALUE || value < Integer.MIN_VALUE) { + throw new MalformedURLException("For input string: \"" + text + "\""); + } + if (value < -1) { + throw new MalformedURLException("Invalid port number :" + value); + } + return (int) value; + } + + /// Resolves the `.` and `..` segments a relative reference brought into + /// a path. + private static String removeDots(String in) { + String path = in; + int i = path.indexOf("/./"); + while (i >= 0) { + path = path.substring(0, i) + path.substring(i + 2); + i = path.indexOf("/./"); + } + i = path.indexOf("/../"); + while (i >= 0) { + // "/a/b/../c" becomes "/a/c"; "/../../a" has nothing to cancel + // and stays. + int previous = i > 0 ? path.lastIndexOf('/', i - 1) : -1; + if (previous >= 0 && path.indexOf("/../", previous) != 0) { + path = path.substring(0, previous) + path.substring(i + 3); + i = path.indexOf("/../"); + } else { + i = path.indexOf("/../", i + 3); + } + } + while (path.endsWith("/..")) { + i = path.indexOf("/.."); + int previous = i > 0 ? path.lastIndexOf('/', i - 1) : -1; + if (previous < 0) { + break; + } + path = path.substring(0, previous + 1); + } + if (path.startsWith("./") && path.length() > 2) { + path = path.substring(2); + } + if (path.endsWith("/.")) { + path = path.substring(0, path.length() - 1); + } + return path; + } + + /// Lower case for the ASCII letters only. A scheme is ASCII by + /// specification, and `String.toLowerCase()` follows the device's locale. + private static String asciiLower(String s) { + StringBuilder sb = null; + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (c >= 'A' && c <= 'Z') { + if (sb == null) { + sb = new StringBuilder(s); + } + sb.setCharAt(i, (char) (c + ('a' - 'A'))); + } + } + return sb == null ? s : sb.toString(); + } + + private static boolean isAsciiLetter(char c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); + } + + private static boolean isValidProtocol(String protocol) { + int len = protocol.length(); + if (len < 1 || !isAsciiLetter(protocol.charAt(0))) { + return false; + } + for (int i = 1; i < len; i++) { + char c = protocol.charAt(i); + boolean digit = c >= '0' && c <= '9'; + if (!isAsciiLetter(c) && !digit && c != '.' && c != '+' && c != '-') { + return false; + } + } + return true; + } + + private static boolean isKnownProtocol(String protocol) { + return "http".equals(protocol) || "https".equals(protocol) || "file".equals(protocol) + || "cn1res".equals(protocol) || "ftp".equals(protocol) || "jar".equals(protocol) + || "mailto".equals(protocol); + } + + public String getProtocol() { + return protocol; + } + + public String getHost() { + return host; + } + + public int getPort() { + return port; + } + + public int getDefaultPort() { + if ("http".equals(protocol)) { + return 80; + } + if ("https".equals(protocol)) { + return 443; + } + if ("ftp".equals(protocol)) { + return 21; + } + return -1; + } + + public String getAuthority() { + return authority; + } + + public String getUserInfo() { + return userInfo; + } + + public String getPath() { + return path; + } + + public String getQuery() { + return query; + } + + public String getRef() { + return ref; + } + + public String getFile() { + return query == null ? path : path + "?" + query; + } + + public String toExternalForm() { + StringBuilder sb = new StringBuilder(); + sb.append(protocol).append(':'); + if (authority != null && authority.length() > 0) { + sb.append("//").append(authority); + } + if (path != null) { + sb.append(path); + } + if (query != null) { + sb.append('?').append(query); + } + if (ref != null) { + sb.append('#').append(ref); + } + return sb.toString(); + } + + @Override + public String toString() { + return toExternalForm(); + } + + public URI toURI() throws URISyntaxException { + return new URI(toString()); + } + + private static boolean same(String a, String b) { + return a == null ? b == null : a.equals(b); + } + + private int effectivePort() { + return port != -1 ? port : getDefaultPort(); + } + + /// Whether the two URLs name the same resource, the fragment aside. + public boolean sameFile(URL other) { + if (other == null) { + return false; + } + boolean hostsEqual = host == null || other.host == null + ? host == null && other.host == null + : host.equalsIgnoreCase(other.host); + return protocol.equals(other.protocol) && same(getFile(), other.getFile()) + && effectivePort() == other.effectivePort() && hostsEqual; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof URL)) { + return false; + } + URL other = (URL) obj; + return same(ref, other.ref) && sameFile(other); + } + + @Override + public int hashCode() { + int h = protocol.hashCode(); + if (host != null) { + h += asciiLower(host).hashCode(); + } + String file = getFile(); + if (file != null) { + h += file.hashCode(); + } + h += effectivePort(); + if (ref != null) { + h += ref.hashCode(); + } + return h; + } + + private static int hex(char c) { + if (c >= '0' && c <= '9') { + return c - '0'; + } + if (c >= 'a' && c <= 'f') { + return c - 'a' + 10; + } + if (c >= 'A' && c <= 'F') { + return c - 'A' + 10; + } + return -1; + } + + /// Replaces each `%XX` by the byte it stands for and reads the result + /// as UTF-8. A `%` not followed by two hex digits is kept. + private static String percentDecode(String s) throws IOException { + if (s.indexOf('%') < 0) { + return s; + } + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + int i = 0; + while (i < s.length()) { + if (s.charAt(i) == '%' && i + 2 < s.length() && hex(s.charAt(i + 1)) >= 0 + && hex(s.charAt(i + 2)) >= 0) { + bytes.write((hex(s.charAt(i + 1)) << 4) | hex(s.charAt(i + 2))); + i += 3; + } else { + // Up to the next escape in one piece, so that a surrogate + // pair is encoded as the one character it is. + int next = s.indexOf('%', i + 1); + if (next < 0) { + next = s.length(); + } + bytes.write(s.substring(i, next).getBytes("UTF-8")); + i = next; + } + } + return new String(bytes.toByteArray(), "UTF-8"); + } + + /// Opens the resource for reading; see the class description for what + /// each protocol reads. + /// + /// #### Throws + /// + /// - `FileNotFoundException`: if the file or resource does not exist, or + /// the server answers 404 or 410 + /// + /// - `IOException`: if the request fails, the server answers another + /// error status, or the protocol cannot be read on the device + public InputStream openStream() throws IOException { + if ("file".equals(protocol)) { + return new FileInputStream(new File(percentDecode(path))); + } + if ("cn1res".equals(protocol)) { + // Through Resources, which knows the flat name the build shipped + // the resource under; the path here is the one it had on a desktop. + InputStream in = com.codename1.desktopcompat.shared.Resources.openUrlPath(path); + if (in == null) { + throw new FileNotFoundException(toExternalForm()); + } + return in; + } + if ("http".equals(protocol) || "https".equals(protocol)) { + return openHttp(); + } + throw new IOException("Cannot read the " + protocol + " protocol: " + toExternalForm()); + } + + private InputStream openHttp() throws IOException { + StringBuilder address = new StringBuilder(); + address.append(protocol).append("://").append(authority == null ? "" : authority).append(path); + if (query != null) { + address.append('?').append(query); + } + return fetch(address.toString()); + } + + /// Reads `url` whole through `NetworkManager`. + private static InputStream fetch(String url) throws IOException { + com.codename1.io.ConnectionRequest request = new com.codename1.io.ConnectionRequest(); + request.setUrl(url); + request.setPost(false); + // Failures reach the caller as exceptions, not the user as the + // default error dialog. + request.setFailSilently(true); + com.codename1.io.NetworkManager.getInstance().addToQueueAndWait(request); + int code = request.getResponseCode(); + if (code == 404 || code == 410) { + throw new FileNotFoundException(url); + } + if (code >= 400) { + throw new IOException("Server returned HTTP response code: " + code + " for URL: " + url); + } + byte[] data = request.getResponseData(); + if (data == null) { + throw new IOException("No response from " + url); + } + return new ByteArrayInputStream(data); + } +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/net/package-info.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/net/package-info.java new file mode 100644 index 00000000000..cd66e5e3fc9 --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/net/package-info.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ + +/// `java.net.URL` for relocated desktop applications: `file:` through the +/// `File` shim, `cn1res:` through the packaged resources and `http(s):` +/// through `NetworkManager`. +package com.codename1.desktopcompat.shared.jdk.java.net; diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/AccessDeniedException.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/AccessDeniedException.java new file mode 100644 index 00000000000..f8520df0b6a --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/AccessDeniedException.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.nio.file; + +/// The file system refused the operation. +public class AccessDeniedException extends FileSystemException { + private static final long serialVersionUID = 1L; + + public AccessDeniedException(String file) { + super(file); + } + + public AccessDeniedException(String file, String other, String reason) { + super(file, other, reason); + } +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/CopyOption.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/CopyOption.java new file mode 100644 index 00000000000..90aada371cd --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/CopyOption.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.nio.file; + +/// How a file is +/// copied or moved. [StandardCopyOption] has the values. +public interface CopyOption { +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/DirectoryIteratorException.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/DirectoryIteratorException.java new file mode 100644 index 00000000000..439ebdfe0c7 --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/DirectoryIteratorException.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.nio.file; + +import java.io.IOException; + +/// An `IOException` met while iterating a directory, which an iterator +/// cannot throw as it is. +public final class DirectoryIteratorException extends RuntimeException { + private static final long serialVersionUID = 1L; + private final IOException cause; + + public DirectoryIteratorException(IOException cause) { + super(text(cause)); + this.cause = cause; + } + + private static String text(IOException cause) { + if (cause == null) { + throw new NullPointerException(); + } + return cause.toString(); + } + + @Override + public IOException getCause() { + return cause; + } +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/DirectoryNotEmptyException.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/DirectoryNotEmptyException.java new file mode 100644 index 00000000000..3bb6ff2d738 --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/DirectoryNotEmptyException.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.nio.file; + +/// A directory that has entries cannot be deleted or replaced. +public class DirectoryNotEmptyException extends FileSystemException { + private static final long serialVersionUID = 1L; + + public DirectoryNotEmptyException(String file) { + super(file); + } +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/DirectoryStream.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/DirectoryStream.java new file mode 100644 index 00000000000..54b04e6e18e --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/DirectoryStream.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.nio.file; + +import java.io.IOException; + +/// The entries +/// of a directory, read when the stream was opened. It can be iterated once. +public interface DirectoryStream extends AutoCloseable, Iterable { + + /// Decides which entries a directory stream answers. + interface Filter { + boolean accept(T entry) throws IOException; + } + + @Override + void close() throws IOException; +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/FileAlreadyExistsException.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/FileAlreadyExistsException.java new file mode 100644 index 00000000000..68b624fb5fd --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/FileAlreadyExistsException.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.nio.file; + +/// Something is already at the path. +public class FileAlreadyExistsException extends FileSystemException { + private static final long serialVersionUID = 1L; + + public FileAlreadyExistsException(String file) { + super(file); + } + + public FileAlreadyExistsException(String file, String other, String reason) { + super(file, other, reason); + } +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/FileSystemException.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/FileSystemException.java new file mode 100644 index 00000000000..0dd4b7434fd --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/FileSystemException.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.nio.file; + +import java.io.IOException; + +/// An operation on a file failed; names the file, the other file of a copy +/// or move, and the reason. +public class FileSystemException extends IOException { + private static final long serialVersionUID = 1L; + private final String file; + private final String other; + private final String reason; + + public FileSystemException(String file) { + this(file, null, null); + } + + public FileSystemException(String file, String other, String reason) { + super((String) null); + this.file = file; + this.other = other; + this.reason = reason; + } + + public String getFile() { + return file; + } + + public String getOtherFile() { + return other; + } + + public String getReason() { + return reason; + } + + @Override + public String getMessage() { + if (file == null && other == null) { + return reason; + } + StringBuilder sb = new StringBuilder(); + if (file != null) { + sb.append(file); + } + if (other != null) { + sb.append(" -> ").append(other); + } + if (reason != null) { + sb.append(": ").append(reason); + } + return sb.toString(); + } +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/FileVisitOption.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/FileVisitOption.java new file mode 100644 index 00000000000..4df81cb98b8 --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/FileVisitOption.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.nio.file; + +/// Accepted +/// and without effect: symbolic links are never followed. +public enum FileVisitOption { + FOLLOW_LINKS +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/FileVisitResult.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/FileVisitResult.java new file mode 100644 index 00000000000..d9a411d28d3 --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/FileVisitResult.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.nio.file; + +/// What a +/// [FileVisitor] tells `Files.walkFileTree` to do next. +public enum FileVisitResult { + CONTINUE, + TERMINATE, + SKIP_SUBTREE, + SKIP_SIBLINGS +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/FileVisitor.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/FileVisitor.java new file mode 100644 index 00000000000..6b6c7b246b2 --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/FileVisitor.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.nio.file; + +import java.io.IOException; +import com.codename1.desktopcompat.shared.jdk.java.nio.file.attribute.BasicFileAttributes; + +/// What +/// `Files.walkFileTree` calls for each directory and file it reaches. +public interface FileVisitor { + + FileVisitResult preVisitDirectory(T dir, BasicFileAttributes attrs) throws IOException; + + FileVisitResult visitFile(T file, BasicFileAttributes attrs) throws IOException; + + FileVisitResult visitFileFailed(T file, IOException exc) throws IOException; + + FileVisitResult postVisitDirectory(T dir, IOException exc) throws IOException; +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/Files.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/Files.java new file mode 100644 index 00000000000..08f43e2c9e7 --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/Files.java @@ -0,0 +1,1104 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.nio.file; + +import com.codename1.io.FileSystemStorage; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.ByteArrayOutputStream; +import com.codename1.desktopcompat.shared.jdk.java.io.File; +import com.codename1.desktopcompat.shared.jdk.java.io.FileInputStream; +import com.codename1.desktopcompat.shared.jdk.java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.UncheckedIOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import com.codename1.desktopcompat.shared.jdk.java.nio.file.attribute.BasicFileAttributes; +import com.codename1.desktopcompat.shared.jdk.java.nio.file.attribute.FileAttribute; +import com.codename1.desktopcompat.shared.jdk.java.nio.file.attribute.FileTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.BiPredicate; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +/// Operations on files, directories and their contents, over `com.codename1.desktopcompat.shared.jdk.java.io.File` +/// and the file streams, so over the platform's file system as +/// `FileSystemStorage` provides it. +/// +/// #### What differs from a desktop file system +/// +/// - A directory's entries come back sorted by name. The JDK promises no +/// order, and a device's file systems disagree with each other. +/// - `newDirectoryStream` reads the entries when it is called, not as the +/// result is consumed. +/// - Nothing is a symbolic link, and a `LinkOption` changes nothing. +/// - The device keeps one time per file. `getLastModifiedTime` answers it, +/// and so do the creation and access times of [BasicFileAttributes]. +/// - A file is executable when it is a directory, which is all "executable" +/// can mean where no program can be started. +/// - A temporary file goes to the caches directory where the device has +/// one, and to the application's home otherwise. Nothing deletes it. +/// - Text is UTF-8 unless a `Charset` says otherwise; undecodable bytes are +/// replaced rather than reported. +/// +/// #### What is not provided +/// +/// Channels (`newByteChannel`), POSIX permissions, owners, file stores, hard +/// and symbolic links, `probeContentType`, `setLastModifiedTime` and the +/// attribute views by name. A device has no equivalent, and a call to any of +/// them is a build error. +/// +/// `lines`, `list`, `walk` and `find` are lazy, as in the JDK: `lines` reads +/// the file as the stream asks for lines, and `walk` lists a directory only +/// when it is about to enter it, so `walk(root).limit(1)` lists nothing. A +/// failure found that late surfaces as an `UncheckedIOException`. Close the +/// stream `lines` answers (try-with-resources) to close the file. +public final class Files { + + private static final String LINE = "\n"; + + private Files() { + } + + private static File file(Path path) { + if (path == null) { + throw new NullPointerException(); + } + return path.toFile(); + } + + private static boolean has(Object[] options, Object option) { + if (options != null) { + for (Object o : options) { + if (o == null) { + throw new NullPointerException(); + } + if (o == option) { + return true; + } + } + } + return false; + } + + // ---- what is there ---- + + public static boolean exists(Path path, LinkOption... options) { + return file(path).exists(); + } + + public static boolean notExists(Path path, LinkOption... options) { + return !file(path).exists(); + } + + public static boolean isDirectory(Path path, LinkOption... options) { + return file(path).isDirectory(); + } + + public static boolean isRegularFile(Path path, LinkOption... options) { + return file(path).isFile(); + } + + public static boolean isSymbolicLink(Path path) { + file(path); + return false; + } + + public static boolean isReadable(Path path) { + return file(path).exists(); + } + + public static boolean isWritable(Path path) { + return file(path).exists(); + } + + public static boolean isExecutable(Path path) { + return file(path).isDirectory(); + } + + public static boolean isHidden(Path path) throws IOException { + return file(path).isHidden(); + } + + public static boolean isSameFile(Path path, Path path2) throws IOException { + if (path.equals(path2)) { + return true; + } + return path.toRealPath().equals(path2.toRealPath()); + } + + private static File existing(Path path) throws IOException { + File f = file(path); + if (!f.exists()) { + throw new NoSuchFileException(path.toString()); + } + return f; + } + + public static long size(Path path) throws IOException { + return existing(path).length(); + } + + public static FileTime getLastModifiedTime(Path path, LinkOption... options) throws IOException { + return FileTime.fromMillis(existing(path).lastModified()); + } + + /// The attributes of `path`. `type` has to be `BasicFileAttributes.class`, + /// the one set a device has. + @SuppressWarnings("unchecked") + public static A readAttributes(Path path, Class type, LinkOption... options) + throws IOException { + if (type != BasicFileAttributes.class) { + throw new UnsupportedOperationException("Only BasicFileAttributes can be read on a device"); + } + return (A) attributes(existing(path)); + } + + private static BasicFileAttributes attributes(File f) { + final boolean directory = f.isDirectory(); + final long size = f.length(); + final FileTime time = FileTime.fromMillis(f.lastModified()); + return new BasicFileAttributes() { + @Override + public FileTime lastModifiedTime() { + return time; + } + + @Override + public FileTime lastAccessTime() { + return time; + } + + @Override + public FileTime creationTime() { + return time; + } + + @Override + public boolean isRegularFile() { + return !directory; + } + + @Override + public boolean isDirectory() { + return directory; + } + + @Override + public boolean isSymbolicLink() { + return false; + } + + @Override + public boolean isOther() { + return false; + } + + @Override + public long size() { + return size; + } + + @Override + public Object fileKey() { + return null; + } + }; + } + + // ---- streams ---- + + public static InputStream newInputStream(Path path, OpenOption... options) throws IOException { + if (has(options, StandardOpenOption.WRITE) || has(options, StandardOpenOption.APPEND)) { + throw new UnsupportedOperationException("An input stream cannot be opened for writing"); + } + File f = existing(path); + if (f.isDirectory()) { + throw new IOException(path + ": Is a directory"); + } + return new FileInputStream(f); + } + + public static OutputStream newOutputStream(Path path, OpenOption... options) throws IOException { + if (has(options, StandardOpenOption.READ)) { + throw new IllegalArgumentException("READ not allowed"); + } + boolean none = options == null || options.length == 0; + boolean append = has(options, StandardOpenOption.APPEND); + if (append && has(options, StandardOpenOption.TRUNCATE_EXISTING)) { + throw new IllegalArgumentException("APPEND + TRUNCATE_EXISTING not allowed"); + } + File f = file(path); + boolean exists = f.exists(); + if (has(options, StandardOpenOption.CREATE_NEW)) { + if (exists) { + throw new FileAlreadyExistsException(path.toString()); + } + } else if (!exists && !none && !has(options, StandardOpenOption.CREATE)) { + throw new NoSuchFileException(path.toString()); + } + if (f.isDirectory()) { + throw new FileSystemException(path.toString(), null, "Is a directory"); + } + File parent = f.getParentFile(); + if (parent != null && !parent.isDirectory()) { + throw new NoSuchFileException(path.toString()); + } + // Without APPEND or TRUNCATE_EXISTING the JDK overwrites from the + // start and leaves a longer file's tail in place. A device stream + // replaces the file, so the tail is put back when there is one. + byte[] tail = null; + if (exists && !none && !append && !has(options, StandardOpenOption.TRUNCATE_EXISTING)) { + tail = readAllBytes(path); + } + // CREATE_NEW claims the path with the exclusive create before opening + // it: the check above alone lets two callers both pass and the later + // open erase what the first one wrote. + if (has(options, StandardOpenOption.CREATE_NEW) && !f.createNewFile()) { + throw new FileAlreadyExistsException(path.toString()); + } + OutputStream out = new FileOutputStream(f, append); + return tail == null || tail.length == 0 ? out : new Overwrite(out, tail); + } + + /// A stream that writes over the start of what a file held and keeps the + /// rest: what opening a file with `WRITE` alone means. + private static final class Overwrite extends OutputStream { + private final OutputStream out; + private final byte[] old; + private long written; + private boolean closed; + + Overwrite(OutputStream out, byte[] old) { + this.out = out; + this.old = old; + } + + @Override + public void write(int b) throws IOException { + out.write(b); + written++; + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + out.write(b, off, len); + written += len; + } + + @Override + public void flush() throws IOException { + out.flush(); + } + + @Override + public void close() throws IOException { + if (!closed) { + closed = true; + if (written < old.length) { + out.write(old, (int) written, old.length - (int) written); + } + out.close(); + } + } + } + + public static BufferedReader newBufferedReader(Path path, Charset cs) throws IOException { + return new BufferedReader(new InputStreamReader(newInputStream(path), cs.name())); + } + + public static BufferedReader newBufferedReader(Path path) throws IOException { + return newBufferedReader(path, StandardCharsets.UTF_8); + } + + public static BufferedWriter newBufferedWriter(Path path, Charset cs, OpenOption... options) throws IOException { + return new BufferedWriter(new OutputStreamWriter(newOutputStream(path, options), cs.name())); + } + + public static BufferedWriter newBufferedWriter(Path path, OpenOption... options) throws IOException { + return newBufferedWriter(path, StandardCharsets.UTF_8, options); + } + + // ---- reading ---- + + public static byte[] readAllBytes(Path path) throws IOException { + InputStream in = newInputStream(path); + try { + // Read here rather than by InputStream.readAllBytes, which a Java 8 + // class library lacks: the differential tests run this class over + // the JDK's own streams. + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buf = new byte[8192]; + int n; + while ((n = in.read(buf, 0, buf.length)) > 0) { + out.write(buf, 0, n); + } + return out.toByteArray(); + } finally { + in.close(); + } + } + + public static String readString(Path path) throws IOException { + return readString(path, StandardCharsets.UTF_8); + } + + public static String readString(Path path, Charset cs) throws IOException { + return new String(readAllBytes(path), cs.name()); + } + + public static List readAllLines(Path path) throws IOException { + return readAllLines(path, StandardCharsets.UTF_8); + } + + /// The lines of the file. A line ends at `\n`, `\r` or `\r\n`, and a + /// file that ends with a terminator has no empty last line. + public static List readAllLines(Path path, Charset cs) throws IOException { + String text = readString(path, cs); + List lines = new ArrayList(); + int n = text.length(); + int start = 0; + for (int i = 0; i < n; i++) { + char c = text.charAt(i); + if (c == '\n' || c == '\r') { + lines.add(text.substring(start, i)); + if (c == '\r' && i + 1 < n && text.charAt(i + 1) == '\n') { + i++; + } + start = i + 1; + } + } + if (start < n) { + lines.add(text.substring(start)); + } + return lines; + } + + // ---- writing ---- + + public static Path write(Path path, byte[] bytes, OpenOption... options) throws IOException { + if (bytes == null) { + throw new NullPointerException(); + } + OutputStream out = newOutputStream(path, options); + try { + out.write(bytes, 0, bytes.length); + } finally { + out.close(); + } + return path; + } + + public static Path write(Path path, Iterable lines, Charset cs, OpenOption... options) + throws IOException { + if (lines == null) { + throw new NullPointerException(); + } + StringBuilder sb = new StringBuilder(); + for (CharSequence line : lines) { + sb.append(line.toString()).append(LINE); + } + return write(path, sb.toString().getBytes(cs.name()), options); + } + + public static Path write(Path path, Iterable lines, OpenOption... options) + throws IOException { + return write(path, lines, StandardCharsets.UTF_8, options); + } + + public static Path writeString(Path path, CharSequence csq, OpenOption... options) throws IOException { + return writeString(path, csq, StandardCharsets.UTF_8, options); + } + + public static Path writeString(Path path, CharSequence csq, Charset cs, OpenOption... options) + throws IOException { + if (csq == null) { + throw new NullPointerException(); + } + return write(path, csq.toString().getBytes(cs.name()), options); + } + + // ---- creating and deleting ---- + + public static Path createFile(Path path, FileAttribute... attrs) throws IOException { + File f = file(path); + if (f.exists()) { + throw new FileAlreadyExistsException(path.toString()); + } + File parent = f.getParentFile(); + if (parent != null && !parent.isDirectory()) { + throw new NoSuchFileException(path.toString()); + } + // Claimed with the exclusive create, so of two callers racing for + // the path exactly one succeeds and nothing is truncated. + if (!f.createNewFile()) { + throw new FileAlreadyExistsException(path.toString()); + } + return path; + } + + public static Path createDirectory(Path dir, FileAttribute... attrs) throws IOException { + File f = file(dir); + if (f.exists()) { + throw new FileAlreadyExistsException(dir.toString()); + } + File parent = f.getParentFile(); + if (parent != null && !parent.isDirectory()) { + throw new NoSuchFileException(dir.toString()); + } + if (!f.mkdir()) { + // mkdir is the atomic claim: a caller that lost the race for the + // name is told so, as createFile tells it. + if (f.exists()) { + throw new FileAlreadyExistsException(dir.toString()); + } + throw new IOException("Could not create the directory " + dir); + } + return dir; + } + + public static Path createDirectories(Path dir, FileAttribute... attrs) throws IOException { + File f = file(dir); + if (f.isDirectory()) { + return dir; + } + if (f.exists()) { + throw new FileAlreadyExistsException(dir.toString()); + } + f.mkdirs(); + if (!f.isDirectory()) { + throw new IOException("Could not create the directory " + dir); + } + return dir; + } + + private static Path temporaryDirectory() { + FileSystemStorage fs = FileSystemStorage.getInstance(); + return PathImpl.of(fs.hasCachesDir() ? fs.getCachesDir() : fs.getAppHomePath()); + } + + private static Path temporary(Path dir, String prefix, String suffix, boolean directory) throws IOException { + Path parent = dir == null ? temporaryDirectory() : dir; + String p = prefix == null ? "" : prefix; + long n = System.currentTimeMillis(); + while (true) { + Path candidate = parent.resolve(p + n + suffix); + if (!file(candidate).exists()) { + // Another caller can claim the same name between the check and + // the create; the create is exclusive, so the loser moves on. + try { + return directory ? createDirectory(candidate) : createFile(candidate); + } catch (FileAlreadyExistsException e) { + // Taken meanwhile: try the next name. + } + } + n++; + } + } + + public static Path createTempFile(Path dir, String prefix, String suffix, FileAttribute... attrs) + throws IOException { + if (dir == null) { + throw new NullPointerException(); + } + return temporary(dir, prefix, suffix == null ? ".tmp" : suffix, false); + } + + public static Path createTempFile(String prefix, String suffix, FileAttribute... attrs) throws IOException { + return temporary(null, prefix, suffix == null ? ".tmp" : suffix, false); + } + + public static Path createTempDirectory(Path dir, String prefix, FileAttribute... attrs) throws IOException { + if (dir == null) { + throw new NullPointerException(); + } + return temporary(dir, prefix, "", true); + } + + public static Path createTempDirectory(String prefix, FileAttribute... attrs) throws IOException { + return temporary(null, prefix, "", true); + } + + public static void delete(Path path) throws IOException { + File f = existing(path); + if (f.isDirectory()) { + String[] entries = f.list(); + if (entries != null && entries.length > 0) { + throw new DirectoryNotEmptyException(path.toString()); + } + } + if (!f.delete()) { + throw new IOException("Could not delete " + path); + } + } + + public static boolean deleteIfExists(Path path) throws IOException { + if (!file(path).exists()) { + return false; + } + delete(path); + return true; + } + + // ---- copying and moving ---- + + /// Clears the way for a copy or a move to `target`, or says why not. + private static void replace(Path target, CopyOption[] options) throws IOException { + if (file(target).exists()) { + if (!has(options, StandardCopyOption.REPLACE_EXISTING)) { + throw new FileAlreadyExistsException(target.toString()); + } + delete(target); + } + } + + private static void transfer(File from, File to) throws IOException { + InputStream in = new FileInputStream(from); + try { + OutputStream out = new FileOutputStream(to, false); + try { + pump(in, out); + } finally { + out.close(); + } + } finally { + in.close(); + } + } + + private static long pump(InputStream in, OutputStream out) throws IOException { + byte[] buffer = new byte[8192]; + long total = 0; + int n; + while ((n = in.read(buffer, 0, buffer.length)) >= 0) { + out.write(buffer, 0, n); + total += n; + } + return total; + } + + /// Copies a file, or creates an empty directory where `source` is one: + /// a directory's entries are not copied, as in the JDK. + public static Path copy(Path source, Path target, CopyOption... options) throws IOException { + File from = existing(source); + if (file(target).exists() && isSameFile(source, target)) { + return target; + } + replace(target, options); + if (from.isDirectory()) { + createDirectory(target); + } else { + File to = file(target); + File parent = to.getParentFile(); + if (parent != null && !parent.isDirectory()) { + throw new NoSuchFileException(target.toString()); + } + transfer(from, to); + } + return target; + } + + public static long copy(InputStream in, Path target, CopyOption... options) throws IOException { + if (in == null) { + throw new NullPointerException(); + } + replace(target, options); + OutputStream out = newOutputStream(target, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE); + try { + return pump(in, out); + } finally { + out.close(); + } + } + + public static long copy(Path source, OutputStream out) throws IOException { + if (out == null) { + throw new NullPointerException(); + } + InputStream in = newInputStream(source); + try { + return pump(in, out); + } finally { + in.close(); + } + } + + private static void copyTree(File from, File to) throws IOException { + if (from.isDirectory()) { + if (!to.mkdir()) { + throw new IOException("Could not create the directory " + to.getPath()); + } + String[] names = from.list(); + if (names != null) { + for (String name : names) { + copyTree(new File(from, name), new File(to, name)); + } + } + } else { + transfer(from, to); + } + } + + /// Deletes what a move has copied. An entry that will not go is an + /// error: answering normally would report a move that left the source, + /// or half of it, where it was. + private static void deleteTree(File f) throws IOException { + if (f.isDirectory()) { + String[] names = f.list(); + if (names != null) { + for (String name : names) { + deleteTree(new File(f, name)); + } + } + } + if (!f.delete() && f.exists()) { + throw new IOException("Moved " + f.getPath() + " but could not delete the original"); + } + } + + /// Moves a file or a directory with everything in it. Where the device + /// cannot rename in place the tree is copied and the original deleted. + public static Path move(Path source, Path target, CopyOption... options) throws IOException { + File from = existing(source); + if (file(target).exists() && isSameFile(source, target)) { + return target; + } + replace(target, options); + File to = file(target); + File parent = to.getParentFile(); + if (parent != null && !parent.isDirectory()) { + throw new NoSuchFileException(target.toString()); + } + if (!from.renameTo(to)) { + // No copy fallback for a regular file: File.renameTo already is + // one -- outside its directory it copies the bytes and deletes + // the original -- so a rename that failed is a copy or a delete + // that failed, and trying the same again would fail the same way. + if (!from.isDirectory()) { + throw new IOException("Could not move " + source + " to " + target); + } + // The copy fallback must not run when the source cannot leave its + // directory: the JDK's rename fails there and touches nothing, + // while copying first left a full copy at the target and emptied + // the source of every entry that could be deleted. + File fromParent = from.getAbsoluteFile().getParentFile(); + if (fromParent != null && !fromParent.canWrite()) { + throw new AccessDeniedException(source.toString()); + } + copyTree(from, to); + try { + deleteTree(from); + } catch (IOException e) { + // Undo the copy, so a failed move leaves no second tree. + try { + deleteTree(to); + } catch (IOException ignored) { + // The original error is the one to report. + } + throw e; + } + } + return target; + } + + /// The position of the first byte two files differ in, or -1 when they + /// hold the same bytes. Both are read a block at a time, as the JDK + /// reads them, so two large files that differ early are not loaded + /// whole, and a file larger than an array can be compared at all. + public static long mismatch(Path path, Path path2) throws IOException { + if (isSameFile(path, path2)) { + return -1L; + } + InputStream a = newInputStream(path); + try { + InputStream b = newInputStream(path2); + try { + byte[] bufA = new byte[8192]; + byte[] bufB = new byte[8192]; + long at = 0; + while (true) { + int na = fill(a, bufA); + int nb = fill(b, bufB); + int n = Math.min(na, nb); + for (int i = 0; i < n; i++) { + if (bufA[i] != bufB[i]) { + return at + i; + } + } + if (na != nb) { + return at + n; + } + if (na < bufA.length) { + return -1L; + } + at += n; + } + } finally { + b.close(); + } + } finally { + a.close(); + } + } + + /// Reads into `buf` until it is full or the stream ends; how many bytes + /// were read. A stream may answer fewer than asked before its end. + private static int fill(InputStream in, byte[] buf) throws IOException { + int total = 0; + while (total < buf.length) { + int n = in.read(buf, total, buf.length - total); + if (n < 0) { + break; + } + total += n; + } + return total; + } + + // ---- directories ---- + + private static List entries(Path dir) throws IOException { + File f = existing(dir); + if (!f.isDirectory()) { + throw new NotDirectoryException(dir.toString()); + } + String[] names = f.list(); + List out = new ArrayList(); + if (names != null) { + Arrays.sort(names); + for (String name : names) { + out.add(dir.resolve(name)); + } + } + return out; + } + + /// The lines of `path` as UTF-8. See [#lines(Path, Charset)]. + public static Stream lines(Path path) throws IOException { + return lines(path, StandardCharsets.UTF_8); + } + + /// The lines of `path`, read as the stream asks for them: `findFirst` + /// reads the start of a large file, not all of it. The file is opened + /// here, so a missing one throws now, and closing the stream closes it. + public static Stream lines(Path path, Charset cs) throws IOException { + BufferedReader reader = newBufferedReader(path, cs); + return reader.lines().onClose(new CloseReader(reader)); + } + + /// The entries of `dir`, sorted by name. + public static Stream list(Path dir) throws IOException { + return lazy(entries(dir).iterator()); + } + + /// The paths under `start`, `start` first, depth first, to `maxDepth` + /// levels below it. + public static Stream walk(Path start, int maxDepth, FileVisitOption... options) throws IOException { + if (maxDepth < 0) { + throw new IllegalArgumentException("'maxDepth' is negative"); + } + existing(start); + return lazy(new Walker(start, maxDepth)); + } + + /// The paths under `start`, at every depth. See + /// [#walk(Path, int, FileVisitOption...)]. + public static Stream walk(Path start, FileVisitOption... options) throws IOException { + return walk(start, Integer.MAX_VALUE, options); + } + + /// The paths [#walk(Path, int, FileVisitOption...)] would answer for + /// which `matcher` holds, given each path's attributes. + public static Stream find(Path start, int maxDepth, BiPredicate matcher, + FileVisitOption... options) throws IOException { + if (maxDepth < 0) { + throw new IllegalArgumentException("'maxDepth' is negative"); + } + if (matcher == null) { + throw new NullPointerException(); + } + existing(start); + return lazy(new Finder(new Walker(start, maxDepth), matcher)); + } + + private static Stream lazy(Iterator it) { + return StreamSupport.stream(Spliterators.spliteratorUnknownSize(it, + Spliterator.ORDERED | Spliterator.NONNULL), false); + } + + private static final class CloseReader implements Runnable { + private final BufferedReader reader; + + CloseReader(BufferedReader reader) { + this.reader = reader; + } + + @Override + public void run() { + try { + reader.close(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + } + + /// Depth first, discovering as it goes: a directory is listed when the + /// walk advances past it, never before, and a failure to list it + /// surfaces then. + private static final class Walker implements Iterator { + /// The entries still to visit, one iterator per open directory. + private final List> levels = new ArrayList>(); + private final int maxDepth; + private Path pending; + private int pendingDepth; + /// The path answered last, entered on the next advance if it is a + /// directory within the depth limit. + private Path last; + private int lastDepth; + + Walker(Path start, int maxDepth) { + this.maxDepth = maxDepth; + this.pending = start; + } + + @Override + public boolean hasNext() { + if (pending != null) { + return true; + } + if (last != null && lastDepth < maxDepth && file(last).isDirectory()) { + try { + levels.add(entries(last).iterator()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + last = null; + while (!levels.isEmpty()) { + Iterator top = levels.get(levels.size() - 1); + if (top.hasNext()) { + pending = top.next(); + pendingDepth = levels.size(); + return true; + } + levels.remove(levels.size() - 1); + } + return false; + } + + @Override + public Path next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + Path p = pending; + pending = null; + last = p; + lastDepth = pendingDepth; + return p; + } + } + + private static final class Finder implements Iterator { + private final Iterator all; + private final BiPredicate matcher; + private Path next; + + Finder(Iterator all, BiPredicate matcher) { + this.all = all; + this.matcher = matcher; + } + + @Override + public boolean hasNext() { + while (next == null && all.hasNext()) { + Path p = all.next(); + if (matcher.test(p, attributes(file(p)))) { + next = p; + } + } + return next != null; + } + + @Override + public Path next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + Path p = next; + next = null; + return p; + } + } + + public static Path walkFileTree(Path start, FileVisitor visitor) throws IOException { + return walkFileTree(start, null, Integer.MAX_VALUE, visitor); + } + + public static Path walkFileTree(Path start, Set options, int maxDepth, + FileVisitor visitor) throws IOException { + if (maxDepth < 0) { + throw new IllegalArgumentException("'maxDepth' is negative"); + } + if (visitor == null) { + throw new NullPointerException(); + } + visit(start, 0, maxDepth, visitor); + return start; + } + + private static FileVisitResult checked(FileVisitResult result) { + if (result == null) { + throw new NullPointerException(); + } + return result; + } + + /// Visits `path` and what is under it. Answers `TERMINATE` to stop the + /// walk, `SKIP_SIBLINGS` to stop the directory it is an entry of, and + /// `CONTINUE` otherwise. + private static FileVisitResult visit(Path path, int depth, int maxDepth, FileVisitor visitor) + throws IOException { + File f = file(path); + if (!f.exists()) { + return checked(visitor.visitFileFailed(path, new NoSuchFileException(path.toString()))); + } + BasicFileAttributes attrs = attributes(f); + if (!attrs.isDirectory() || depth >= maxDepth) { + return checked(visitor.visitFile(path, attrs)); + } + FileVisitResult pre = checked(visitor.preVisitDirectory(path, attrs)); + if (pre == FileVisitResult.SKIP_SUBTREE) { + return FileVisitResult.CONTINUE; + } + if (pre != FileVisitResult.CONTINUE) { + return pre; + } + for (Path entry : entries(path)) { + FileVisitResult result = visit(entry, depth + 1, maxDepth, visitor); + if (result == FileVisitResult.TERMINATE) { + return result; + } + if (result == FileVisitResult.SKIP_SIBLINGS) { + break; + } + } + FileVisitResult post = checked(visitor.postVisitDirectory(path, null)); + return post == FileVisitResult.SKIP_SUBTREE ? FileVisitResult.CONTINUE : post; + } + + public static DirectoryStream newDirectoryStream(Path dir) throws IOException { + return new Listing(entries(dir)); + } + + public static DirectoryStream newDirectoryStream(Path dir, DirectoryStream.Filter filter) + throws IOException { + if (filter == null) { + throw new NullPointerException(); + } + List out = new ArrayList(); + for (Path entry : entries(dir)) { + if (filter.accept(entry)) { + out.add(entry); + } + } + return new Listing(out); + } + + /// The entries of `dir` whose names match `glob`: `*` and `?` for any + /// characters and any one, `[a-c]` and `[!a-c]` for a set of them, + /// `{one,two}` for alternatives, and a backslash to take the next + /// character literally. + public static DirectoryStream newDirectoryStream(Path dir, String glob) throws IOException { + if (glob == null) { + throw new NullPointerException(); + } + List out = new ArrayList(); + for (Path entry : entries(dir)) { + String name = entry.getFileName().toString(); + if (Glob.matches(glob, 0, glob.length(), name, 0)) { + out.add(entry); + } + } + return new Listing(out); + } + + private static final class Listing implements DirectoryStream { + private final List entries; + private boolean iterated; + private boolean closed; + + Listing(List entries) { + this.entries = entries; + } + + @Override + public Iterator iterator() { + if (closed) { + throw new IllegalStateException("Directory stream is closed"); + } + if (iterated) { + throw new IllegalStateException("Iterator already obtained"); + } + iterated = true; + final Iterator it = entries.iterator(); + return new Iterator() { + @Override + public boolean hasNext() { + return !closed && it.hasNext(); + } + + @Override + public Path next() { + if (closed) { + throw new NoSuchElementException(); + } + return it.next(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + @Override + public void close() { + closed = true; + } + } +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/Glob.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/Glob.java new file mode 100644 index 00000000000..0e06f9c8e1f --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/Glob.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.nio.file; + +/// The glob syntax of `Files.newDirectoryStream(dir, glob)`, matched against +/// one file name. +final class Glob { + + private Glob() { + } + + /// Whether `name` from `ni` on matches `glob` between `gi` and `gEnd`. + static boolean matches(String glob, int gi, int gEnd, String name, int ni) { + int n = name.length(); + while (gi < gEnd) { + char g = glob.charAt(gi); + if (g == '*') { + while (gi < gEnd && glob.charAt(gi) == '*') { + gi++; + } + if (gi == gEnd) { + return true; + } + for (int k = ni; k <= n; k++) { + if (matches(glob, gi, gEnd, name, k)) { + return true; + } + } + return false; + } + if (g == '{') { + int close = closing(glob, gi, gEnd); + if (close < 0) { + throw new IllegalArgumentException("Missing '}' in glob: " + glob); + } + String rest = glob.substring(close + 1, gEnd); + int start = gi + 1; + for (int i = start; i <= close; i++) { + char c = glob.charAt(i); + if (c == '\\') { + i++; + } else if (c == ',' || i == close) { + String alternative = glob.substring(start, i) + rest; + if (matches(alternative, 0, alternative.length(), name, ni)) { + return true; + } + start = i + 1; + } + } + return false; + } + if (ni >= n) { + return false; + } + char c = name.charAt(ni); + if (g == '?') { + gi++; + } else if (g == '[') { + int close = gi + 1; + boolean negate = close < gEnd && glob.charAt(close) == '!'; + if (negate) { + close++; + } + int first = close; + boolean found = false; + while (close < gEnd && (glob.charAt(close) != ']' || close == first)) { + char lo = glob.charAt(close); + char hi = lo; + if (close + 2 < gEnd && glob.charAt(close + 1) == '-' && glob.charAt(close + 2) != ']') { + hi = glob.charAt(close + 2); + close += 2; + } + found = found || (c >= lo && c <= hi); + close++; + } + if (close >= gEnd) { + throw new IllegalArgumentException("Missing ']' in glob: " + glob); + } + if (found == negate) { + return false; + } + gi = close + 1; + } else { + if (g == '\\') { + gi++; + if (gi >= gEnd) { + throw new IllegalArgumentException("No character to escape in glob: " + glob); + } + g = glob.charAt(gi); + } + if (g != c) { + return false; + } + gi++; + } + ni++; + } + return ni == n; + } + + private static int closing(String glob, int open, int end) { + for (int i = open + 1; i < end; i++) { + char c = glob.charAt(i); + if (c == '\\') { + i++; + } else if (c == '}') { + return i; + } + } + return -1; + } +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/InvalidPathException.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/InvalidPathException.java new file mode 100644 index 00000000000..be631fb6b7b --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/InvalidPathException.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.nio.file; + +/// A string that cannot be made a path. +public class InvalidPathException extends IllegalArgumentException { + private static final long serialVersionUID = 1L; + private final String input; + private final String reason; + private final int index; + + public InvalidPathException(String input, String reason, int index) { + super(reason); + if (input == null || reason == null) { + throw new NullPointerException(); + } + if (index < -1) { + throw new IllegalArgumentException(); + } + this.input = input; + this.reason = reason; + this.index = index; + } + + public InvalidPathException(String input, String reason) { + this(input, reason, -1); + } + + public String getInput() { + return input; + } + + public String getReason() { + return reason; + } + + public int getIndex() { + return index; + } + + @Override + public String getMessage() { + StringBuilder sb = new StringBuilder(reason); + if (index > -1) { + sb.append(" at index ").append(index); + } + return sb.append(": ").append(input).toString(); + } +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/LinkOption.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/LinkOption.java new file mode 100644 index 00000000000..c60a6287838 --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/LinkOption.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.nio.file; + +/// The device's +/// file system is not asked about symbolic links, so the option is accepted +/// wherever the JDK takes it and changes nothing. +public enum LinkOption implements OpenOption, CopyOption { + NOFOLLOW_LINKS +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/NoSuchFileException.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/NoSuchFileException.java new file mode 100644 index 00000000000..49bbe449a38 --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/NoSuchFileException.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.nio.file; + +/// Nothing is at the path. +public class NoSuchFileException extends FileSystemException { + private static final long serialVersionUID = 1L; + + public NoSuchFileException(String file) { + super(file); + } + + public NoSuchFileException(String file, String other, String reason) { + super(file, other, reason); + } +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/NotDirectoryException.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/NotDirectoryException.java new file mode 100644 index 00000000000..d477defcd5f --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/NotDirectoryException.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.nio.file; + +/// The path is not a directory. +public class NotDirectoryException extends FileSystemException { + private static final long serialVersionUID = 1L; + + public NotDirectoryException(String file) { + super(file); + } +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/OpenOption.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/OpenOption.java new file mode 100644 index 00000000000..774d2d52c29 --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/OpenOption.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.nio.file; + +/// How a file is +/// opened. [StandardOpenOption] has the values. +public interface OpenOption { +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/Path.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/Path.java new file mode 100644 index 00000000000..c1dfd2988b4 --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/Path.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.nio.file; + +import com.codename1.desktopcompat.shared.jdk.java.io.File; +import java.io.IOException; +import java.net.URI; +import java.util.Iterator; + +/// A path in the platform's file system, the one `com.codename1.desktopcompat.shared.jdk.java.io.File` reaches. +/// +/// #### What a path is here +/// +/// A sequence of names separated by `/`, after an optional root. A backslash +/// is read as a separator. The root is `/`, or -- because Codename One names +/// its files `file:///...` and `com.codename1.desktopcompat.shared.jdk.java.io.File.getAbsolutePath()` answers that +/// form -- the `file:` scheme with the slashes after it. A relative path is +/// resolved against the application's home directory when it is used, which +/// is also what [#toAbsolutePath()] answers. +/// +/// The methods that only rearrange names (`resolve`, `relativize`, +/// `normalize`, `getParent`, `startsWith`, ...) answer what the JDK answers +/// on a Unix file system. +/// +/// #### What is not provided +/// +/// `getFileSystem()` and `register(...)`: there is one file system and it +/// cannot be watched. [#toRealPath] is `File.getCanonicalPath()`: where the +/// file system cannot resolve symbolic links it answers the absolute path +/// with `.` and `..` resolved. +public interface Path extends Comparable, Iterable { + + /// The path `first`, with each of `more` appended as further names. + static Path of(String first, String... more) { + return PathImpl.of(first, more); + } + + /// The path a `file:` URI names. + static Path of(URI uri) { + return PathImpl.of(uri); + } + + boolean isAbsolute(); + + Path getRoot(); + + Path getFileName(); + + Path getParent(); + + int getNameCount(); + + Path getName(int index); + + Path subpath(int beginIndex, int endIndex); + + boolean startsWith(Path other); + + boolean startsWith(String other); + + boolean endsWith(Path other); + + boolean endsWith(String other); + + Path normalize(); + + Path resolve(Path other); + + Path resolve(String other); + + Path resolveSibling(Path other); + + Path resolveSibling(String other); + + Path relativize(Path other); + + URI toUri(); + + Path toAbsolutePath(); + + /// The absolute path with `.` and `..` resolved, and symbolic links where + /// the file system can. Throws [NoSuchFileException] when nothing is there. + Path toRealPath(LinkOption... options) throws IOException; + + File toFile(); + + @Override + Iterator iterator(); + + @Override + int compareTo(Path other); +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/PathImpl.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/PathImpl.java new file mode 100644 index 00000000000..8ccb2cbc686 --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/PathImpl.java @@ -0,0 +1,515 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.nio.file; + +import com.codename1.desktopcompat.shared.jdk.java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +/// The one implementation of [Path]: a normalized path string, split into a +/// root and names when a method needs them. +final class PathImpl implements Path { + + private static final String[] NO_NAMES = new String[0]; + private static final String SCHEME = "file:"; + + /// Separators collapsed, no trailing separator unless the path is a root. + private final String path; + private final int rootLength; + private String[] names; + + private PathImpl(String path) { + this.path = path; + this.rootLength = rootLength(path); + } + + static PathImpl of(String first, String[] more) { + if (first == null) { + throw new NullPointerException(); + } + String joined = first; + if (more != null && more.length > 0) { + StringBuilder sb = new StringBuilder(first); + for (String m : more) { + if (m == null) { + throw new NullPointerException(); + } + if (m.length() > 0) { + if (sb.length() > 0) { + sb.append('/'); + } + sb.append(m); + } + } + joined = sb.toString(); + } + if (joined.indexOf('\0') >= 0) { + throw new InvalidPathException(joined, "Nul character not allowed"); + } + return new PathImpl(clean(joined)); + } + + static PathImpl of(String path) { + return of(path, null); + } + + static PathImpl of(URI uri) { + String scheme = uri.getScheme(); + if (scheme == null || !"file".equalsIgnoreCase(scheme)) { + throw new IllegalArgumentException("URI scheme is not \"file\""); + } + // Only what names a file on this device is a path, as in the JDK: + // a host, a query or a fragment is not dropped, it is refused, so + // that file://server/share/x does not quietly become /share/x. + if (uri.isOpaque()) { + throw new IllegalArgumentException("URI is not hierarchical"); + } + if (present(uri.getRawAuthority())) { + throw new IllegalArgumentException("URI has an authority component"); + } + if (present(uri.getRawFragment())) { + throw new IllegalArgumentException("URI has a fragment component"); + } + if (present(uri.getRawQuery())) { + throw new IllegalArgumentException("URI has a query component"); + } + String p = uri.getPath(); + if (p == null || p.length() == 0) { + throw new IllegalArgumentException("URI has no path component"); + } + return of(p); + } + + /// Whether a URI has the component. An empty one counts as none: the + /// device's `URI` answers an empty authority for `file:///x`, where the + /// JDK's answers null. + private static boolean present(String component) { + return component != null && component.length() > 0; + } + + /// How many leading characters are the root: one for `/`, the scheme and + /// its slashes for a `file:` path, none for a relative path. + private static int rootLength(String p) { + if (p.startsWith(SCHEME)) { + int i = SCHEME.length(); + while (i < p.length() && p.charAt(i) == '/') { + i++; + } + return i; + } + return p.startsWith("/") ? 1 : 0; + } + + /// Collapses runs of separators and drops a trailing one, leaving the + /// slashes of a `file:` root as they were written. + private static String clean(String p) { + int root = 0; + StringBuilder sb = new StringBuilder(p.length()); + if (p.startsWith(SCHEME)) { + root = rootLength(p); + sb.append(p.substring(0, root)); + } + boolean separator = false; + for (int i = root; i < p.length(); i++) { + char c = p.charAt(i); + if (c == '\\') { + c = '/'; + } + if (c == '/') { + if (sb.length() == 0) { + sb.append('/'); + root = 1; + } else { + separator = sb.length() > root; + } + continue; + } + if (separator) { + sb.append('/'); + separator = false; + } + sb.append(c); + } + return sb.toString(); + } + + private String[] names() { + if (names == null) { + if (path.length() == rootLength) { + // The empty path is one empty name; a root has none. + names = rootLength == 0 ? new String[] {""} : NO_NAMES; + } else { + List out = new ArrayList(); + int start = rootLength; + for (int i = rootLength; i <= path.length(); i++) { + if (i == path.length() || path.charAt(i) == '/') { + out.add(path.substring(start, i)); + start = i + 1; + } + } + names = out.toArray(new String[out.size()]); + } + } + return names; + } + + private boolean isEmpty() { + return path.length() == 0; + } + + private String root() { + return path.substring(0, rootLength); + } + + private static PathImpl build(String root, String[] names, int from, int to) { + StringBuilder sb = new StringBuilder(root); + for (int i = from; i < to; i++) { + if (i > from) { + sb.append('/'); + } + sb.append(names[i]); + } + return new PathImpl(sb.toString()); + } + + private static PathImpl impl(Path other) { + if (other == null) { + throw new NullPointerException(); + } + if (other instanceof PathImpl) { + return (PathImpl) other; + } + return of(other.toString()); + } + + @Override + public boolean isAbsolute() { + return rootLength > 0; + } + + @Override + public Path getRoot() { + return rootLength == 0 ? null : new PathImpl(root()); + } + + @Override + public Path getFileName() { + String[] n = names(); + return n.length == 0 ? null : new PathImpl(n[n.length - 1]); + } + + @Override + public Path getParent() { + String[] n = names(); + if (n.length == 0) { + return null; + } + if (n.length == 1) { + return rootLength == 0 ? null : new PathImpl(root()); + } + return build(root(), n, 0, n.length - 1); + } + + @Override + public int getNameCount() { + return names().length; + } + + @Override + public Path getName(int index) { + String[] n = names(); + if (index < 0 || index >= n.length) { + throw new IllegalArgumentException(); + } + return new PathImpl(n[index]); + } + + @Override + public Path subpath(int beginIndex, int endIndex) { + String[] n = names(); + if (beginIndex < 0 || beginIndex >= n.length || endIndex > n.length || beginIndex >= endIndex) { + throw new IllegalArgumentException(); + } + return build("", n, beginIndex, endIndex); + } + + @Override + public boolean startsWith(Path other) { + PathImpl o = impl(other); + if (!root().equals(o.root())) { + return false; + } + String[] a = names(); + String[] b = o.names(); + if (b.length > a.length) { + return false; + } + for (int i = 0; i < b.length; i++) { + if (!a[i].equals(b[i])) { + return false; + } + } + return true; + } + + @Override + public boolean startsWith(String other) { + return startsWith(of(other)); + } + + @Override + public boolean endsWith(Path other) { + PathImpl o = impl(other); + if (o.rootLength > 0) { + return equals(o); + } + String[] a = names(); + String[] b = o.names(); + if (b.length > a.length) { + return false; + } + for (int i = 0; i < b.length; i++) { + if (!a[a.length - b.length + i].equals(b[i])) { + return false; + } + } + return true; + } + + @Override + public boolean endsWith(String other) { + return endsWith(of(other)); + } + + @Override + public Path normalize() { + String[] n = names(); + List out = new ArrayList(n.length); + for (String name : n) { + if (".".equals(name) || name.length() == 0) { + continue; + } + if ("..".equals(name)) { + int last = out.size() - 1; + if (last >= 0 && !"..".equals(out.get(last))) { + out.remove(last); + continue; + } + if (rootLength > 0) { + // Above the root there is the root. + continue; + } + } + out.add(name); + } + if (out.size() == n.length) { + return this; + } + return build(root(), out.toArray(new String[out.size()]), 0, out.size()); + } + + @Override + public Path resolve(Path other) { + PathImpl o = impl(other); + if (o.rootLength > 0 || isEmpty()) { + return o; + } + if (o.isEmpty()) { + return this; + } + return new PathImpl(path.length() == rootLength ? path + o.path : path + "/" + o.path); + } + + @Override + public Path resolve(String other) { + return resolve(of(other)); + } + + @Override + public Path resolveSibling(Path other) { + PathImpl o = impl(other); + Path parent = getParent(); + return parent == null ? o : parent.resolve(o); + } + + @Override + public Path resolveSibling(String other) { + return resolveSibling(of(other)); + } + + private boolean hasDots() { + if (isEmpty()) { + return false; + } + for (String name : names()) { + if (".".equals(name) || "..".equals(name)) { + return true; + } + } + return false; + } + + @Override + public Path relativize(Path other) { + PathImpl o = impl(other); + if (!root().equals(o.root())) { + throw new IllegalArgumentException("'other' is different type of Path"); + } + if (path.equals(o.path)) { + return new PathImpl(""); + } + // A `.` or `..` anywhere has both sides normalized first, as the + // JDK has done since it stopped answering `../../b` for `a/..` + // against `b`: a name that cancels the one before it is no step + // away from anything. What is left of a `..` afterwards leads the + // path, cancels nothing, and is refused below. + PathImpl from = this; + if (!isEmpty() && (hasDots() || o.hasDots())) { + from = impl(normalize()); + o = impl(o.normalize()); + } + String[] a = from.isEmpty() ? NO_NAMES : from.names(); + String[] b = o.isEmpty() ? NO_NAMES : o.names(); + int common = 0; + while (common < a.length && common < b.length && a[common].equals(b[common])) { + common++; + } + StringBuilder sb = new StringBuilder(); + for (int i = common; i < a.length; i++) { + if ("..".equals(a[i])) { + throw new IllegalArgumentException("Unable to compute relative path from " + this + " to " + other); + } + if (sb.length() > 0) { + sb.append('/'); + } + sb.append(".."); + } + for (int i = common; i < b.length; i++) { + if (sb.length() > 0) { + sb.append('/'); + } + sb.append(b[i]); + } + return new PathImpl(sb.toString()); + } + + @Override + public URI toUri() { + // The storage path is "file://" and an absolute path, as typed: a + // space, '#' or '%' in a file's name is not legal in a URI as it + // stands. Handing the path over as a component has it escaped. + String storage = toFile().getAbsolutePath(); + int at = storage.startsWith(SCHEME) ? SCHEME.length() : 0; + while (at + 1 < storage.length() && storage.charAt(at) == '/' && storage.charAt(at + 1) == '/') { + at++; + } + String uriPath = storage.substring(at); + // A directory's URI ends in '/', as the JDK's does, so that resolving + // a name against it lands inside the directory rather than beside it. + if (!uriPath.endsWith("/") && toFile().isDirectory()) { + uriPath = uriPath + "/"; + } + try { + return new URI("file", null, uriPath, null, null); + } catch (URISyntaxException e) { + throw new IllegalArgumentException(e.getMessage()); + } + } + + @Override + public Path toAbsolutePath() { + return rootLength > 0 ? this : new PathImpl(clean(toFile().getAbsolutePath())); + } + + @Override + public Path toRealPath(LinkOption... options) throws IOException { + File file = toFile(); + if (!file.exists()) { + throw new NoSuchFileException(path); + } + return new PathImpl(clean(file.getCanonicalPath())); + } + + @Override + public File toFile() { + return new File(path); + } + + @Override + public Iterator iterator() { + return new Names(names()); + } + + /// The names of a path, each as a path of its own. + private static final class Names implements Iterator { + private final String[] n; + private int next; + + Names(String[] n) { + this.n = n; + } + + @Override + public boolean hasNext() { + return next < n.length; + } + + @Override + public Path next() { + if (next >= n.length) { + throw new NoSuchElementException(); + } + return new PathImpl(n[next++]); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } + + @Override + public int compareTo(Path other) { + return path.compareTo(impl(other).path); + } + + @Override + public boolean equals(Object o) { + return o instanceof PathImpl && ((PathImpl) o).path.equals(path); + } + + @Override + public int hashCode() { + return path.hashCode(); + } + + @Override + public String toString() { + return path; + } +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/Paths.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/Paths.java new file mode 100644 index 00000000000..9b8ba55ffdc --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/Paths.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.nio.file; + +import java.net.URI; + +/// Makes paths; see [Path]. +public final class Paths { + + private Paths() { + } + + public static Path get(String first, String... more) { + return PathImpl.of(first, more); + } + + public static Path get(URI uri) { + return PathImpl.of(uri); + } +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/SimpleFileVisitor.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/SimpleFileVisitor.java new file mode 100644 index 00000000000..eb2960118cc --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/SimpleFileVisitor.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.nio.file; + +import java.io.IOException; +import com.codename1.desktopcompat.shared.jdk.java.nio.file.attribute.BasicFileAttributes; + +/// A +/// [FileVisitor] that visits everything and rethrows what failed. +public class SimpleFileVisitor implements FileVisitor { + + protected SimpleFileVisitor() { + } + + @Override + public FileVisitResult preVisitDirectory(T dir, BasicFileAttributes attrs) throws IOException { + if (dir == null || attrs == null) { + throw new NullPointerException(); + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(T file, BasicFileAttributes attrs) throws IOException { + if (file == null || attrs == null) { + throw new NullPointerException(); + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed(T file, IOException exc) throws IOException { + if (file == null) { + throw new NullPointerException(); + } + throw exc; + } + + @Override + public FileVisitResult postVisitDirectory(T dir, IOException exc) throws IOException { + if (dir == null) { + throw new NullPointerException(); + } + if (exc != null) { + throw exc; + } + return FileVisitResult.CONTINUE; + } +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/StandardCopyOption.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/StandardCopyOption.java new file mode 100644 index 00000000000..addb33c850f --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/StandardCopyOption.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.nio.file; + +/// How a file is copied or moved. +/// `REPLACE_EXISTING` is honoured; `COPY_ATTRIBUTES` and `ATOMIC_MOVE` are +/// accepted and have no effect. +public enum StandardCopyOption implements CopyOption { + REPLACE_EXISTING, + COPY_ATTRIBUTES, + ATOMIC_MOVE +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/StandardOpenOption.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/StandardOpenOption.java new file mode 100644 index 00000000000..acc360e760c --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/StandardOpenOption.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.nio.file; + +/// How a file is opened. +/// +/// `READ`, `WRITE`, `APPEND`, `TRUNCATE_EXISTING`, `CREATE` and `CREATE_NEW` +/// mean what they mean in the JDK. `DELETE_ON_CLOSE`, `SPARSE`, `SYNC` and +/// `DSYNC` are accepted and have no effect: a device writes a file when the +/// stream is closed and has nothing to say about how. +public enum StandardOpenOption implements OpenOption { + READ, + WRITE, + APPEND, + TRUNCATE_EXISTING, + CREATE, + CREATE_NEW, + DELETE_ON_CLOSE, + SPARSE, + SYNC, + DSYNC +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/attribute/BasicFileAttributes.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/attribute/BasicFileAttributes.java new file mode 100644 index 00000000000..cac5accc707 --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/attribute/BasicFileAttributes.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.nio.file.attribute; + +/// What the device's file system knows about a file. +/// +/// The three times are all the time of the last modification, which is the +/// only one the device records. Nothing is a symbolic link, and there is no +/// file key. +public interface BasicFileAttributes { + + FileTime lastModifiedTime(); + + FileTime lastAccessTime(); + + FileTime creationTime(); + + boolean isRegularFile(); + + boolean isDirectory(); + + boolean isSymbolicLink(); + + boolean isOther(); + + long size(); + + Object fileKey(); +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/attribute/FileAttribute.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/attribute/FileAttribute.java new file mode 100644 index 00000000000..9f505bc0592 --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/attribute/FileAttribute.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.nio.file.attribute; + +/// The +/// methods that create a file take these and a device has none to set, so +/// the type exists for the empty array every such call passes. +public interface FileAttribute { + + String name(); + + T value(); +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/attribute/FileTime.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/attribute/FileTime.java new file mode 100644 index 00000000000..c8e03c8e167 --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/attribute/FileTime.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.nio.file.attribute; + +import java.time.Instant; +import java.util.concurrent.TimeUnit; + +/// A file's +/// time stamp, kept to the millisecond, which is as fine as the device's +/// file system reports it. +public final class FileTime implements Comparable { + + private final long millis; + + private FileTime(long millis) { + this.millis = millis; + } + + public static FileTime fromMillis(long value) { + return new FileTime(value); + } + + public static FileTime from(long value, TimeUnit unit) { + return new FileTime(unit.toMillis(value)); + } + + public static FileTime from(Instant instant) { + // Cut to the millisecond on purpose, unlike the JDK's: no device file system + // this runs on stores a finer stamp, so keeping nanoseconds would only make two + // stamps of the same file compare unequal before and after a round trip + // through the disk. + return new FileTime(instant.toEpochMilli()); + } + + public long toMillis() { + return millis; + } + + public long to(TimeUnit unit) { + return unit.convert(millis, TimeUnit.MILLISECONDS); + } + + public Instant toInstant() { + return Instant.ofEpochMilli(millis); + } + + @Override + public int compareTo(FileTime other) { + return millis < other.millis ? -1 : millis == other.millis ? 0 : 1; + } + + @Override + public boolean equals(Object o) { + return o instanceof FileTime && ((FileTime) o).millis == millis; + } + + @Override + public int hashCode() { + return (int) (millis ^ (millis >>> 32)); + } + + private static long floorDiv(long x, long y) { + long q = x / y; + return (x % y != 0 && (x < 0) != (y < 0)) ? q - 1 : q; + } + + private static void pad(StringBuilder sb, long value, int width) { + String digits = Long.toString(value); + for (int i = digits.length(); i < width; i++) { + sb.append('0'); + } + sb.append(digits); + } + + /// The time in ISO 8601, in UTC, as the JDK writes it: + /// `2024-02-29T12:30:05.25Z`, the fraction left out when it is zero. + @Override + public String toString() { + long days = floorDiv(millis, 86400000L); + long ofDay = millis - floorDiv(millis, 86400000L) * 86400000L; + // Days since 1970-01-01 to a civil date, in the proleptic Gregorian + // calendar: eras of 400 years, counted from the first of March. + long z = days + 719468L; + long era = floorDiv(z, 146097L); + long doe = z - era * 146097L; + long yoe = (doe - doe / 1460L + doe / 36524L - doe / 146096L) / 365L; + long doy = doe - (365L * yoe + yoe / 4L - yoe / 100L); + long mp = (5L * doy + 2L) / 153L; + long day = doy - (153L * mp + 2L) / 5L + 1L; + long month = mp < 10 ? mp + 3 : mp - 9; + long year = yoe + era * 400L + (month <= 2 ? 1 : 0); + StringBuilder sb = new StringBuilder(30); + if (year < 0) { + sb.append('-'); + pad(sb, -year, 4); + } else { + pad(sb, year, 4); + } + sb.append('-'); + pad(sb, month, 2); + sb.append('-'); + pad(sb, day, 2); + sb.append('T'); + pad(sb, ofDay / 3600000L, 2); + sb.append(':'); + pad(sb, ofDay / 60000L % 60L, 2); + sb.append(':'); + pad(sb, ofDay / 1000L % 60L, 2); + long fraction = ofDay % 1000L; + if (fraction != 0) { + sb.append('.'); + pad(sb, fraction, 3); + while (sb.charAt(sb.length() - 1) == '0') { + sb.setLength(sb.length() - 1); + } + } + return sb.append('Z').toString(); + } +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/attribute/package-info.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/attribute/package-info.java new file mode 100644 index 00000000000..8b56a428dbb --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/attribute/package-info.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +/// The attributes of a file: its type, size and time stamp. +package com.codename1.desktopcompat.shared.jdk.java.nio.file.attribute; diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/package-info.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/package-info.java new file mode 100644 index 00000000000..eead63e7e21 --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/nio/file/package-info.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +/// Paths, and the operations on the files and directories they name, over +/// the platform's file system as `com.codename1.desktopcompat.shared.jdk.java.io.File` reaches it. +/// +/// `Files` reads, writes, copies, moves and walks; `Path` and `Paths` make +/// and take apart paths. The methods of `Files` that answer a stream of +/// lines or paths are not provided; `readAllLines`, `newDirectoryStream` and +/// `walkFileTree` do the same work. +package com.codename1.desktopcompat.shared.jdk.java.nio.file; diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/util/ListResourceBundle.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/util/ListResourceBundle.java new file mode 100644 index 00000000000..b088ca1786e --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/util/ListResourceBundle.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.util; + +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + + +/// A resource bundle whose content a subclass supplies as an array of key +/// and value pairs. +public abstract class ListResourceBundle extends ResourceBundle { + + private Map lookup; + + public ListResourceBundle() { + super(); + } + + /// The bundle's content: each element is a pair, the key (a `String`) + /// and its value. + protected abstract Object[][] getContents(); + + private Map lookup() { + if (lookup == null) { + Map built = new HashMap(); + Object[][] contents = getContents(); + for (int i = 0; i < contents.length; i++) { + Object key = contents[i][0]; + Object value = contents[i][1]; + if (!(key instanceof String) || value == null) { + throw new NullPointerException(); + } + built.put((String) key, value); + } + lookup = built; + } + return lookup; + } + + public final Object handleGetObject(String key) { + if (key == null) { + throw new NullPointerException(); + } + return lookup().get(key); + } + + public Enumeration getKeys() { + return new ResourceBundle.KeyEnumeration(keySet().iterator()); + } + + protected Set handleKeySet() { + return lookup().keySet(); + } +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/util/PropertyResourceBundle.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/util/PropertyResourceBundle.java new file mode 100644 index 00000000000..36497aed094 --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/util/PropertyResourceBundle.java @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.util; + +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; + +/// A resource bundle read from a `.properties` file. +/// +/// The parser implements the whole `java.util.Properties` text format: `#` and `!` +/// comment lines, `=`, `:` or whitespace between key and value, a logical line +/// continued by a trailing backslash, and the escapes -- tab, newline, form +/// feed, carriage return, backslash-u followed by four hex digits, and a +/// backslash before any other character standing for that character. +/// +/// A stream is decoded as ISO-8859-1, one byte to one character, which is +/// what the JDK did for as long as `.properties` files have carried +/// backslash-u escapes for everything else. Pass a `Reader` to choose another +/// encoding. +public class PropertyResourceBundle extends ResourceBundle { + + private final Map lookup = new HashMap(); + + public PropertyResourceBundle(InputStream stream) throws IOException { + StringBuilder text = new StringBuilder(); + byte[] buffer = new byte[4096]; + int n = stream.read(buffer); + while (n >= 0) { + for (int i = 0; i < n; i++) { + text.append((char) (buffer[i] & 0xff)); + } + n = stream.read(buffer); + } + parse(text.toString(), lookup); + } + + public PropertyResourceBundle(Reader reader) throws IOException { + StringBuilder text = new StringBuilder(); + char[] buffer = new char[4096]; + int n = reader.read(buffer, 0, buffer.length); + while (n >= 0) { + text.append(buffer, 0, n); + n = reader.read(buffer, 0, buffer.length); + } + parse(text.toString(), lookup); + } + + public Object handleGetObject(String key) { + if (key == null) { + throw new NullPointerException(); + } + return lookup.get(key); + } + + public Enumeration getKeys() { + return new ResourceBundle.KeyEnumeration(keySet().iterator()); + } + + protected Set handleKeySet() { + return lookup.keySet(); + } + + private static boolean isBlank(char c) { + return c == ' ' || c == '\t' || c == '\f'; + } + + private static boolean isLineEnd(char c) { + return c == '\n' || c == '\r'; + } + + /// Reads `.properties` text into `out`, a later definition of a key + /// replacing an earlier one. + static void parse(String text, Map out) { + int n = text.length(); + int i = 0; + StringBuilder line = new StringBuilder(); + while (i < n) { + // Blank lines and the indentation of a logical line carry nothing. + while (i < n && (isBlank(text.charAt(i)) || isLineEnd(text.charAt(i)))) { + i++; + } + if (i >= n) { + break; + } + char c = text.charAt(i); + if (c == '#' || c == '!') { + // A comment runs to the end of its natural line and cannot be + // continued, whatever it ends with. + while (i < n && !isLineEnd(text.charAt(i))) { + i++; + } + continue; + } + line.setLength(0); + boolean escaped = false; + // A line that is nothing but a continuation is no entry, unless + // the input ends in the middle of it: then the JDK defines the + // empty key, and so does this. + boolean cutShort = false; + while (i < n) { + c = text.charAt(i); + if (isLineEnd(c)) { + if (!escaped) { + break; + } + // An odd run of backslashes before the terminator joins + // the next natural line, minus its indentation. + line.setLength(line.length() - 1); + i++; + cutShort = i >= n; + if (c == '\r' && i < n && text.charAt(i) == '\n') { + i++; + } + while (i < n && isBlank(text.charAt(i))) { + i++; + } + escaped = false; + continue; + } + line.append(c); + escaped = c == '\\' && !escaped; + i++; + } + if (escaped) { + // A lone backslash at the end of the input escapes nothing. + line.setLength(line.length() - 1); + cutShort = true; + } + if (line.length() > 0 || cutShort) { + store(line, out); + } + } + } + + private static void store(StringBuilder line, Map out) { + int len = line.length(); + int keyLen = 0; + int valueStart = len; + boolean hasSeparator = false; + boolean escaped = false; + while (keyLen < len) { + char c = line.charAt(keyLen); + if ((c == '=' || c == ':') && !escaped) { + valueStart = keyLen + 1; + hasSeparator = true; + break; + } + if (isBlank(c) && !escaped) { + valueStart = keyLen + 1; + break; + } + escaped = c == '\\' && !escaped; + keyLen++; + } + // Whitespace may surround the one separator character. + while (valueStart < len) { + char c = line.charAt(valueStart); + if (!isBlank(c)) { + if (!hasSeparator && (c == '=' || c == ':')) { + hasSeparator = true; + } else { + break; + } + } + valueStart++; + } + out.put(unescape(line, 0, keyLen), unescape(line, valueStart, len)); + } + + private static int hexDigit(char c) { + if (c >= '0' && c <= '9') { + return c - '0'; + } + if (c >= 'a' && c <= 'f') { + return c - 'a' + 10; + } + if (c >= 'A' && c <= 'F') { + return c - 'A' + 10; + } + throw new IllegalArgumentException("Malformed unicode escape in properties text"); + } + + private static String unescape(StringBuilder in, int from, int to) { + StringBuilder sb = new StringBuilder(to - from); + int i = from; + while (i < to) { + char c = in.charAt(i++); + if (c != '\\' || i >= to) { + if (c != '\\') { + sb.append(c); + } + continue; + } + c = in.charAt(i++); + if (c == 'u') { + if (i + 4 > to) { + throw new IllegalArgumentException("Malformed unicode escape in properties text"); + } + int value = 0; + for (int k = 0; k < 4; k++) { + value = (value << 4) | hexDigit(in.charAt(i++)); + } + sb.append((char) value); + } else if (c == 't') { + sb.append('\t'); + } else if (c == 'r') { + sb.append('\r'); + } else if (c == 'n') { + sb.append('\n'); + } else if (c == 'f') { + sb.append('\f'); + } else { + sb.append(c); + } + } + return sb.toString(); + } +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/util/ResourceBundle.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/util/ResourceBundle.java new file mode 100644 index 00000000000..511a2771db1 --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/util/ResourceBundle.java @@ -0,0 +1,315 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.util; + +import com.codename1.desktopcompat.shared.CompatRegistryBase; +import com.codename1.desktopcompat.shared.Resources; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; +import java.util.MissingResourceException; +import java.util.Set; + +/// `java.util.ResourceBundle` for desktop sources: locale specific strings and +/// objects, looked up by key, with a chain of less specific bundles behind +/// each one. +/// +/// `getBundle("com.example.Messages", locale)` looks for three bundles, most +/// specific first -- `Messages__`, `Messages_` +/// and `Messages` -- and chains the ones that exist, so a key missing from the +/// first is answered by the next. When nothing more specific than the base +/// bundle exists for the requested locale, the default locale is tried the +/// same way before the base bundle is settled for. Finding none at all throws +/// `MissingResourceException`. +/// +/// Each of the three is first created as a class of that name extending +/// `ResourceBundle`, then read as the resource +/// `com/example/Messages_.properties`. A device creates no class from +/// its name, so the classes come from the registry the build generates with +/// the application ([CompatRegistryBase]), which creates each bundle class it +/// found with `new`; the files are read through [Resources], which knows where +/// the build put them. +/// +/// Bundles are cached for the life of the application, or until +/// `clearCache()`. `ResourceBundle.Control`, and the lookups that take one, +/// are not provided. +public abstract class ResourceBundle { + + /// Every bundle already loaded, by base name and suffix; MISSING for one known not to exist. + private static final Map CACHE = new HashMap(); + + private static final Object MISSING = new Object(); + + /// The bundle consulted for a key this one does not have, or null. + protected ResourceBundle parent; + + private Locale locale; + private String baseName; + + public ResourceBundle() { + } + + public static final ResourceBundle getBundle(String baseName) { + return getBundle(baseName, Locale.getDefault()); + } + + /// Looks the bundle up as getBundle(String, Locale) does. The class loader + /// is not used: an application has one. + public static ResourceBundle getBundle(String baseName, Locale locale, ClassLoader loader) { + return getBundle(baseName, locale); + } + + public static final ResourceBundle getBundle(String baseName, Locale locale) { + if (baseName == null || locale == null) { + throw new NullPointerException(); + } + String name = baseName.replace('/', '.'); + String language = nonNull(locale.getLanguage()); + String country = nonNull(locale.getCountry()); + ResourceBundle found; + synchronized (CACHE) { + found = findChain(name, language, country); + // A bundle found by the chain has had its locale set; one that + // has none is the base bundle, like one of the empty language. + Locale foundLocale = found == null ? null : found.locale; + if (foundLocale == null || nonNull(foundLocale.getLanguage()).length() == 0) { + // Nothing specific to the requested locale: the default + // locale's bundle is preferred to the base bundle. + Locale fallback = Locale.getDefault(); + String fallbackLanguage = nonNull(fallback.getLanguage()); + String fallbackCountry = nonNull(fallback.getCountry()); + boolean requestedRoot = language.length() == 0; + boolean sameLocale = fallbackLanguage.equals(language) && fallbackCountry.equals(country); + if (!sameLocale && !(requestedRoot && found != null)) { + ResourceBundle viaDefault = findChain(name, fallbackLanguage, fallbackCountry); + if (viaDefault != null) { + found = viaDefault; + } + } + } + } + if (found == null) { + String localeName = localeSuffix(language, country); + throw new MissingResourceException( + "Can't find bundle for base name " + baseName + ", locale " + + (localeName.length() == 0 ? "" : localeName.substring(1)), + baseName + localeName, ""); + } + return found; + } + + public static final void clearCache() { + synchronized (CACHE) { + CACHE.clear(); + } + } + + private static String nonNull(String s) { + return s == null ? "" : s; + } + + private static String localeSuffix(String language, String country) { + if (language.length() == 0) { + return ""; + } + return country.length() == 0 ? "_" + language : "_" + language + "_" + country; + } + + /// The most specific bundle that exists for the locale, with the less + /// specific ones chained behind it, or null when not even the base bundle + /// exists. + private static ResourceBundle findChain(String baseName, String language, String country) { + ResourceBundle result = load(baseName, "", "", "", null); + if (language.length() > 0) { + ResourceBundle forLanguage = load(baseName, "_" + language, language, "", result); + if (forLanguage != null) { + result = forLanguage; + } + if (country.length() > 0) { + ResourceBundle forCountry = + load(baseName, "_" + language + "_" + country, language, country, result); + if (forCountry != null) { + result = forCountry; + } + } + } + return result; + } + + private static ResourceBundle load(String baseName, String suffix, String language, String country, + ResourceBundle parent) { + String key = baseName + '\n' + suffix; + Object cached = CACHE.get(key); + if (cached instanceof ResourceBundle) { + return (ResourceBundle) cached; + } + if (cached != null) { + return null; + } + ResourceBundle bundle = create(baseName, suffix); + if (bundle == null) { + CACHE.put(key, MISSING); + return null; + } + if (bundle.parent == null) { + // The less specific bundle of a name is the same whichever locale + // led here, so the chain is fixed the first time it is built. + bundle.parent = parent; + } + bundle.locale = new Locale(language, country); + bundle.baseName = baseName; + CACHE.put(key, bundle); + return bundle; + } + + private static ResourceBundle create(String baseName, String suffix) { + String name = baseName + suffix; + Object made = CompatRegistryBase.cn1NewInstance(name); + if (made instanceof ResourceBundle) { + return (ResourceBundle) made; + } + InputStream in = Resources.open(baseName.replace('.', '/') + suffix + ".properties"); + if (in == null) { + return null; + } + try { + return new PropertyResourceBundle(in); + } catch (IOException e) { + // A bundle that cannot be read is a bundle that is not there. + return null; + } finally { + try { + in.close(); + } catch (IOException e) { + // Nothing to do about a failed close of a stream already read. + } + } + } + + public final String getString(String key) { + Object value = getObject(key); + if (value instanceof String) { + return (String) value; + } + throw new ClassCastException("Resource " + key + " is not a String"); + } + + public final String[] getStringArray(String key) { + Object value = getObject(key); + if (value instanceof String[]) { + return (String[]) value; + } + throw new ClassCastException("Resource " + key + " is not a String array"); + } + + public final Object getObject(String key) { + if (key == null) { + throw new NullPointerException(); + } + for (ResourceBundle b = this; b != null; b = b.parent) { + Object value = b.handleGetObject(key); + if (value != null) { + return value; + } + } + throw new MissingResourceException( + "Can't find resource for bundle " + getClass().getName() + ", key " + key, + getClass().getName(), key); + } + + /// The locale this bundle was found for, which may be less specific than + /// the one asked for. The base bundle's has an empty language. + public Locale getLocale() { + return locale; + } + + public String getBaseBundleName() { + return baseName; + } + + protected void setParent(ResourceBundle parent) { + this.parent = parent; + } + + public boolean containsKey(String key) { + if (key == null) { + throw new NullPointerException(); + } + for (ResourceBundle b = this; b != null; b = b.parent) { + if (b.handleKeySet().contains(key)) { + return true; + } + } + return false; + } + + /// Every key of this bundle and of the bundles behind it. + public Set keySet() { + Set keys = new HashSet(); + for (ResourceBundle b = this; b != null; b = b.parent) { + keys.addAll(b.handleKeySet()); + } + return keys; + } + + /// The keys this bundle itself defines. The default derives them from + /// getKeys(); a subclass that has them at hand should override it. + protected Set handleKeySet() { + Set keys = new HashSet(); + Enumeration all = getKeys(); + while (all.hasMoreElements()) { + String key = all.nextElement(); + if (handleGetObject(key) != null) { + keys.add(key); + } + } + return keys; + } + + protected abstract Object handleGetObject(String key); + + public abstract Enumeration getKeys(); + + /// An Enumeration over an iterator, for the getKeys implementations. + static final class KeyEnumeration implements Enumeration { + private final Iterator it; + + KeyEnumeration(Iterator it) { + this.it = it; + } + + public boolean hasMoreElements() { + return it.hasNext(); + } + + public String nextElement() { + return it.next(); + } + } +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/util/logging/Level.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/util/logging/Level.java new file mode 100644 index 00000000000..d32a2eb7997 --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/util/logging/Level.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.util.logging; + +/// A logging level: the standard +/// levels, and room for an application's own -- the constructor is +/// protected, as the JDK's is, so a class may extend this one to add them. +/// +/// [Logger] writes through the platform log, which has four levels; see +/// there for how these map onto them. +public class Level implements java.io.Serializable { + + private static final long serialVersionUID = 1L; + + public static final Level OFF = new Level("OFF", Integer.MAX_VALUE); + public static final Level SEVERE = new Level("SEVERE", 1000); + public static final Level WARNING = new Level("WARNING", 900); + public static final Level INFO = new Level("INFO", 800); + public static final Level CONFIG = new Level("CONFIG", 700); + public static final Level FINE = new Level("FINE", 500); + public static final Level FINER = new Level("FINER", 400); + public static final Level FINEST = new Level("FINEST", 300); + public static final Level ALL = new Level("ALL", Integer.MIN_VALUE); + + private final String name; + private final int value; + private final String resourceBundleName; + + protected Level(String name, int value) { + this(name, value, null); + } + + protected Level(String name, int value, String resourceBundleName) { + if (name == null) { + throw new NullPointerException(); + } + this.name = name; + this.value = value; + this.resourceBundleName = resourceBundleName; + } + + public String getName() { + return name; + } + + /// The name: a device carries no translations of the level names. + public String getLocalizedName() { + return name; + } + + public String getResourceBundleName() { + return resourceBundleName; + } + + public final int intValue() { + return value; + } + + private static Level[] standard() { + return new Level[] {OFF, SEVERE, WARNING, INFO, CONFIG, FINE, FINER, FINEST, ALL}; + } + + /// The standard level called `name`, in any case, or whose number + /// `name` spells. A number that belongs to no standard level makes a + /// level of its own, named by the number. + public static Level parse(String name) { + if (name == null) { + throw new NullPointerException(); + } + String trimmed = name.trim(); + Level[] all = standard(); + for (Level level : all) { + if (level.name.equalsIgnoreCase(trimmed)) { + return level; + } + } + int number; + try { + number = Integer.parseInt(trimmed); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Bad level \"" + name + "\""); + } + for (Level level : all) { + if (level.value == number) { + return level; + } + } + return new Level(trimmed, number); + } + + @Override + public final String toString() { + return name; + } + + @Override + public boolean equals(Object other) { + return other instanceof Level && ((Level) other).value == value; + } + + @Override + public int hashCode() { + return value; + } +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/util/logging/Logger.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/util/logging/Logger.java new file mode 100644 index 00000000000..80c09bb56bc --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/util/logging/Logger.java @@ -0,0 +1,309 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.util.logging; + +import com.codename1.io.Log; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; + +/// `java.util.logging.Logger` for desktop sources, writing through +/// `com.codename1.io.Log`. +/// +/// The platform log has four levels where the JDK has seven, so they fold: +/// `SEVERE` is an error, `WARNING` a warning, `INFO` and `CONFIG` are +/// information, and the three `FINE` levels are debug output. A record +/// carries its logger's name in front of the message, and an attached +/// exception is logged with it, which is what prints a stack trace on a +/// device. +/// +/// A logger with no level of its own logs `INFO` and above, the JDK's +/// default; the platform log's own level may then filter once more. There are no +/// handlers, filters, formatters or parents to configure: a call that would +/// install one has no method here, and the build reports it. +public class Logger { + + public static final String GLOBAL_LOGGER_NAME = "global"; + + private static final Map LOGGERS = new HashMap(); + + private final String name; + private Level level; + + protected Logger(String name, String resourceBundleName) { + this.name = name; + } + + public static Logger getLogger(String name) { + if (name == null) { + throw new NullPointerException(); + } + synchronized (LOGGERS) { + Logger logger = LOGGERS.get(name); + if (logger == null) { + logger = new Logger(name, null); + LOGGERS.put(name, logger); + } + return logger; + } + } + + /// The bundle is not consulted: messages are logged as they are given. + public static Logger getLogger(String name, String resourceBundleName) { + return getLogger(name); + } + + public static Logger getGlobal() { + return getLogger(GLOBAL_LOGGER_NAME); + } + + public static Logger getAnonymousLogger() { + return new Logger(null, null); + } + + public String getName() { + return name; + } + + public Level getLevel() { + return level; + } + + public void setLevel(Level newLevel) { + this.level = newLevel; + } + + public Logger getParent() { + return null; + } + + public void setUseParentHandlers(boolean useParentHandlers) { + // There are no handlers; see the class description. + } + + public boolean getUseParentHandlers() { + return false; + } + + public boolean isLoggable(Level l) { + int floor = (level == null ? Level.INFO : level).intValue(); + return l.intValue() >= floor && floor != Level.OFF.intValue(); + } + + private static int logLevel(Level l) { + int value = l.intValue(); + if (value >= Level.SEVERE.intValue()) { + return Log.ERROR; + } + if (value >= Level.WARNING.intValue()) { + return Log.WARNING; + } + if (value >= Level.CONFIG.intValue()) { + return Log.INFO; + } + return Log.DEBUG; + } + + public void log(Level l, String msg) { + if (isLoggable(l)) { + write(logLevel(l), name == null ? String.valueOf(msg) : name + ": " + msg, null); + } + } + + public void log(Level l, Supplier msgSupplier) { + if (isLoggable(l)) { + log(l, msgSupplier.get()); + } + } + + public void log(Level l, String msg, Object param1) { + log(l, msg, new Object[] {param1}); + } + + /// Logs `msg` with each `{n}` replaced by parameter `n`, as the JDK's + /// formatter does. + public void log(Level l, String msg, Object[] params) { + if (!isLoggable(l)) { + return; + } + String text = msg; + if (text != null && params != null && params.length > 0 && text.indexOf('{') >= 0) { + text = substitute(text, params); + } + log(l, text); + } + + /// `msg` with each `{n}` for which there is a parameter replaced by it. + /// The JDK formats with `MessageFormat`; this covers the plain index + /// form, which is what a log message uses, and leaves anything else -- + /// a format type, quoting, a brace with no parameter -- as written. + private static String substitute(String msg, Object[] params) { + StringBuilder out = new StringBuilder(msg.length() + 16); + int i = 0; + while (i < msg.length()) { + char c = msg.charAt(i); + if (c == '{') { + int close = msg.indexOf('}', i + 1); + int index = close < 0 ? -1 : index(msg, i + 1, close); + if (index >= 0 && index < params.length) { + out.append(String.valueOf(params[index])); + i = close + 1; + continue; + } + } + out.append(c); + i++; + } + return out.toString(); + } + + /// The decimal number spelled from `start` to `end`, or -1. + private static int index(String text, int start, int end) { + if (start == end || end - start > 4) { + return -1; + } + int value = 0; + for (int i = start; i < end; i++) { + char c = text.charAt(i); + if (c < '0' || c > '9') { + return -1; + } + value = value * 10 + (c - '0'); + } + return value; + } + + public void log(Level l, String msg, Throwable thrown) { + if (isLoggable(l)) { + write(logLevel(l), name == null ? String.valueOf(msg) : name + ": " + msg, thrown); + } + } + + public void log(Level l, Throwable thrown, Supplier msgSupplier) { + if (isLoggable(l)) { + log(l, msgSupplier.get(), thrown); + } + } + + public void logp(Level l, String sourceClass, String sourceMethod, String msg) { + log(l, msg); + } + + public void logp(Level l, String sourceClass, String sourceMethod, String msg, Object param1) { + log(l, msg, param1); + } + + public void logp(Level l, String sourceClass, String sourceMethod, String msg, Object[] params) { + log(l, msg, params); + } + + public void logp(Level l, String sourceClass, String sourceMethod, String msg, Throwable thrown) { + log(l, msg, thrown); + } + + public void entering(String sourceClass, String sourceMethod) { + log(Level.FINER, "ENTRY " + sourceClass + "." + sourceMethod); + } + + public void entering(String sourceClass, String sourceMethod, Object param1) { + log(Level.FINER, "ENTRY " + sourceClass + "." + sourceMethod + " " + param1); + } + + public void exiting(String sourceClass, String sourceMethod) { + log(Level.FINER, "RETURN " + sourceClass + "." + sourceMethod); + } + + public void exiting(String sourceClass, String sourceMethod, Object result) { + log(Level.FINER, "RETURN " + sourceClass + "." + sourceMethod + " " + result); + } + + public void throwing(String sourceClass, String sourceMethod, Throwable thrown) { + log(Level.FINER, "THROW " + sourceClass + "." + sourceMethod, thrown); + } + + public void severe(String msg) { + log(Level.SEVERE, msg); + } + + public void warning(String msg) { + log(Level.WARNING, msg); + } + + public void info(String msg) { + log(Level.INFO, msg); + } + + public void config(String msg) { + log(Level.CONFIG, msg); + } + + public void fine(String msg) { + log(Level.FINE, msg); + } + + public void finer(String msg) { + log(Level.FINER, msg); + } + + public void finest(String msg) { + log(Level.FINEST, msg); + } + + public void severe(Supplier msgSupplier) { + log(Level.SEVERE, msgSupplier); + } + + public void warning(Supplier msgSupplier) { + log(Level.WARNING, msgSupplier); + } + + public void info(Supplier msgSupplier) { + log(Level.INFO, msgSupplier); + } + + public void config(Supplier msgSupplier) { + log(Level.CONFIG, msgSupplier); + } + + public void fine(Supplier msgSupplier) { + log(Level.FINE, msgSupplier); + } + + public void finer(Supplier msgSupplier) { + log(Level.FINER, msgSupplier); + } + + public void finest(Supplier msgSupplier) { + log(Level.FINEST, msgSupplier); + } + + /// One record into the Codename One log; an attached exception follows + /// it, which is what prints its stack trace on a device. + private static void write(int level, String message, Throwable thrown) { + Log.p(message, level); + if (thrown != null) { + Log.e(thrown); + } + } +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/util/prefs/BackingStoreException.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/util/prefs/BackingStoreException.java new file mode 100644 index 00000000000..2cdf78eb398 --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/util/prefs/BackingStoreException.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.util.prefs; + +/// A failure of the store under the preferences. +/// [Preferences] declares it where the JDK does and never throws it: the +/// store it writes to does not fail that way. +public class BackingStoreException extends Exception { + + private static final long serialVersionUID = 1L; + + public BackingStoreException(String s) { + super(s); + } + + public BackingStoreException(Throwable cause) { + super(cause); + } +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/util/prefs/Preferences.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/util/prefs/Preferences.java new file mode 100644 index 00000000000..cb4aa89d0ad --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/jdk/java/util/prefs/Preferences.java @@ -0,0 +1,526 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.desktopcompat.shared.jdk.java.util.prefs; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/// `java.util.prefs.Preferences` for desktop sources, kept in Codename One's +/// `com.codename1.io.Preferences`. +/// +/// The store is one flat table of the application's own, so a node is a +/// prefix: the value of `key` in the node `/com/acme` is stored under the +/// node's path and the key. The user and the system trees are the same +/// tree -- an application on a device has no machine-wide settings to write. +/// Every value is stored as text, as the JDK stores it, so `getInt` of +/// something written with `put` reads it back. +/// +/// Each node keeps the list of its keys beside them, which is what `keys` +/// and `clear` answer from; the store cannot be asked for it. A node also +/// keeps the names of its children that hold a key, or have a descendant +/// that does: that is what `childrenNames` answers and what `removeNode` +/// walks to remove a whole subtree. A node that only ever was asked for, +/// and never written to, is not listed. +/// +/// Writes are saved as they are made. `flush` and `sync` do nothing, and +/// there are no change listeners. +public abstract class Preferences { + + public static final int MAX_KEY_LENGTH = 80; + public static final int MAX_VALUE_LENGTH = 8192; + public static final int MAX_NAME_LENGTH = 80; + + private final String path; + + /// [#removalCount] as it stood when this instance was made: a removal + /// of this node, or of one above it, numbered higher happened to it. + private final long born; + + /// For the JDK's shape; the nodes this class hands out are its own. + protected Preferences() { + this("/"); + } + + private Preferences(String path) { + this.path = path; + synchronized (STORE_LOCK) { + born = removalCount; + } + } + + public static Preferences userRoot() { + return new Node("/"); + } + + public static Preferences systemRoot() { + return new Node("/"); + } + + public static Preferences userNodeForPackage(Class c) { + return new Node(packagePath(c)); + } + + public static Preferences systemNodeForPackage(Class c) { + return new Node(packagePath(c)); + } + + private static String packagePath(Class c) { + if (c.isArray()) { + throw new IllegalArgumentException("Arrays have no associated preferences node."); + } + String name = c.getName(); + int dot = name.lastIndexOf('.'); + return dot < 0 ? "/" : "/" + name.substring(0, dot).replace('.', '/'); + } + + /// The node at `pathName`: absolute when it starts with a slash, else + /// below this one. + public Preferences node(String pathName) { + checkLive(); + if (pathName.length() == 0) { + return this; + } + if (pathName.charAt(0) == '/') { + return new Node(pathName); + } + return new Node("/".equals(path) ? "/" + pathName : path + "/" + pathName); + } + + public String name() { + return "/".equals(path) ? "" : path.substring(path.lastIndexOf('/') + 1); + } + + public String absolutePath() { + return path; + } + + public Preferences parent() { + checkLive(); + if ("/".equals(path)) { + return null; + } + int slash = path.lastIndexOf('/'); + return new Node(slash <= 0 ? "/" : path.substring(0, slash)); + } + + public boolean isUserNode() { + return true; + } + + /// Where `key` of this node is kept in Codename One's flat store: the + /// node's path and the key, each escaped, around one line break. + /// + /// A key may hold a `/`, so joining the two with one aliased entries: + /// `node("a").put("b/c", ...)` and `node("a/b").put("c", ...)` both + /// wrote `/a/b/c`. A node name may hold anything but `/`, a line break + /// too, so both halves are escaped and the one raw line break left is + /// where they meet. The index ([#indexKey()]) ends in a second raw line + /// break, which no escaped key can be. + private String storeKey(String key) { + if (key == null) { + throw new NullPointerException("Null key"); + } + return escape(path) + '\n' + escape(key); + } + + private String indexKey() { + return escape(path) + "\n\n"; + } + + /// Where the names of the node's children are kept: three raw line + /// breaks, which neither a key ([#storeKey]) nor the index can end in. + private String childrenKey() { + return childrenKey(path); + } + + private static String childrenKey(String nodePath) { + return escape(nodePath) + "\n\n\n"; + } + + private static String parentPath(String nodePath) { + int slash = nodePath.lastIndexOf('/'); + return slash <= 0 ? "/" : nodePath.substring(0, slash); + } + + private static String escape(String text) { + if (text.indexOf('\\') < 0 && text.indexOf('\n') < 0) { + return text; + } + StringBuilder out = new StringBuilder(text.length() + 8); + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + if (c == '\\') { + out.append("\\\\"); + } else if (c == '\n') { + out.append("\\n"); + } else { + out.append(c); + } + } + return out.toString(); + } + + /// Guards every use of the store, from every node. Codename One's + /// Preferences, and the Storage under it, are written for its one + /// thread: two workers saving at once threw inside Storage, which logs + /// the failure and goes on, so a value could be lost without a word. + /// It also makes each read-modify-write of an index whole: two threads + /// adding different keys to one node each read the same index, and the + /// one that saved last dropped the other's key for good. Static, since + /// two instances can stand for one node. + private static final Object STORE_LOCK = new Object(); + + /// The nodes removed in this run, each with the number of its removal. + /// Nodes are not cached -- two calls to `node` hand out two instances + /// for one path -- so an instance cannot be told it was removed; it + /// asks here instead, for its path and every one above it. + private static final Map REMOVALS = new HashMap(); + + private static long removalCount; + + /// Throws as the JDK does for any use of a removed node. + private void checkLive() { + synchronized (STORE_LOCK) { + if (isRemoved()) { + throw new IllegalStateException("Node has been removed."); + } + } + } + + /// Called under [#STORE_LOCK]. + private boolean isRemoved() { + if (REMOVALS.isEmpty()) { + return false; + } + String at = path; + while (true) { + Long when = REMOVALS.get(at); + if (when != null && when.longValue() > born) { + return true; + } + if ("/".equals(at)) { + return false; + } + at = parentPath(at); + } + } + + /// The node's keys, one per line. A key may hold any character, a line + /// break too, so a backslash and a line break inside one are written as + /// `\\` and `\n`; see [#saveIndex]. + private List index() { + return readList(indexKey()); + } + + private void saveIndex(List keys) { + saveList(indexKey(), keys); + } + + private static List readList(String storeKey) { + List keys = new ArrayList(); + String all = stored(storeKey, ""); + StringBuilder key = new StringBuilder(); + for (int i = 0; i < all.length(); i++) { + char c = all.charAt(i); + if (c == '\\' && i + 1 < all.length()) { + i++; + key.append(all.charAt(i) == 'n' ? '\n' : all.charAt(i)); + } else if (c == '\n') { + keys.add(key.toString()); + key.setLength(0); + } else { + key.append(c); + } + } + if (key.length() > 0) { + keys.add(key.toString()); + } + return keys; + } + + private static void saveList(String storeKey, List keys) { + if (keys.isEmpty()) { + com.codename1.io.Preferences.delete(storeKey); + return; + } + StringBuilder all = new StringBuilder(); + for (String key : keys) { + for (int i = 0; i < key.length(); i++) { + char c = key.charAt(i); + if (c == '\\') { + all.append("\\\\"); + } else if (c == '\n') { + all.append("\\n"); + } else { + all.append(c); + } + } + all.append('\n'); + } + com.codename1.io.Preferences.set(storeKey, all.toString()); + } + + /// Lists this node with its parent, and so on up, so that removing an + /// ancestor finds it. Stops at the first one already listed: a node is + /// only ever listed after everything above it. + private void register() { + String at = path; + while (!"/".equals(at)) { + String parent = parentPath(at); + String name = at.substring(at.lastIndexOf('/') + 1); + String key = childrenKey(parent); + List names = readList(key); + if (names.contains(name)) { + return; + } + names.add(name); + saveList(key, names); + at = parent; + } + } + + /// Deletes every entry of `nodePath` and of every node listed below it. + private static void removeTree(String nodePath) { + for (String child : readList(childrenKey(nodePath))) { + removeTree("/".equals(nodePath) ? "/" + child : nodePath + "/" + child); + } + String prefix = escape(nodePath) + '\n'; + for (String key : readList(prefix + '\n')) { + com.codename1.io.Preferences.delete(prefix + escape(key)); + } + com.codename1.io.Preferences.delete(prefix + '\n'); + com.codename1.io.Preferences.delete(childrenKey(nodePath)); + } + + public void put(String key, String value) { + if (value == null) { + throw new NullPointerException("Null value"); + } + synchronized (STORE_LOCK) { + checkLive(); + com.codename1.io.Preferences.set(storeKey(key), value); + List keys = index(); + if (!keys.contains(key)) { + keys.add(key); + saveIndex(keys); + } + register(); + } + } + + public String get(String key, String def) { + synchronized (STORE_LOCK) { + checkLive(); + return stored(storeKey(key), def); + } + } + + public void remove(String key) { + synchronized (STORE_LOCK) { + checkLive(); + com.codename1.io.Preferences.delete(storeKey(key)); + List keys = index(); + if (keys.remove(key)) { + saveIndex(keys); + } + } + } + + public void clear() throws BackingStoreException { + synchronized (STORE_LOCK) { + checkLive(); + for (String key : index()) { + com.codename1.io.Preferences.delete(storeKey(key)); + } + saveIndex(new ArrayList()); + } + } + + public String[] keys() throws BackingStoreException { + synchronized (STORE_LOCK) { + checkLive(); + List keys = index(); + return keys.toArray(new String[keys.size()]); + } + } + + /// The children that hold a key, or have a descendant that does; see + /// the class description. + public String[] childrenNames() throws BackingStoreException { + synchronized (STORE_LOCK) { + checkLive(); + List names = readList(childrenKey()); + return names.toArray(new String[names.size()]); + } + } + + /// As the JDK: `nodeExists("")` answers whether this node is still + /// there, and is the one question a removed node may still be asked. + public boolean nodeExists(String pathName) throws BackingStoreException { + synchronized (STORE_LOCK) { + if (pathName.length() == 0) { + return !isRemoved(); + } + Preferences node = node(pathName); + return !node.index().isEmpty() || !readList(node.childrenKey()).isEmpty(); + } + } + + /// Removes this node and everything below it, as the JDK does: their + /// entries are deleted, this instance and every other one for the + /// removed nodes throw `IllegalStateException` from then on, and a node + /// asked for again by its path afterwards is a new, empty one. The root + /// cannot be removed. + public void removeNode() throws BackingStoreException { + if ("/".equals(path)) { + throw new UnsupportedOperationException("Can't remove the root!"); + } + synchronized (STORE_LOCK) { + if (isRemoved()) { + throw new IllegalStateException("Node already removed."); + } + removeTree(path); + String parentKey = childrenKey(parentPath(path)); + List siblings = readList(parentKey); + if (siblings.remove(path.substring(path.lastIndexOf('/') + 1))) { + saveList(parentKey, siblings); + } + recordRemoval(path); + } + } + + /// Called under [#STORE_LOCK]. + private static void recordRemoval(String nodePath) { + removalCount++; + REMOVALS.put(nodePath, Long.valueOf(removalCount)); + } + + public void putInt(String key, int value) { + put(key, Integer.toString(value)); + } + + public int getInt(String key, int def) { + String value = get(key, null); + if (value != null) { + try { + return Integer.parseInt(value.trim()); + } catch (NumberFormatException e) { + return def; + } + } + return def; + } + + public void putLong(String key, long value) { + put(key, Long.toString(value)); + } + + public long getLong(String key, long def) { + String value = get(key, null); + if (value != null) { + try { + return Long.parseLong(value.trim()); + } catch (NumberFormatException e) { + return def; + } + } + return def; + } + + public void putBoolean(String key, boolean value) { + put(key, value ? "true" : "false"); + } + + public boolean getBoolean(String key, boolean def) { + String value = get(key, null); + if ("true".equalsIgnoreCase(value)) { + return true; + } + if ("false".equalsIgnoreCase(value)) { + return false; + } + return def; + } + + public void putFloat(String key, float value) { + put(key, Float.toString(value)); + } + + public float getFloat(String key, float def) { + String value = get(key, null); + if (value != null) { + try { + return Float.parseFloat(value.trim()); + } catch (NumberFormatException e) { + return def; + } + } + return def; + } + + public void putDouble(String key, double value) { + put(key, Double.toString(value)); + } + + public double getDouble(String key, double def) { + String value = get(key, null); + if (value != null) { + try { + return Double.parseDouble(value.trim()); + } catch (NumberFormatException e) { + return def; + } + } + return def; + } + + public void flush() throws BackingStoreException { + // Saved as written; see the class description. + } + + public void sync() throws BackingStoreException { + // Saved as written; see the class description. A removed node + // cannot be synced, as with the JDK (flushing one is allowed). + checkLive(); + } + + @Override + public String toString() { + return "User Preference Node: " + path; + } + + /// The stored value of `storeKey`, or `def`. + private static String stored(String storeKey, String def) { + String value = com.codename1.io.Preferences.get(storeKey, (String) null); + return value == null ? def : value; + } + + /// The one concrete node. + private static final class Node extends Preferences { + Node(String path) { + super(path); + } + } +} diff --git a/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/package-info.java b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/package-info.java new file mode 100644 index 00000000000..c7a4fd29067 --- /dev/null +++ b/maven/desktop-compat-runtime/src/main/java/com/codename1/desktopcompat/shared/package-info.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +/// What an application built from desktop or Android sources needs of the +/// core at run time: the classpath resources it ships, under the flat names a +/// device bundle gives them ([ResourceNames], [Resources]), and the +/// superclass of the registry its build generates ([CompatRegistryBase]), +/// through which the class library creates the resource bundles and service +/// providers it has. +/// +/// Internal: the build and generated code call these classes; an +/// application does not. +package com.codename1.desktopcompat.shared; diff --git a/maven/fxml-compiler/pom.xml b/maven/fxml-compiler/pom.xml new file mode 100644 index 00000000000..7c5a839fa0f --- /dev/null +++ b/maven/fxml-compiler/pom.xml @@ -0,0 +1,197 @@ + + + + + com.codenameone + codenameone + 8.0-SNAPSHOT + + 4.0.0 + + codenameone-fxml-compiler + 8.0-SNAPSHOT + jar + codenameone-fxml-compiler + + Build-time compiler for the FXML documents and JavaFX style sheets of + an application that uses the JavaFX compatibility layer. An FXML + document becomes Java source that builds the same tree with plain + constructors and setters; a style sheet becomes the binary table the + layer's style engine reads; and the members a controller marks @FXML + are reached by a generated dispatcher instead of by reflection. + + + + UTF-8 + 1.8 + 1.8 + ${project.basedir}/../javafx-compat/src/main/java/com/codename1/fxcompat/runtime/css + ${project.build.directory}/generated-sources/fxml-shared-css + + + + + + org.apache.maven.plugins + maven-antrun-plugin + + + + copy-shared-css-sources + generate-sources + + run + + + + + + + + + + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-shared-css-sources + generate-sources + + add-source + + + + ${fxml.shared.css.dir} + + + + + + + com.github.spotbugs + spotbugs-maven-plugin + 4.7.3.4 + + Max + Low + false + true + ${project.build.directory} + ${project.basedir}/spotbugs-exclude.xml + + + + spotbugs + verify + + spotbugs + + + + + + maven-compiler-plugin + + 1.8 + 1.8 + + + + + + + + + jdk8-without-bundled-javafx + + 1.8 + + + + + maven-compiler-plugin + + + -extdirs + ${project.build.directory}/no-jdk-extensions + + + + + maven-surefire-plugin + + -Djava.ext.dirs=${project.build.directory}/no-jdk-extensions + + + + + + + + + + + com.codenameone + codenameone-core + ${project.version} + + + com.codenameone + codenameone-desktop-compat-runtime + ${project.version} + + + org.ow2.asm + asm + + + junit + junit + test + + + + com.codenameone + codenameone-javafx-compat + ${project.version} + test + + + com.codenameone + codenameone-compat-testing + ${project.version} + test + + + diff --git a/maven/fxml-compiler/spotbugs-exclude.xml b/maven/fxml-compiler/spotbugs-exclude.xml new file mode 100644 index 00000000000..bf1c6466e52 --- /dev/null +++ b/maven/fxml-compiler/spotbugs-exclude.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/maven/fxml-compiler/src/main/java/com/codename1/fxml/ClassModel.java b/maven/fxml-compiler/src/main/java/com/codename1/fxml/ClassModel.java new file mode 100644 index 00000000000..4cfa7658d04 --- /dev/null +++ b/maven/fxml-compiler/src/main/java/com/codename1/fxml/ClassModel.java @@ -0,0 +1,385 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.fxml; + +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +/// What the FXML compiler knows about the classes a document names: their +/// constructors, setters, getters and constants, read out of the class +/// files on the application's class path. +/// +/// No class is ever loaded. The class path is the application's, built for +/// a device, and running a static initializer of it inside the build would +/// be running device code on the build machine; a class file read as data +/// says everything the compiler asks. Classes of the JDK itself are read +/// the same way, as resources of the build's own runtime. +final class ClassModel implements Closeable { + + /// One method or constructor. + static final class Method { + final String name; + final String descriptor; + final String signature; + final int access; + final Type[] parameters; + /// The `@NamedArg` name of each parameter, `null` where one has + /// none. + final String[] argNames; + /// The `@NamedArg` default of each parameter, `null` where none + /// was given. + final String[] argDefaults; + + Method(String name, String descriptor, String signature, int access) { + this.name = name; + this.descriptor = descriptor; + this.signature = signature; + this.access = access; + this.parameters = Type.getArgumentTypes(descriptor); + this.argNames = new String[parameters.length]; + this.argDefaults = new String[parameters.length]; + } + + boolean isPublic() { + return (access & Opcodes.ACC_PUBLIC) != 0; + } + + boolean isStatic() { + return (access & Opcodes.ACC_STATIC) != 0; + } + + Type returnType() { + return Type.getReturnType(descriptor); + } + + boolean allArgsNamed() { + for (int i = 0; i < argNames.length; i++) { + if (argNames[i] == null) { + return false; + } + } + return true; + } + } + + /// One field. + static final class Field { + final String name; + final String descriptor; + final int access; + + Field(String name, String descriptor, int access) { + this.name = name; + this.descriptor = descriptor; + this.access = access; + } + } + + /// One class. + static final class Info { + String name; + String superName; + String[] interfaces = new String[0]; + int access; + String defaultProperty; + final List methods = new ArrayList(); + final List fields = new ArrayList(); + + boolean isEnum() { + return (access & Opcodes.ACC_ENUM) != 0; + } + + boolean isAbstract() { + return (access & (Opcodes.ACC_ABSTRACT | Opcodes.ACC_INTERFACE)) != 0; + } + + boolean isPublic() { + return (access & Opcodes.ACC_PUBLIC) != 0; + } + + /// The name as Java source spells it. + String sourceName() { + return name.replace('/', '.').replace('$', '.'); + } + } + + private static final Info ABSENT = new Info(); + + private final List directories = new ArrayList(); + private final List jars = new ArrayList(); + private final Map cache = new HashMap(); + private final String namedArg; + private final String defaultProperty; + + /// Creates a model over a class path of directories and jars. Entries + /// that do not exist are ignored, as javac ignores them. + ClassModel(List classpath) throws IOException { + this(classpath, "Ljavafx/beans/NamedArg;", "Ljavafx/beans/DefaultProperty;"); + } + + ClassModel(List classpath, String namedArgDescriptor, String defaultPropertyDescriptor) + throws IOException { + this.namedArg = namedArgDescriptor; + this.defaultProperty = defaultPropertyDescriptor; + for (File f : classpath) { + if (f.isDirectory()) { + directories.add(f); + } else if (f.isFile()) { + jars.add(new ZipFile(f)); + } + } + } + + @Override + public void close() throws IOException { + IOException first = null; + for (ZipFile jar : jars) { + try { + jar.close(); + } catch (IOException e) { + first = first == null ? e : first; + } + } + jars.clear(); + if (first != null) { + throw first; + } + } + + private byte[] bytes(String internalName) throws IOException { + String entry = internalName + ".class"; + for (File dir : directories) { + File f = new File(dir, entry); + if (f.isFile()) { + InputStream in = new FileInputStream(f); + try { + return drain(in); + } finally { + in.close(); + } + } + } + for (ZipFile jar : jars) { + ZipEntry e = jar.getEntry(entry); + if (e != null) { + InputStream in = jar.getInputStream(e); + try { + return drain(in); + } finally { + in.close(); + } + } + } + if (internalName.startsWith("java/")) { + InputStream in = ClassLoader.getSystemResourceAsStream(entry); + if (in != null) { + try { + return drain(in); + } finally { + in.close(); + } + } + } + return null; + } + + private static byte[] drain(InputStream in) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buffer = new byte[8192]; + int n; + while ((n = in.read(buffer)) > 0) { + out.write(buffer, 0, n); + } + return out.toByteArray(); + } + + /// The class of an internal name (`javafx/scene/control/Button`), or + /// `null` when the class path has none. + Info find(String internalName) throws IOException { + Info known = cache.get(internalName); + if (known == null) { + byte[] bytes = bytes(internalName); + known = bytes == null ? ABSENT : read(bytes); + cache.put(internalName, known); + } + return known == ABSENT ? null : known; + } + + private Info read(byte[] bytes) { + final Info info = new Info(); + new ClassReader(bytes).accept(new ClassVisitor(Opcodes.ASM9) { + @Override + public void visit(int version, int access, String name, String signature, String superName, + String[] interfaces) { + info.name = name; + info.superName = superName; + info.access = access; + info.interfaces = interfaces == null ? new String[0] : interfaces; + } + + @Override + public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { + if (!defaultProperty.equals(descriptor)) { + return null; + } + return new AnnotationVisitor(Opcodes.ASM9) { + @Override + public void visit(String name, Object value) { + if ("value".equals(name) && value instanceof String) { + info.defaultProperty = (String) value; + } + } + }; + } + + @Override + public FieldVisitor visitField(int access, String name, String descriptor, String signature, + Object value) { + info.fields.add(new Field(name, descriptor, access)); + return null; + } + + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, + String[] exceptions) { + if ((access & (Opcodes.ACC_SYNTHETIC | Opcodes.ACC_BRIDGE)) != 0) { + return null; + } + final Method m = new Method(name, descriptor, signature, access); + info.methods.add(m); + return new MethodVisitor(Opcodes.ASM9) { + @Override + public AnnotationVisitor visitParameterAnnotation(final int parameter, String annotation, + boolean visible) { + if (!namedArg.equals(annotation) || parameter >= m.argNames.length) { + return null; + } + return new AnnotationVisitor(Opcodes.ASM9) { + @Override + public void visit(String key, Object value) { + if ("value".equals(key) && value instanceof String) { + m.argNames[parameter] = (String) value; + } else if ("defaultValue".equals(key) && value instanceof String) { + m.argDefaults[parameter] = (String) value; + } + } + }; + } + }; + } + }, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + return info; + } + + /// Whether a value of the class `from` can be assigned to a variable of + /// the class `to`. A class the class path does not have is assumed not + /// to be assignable to anything but itself and `Object`. + boolean isAssignable(String from, String to) throws IOException { + if (from.equals(to) || "java/lang/Object".equals(to)) { + return true; + } + Info info = find(from); + if (info == null) { + return false; + } + if (info.superName != null && isAssignable(info.superName, to)) { + return true; + } + for (String i : info.interfaces) { + if (isAssignable(i, to)) { + return true; + } + } + return false; + } + + /// The public methods named `name` of a class and of everything it + /// extends or implements, the most derived first. + List methods(String internalName, String name) throws IOException { + List out = new ArrayList(); + collect(internalName, name, out, new ArrayList()); + return out; + } + + private void collect(String internalName, String name, List out, List seen) + throws IOException { + if (internalName == null || seen.contains(internalName)) { + return; + } + seen.add(internalName); + Info info = find(internalName); + if (info == null) { + return; + } + for (Method m : info.methods) { + if (m.name.equals(name) && m.isPublic()) { + boolean shadowed = false; + for (Method have : out) { + shadowed |= have.descriptor.equals(m.descriptor); + } + if (!shadowed) { + out.add(m); + } + } + } + collect(info.superName, name, out, seen); + for (String i : info.interfaces) { + collect(i, name, out, seen); + } + } + + /// The default property of a class: what `@DefaultProperty` says on it + /// or on the nearest class above it that has one, or `null`. + String annotatedDefaultProperty(String internalName) throws IOException { + String at = internalName; + while (at != null) { + Info info = find(at); + if (info == null) { + return null; + } + if (info.defaultProperty != null) { + return info.defaultProperty; + } + at = info.superName; + } + return null; + } +} diff --git a/maven/fxml-compiler/src/main/java/com/codename1/fxml/CssCompiler.java b/maven/fxml-compiler/src/main/java/com/codename1/fxml/CssCompiler.java new file mode 100644 index 00000000000..82cfdf9465c --- /dev/null +++ b/maven/fxml-compiler/src/main/java/com/codename1/fxml/CssCompiler.java @@ -0,0 +1,361 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.fxml; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import com.codename1.desktopcompat.shared.ResourceNames; +import com.codename1.fxml.css.CssDeclaration; +import com.codename1.fxml.css.CssDeclarations; +import com.codename1.fxml.css.CssProperties; +import com.codename1.fxml.css.CssSelector; +import com.codename1.fxml.css.CssSheetData; +import com.codename1.fxml.css.CssValueParser; + +/// Compiles one JavaFX style sheet into the table the device reads +/// ([CssSheetData]). +/// +/// #### What is an error and what is a warning +/// +/// A style sheet written for the desktop names properties this layer does +/// not have and values it cannot draw, and JavaFX itself only logs those. +/// So a declaration that cannot be used -- an unknown property, a value +/// that does not parse, a selector form that is not supported -- is a +/// WARNING with its file and line, and is left out of the table. A sheet +/// whose structure is broken -- a block that is never closed, a `}` with no +/// block, declarations with no selector -- is an ERROR: nothing after that +/// point can be trusted to mean what the author wrote. +/// +/// #### At-rules +/// +/// `@import` is resolved against the sheet's own path and recorded; the +/// device reads the imported table first. `@font-face` is recorded as a +/// family and the font file it names; the device loads the file when it +/// reads the sheet, and the family is one more name of that font. Any +/// other at-rule is skipped with a warning. +public final class CssCompiler { + + private final String file; + private final String path; + private final String text; + private final Messages messages; + private final Set knownSheets; + private final CssValueParser parser = new CssValueParser(); + + private final List imports = new ArrayList(); + private final List fontFamilies = new ArrayList(); + private final List fontSources = new ArrayList(); + private final List selectors = new ArrayList(); + private final List selectorBlocks = new ArrayList(); + private final List blocks = new ArrayList(); + + private CssCompiler(String file, String path, String text, Set knownSheets, Messages messages) { + this.file = file; + this.path = path; + // An address in a value -- the picture of a background -- is + // written from where the sheet is. + int directory = path == null ? -1 : path.lastIndexOf('/'); + parser.setBase(directory < 0 ? "" : path.substring(0, directory + 1)); + this.text = CssDeclarations.stripComments(text); + this.knownSheets = knownSheets; + this.messages = messages; + } + + /// Compiles a style sheet. Answers `null`, with the reason recorded as + /// an error, for a sheet whose structure is broken. + /// + /// #### Parameters + /// + /// - `file`: the file as messages name it + /// + /// - `path`: the resource path of the sheet, without a leading slash, + /// which its imports are relative to + /// + /// - `text`: the content of the sheet + /// + /// - `knownSheets`: the resource paths of every style sheet of the + /// application, to warn about an import of none of them; `null` to + /// skip that check + /// + /// - `messages`: where warnings and errors go + public static CssSheetData compile(String file, String path, String text, Set knownSheets, + Messages messages) { + CssCompiler c = new CssCompiler(file, path, text, knownSheets, messages); + if (!c.parse()) { + return null; + } + int[] blockOf = new int[c.selectorBlocks.size()]; + for (int i = 0; i < blockOf.length; i++) { + blockOf[i] = c.selectorBlocks.get(i).intValue(); + } + return new CssSheetData(c.imports.toArray(new String[0]), c.fontFamilies.toArray(new String[0]), + c.fontSources.toArray(new String[0]), c.selectors.toArray(new CssSelector[0]), blockOf, + c.blocks.toArray(new CssDeclaration[0][])); + } + + private static boolean isSpace(char c) { + return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f'; + } + + /// The index of the first of `stops` at or after `from` that is outside + /// a string and outside parentheses, or -1. + private int scan(int from, String stops) { + char quote = 0; + int depth = 0; + for (int i = from; i < text.length(); i++) { + char c = text.charAt(i); + if (quote != 0) { + if (c == '\\') { + i++; + } else if (c == quote) { + quote = 0; + } + } else if (c == '"' || c == '\'') { + quote = c; + } else if (c == '(') { + depth++; + } else if (c == ')') { + depth--; + } else if (depth <= 0 && stops.indexOf(c) >= 0) { + return i; + } + } + return -1; + } + + /// The `}` that closes the block opened at `open`, nested blocks + /// skipped, or -1. + private int closing(int open) { + int depth = 0; + int i = open; + while (true) { + int at = scan(i, "{}"); + if (at < 0) { + return -1; + } + if (text.charAt(at) == '{') { + depth++; + } else if (--depth == 0) { + return at; + } + i = at + 1; + } + } + + private boolean parse() { + int i = 0; + int n = text.length(); + while (i < n) { + while (i < n && isSpace(text.charAt(i))) { + i++; + } + if (i >= n) { + break; + } + int start = i; + if (text.charAt(i) == '}') { + messages.error(file, text, i, "'}' without a block to close"); + return false; + } + int stop = scan(i, text.charAt(i) == '@' ? "{;" : "{}"); + if (stop < 0) { + if (text.charAt(i) == '@') { + messages.error(file, text, start, "'" + head(start) + "' is not ended by ';' or a block"); + } else { + messages.error(file, text, start, "'" + head(start) + "' is not followed by a '{' block"); + } + return false; + } + if (text.charAt(stop) == '}') { + messages.error(file, text, stop, "'}' without a block to close, after '" + head(start) + "'"); + return false; + } + int end = stop; + if (text.charAt(stop) == '{') { + end = closing(stop); + if (end < 0) { + messages.error(file, text, stop, "the block of '" + text.substring(start, stop).trim() + + "' is never closed"); + return false; + } + } + if (text.charAt(start) == '@') { + atRule(start, stop, end); + } else { + rule(start, stop, end); + } + i = end + 1; + } + return true; + } + + private String head(int start) { + int end = start; + while (end < text.length() && end - start < 40 && text.charAt(end) != '\n') { + end++; + } + return text.substring(start, end).trim(); + } + + private void rule(int start, int open, int close) { + String list = text.substring(start, open); + if (list.trim().length() == 0) { + messages.warning(file, text, start, "a block without a selector; it is ignored"); + return; + } + List parsed = new ArrayList(); + int from = 0; + while (from <= list.length()) { + int comma = list.indexOf(',', from); + if (comma < 0) { + comma = list.length(); + } + String one = list.substring(from, comma).trim(); + CssSelector s = one.length() == 0 ? null : CssSelector.parse(one); + if (s == null) { + messages.warning(file, text, start + from, "the selector '" + one + + "' is not supported (type, .class, #id, :state, descendant and '>' are); it is ignored"); + } else { + parsed.add(s); + } + from = comma + 1; + } + List declarations = new ArrayList(); + CssDeclarations.parse(text, open + 1, close, parser, new CssDeclarations.Reporter() { + @Override + public void warning(int offset, String message) { + messages.warning(file, text, offset, message); + } + }, declarations); + if (parsed.isEmpty() || declarations.isEmpty()) { + return; + } + Integer block = Integer.valueOf(blocks.size()); + blocks.add(declarations.toArray(new CssDeclaration[0])); + for (CssSelector s : parsed) { + selectors.add(s); + selectorBlocks.add(block); + } + } + + private void atRule(int start, int stop, int end) { + int nameEnd = start + 1; + while (nameEnd < stop && !isSpace(text.charAt(nameEnd))) { + nameEnd++; + } + String name = CssProperties.lower(text.substring(start, nameEnd)); + if ("@import".equals(name) && text.charAt(stop) == ';') { + String target = address(text.substring(nameEnd, stop).trim()); + String resolved = target == null ? null : resolve(target); + if (resolved == null) { + messages.warning(file, text, start, "'" + text.substring(start, stop).trim() + + "' does not name a style sheet of the application; it is ignored"); + return; + } + if (knownSheets != null && !knownSheets.contains(resolved)) { + messages.warning(file, text, start, "the imported style sheet " + resolved + + " is not among the application's desktop resources; it will not be found"); + } + imports.add(resolved); + return; + } + if ("@font-face".equals(name) && text.charAt(stop) == '{') { + fontFace(start, stop, end); + return; + } + messages.warning(file, text, start, name + " is not supported; it is ignored"); + } + + private void fontFace(int start, int open, int close) { + String family = null; + String source = null; + int i = open + 1; + while (i < close) { + int semi = scan(i, ";}"); + int stop = semi < 0 || semi > close ? close : semi; + String declaration = text.substring(i, stop); + int colon = declaration.indexOf(':'); + if (colon > 0) { + String property = CssProperties.lower(declaration.substring(0, colon).trim()); + String value = declaration.substring(colon + 1).trim(); + if ("font-family".equals(property)) { + family = unquote(value); + } else if ("src".equals(property)) { + String target = address(value); + source = target == null ? null : resolve(target); + } + } + i = stop + 1; + } + if (family == null || source == null) { + messages.warning(file, text, start, + "@font-face needs a font-family and a src that is a file of the application; it is ignored"); + return; + } + fontFamilies.add(family); + fontSources.add(source); + } + + private static String unquote(String s) { + String t = s.trim(); + if (t.length() >= 2 && (t.charAt(0) == '"' || t.charAt(0) == '\'') && t.charAt(t.length() - 1) == t.charAt(0)) { + return t.substring(1, t.length() - 1); + } + return t; + } + + /// The address in `"x"`, `'x'`, `url(x)` or `url("x")`; the first one + /// when a `src` lists several. `null` for anything else. + private static String address(String value) { + String v = value.trim(); + if (v.length() > 4 && "url(".equals(CssProperties.lower(v.substring(0, 4)))) { + int close = v.indexOf(')'); + if (close < 0) { + return null; + } + v = v.substring(4, close); + } else { + int end = v.indexOf(' '); + if (end > 0 && v.charAt(0) != '"' && v.charAt(0) != '\'') { + v = v.substring(0, end); + } + } + v = unquote(v); + return v.length() == 0 ? null : v; + } + + /// The resource path an address in this sheet names, or `null` for one + /// that is not a file of the application (an `http:` address). + private String resolve(String address) { + if (address.indexOf(':') >= 0) { + return null; + } + if (address.startsWith("/")) { + return ResourceNames.normalize(address); + } + int slash = path.lastIndexOf('/'); + return ResourceNames.normalize(slash < 0 ? address : path.substring(0, slash + 1) + address); + } +} diff --git a/maven/fxml-compiler/src/main/java/com/codename1/fxml/DesktopResourceCompiler.java b/maven/fxml-compiler/src/main/java/com/codename1/fxml/DesktopResourceCompiler.java new file mode 100644 index 00000000000..e5246fd30fb --- /dev/null +++ b/maven/fxml-compiler/src/main/java/com/codename1/fxml/DesktopResourceCompiler.java @@ -0,0 +1,287 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.fxml; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +import com.codename1.desktopcompat.shared.ResourceNames; +import com.codename1.fxml.css.CssSheetData; + +/// Compiles the style sheets among an application's desktop resources and +/// prepares its FXML documents for [FxmlClassCompiler]; the entry point both +/// build plugins call before the application's sources are compiled. +/// +/// #### What goes where +/// +/// - Every `*.css` becomes `.cn1css` at the root of the resource +/// output directory, where `` is what +/// `ResourceNames.flatName` gives the sheet's path: `styles/app.css` is +/// compiled to `styles__app.css.cn1css`. The build ships that directory +/// with the application's other resources, and a file at its root keeps +/// its name ([CssCompiler]). +/// - Every `*.fxml` is parsed, so that a document that is not well formed +/// fails the build before anything is compiled, and the documents together +/// become one small source, +/// `com/codename1/generated/fxml/FxmlDocuments.java`, under the Java output +/// directory, which the build adds to the sources javac compiles. +/// +/// #### Why the documents are not compiled here +/// +/// A document names classes -- every element is one -- and a custom control +/// is a class of the application, which does not exist until javac has +/// compiled the application. So the documents are compiled after javac, by +/// [FxmlClassCompiler]. Nothing the application wrote names a compiled +/// document: it calls `FXMLLoader.load` with a location, and only the +/// registry the build generates afterwards refers to the classes. +/// +/// What is written here is what makes that later step safe. The source holds +/// a digest of every document, so a build in which only a document changed +/// changes a source and javac compiles the application again; and it names +/// `javafx.fxml.FXMLLoader`, which the relocation that ends a build renames, +/// so the class javac makes of it says whether the classes directory is as +/// javac left it or as the last build's relocation did +/// ([FxmlClassCompiler#relocated(File)]). +/// +/// The path of a file relative to its resource directory is its resource +/// path, the one `getResource` finds it under on a desktop. With several +/// resource directories the first that has a path wins, as on a class path. +/// +/// #### Idempotence +/// +/// A file is only written when its content changes, and an output of an +/// earlier run whose source is gone is deleted, so a build that changes no +/// document recompiles nothing downstream. +public final class DesktopResourceCompiler { + + /// Where progress and warnings are reported. + public interface Log { + /// Something worth a line of build output. + void info(String message); + + /// Something that was left out of the build's result. + void warn(String message); + } + + private final List resourceDirs; + private final File javaOut; + private final File resourcesOut; + private final Log log; + private int fxmlCount; + private int cssCount; + + /// Creates a compiler. + /// + /// #### Parameters + /// + /// - `resourceDirs`: the directories of desktop resources; one that + /// does not exist is ignored + /// + /// - `javaOut`: the root of the generated Java sources + /// + /// - `resourcesOut`: the directory of the compiled style sheets + /// + /// - `log`: where to report + public DesktopResourceCompiler(List resourceDirs, File javaOut, File resourcesOut, Log log) { + this.resourceDirs = new ArrayList(resourceDirs); + this.javaOut = javaOut; + this.resourcesOut = resourcesOut; + this.log = log; + } + + /// How many documents the last run found. + public int fxmlCount() { + return fxmlCount; + } + + /// How many style sheets the last run compiled. + public int cssCount() { + return cssCount; + } + + /// Compiles everything. Warnings go to the log; errors are answered, + /// each as `file:line:column: message`, and an empty list means the + /// outputs are complete. With errors the outputs of the documents that + /// failed are absent. + public List run() throws IOException { + Map fxml = new TreeMap(); + Map css = new TreeMap(); + for (File dir : resourceDirs) { + if (dir.isDirectory()) { + collect(dir, "", fxml, css); + } + } + Messages messages = new Messages(); + prepareFxml(fxml, messages); + compileCss(css, messages); + for (String warning : messages.warnings()) { + log.warn(warning); + } + fxmlCount = fxml.size(); + cssCount = css.size(); + if (fxmlCount + cssCount > 0 && !messages.hasErrors()) { + log.info("Compiled " + cssCount + " style sheet" + (cssCount == 1 ? "" : "s") + "; " + fxmlCount + + " FXML document" + (fxmlCount == 1 ? " is" : "s are") + " compiled after the application's" + + " classes"); + } + return messages.errors(); + } + + /// Deletes everything an earlier run wrote, as a run that found no + /// document and no sheet does. A build calls it instead of [#run()] when + /// the JavaFX layer has left its class path: the generated source names + /// the JavaFX API, and left in place it would no longer compile. + public void clear() throws IOException { + Messages messages = new Messages(); + prepareFxml(new TreeMap(), messages); + compileCss(new TreeMap(), messages); + fxmlCount = 0; + cssCount = 0; + } + + /// The FXML documents under `resourceDirs` by resource path, in the + /// order of their paths. + static Map documents(List resourceDirs) { + Map fxml = new TreeMap(); + Map css = new TreeMap(); + for (File dir : resourceDirs) { + if (dir != null && dir.isDirectory()) { + collect(dir, "", fxml, css); + } + } + return fxml; + } + + private static void collect(File dir, String prefix, Map fxml, Map css) { + File[] files = dir.listFiles(); + if (files == null) { + return; + } + Arrays.sort(files); + for (File f : files) { + String name = f.getName(); + if (f.isDirectory()) { + collect(f, prefix + name + "/", fxml, css); + } else if (name.endsWith(".fxml") && !fxml.containsKey(prefix + name)) { + fxml.put(prefix + name, f); + } else if (name.endsWith(".css") && !css.containsKey(prefix + name)) { + css.put(prefix + name, f); + } + } + } + + static byte[] read(File f) throws IOException { + InputStream in = new FileInputStream(f); + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buffer = new byte[8192]; + int n; + while ((n = in.read(buffer)) > 0) { + out.write(buffer, 0, n); + } + return out.toByteArray(); + } finally { + in.close(); + } + } + + /// Parses every document and writes the source that stands for them + /// all; see the class description. + private void prepareFxml(Map files, Messages messages) throws IOException { + File marker = new File(new File(javaOut, FxmlCompiler.PACKAGE.replace('.', '/')), + FxmlClassCompiler.MARKER + ".java"); + // A source an earlier form of this step wrote there, which javac must + // not find: it names classes javac has not compiled yet. + deleteStale(marker.getParentFile(), "Fxml_", ".java", new HashSet()); + if (files.isEmpty()) { + if (marker.isFile() && !marker.delete()) { + throw new IOException("Could not delete the stale " + marker); + } + return; + } + parse(files, messages); + FxmlDispatchGenerator.writeIfDifferent(marker, + FxmlClassCompiler.markerSource(files).getBytes(StandardCharsets.UTF_8)); + } + + /// The parsed form of every document, by resource path. One that is not + /// well formed is left out, with the reason among `messages`. + static Map parse(Map files, Messages messages) throws IOException { + Map documents = new LinkedHashMap(); + for (Map.Entry e : files.entrySet()) { + InputStream in = new FileInputStream(e.getValue()); + try { + FxmlDocument doc = FxmlDocument.parse(e.getValue().getPath(), e.getKey(), in, messages); + if (doc != null) { + documents.put(e.getKey(), doc); + } + } finally { + in.close(); + } + } + return documents; + } + + private void compileCss(Map files, Messages messages) throws IOException { + Set written = new HashSet(); + for (Map.Entry e : files.entrySet()) { + String text = new String(read(e.getValue()), StandardCharsets.UTF_8); + CssSheetData data = CssCompiler.compile(e.getValue().getPath(), e.getKey(), text, files.keySet(), + messages); + if (data != null) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + data.write(out); + String name = CssSheetData.compiledName(ResourceNames.flatName(e.getKey())); + FxmlDispatchGenerator.writeIfDifferent(new File(resourcesOut, name), out.toByteArray()); + written.add(name); + } + } + deleteStale(resourcesOut, "", CssSheetData.SUFFIX, written); + } + + static void deleteStale(File dir, String prefix, String suffix, Set keep) throws IOException { + File[] files = dir.listFiles(); + if (files == null) { + return; + } + for (File f : files) { + String name = f.getName(); + if (f.isFile() && name.startsWith(prefix) && name.endsWith(suffix) && !keep.contains(name) + && !f.delete()) { + throw new IOException("Could not delete the stale " + f); + } + } + } +} diff --git a/maven/fxml-compiler/src/main/java/com/codename1/fxml/FxmlClassCompiler.java b/maven/fxml-compiler/src/main/java/com/codename1/fxml/FxmlClassCompiler.java new file mode 100644 index 00000000000..17dab394a7f --- /dev/null +++ b/maven/fxml-compiler/src/main/java/com/codename1/fxml/FxmlClassCompiler.java @@ -0,0 +1,364 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.fxml; + +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import javax.tools.Diagnostic; +import javax.tools.DiagnosticCollector; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; +import javax.tools.SimpleJavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.StandardLocation; +import javax.tools.ToolProvider; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +/// Compiles an application's FXML documents into classes, after javac has +/// compiled the application and before the build relocates it. +/// +/// #### Why after javac +/// +/// An element of a document is a class, and the compiler has to read that +/// class to know its constructors and the type of each setter +/// ([FxmlCompiler]). A custom control -- a class of the application used as +/// an element -- has no class file until javac has run, so a document can +/// only be compiled afterwards. That costs the application nothing: no +/// source of its own names a compiled document. +/// +/// So this step reads the documents' classes from the layer's jar AND from +/// the directories javac (and Kotlin) compiled into, writes one Java source +/// per document, and compiles those sources itself, in process, into the +/// classes directory. It is the second, small javac run of a build. +/// +/// #### Why before the relocation, and how that is known +/// +/// The generated source names the JavaFX API as an application does +/// (`javafx.scene.control.Button`), and is compiled against the classes the +/// application's own javac run left. Once the build has relocated those, a +/// custom control extends `com.codename1.fxcompat.javafx...` and no longer +/// fits where the source expects a `javafx.scene.Node`. +/// +/// A build in which nothing changed does not run javac, and its classes +/// directory is still the relocated one of the build before. The step must +/// then do nothing, and it knows from the class +/// [DesktopResourceCompiler] had javac compile with the application: that +/// class names `javafx.fxml.FXMLLoader`, which a relocation renames +/// ([#relocated(File)]). The same class is why the other case cannot arise -- +/// its source holds a digest of the documents, so a changed document is a +/// changed source, and javac compiles the application again. +/// +/// #### Class file version +/// +/// The sources are compiled at Java 8 whatever JDK runs the build: they use +/// nothing newer, and every consumer of an application's classes reads that +/// version. +public final class FxmlClassCompiler { + + /// The simple name of the class that stands for all the documents; see + /// [DesktopResourceCompiler]. + static final String MARKER = "FxmlDocuments"; + /// What the class of [#MARKER] names until a relocation renames it. + private static final String UNRELOCATED = "javafx/fxml/FXMLLoader"; + + private final List resourceDirs; + private final List classpath; + private final List classDirs; + private final File classesOut; + private final File sourcesOut; + private final DesktopResourceCompiler.Log log; + private int compiled; + + /// Creates a compiler. + /// + /// #### Parameters + /// + /// - `resourceDirs`: the directories of desktop resources; one that + /// does not exist is ignored + /// + /// - `classpath`: the application's compile class path, with the + /// JavaFX layer's jar on it + /// + /// - `classDirs`: every directory the application's own classes were + /// compiled into, as javac and Kotlin left them + /// + /// - `classesOut`: where the documents' classes are written -- the + /// application's classes directory, which is also read as one of + /// `classDirs` + /// + /// - `sourcesOut`: where the generated sources are kept for a reader, + /// or `null` to keep none. Never a directory the application's javac + /// run compiles from + /// + /// - `log`: where to report + public FxmlClassCompiler(List resourceDirs, List classpath, List classDirs, File classesOut, + File sourcesOut, DesktopResourceCompiler.Log log) { + this.resourceDirs = new ArrayList(resourceDirs); + this.classpath = new ArrayList(classpath); + this.classDirs = new ArrayList(); + this.classDirs.add(classesOut); + for (File dir : classDirs) { + if (dir != null && !this.classDirs.contains(dir)) { + this.classDirs.add(dir); + } + } + this.classesOut = classesOut; + this.sourcesOut = sourcesOut; + this.log = log; + } + + /// How many documents the last run compiled; 0 when it had nothing to + /// do. + public int compiled() { + return compiled; + } + + /// The source of the class that stands for `files`, the documents by + /// resource path. + static String markerSource(Map files) throws IOException { + MessageDigest digest; + try { + digest = MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException e) { + throw new IOException("This JDK has no SHA-256", e); + } + for (Map.Entry e : files.entrySet()) { + digest.update(e.getKey().getBytes(StandardCharsets.UTF_8)); + digest.update((byte) 0); + digest.update(DesktopResourceCompiler.read(e.getValue())); + digest.update((byte) 0); + } + StringBuilder hex = new StringBuilder(); + for (byte b : digest.digest()) { + hex.append(Character.forDigit((b >> 4) & 0xf, 16)).append(Character.forDigit(b & 0xf, 16)); + } + StringBuilder s = new StringBuilder(); + s.append("// Generated by the Codename One FXML compiler from the application's .fxml documents.\n"); + s.append("// Do not edit: the file is written again by every build.\n"); + s.append("package ").append(FxmlCompiler.PACKAGE).append(";\n\n"); + s.append("/// Stands for the FXML documents of the application, which are compiled once this\n"); + s.append("/// class is: a changed document changes the digest, and so recompiles the application.\n"); + s.append("public final class ").append(MARKER).append(" {\n\n"); + s.append(" public static final int CN1_COUNT = ").append(files.size()).append(";\n"); + s.append(" public static final String CN1_DIGEST = \"").append(hex).append("\";\n\n"); + s.append(" private ").append(MARKER).append("() {\n }\n\n"); + s.append(" /// The loader the documents are read through. The build tells a classes\n"); + s.append(" /// directory it has relocated from a freshly compiled one by this name.\n"); + s.append(" public static Class cn1Loader() {\n"); + s.append(" return javafx.fxml.FXMLLoader.class;\n"); + s.append(" }\n"); + s.append("}\n"); + return s.toString(); + } + + /// Whether a build has relocated `classesDir` since javac last compiled + /// into it: the class that stands for the documents is there and no + /// longer names `javafx.fxml.FXMLLoader`. False when the class is + /// absent, which is the state of a directory nothing was built into. + public static boolean relocated(File classesDir) throws IOException { + File marker = new File(classesDir, FxmlCompiler.PACKAGE.replace('.', '/') + "/" + MARKER + ".class"); + if (!marker.isFile()) { + return false; + } + final boolean[] fresh = {false}; + new ClassReader(DesktopResourceCompiler.read(marker)).accept(new ClassVisitor(Opcodes.ASM9) { + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, + String[] exceptions) { + return new MethodVisitor(Opcodes.ASM9) { + @Override + public void visitLdcInsn(Object value) { + if (value instanceof Type && UNRELOCATED.equals(((Type) value).getInternalName())) { + fresh[0] = true; + } + } + }; + } + }, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + return !fresh[0]; + } + + /// Compiles every document. Answers the errors, each as + /// `file:line:column: message`; an empty list means the documents' + /// classes are in place, or that there was nothing to do. + public List run() throws IOException { + compiled = 0; + Map files = DesktopResourceCompiler.documents(resourceDirs); + File packageDir = new File(classesOut, FxmlCompiler.PACKAGE.replace('.', '/')); + File sourceDir = sourcesOut == null ? null : new File(sourcesOut, FxmlCompiler.PACKAGE.replace('.', '/')); + if (files.isEmpty()) { + DesktopResourceCompiler.deleteStale(packageDir, "Fxml_", ".class", new HashSet()); + if (sourceDir != null) { + DesktopResourceCompiler.deleteStale(sourceDir, "Fxml_", ".java", new HashSet()); + } + return Collections.emptyList(); + } + if (relocated(classesOut)) { + // Nothing was compiled since the last build relocated this + // directory, so no document changed either: its classes are the + // ones that build made. + return Collections.emptyList(); + } + Messages messages = new Messages(); + Map documents = DesktopResourceCompiler.parse(files, messages); + Map sources = new LinkedHashMap(); + List modelPath = new ArrayList(classDirs); + modelPath.addAll(classpath); + ClassModel model = new ClassModel(modelPath); + try { + FxmlCompiler compiler = new FxmlCompiler(model, documents, messages); + for (FxmlDocument doc : documents.values()) { + String source = compiler.compile(doc); + if (source != null) { + sources.put(FxmlCompiler.className(doc.path), source); + } + } + } finally { + model.close(); + } + // The classes of the build before: of a document that is gone, or + // that no longer compiles. + DesktopResourceCompiler.deleteStale(packageDir, "Fxml_", ".class", new HashSet()); + if (sourceDir != null) { + Set written = new HashSet(); + for (Map.Entry e : sources.entrySet()) { + FxmlDispatchGenerator.writeIfDifferent(new File(sourceDir, e.getKey() + ".java"), + e.getValue().getBytes(StandardCharsets.UTF_8)); + written.add(e.getKey() + ".java"); + } + DesktopResourceCompiler.deleteStale(sourceDir, "Fxml_", ".java", written); + } + if (messages.hasErrors()) { + return messages.errors(); + } + List errors = javac(sources, sourceDir); + if (errors.isEmpty()) { + compiled = sources.size(); + log.info("Compiled " + compiled + " FXML document" + (compiled == 1 ? "" : "s")); + } + return errors; + } + + /// A generated source, handed to javac from memory under the name it + /// has (or would have) on disk. + private static final class Source extends SimpleJavaFileObject { + private final String text; + + Source(URI uri, String text) { + super(uri, Kind.SOURCE); + this.text = text; + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) { + return text; + } + } + + private List javac(Map sources, File sourceDir) throws IOException { + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + if (compiler == null) { + return Collections.singletonList("The FXML documents cannot be compiled: the build is running on a Java" + + " runtime without a compiler. Run it on a JDK."); + } + DiagnosticCollector diagnostics = new DiagnosticCollector(); + StandardJavaFileManager fm = compiler.getStandardFileManager(diagnostics, Locale.ROOT, + StandardCharsets.UTF_8); + try { + if (!classesOut.isDirectory() && !classesOut.mkdirs() && !classesOut.isDirectory()) { + throw new IOException("Could not create " + classesOut); + } + fm.setLocation(StandardLocation.CLASS_OUTPUT, Collections.singletonList(classesOut)); + List cp = new ArrayList(); + for (File dir : classDirs) { + if (dir.isDirectory()) { + cp.add(dir); + } + } + for (File f : classpath) { + if (f.exists()) { + cp.add(f); + } + } + fm.setLocation(StandardLocation.CLASS_PATH, cp); + // Empty rather than unset: without a source path javac looks for + // sources on the class path, and a classes directory may hold + // some as resources. + fm.setLocation(StandardLocation.SOURCE_PATH, Collections.emptyList()); + List units = new ArrayList(); + for (Map.Entry e : sources.entrySet()) { + URI uri = sourceDir != null ? new File(sourceDir, e.getKey() + ".java").toURI() + : URI.create("string:///" + FxmlCompiler.PACKAGE.replace('.', '/') + "/" + e.getKey() + + ".java"); + units.add(new Source(uri, e.getValue())); + } + // -source/-target rather than --release: the build plugins still + // run on a JDK 8. The warning newer compilers print for it is a + // lint warning, which -Xlint:none turns off. + List options = Arrays.asList("-Xlint:none", "-proc:none", "-source", "1.8", "-target", "1.8"); + StringWriter out = new StringWriter(); + Boolean ok = compiler.getTask(out, fm, diagnostics, options, null, units).call(); + List errors = new ArrayList(); + if (ok == null || !ok.booleanValue()) { + for (Diagnostic d : diagnostics.getDiagnostics()) { + if (d.getKind() == Diagnostic.Kind.ERROR) { + JavaFileObject at = d.getSource(); + String where = at == null ? "javac" : sourceDir != null ? new File(at.toUri()).getPath() + : at.getName(); + errors.add(where + ":" + Math.max(d.getLineNumber(), 0) + ":" + + Math.max(d.getColumnNumber(), 0) + ": " + d.getMessage(Locale.ROOT) + + " (in the Java source generated from an FXML document; a member of a" + + " controller or custom control the document names is missing or has another" + + " type)"); + } + } + if (errors.isEmpty()) { + errors.add("javac failed on the sources generated from the FXML documents: " + out); + } + } + return errors; + } finally { + fm.close(); + } + } +} diff --git a/maven/fxml-compiler/src/main/java/com/codename1/fxml/FxmlCompiler.java b/maven/fxml-compiler/src/main/java/com/codename1/fxml/FxmlCompiler.java new file mode 100644 index 00000000000..6c007396811 --- /dev/null +++ b/maven/fxml-compiler/src/main/java/com/codename1/fxml/FxmlCompiler.java @@ -0,0 +1,1296 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.fxml; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +import com.codename1.desktopcompat.shared.ResourceNames; +import com.codename1.fxml.FxmlDocument.Attribute; +import com.codename1.fxml.FxmlDocument.Element; +import com.codename1.fxml.css.CssValue; +import com.codename1.fxml.css.CssValueParser; + +/// Turns one FXML document into the Java source of a class that builds the +/// same object tree with plain constructors and setters. +/// +/// #### The generated class +/// +/// `com.codename1.generated.fxml.Fxml_`, where `` is the +/// document's resource path without its extension, encoded so that two +/// paths never share a name ([#className(String)]). It has +/// +/// - `CN1_PATH`, the resource path; `CN1_CONTROLLER`, the binary name of +/// the `fx:controller` class or an empty string; `CN1_HANDLERS`, the +/// `#handler` names the document uses, separated by commas. The step +/// that generates the dispatcher after compilation reads these three out +/// of the class file. +/// - `load(FxmlContext)`, which builds the tree and answers the root. +/// - one private method per element, so that a large document does not +/// outgrow the size a single method may have. +/// +/// #### What decides the code +/// +/// Every class a document names is looked up in the class files of the +/// application and of its class path ([ClassModel]); the compiler therefore +/// knows the parameter type of each setter and converts an attribute's text +/// to it while compiling. A class or a property that is not there is an +/// error with the line of the element -- not something to find out on a +/// device. The application's own classes are among them, because documents +/// are compiled after it is ([FxmlClassCompiler]): a custom control is an +/// element like any other. The generated source is then compiled by javac, +/// which checks what this compiler does not. +final class FxmlCompiler { + + /// The package of every generated class. + static final String PACKAGE = "com.codename1.generated.fxml"; + private static final String CONTEXT = "com.codename1.fxcompat.runtime.FxmlContext"; + private static final String EVENT_HANDLER = "javafx/event/EventHandler"; + private static final String LIST = "java/util/List"; + private static final Type OBJECT = Type.getObjectType("java/lang/Object"); + private static final Type STRING = Type.getObjectType("java/lang/String"); + + /// The default property of the classes of the layer, for the ones that + /// do not say it with `@DefaultProperty`: nearest class first. + private static final String[][] DEFAULT_PROPERTIES = { + {"javafx/scene/layout/Pane", "children"}, {"javafx/scene/Group", "children"}, + {"javafx/scene/text/TextFlow", "children"}, {"javafx/scene/control/ScrollPane", "content"}, + {"javafx/scene/control/TabPane", "tabs"}, {"javafx/scene/control/Tab", "content"}, + {"javafx/scene/control/TitledPane", "content"}, {"javafx/scene/control/Accordion", "panes"}, + {"javafx/scene/control/SplitPane", "items"}, {"javafx/scene/control/ToolBar", "items"}, + {"javafx/scene/control/MenuBar", "menus"}, {"javafx/scene/control/Menu", "items"}, + {"javafx/scene/control/MenuButton", "items"}, {"javafx/scene/control/ContextMenu", "items"}, + {"javafx/scene/control/ButtonBar", "buttons"}, {"javafx/scene/control/ListView", "items"}, + {"javafx/scene/control/TableView", "items"}, {"javafx/scene/control/TableColumn", "columns"}, + {"javafx/scene/control/TreeView", "root"}, {"javafx/scene/control/TreeItem", "children"}, + {"javafx/scene/control/ComboBox", "items"}, {"javafx/scene/control/ChoiceBox", "items"}, + {"javafx/scene/control/ToggleGroup", "toggles"}, {"javafx/scene/control/DialogPane", "content"}, + {"javafx/scene/control/Labeled", "text"}, {"javafx/scene/control/TextInputControl", "text"}, + {"javafx/scene/text/Text", "text"}, {"javafx/scene/image/ImageView", "image"}, + {"javafx/scene/shape/Path", "elements"}, {"javafx/scene/shape/Polygon", "points"}, + {"javafx/scene/shape/Polyline", "points"}, {"javafx/scene/Scene", "root"}, + {"javafx/stage/Stage", "scene"}, {"javafx/animation/Timeline", "keyFrames"}, + {"javafx/animation/SequentialTransition", "children"}, + {"javafx/animation/ParallelTransition", "children"}, + }; + + /// A compile error with the element it is about. + private static final class Failure extends Exception { + private static final long serialVersionUID = 1L; + final transient Element at; + + Failure(Element at, String message) { + super(message); + this.at = at; + } + } + + /// An expression of the generated source and the type it has. + private static final class Value { + final String code; + final Type type; + + Value(String code, Type type) { + this.code = code; + this.type = type; + } + } + + private final ClassModel model; + private final Map documents; + private final Messages messages; + private final CssValueParser values = new CssValueParser(); + + // The document being compiled. + private FxmlDocument doc; + private final List methods = new ArrayList(); + private final Map ids = new HashMap(); + private final Set handlers = new LinkedHashSet(); + private String controller; + private int counter; + + /// Creates a compiler. + /// + /// #### Parameters + /// + /// - `model`: the classes of the application's class path + /// + /// - `documents`: every document of the application by resource path, + /// which an `` is resolved in + /// + /// - `messages`: where errors go + FxmlCompiler(ClassModel model, Map documents, Messages messages) { + this.model = model; + this.documents = documents; + this.messages = messages; + } + + /// The simple name of the class generated for the document at a + /// resource path. The encoding is one to one: a letter or a digit + /// stands for itself, `/` is `__`, `_` is `_u`, `.` is `_d`, `-` is + /// `_m` and any other character is `_x` and its code in four hex + /// digits. + static String className(String path) { + String p = path.endsWith(".fxml") ? path.substring(0, path.length() - 5) : path; + StringBuilder s = new StringBuilder("Fxml_"); + for (int i = 0; i < p.length(); i++) { + char c = p.charAt(i); + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) { + s.append(c); + } else if (c == '/') { + s.append("__"); + } else if (c == '_') { + s.append("_u"); + } else if (c == '.') { + s.append("_d"); + } else if (c == '-') { + s.append("_m"); + } else { + String hex = Integer.toHexString(c); + s.append("_x").append("0000".substring(hex.length())).append(hex); + } + } + return s.toString(); + } + + /// Compiles a document. Answers the source of its class, or `null` + /// with the reason recorded as an error. + String compile(FxmlDocument document) throws IOException { + doc = document; + methods.clear(); + ids.clear(); + handlers.clear(); + controller = null; + counter = 0; + try { + return generate(); + } catch (Failure f) { + Element at = f.at == null ? doc.root : f.at; + messages.error(doc.file, at.line, at.column, f.getMessage() + " (at " + at.text() + ")"); + return null; + } + } + + private String generate() throws Failure, IOException { + Element root = doc.root; + String declared = root.fx("controller"); + if (declared != null) { + controller = declared.trim(); + if (!isQualifiedName(controller)) { + throw new Failure(root, "fx:controller=\"" + declared + "\" is not a class name"); + } + } + Value tree = value(root, true); + String name = className(doc.path); + StringBuilder s = new StringBuilder(); + s.append("// Generated from ").append(doc.path).append(" by the Codename One FXML compiler.\n"); + s.append("// Do not edit: the file is written again by every build.\n"); + s.append("package ").append(PACKAGE).append(";\n\n"); + s.append("@SuppressWarnings({\"rawtypes\", \"unchecked\", \"cast\", \"deprecation\"})\n"); + s.append("public final class ").append(name).append(" {\n\n"); + s.append(" public static final String CN1_PATH = ").append(literal(doc.path)).append(";\n"); + s.append(" public static final String CN1_CONTROLLER = ") + .append(literal(controller == null ? "" : controller)).append(";\n"); + StringBuilder names = new StringBuilder(); + for (String h : handlers) { + names.append(names.length() == 0 ? "" : ",").append(h); + } + s.append(" public static final String CN1_HANDLERS = ").append(literal(names.toString())).append(";\n\n"); + s.append(" private ").append(name).append("() {\n }\n\n"); + s.append(" public static Object load(").append(CONTEXT).append(" c) throws Exception {\n"); + s.append(" Object root = ").append(tree.code).append(";\n"); + s.append(" c.done();\n"); + s.append(" return root;\n"); + s.append(" }\n"); + for (String m : methods) { + s.append('\n').append(m); + } + s.append("}\n"); + return s.toString(); + } + + // ------------------------------------------------------------- names + + private static boolean isIdentifier(String s) { + if (s.length() == 0 || !Character.isJavaIdentifierStart(s.charAt(0))) { + return false; + } + for (int i = 1; i < s.length(); i++) { + if (!Character.isJavaIdentifierPart(s.charAt(i))) { + return false; + } + } + return true; + } + + private static boolean isQualifiedName(String s) { + int from = 0; + while (true) { + int dot = s.indexOf('.', from); + if (!isIdentifier(s.substring(from, dot < 0 ? s.length() : dot))) { + return false; + } + if (dot < 0) { + return true; + } + from = dot + 1; + } + } + + /// A Java string literal in ASCII. + static String literal(String text) { + StringBuilder s = new StringBuilder("\""); + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + if (c == '"' || c == '\\') { + s.append('\\').append(c); + } else if (c == '\n') { + s.append('\\').append('n'); + } else if (c == '\r') { + s.append('\\').append('r'); + } else if (c == '\t') { + s.append('\\').append('t'); + } else if (c < ' ' || c > '~') { + String hex = Integer.toHexString(c); + s.append('\\').append('u').append("0000".substring(hex.length())).append(hex); + } else { + s.append(c); + } + } + return s.append('"').toString(); + } + + private static String cap(String property) { + if (property.length() == 0) { + return property; + } + char first = property.charAt(0); + // By hand: a property name is ASCII, and the locale must not fold it. + return (first >= 'a' && first <= 'z' ? (char) (first - ('a' - 'A')) : first) + property.substring(1); + } + + /// The type as Java source spells it. + private static String java(Type t) { + if (t.getSort() == Type.ARRAY) { + StringBuilder s = new StringBuilder(java(t.getElementType())); + for (int i = 0; i < t.getDimensions(); i++) { + s.append("[]"); + } + return s.toString(); + } + return t.getClassName().replace('$', '.'); + } + + private static boolean isObject(Type t) { + return t.getSort() == Type.OBJECT; + } + + /// The class a name in the document stands for: a qualified name, or a + /// simple one found through the imports. + private ClassModel.Info resolve(String name) throws IOException { + if (name.indexOf('.') >= 0) { + ClassModel.Info direct = qualified(name); + if (direct != null) { + return direct; + } + } + for (String imported : doc.imports) { + ClassModel.Info found = null; + if (imported.endsWith(".*")) { + found = qualified(imported.substring(0, imported.length() - 1) + name); + } else if (imported.equals(name) || imported.endsWith("." + name)) { + found = qualified(imported); + } else { + int dot = name.indexOf('.'); + if (dot > 0 && (imported.equals(name.substring(0, dot)) + || imported.endsWith("." + name.substring(0, dot)))) { + // A nested class named through its imported outer class. + found = qualified(imported + name.substring(dot)); + } + } + if (found != null) { + return found; + } + } + return name.indexOf('.') < 0 ? model.find("java/lang/" + name) : null; + } + + /// The class of a dotted name, trying each split between package and + /// nested class. + private ClassModel.Info qualified(String dotted) throws IOException { + String internal = dotted.replace('.', '/'); + while (true) { + ClassModel.Info info = model.find(internal); + if (info != null) { + return info; + } + int slash = internal.lastIndexOf('/'); + if (slash < 0) { + return null; + } + internal = internal.substring(0, slash) + '$' + internal.substring(slash + 1); + } + } + + private ClassModel.Info need(String name, Element at) throws Failure, IOException { + ClassModel.Info info = resolve(name); + if (info == null) { + throw new Failure(at, "unknown class " + name + ": it is not imported by a of this" + + " document, or it is neither a class of the application nor on its class path. Import a" + + " custom control by its full name () or its package" + + " ()"); + } + if (!info.isPublic()) { + throw new Failure(at, "the class " + info.sourceName() + " is not public"); + } + return info; + } + + // ------------------------------------------------------- properties + + private ClassModel.Method setter(String owner, String property) throws IOException { + List all = model.methods(owner, "set" + cap(property)); + ClassModel.Method getter = getter(owner, property); + ClassModel.Method first = null; + for (ClassModel.Method m : all) { + if (m.isStatic() || m.parameters.length != 1) { + continue; + } + if (getter != null && getter.returnType().equals(m.parameters[0])) { + return m; + } + first = first == null ? m : first; + } + return first; + } + + private ClassModel.Method getter(String owner, String property) throws IOException { + for (String prefix : new String[] {"get", "is"}) { + for (ClassModel.Method m : model.methods(owner, prefix + cap(property))) { + if (!m.isStatic() && m.parameters.length == 0 && m.returnType().getSort() != Type.VOID) { + return m; + } + } + } + return null; + } + + private ClassModel.Method staticSetter(String owner, String property) throws IOException { + for (ClassModel.Method m : model.methods(owner, "set" + cap(property))) { + if (m.isStatic() && m.parameters.length == 2) { + return m; + } + } + return null; + } + + private boolean isList(Type t) throws IOException { + return isObject(t) && model.isAssignable(t.getInternalName(), LIST); + } + + /// The element type a list getter declares, or `null` when the class + /// file does not say (a raw list, a type variable, a wildcard). + private static Type elementType(ClassModel.Method getter) { + String sig = getter.signature; + if (sig == null) { + return null; + } + int close = sig.indexOf(')'); + int open = sig.indexOf('<', close); + if (close < 0 || open < 0 || open + 1 >= sig.length() || sig.charAt(open + 1) != 'L') { + return null; + } + int end = open + 2; + while (end < sig.length() && sig.charAt(end) != ';' && sig.charAt(end) != '<') { + end++; + } + return Type.getObjectType(sig.substring(open + 2, end)); + } + + private String defaultProperty(String owner) throws IOException { + String annotated = model.annotatedDefaultProperty(owner); + if (annotated != null) { + return annotated; + } + String best = null; + int bestDepth = Integer.MAX_VALUE; + for (String[] entry : DEFAULT_PROPERTIES) { + int depth = depth(owner, entry[0]); + if (depth >= 0 && depth < bestDepth) { + best = entry[1]; + bestDepth = depth; + } + } + return best; + } + + /// How many classes `ancestor` is above `cls`; -1 when it is not. + private int depth(String cls, String ancestor) throws IOException { + int depth = 0; + String at = cls; + while (at != null) { + if (at.equals(ancestor)) { + return depth; + } + ClassModel.Info info = model.find(at); + at = info == null ? null : info.superName; + depth++; + } + return -1; + } + + // ----------------------------------------------------------- values + + /// Builds the value an element stands for, in a method of its own, and + /// answers the call of that method. + private Value value(Element e, boolean isRoot) throws Failure, IOException { + StringBuilder body = new StringBuilder(); + Type type; + boolean instance = false; + Set consumed = new LinkedHashSet(); + Set consumedElements = new LinkedHashSet(); + if (e.fx) { + if ("root".equals(e.name)) { + if (!isRoot) { + throw new Failure(e, " can only be the root element of a document"); + } + String typeName = e.plain("type"); + if (typeName == null) { + throw new Failure(e, " needs a type attribute"); + } + ClassModel.Info info = need(typeName.trim(), e); + type = Type.getObjectType(info.name); + body.append(" Object given = c.root();\n"); + body.append(" if (!(given instanceof ").append(java(type)).append(")) {\n"); + body.append(" throw c.error(").append(literal("the root set on the loader must be a " + + java(type) + ", as the element says")).append(");\n }\n"); + body.append(" ").append(java(type)).append(" v = (").append(java(type)).append(") given;\n"); + instance = true; + } else if ("include".equals(e.name)) { + String source = e.plain("source"); + if (source == null) { + throw new Failure(e, " needs a source attribute"); + } + FxmlDocument included = documents.get(resolvePath(source)); + if (included == null) { + throw new Failure(e, "the included document " + source + " (" + resolvePath(source) + + ") is not among the application's desktop resources"); + } + type = rootType(included, e); + String id = e.fx("id"); + body.append(" Object loaded = c.include(").append(literal(source)).append(", ") + .append(id == null ? "null" : literal(id)).append(");\n"); + body.append(" if (!(loaded instanceof ").append(java(type)).append(")) {\n"); + body.append(" throw c.error(").append(literal("the included document " + source + + " did not answer a " + java(type))).append(");\n }\n"); + body.append(" ").append(java(type)).append(" v = (").append(java(type)) + .append(") loaded;\n"); + if (id != null) { + checkId(id, e); + ids.put(id, type); + String includedController = included.root.fx("controller"); + if (includedController != null) { + ids.put(id + "Controller", Type.getObjectType(includedController.trim().replace('.', '/'))); + } + } + } else if ("reference".equals(e.name)) { + String source = e.plain("source"); + if (source == null) { + throw new Failure(e, " needs a source attribute"); + } + Value ref = reference(source.trim(), e); + type = ref.type; + body.append(" ").append(java(type)).append(" v = ").append(ref.code).append(";\n"); + } else if ("script".equals(e.name)) { + throw new Failure(e, " is not supported: a document is compiled to Java, and a" + + " script would have to be interpreted on the device. Write the code in the controller"); + } else { + throw new Failure(e, " is not supported"); + } + } else { + ClassModel.Info info = need(e.name, e); + type = construct(e, info, body, consumed, consumedElements); + instance = true; + } + if (e.fx("controller") != null && !isRoot) { + throw new Failure(e, "fx:controller can only be on the root element"); + } + if (isRoot && controller != null) { + String source = controller.replace('$', '.'); + body.insert(0, " c.controller(" + source + ".class, " + literal(controller) + ");\n"); + } + String id = e.fx("id"); + if (id != null && !(e.fx && "include".equals(e.name))) { + checkId(id, e); + ids.put(id, type); + body.append(" c.id(").append(literal(id)).append(", v);\n"); + } + for (Attribute a : e.attributes) { + if (a.fx || consumed.contains(a)) { + continue; + } + if (e.fx && ("source".equals(a.name) || "type".equals(a.name) || "resources".equals(a.name) + || "charset".equals(a.name))) { + continue; + } + int dot = a.name.lastIndexOf('.'); + if (dot > 0) { + staticAttribute(e, a, a.name.substring(0, dot), a.name.substring(dot + 1), type, body); + } else if (instance || (e.fx && "include".equals(e.name))) { + attribute(e, a, type, body); + } else { + throw new Failure(e, a.text() + " cannot be set here"); + } + } + if (instance) { + children(e, type, body, consumedElements); + } + String method = "e" + counter++; + StringBuilder m = new StringBuilder(); + m.append(" private static ").append(java(type)).append(' ').append(method).append('(').append(CONTEXT) + .append(" c) throws Exception {\n").append(body).append(" return v;\n }\n"); + methods.add(m.toString()); + return new Value(method + "(c)", type); + } + + private void checkId(String id, Element e) throws Failure { + if (!isIdentifier(id)) { + throw new Failure(e, "fx:id=\"" + id + "\" is not a valid name"); + } + if (ids.containsKey(id)) { + throw new Failure(e, "fx:id=\"" + id + "\" is used twice in this document"); + } + } + + private String resolvePath(String source) { + if (source.startsWith("/")) { + return ResourceNames.normalize(source); + } + int slash = doc.path.lastIndexOf('/'); + return ResourceNames.normalize(slash < 0 ? source : doc.path.substring(0, slash + 1) + source); + } + + /// The type of the root of another document, resolved with that + /// document's own imports. + private Type rootType(FxmlDocument other, Element at) throws Failure, IOException { + FxmlDocument mine = doc; + doc = other; + try { + Element root = other.root; + String name = root.fx && "root".equals(root.name) ? root.plain("type") : root.fx ? null : root.name; + ClassModel.Info info = name == null ? null : resolve(name.trim()); + if (info == null) { + throw new Failure(at, "cannot tell what the included document " + other.path + + " builds: its root element " + root.text() + " is not a known class"); + } + return Type.getObjectType(info.name); + } finally { + doc = mine; + } + } + + private Value reference(String id, Element at) throws Failure { + if ("controller".equals(id)) { + if (controller == null) { + throw new Failure(at, "$controller is used but the document has no fx:controller"); + } + String source = controller.replace('$', '.'); + return new Value("((" + source + ") c.controller())", Type.getObjectType(controller.replace('.', '/'))); + } + Type type = ids.get(id); + if (type == null) { + throw new Failure(at, "nothing with fx:id=\"" + id + "\" is defined before this point"); + } + // The cast is to the type the compiler itself recorded for the id + // in this document, so it cannot fail. + return new Value("((" + java(type) + ") c.get(" + literal(id) + "))", type); + } + + // ----------------------------------------------------- construction + + private Type construct(Element e, ClassModel.Info info, StringBuilder body, Set consumed, + Set consumedElements) throws Failure, IOException { + Type type = Type.getObjectType(info.name); + String source = info.sourceName(); + String value = e.fx("value"); + String factory = e.fx("factory"); + String constant = e.fx("constant"); + if (value != null) { + body.append(" ").append(source).append(" v = ").append(coerce(value, type, e)).append(";\n"); + return type; + } + if (constant != null) { + for (ClassModel.Field f : info.fields) { + if (f.name.equals(constant) && (f.access & Opcodes.ACC_STATIC) != 0 + && (f.access & Opcodes.ACC_PUBLIC) != 0) { + Type ft = boxed(Type.getType(f.descriptor)); + body.append(" ").append(java(ft)).append(" v = ").append(source).append('.') + .append(constant).append(";\n"); + return ft; + } + } + throw new Failure(e, source + " has no public static field " + constant); + } + if (factory != null) { + for (ClassModel.Method m : model.methods(info.name, factory)) { + if (m.isStatic() && m.parameters.length == 0 && isObject(m.returnType())) { + Type rt = m.returnType(); + body.append(" ").append(java(rt)).append(" v = ").append(source).append('.') + .append(factory).append("();\n"); + return rt; + } + } + throw new Failure(e, source + " has no public static method " + factory + "() without arguments"); + } + if (info.isAbstract()) { + throw new Failure(e, source + " is abstract and cannot be an element"); + } + ClassModel.Method noArgs = null; + List named = new ArrayList(); + for (ClassModel.Method m : info.methods) { + if ("".equals(m.name) && m.isPublic()) { + if (m.parameters.length == 0) { + noArgs = m; + } else if (m.allArgsNamed()) { + named.add(m); + } + } + } + // What the document sets: an attribute, or a property element that + // holds one value. + Map given = new LinkedHashMap(); + boolean allSettable = true; + for (Attribute a : e.attributes) { + if (!a.fx && a.name.indexOf('.') < 0) { + given.put(a.name, a); + allSettable &= setter(info.name, a.name) != null || getter(info.name, a.name) != null; + } + } + for (Element child : e.elements()) { + if (!child.fx && child.name.indexOf('.') < 0 && Character.isLowerCase(child.name.charAt(0)) + && !given.containsKey(child.name)) { + given.put(child.name, child); + allSettable &= setter(info.name, child.name) != null || getter(info.name, child.name) != null; + } + } + if (noArgs != null && (allSettable || named.isEmpty())) { + body.append(" ").append(source).append(" v = new ").append(source).append("();\n"); + return type; + } + ClassModel.Method best = null; + int bestMatched = -1; + for (ClassModel.Method m : named) { + int matched = 0; + for (String arg : m.argNames) { + matched += given.containsKey(arg) ? 1 : 0; + } + if (matched > bestMatched || (matched == bestMatched && best != null + && m.parameters.length < best.parameters.length)) { + best = m; + bestMatched = matched; + } + } + if (best == null) { + throw new Failure(e, source + " has no public constructor without arguments and none whose" + + " arguments are all named with @NamedArg, so a document cannot create it"); + } + StringBuilder args = new StringBuilder(); + for (int i = 0; i < best.parameters.length; i++) { + Type pt = best.parameters[i]; + Object from = given.get(best.argNames[i]); + String code; + if (from instanceof Attribute) { + Attribute a = (Attribute) from; + consumed.add(a); + code = attributeValue(a.value, pt, e, a); + } else if (from instanceof Element) { + Element child = (Element) from; + consumedElements.add(child); + List inner = child.elements(); + if (inner.size() == 1) { + Value v = value(inner.get(0), false); + requireAssignable(v.type, pt, child); + code = v.code; + } else if (inner.isEmpty() && child.content().length() > 0) { + code = coerce(child.content(), pt, child); + } else { + throw new Failure(child, "the constructor argument " + child.name + " needs exactly one value"); + } + } else if (best.argDefaults[i] != null && best.argDefaults[i].length() > 0) { + code = coerce(best.argDefaults[i], pt, e); + } else { + code = zero(pt); + } + args.append(i == 0 ? "" : ", ").append(code); + } + body.append(" ").append(source).append(" v = new ").append(source).append('(').append(args) + .append(");\n"); + return type; + } + + private static String zero(Type t) { + switch (t.getSort()) { + case Type.BOOLEAN: + return "false"; + case Type.CHAR: + return "'\\0'"; + case Type.BYTE: + return "(byte) 0"; + case Type.SHORT: + return "(short) 0"; + case Type.INT: + return "0"; + case Type.LONG: + return "0L"; + case Type.FLOAT: + return "0f"; + case Type.DOUBLE: + return "0.0"; + default: + return "(" + java(t) + ") null"; + } + } + + private static Type boxed(Type t) { + switch (t.getSort()) { + case Type.BOOLEAN: + return Type.getObjectType("java/lang/Boolean"); + case Type.CHAR: + return Type.getObjectType("java/lang/Character"); + case Type.BYTE: + return Type.getObjectType("java/lang/Byte"); + case Type.SHORT: + return Type.getObjectType("java/lang/Short"); + case Type.INT: + return Type.getObjectType("java/lang/Integer"); + case Type.LONG: + return Type.getObjectType("java/lang/Long"); + case Type.FLOAT: + return Type.getObjectType("java/lang/Float"); + case Type.DOUBLE: + return Type.getObjectType("java/lang/Double"); + default: + return t; + } + } + + private void requireAssignable(Type from, Type to, Element at) throws Failure, IOException { + Type target = boxed(to); + if (!isObject(from) || !isObject(target)) { + if (from.equals(target)) { + return; + } + throw new Failure(at, "a " + java(from) + " cannot be used where a " + java(to) + " is expected"); + } + if (model.find(from.getInternalName()) == null || model.find(target.getInternalName()) == null) { + // A class the model cannot read; javac decides. + return; + } + if (!model.isAssignable(from.getInternalName(), target.getInternalName())) { + throw new Failure(at, "a " + java(from) + " cannot be used where a " + java(to) + " is expected"); + } + } + + // ------------------------------------------------------- attributes + + private void staticAttribute(Element e, Attribute a, String className, String property, Type type, + StringBuilder body) throws Failure, IOException { + ClassModel.Info owner = need(className, e); + ClassModel.Method m = staticSetter(owner.name, property); + if (m == null) { + throw new Failure(e, a.text() + ": " + owner.sourceName() + " has no static property " + property + + " (a public static set" + cap(property) + " with two arguments)"); + } + requireAssignable(type, m.parameters[0], e); + body.append(" ").append(owner.sourceName()).append(".set").append(cap(property)).append("(v, ") + .append(attributeValue(a.value, m.parameters[1], e, a)).append(");\n"); + } + + private void attribute(Element e, Attribute a, Type type, StringBuilder body) throws Failure, IOException { + if (!isObject(type)) { + throw new Failure(e, a.text() + " cannot be set on a " + java(type)); + } + String owner = type.getInternalName(); + String raw = a.value; + if (raw.startsWith("${") && raw.endsWith("}")) { + binding(e, a, owner, raw.substring(2, raw.length() - 1).trim(), body); + return; + } + ClassModel.Method set = setter(owner, a.name); + if (set != null) { + Type pt = set.parameters[0]; + if (isObject(pt) && EVENT_HANDLER.equals(pt.getInternalName())) { + if (!raw.startsWith("#")) { + throw new Failure(e, a.text() + ": a handler is written as \"#method\", naming a method of" + + " the controller. Script handlers are not supported, because a document is" + + " compiled to Java"); + } + String name = raw.substring(1).trim(); + if (!isIdentifier(name)) { + throw new Failure(e, a.text() + ": '" + name + "' is not a method name"); + } + if (controller == null) { + throw new Failure(e, a.text() + ": the document has no fx:controller for the handler to be a" + + " method of. A controller set with setController() is not known to the compiler;" + + " name its class with fx:controller"); + } + handlers.add(name); + body.append(" v.set").append(cap(a.name)).append("(c.handler(").append(literal(name)) + .append("));\n"); + return; + } + body.append(" v.set").append(cap(a.name)).append('(').append(attributeValue(raw, pt, e, a)) + .append(");\n"); + return; + } + ClassModel.Method get = getter(owner, a.name); + if (get != null && isList(get.returnType())) { + Type element = elementType(get); + Type target = element == null ? STRING : element; + int from = 0; + while (from <= raw.length()) { + int comma = raw.indexOf(',', from); + if (comma < 0) { + comma = raw.length(); + } + String item = raw.substring(from, comma).trim(); + if (item.length() > 0) { + body.append(" ((java.util.List) v.").append(get.name).append("()).add(") + .append(attributeValue(item, target, e, a)).append(");\n"); + } + from = comma + 1; + } + return; + } + throw new Failure(e, a.text() + ": " + java(type) + " has no property " + a.name + " (no public set" + + cap(a.name) + " and no list get" + cap(a.name) + ")"); + } + + /// The code of an attribute's value: a resource key, a location, a + /// reference, or text converted to the target type. + private String attributeValue(String raw, Type target, Element e, Attribute a) throws Failure, IOException { + if (raw.startsWith("\\")) { + return coerce(raw.substring(1), target, e); + } + if (raw.startsWith("%")) { + requireString(target, e, a); + return "c.string(" + literal(raw.substring(1)) + ")"; + } + if (raw.startsWith("@")) { + requireString(target, e, a); + return "c.url(" + literal(raw.substring(1)) + ")"; + } + if (raw.startsWith("${")) { + throw new Failure(e, a.text() + ": a ${binding} can only be the whole value of a property"); + } + if (raw.startsWith("$")) { + Value ref = reference(raw.substring(1).trim(), e); + requireAssignable(ref.type, target, e); + return ref.code; + } + return coerce(raw, target, e); + } + + private void requireString(Type target, Element e, Attribute a) throws Failure { + if (!isObject(target) || !("java/lang/String".equals(target.getInternalName()) + || "java/lang/Object".equals(target.getInternalName()) + || "java/lang/CharSequence".equals(target.getInternalName()))) { + throw new Failure(e, a.text() + ": the value is text, but the property is a " + java(target)); + } + } + + private void binding(Element e, Attribute a, String owner, String expression, StringBuilder body) + throws Failure, IOException { + int dot = expression.indexOf('.'); + if (dot <= 0 || !isIdentifier(expression.substring(0, dot)) || !isIdentifier(expression.substring(dot + 1))) { + throw new Failure(e, a.text() + ": only a binding to one property of the controller or of an element" + + " with an fx:id is supported, as in ${controller.name} or ${slider.value}. Write any" + + " other expression as a binding in the controller's initialize()"); + } + String property = expression.substring(dot + 1); + Value source = reference(expression.substring(0, dot), e); + if (model.find(source.type.getInternalName()) != null + && model.methods(source.type.getInternalName(), property + "Property").isEmpty()) { + throw new Failure(e, a.text() + ": " + java(source.type) + " has no " + property + "Property()"); + } + if (model.methods(owner, a.name + "Property").isEmpty()) { + throw new Failure(e, a.text() + ": " + owner.replace('/', '.') + " has no " + a.name + + "Property() to bind"); + } + ClassModel.Method set = setter(owner, a.name); + String mode = "BIND_SAME"; + if (set != null) { + Type pt = set.parameters[0]; + if (isObject(pt) && "java/lang/String".equals(pt.getInternalName())) { + mode = "BIND_STRING"; + } else if (pt.getSort() >= Type.BYTE && pt.getSort() <= Type.DOUBLE) { + mode = "BIND_NUMBER"; + } + } + body.append(" ").append(CONTEXT).append(".bind(v.").append(a.name).append("Property(), ") + .append(source.code).append('.').append(property).append("Property(), ").append(CONTEXT) + .append('.').append(mode).append(");\n"); + } + + // --------------------------------------------------------- children + + private void children(Element e, Type type, StringBuilder body, Set consumed) + throws Failure, IOException { + List defaults = new ArrayList(); + Element firstDefault = null; + for (Element child : e.elements()) { + if (consumed.contains(child)) { + continue; + } + if (child.fx && "define".equals(child.name)) { + for (Element defined : child.elements()) { + body.append(" ").append(value(defined, false).code).append(";\n"); + } + continue; + } + int dot = child.name.lastIndexOf('.'); + if (!child.fx && dot > 0 && Character.isLowerCase(child.name.charAt(dot + 1)) + && resolve(child.name) == null) { + staticElement(child, child.name.substring(0, dot), child.name.substring(dot + 1), type, body); + } else if (!child.fx && dot < 0 && Character.isLowerCase(child.name.charAt(0))) { + propertyElement(child, type, body); + } else { + firstDefault = firstDefault == null ? child : firstDefault; + defaults.add(value(child, false)); + } + } + String text = e.content(); + if (defaults.isEmpty() && text.length() == 0) { + return; + } + if (!isObject(type)) { + throw new Failure(e, "a " + java(type) + " cannot have content"); + } + if (isList(type)) { + for (Value v : defaults) { + body.append(" ((java.util.List) v).add(").append(v.code).append(");\n"); + } + if (text.length() > 0) { + body.append(" ((java.util.List) v).add(").append(literal(text)).append(");\n"); + } + return; + } + String property = defaultProperty(type.getInternalName()); + if (property == null) { + throw new Failure(firstDefault == null ? e : firstDefault, java(type) + " has no default property," + + " so content must be inside a property element such as "); + } + assign(e, type, property, defaults, text, body); + } + + private void propertyElement(Element child, Type type, StringBuilder body) throws Failure, IOException { + if (!isObject(type)) { + throw new Failure(child, "a " + java(type) + " has no properties"); + } + for (Attribute a : child.attributes) { + throw new Failure(child, a.text() + ": a property element takes no attributes"); + } + List values = new ArrayList(); + for (Element inner : child.elements()) { + if (inner.fx && "define".equals(inner.name)) { + throw new Failure(inner, " cannot be inside a property element"); + } + values.add(value(inner, false)); + } + assign(child, type, child.name, values, child.content(), body); + } + + /// Gives a property its content: one value through the setter, or + /// every value added to a list the getter answers. + private void assign(Element at, Type type, String property, List values, String text, + StringBuilder body) throws Failure, IOException { + String owner = type.getInternalName(); + ClassModel.Method set = setter(owner, property); + ClassModel.Method get = getter(owner, property); + boolean list = get != null && isList(get.returnType()); + if (set != null && values.size() == 1 && text.length() == 0) { + Type from = values.get(0).type; + Type to = boxed(set.parameters[0]); + boolean fits = !isObject(from) || !isObject(to) || model.find(from.getInternalName()) == null + || model.isAssignable(from.getInternalName(), to.getInternalName()); + if (fits || !list) { + requireAssignable(from, set.parameters[0], at); + body.append(" v.set").append(cap(property)).append('(').append(values.get(0).code) + .append(");\n"); + return; + } + } + if (set != null && values.isEmpty() && text.length() > 0 && !list) { + body.append(" v.set").append(cap(property)).append('(') + .append(coerce(text, set.parameters[0], at)).append(");\n"); + return; + } + if (list) { + Type element = elementType(get); + for (Value v : values) { + if (element != null) { + requireAssignable(v.type, element, at); + } + body.append(" ((java.util.List) v.").append(get.name).append("()).add(").append(v.code) + .append(");\n"); + } + if (text.length() > 0) { + body.append(" ((java.util.List) v.").append(get.name).append("()).add(") + .append(coerce(text, element == null ? STRING : element, at)).append(");\n"); + } + return; + } + if (set == null && get == null) { + throw new Failure(at, java(type) + " has no property " + property + " (no public set" + cap(property) + + " and no get" + cap(property) + ")"); + } + throw new Failure(at, "the property " + property + " of " + java(type) + " takes one value, and " + + (values.isEmpty() ? "none" : String.valueOf(values.size())) + " were given"); + } + + private void staticElement(Element child, String className, String property, Type type, StringBuilder body) + throws Failure, IOException { + ClassModel.Info owner = need(className, child); + ClassModel.Method m = staticSetter(owner.name, property); + if (m == null) { + throw new Failure(child, owner.sourceName() + " has no static property " + property + + " (a public static set" + cap(property) + " with two arguments)"); + } + requireAssignable(type, m.parameters[0], child); + List inner = child.elements(); + String code; + if (inner.size() == 1) { + Value v = value(inner.get(0), false); + requireAssignable(v.type, m.parameters[1], child); + code = v.code; + } else if (inner.isEmpty() && child.content().length() > 0) { + code = coerce(child.content(), m.parameters[1], child); + } else { + throw new Failure(child, "a static property takes exactly one value"); + } + body.append(" ").append(owner.sourceName()).append(".set").append(cap(property)).append("(v, ") + .append(code).append(");\n"); + } + + // ---------------------------------------------------------- coercion + + private static String fold(String s) { + StringBuilder out = new StringBuilder(s.length()); + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + out.append(c >= 'a' && c <= 'z' ? (char) (c - ('a' - 'A')) : c == '-' ? '_' : c); + } + return out.toString(); + } + + private Failure cannot(String text, Type target, Element at) { + return new Failure(at, "\"" + text + "\" is not a " + java(target)); + } + + /// The code of `text` as a value of `target`. + private String coerce(String text, Type target, Element at) throws Failure, IOException { + String t = text.trim(); + switch (target.getSort()) { + case Type.BOOLEAN: + if ("true".equalsIgnoreCase(t) || "false".equalsIgnoreCase(t)) { + return "true".equalsIgnoreCase(t) ? "true" : "false"; + } + throw cannot(text, target, at); + case Type.CHAR: + if (text.length() == 1) { + String quoted = literal(text); + return "'" + ("\"".equals(text) ? "\\\"" : "'".equals(text) ? "\\'" + : quoted.substring(1, quoted.length() - 1)) + "'"; + } + throw cannot(text, target, at); + case Type.BYTE: + case Type.SHORT: + case Type.INT: + case Type.LONG: + try { + long n = Long.parseLong(t); + if (target.getSort() == Type.LONG) { + return n + "L"; + } + if (n < Integer.MIN_VALUE || n > Integer.MAX_VALUE) { + throw cannot(text, target, at); + } + return target.getSort() == Type.INT ? String.valueOf(n) : "(" + java(target) + ") " + n; + } catch (NumberFormatException e) { + throw cannot(text, target, at); + } + case Type.FLOAT: + return "(float) " + number(t, target, at); + case Type.DOUBLE: + return number(t, target, at); + case Type.OBJECT: + return coerceObject(text, t, target, at); + case Type.ARRAY: + // A comma separated list is an array of a primitive, as the + // dividerPositions of a SplitPane are. + if (target.getDimensions() == 1 && target.getElementType().getSort() != Type.OBJECT) { + StringBuilder array = new StringBuilder("new " + java(target) + " {"); + if (t.length() > 0) { + String[] parts = t.split(",", -1); + for (int i = 0; i < parts.length; i++) { + array.append(i > 0 ? ", " : "").append(coerce(parts[i], target.getElementType(), at)); + } + } + return array.append('}').toString(); + } + throw cannot(text, target, at); + default: + throw cannot(text, target, at); + } + } + + private String number(String t, Type target, Element at) throws Failure { + if ("Infinity".equals(t) || "+Infinity".equals(t)) { + return "Double.POSITIVE_INFINITY"; + } + if ("-Infinity".equals(t)) { + return "Double.NEGATIVE_INFINITY"; + } + if ("NaN".equals(t)) { + return "Double.NaN"; + } + try { + double d = Double.parseDouble(t); + if (Double.isInfinite(d) || Double.isNaN(d)) { + throw cannot(t, target, at); + } + return Double.toString(d); + } catch (NumberFormatException e) { + throw cannot(t, target, at); + } + } + + private String coerceObject(String text, String t, Type target, Element at) throws Failure, IOException { + String name = target.getInternalName(); + if ("java/lang/String".equals(name) || "java/lang/Object".equals(name) + || "java/lang/CharSequence".equals(name)) { + return literal(text); + } + if ("java/lang/Boolean".equals(name)) { + return "true".equals(coerce(t, Type.BOOLEAN_TYPE, at)) ? "Boolean.TRUE" : "Boolean.FALSE"; + } + if ("java/lang/Integer".equals(name) || "java/lang/Short".equals(name) || "java/lang/Byte".equals(name)) { + return "Integer".equals(java(target).substring(10)) + ? "Integer.valueOf(" + coerce(t, Type.INT_TYPE, at) + ")" + : java(target) + ".valueOf((" + ("java/lang/Short".equals(name) ? "short" : "byte") + ") " + + coerce(t, Type.INT_TYPE, at) + ")"; + } + if ("java/lang/Long".equals(name)) { + return "Long.valueOf(" + coerce(t, Type.LONG_TYPE, at) + ")"; + } + if ("java/lang/Double".equals(name) || "java/lang/Number".equals(name)) { + return "Double.valueOf(" + number(t, target, at) + ")"; + } + if ("java/lang/Float".equals(name)) { + return "Float.valueOf((float) " + number(t, target, at) + ")"; + } + if ("java/lang/Character".equals(name)) { + return "Character.valueOf(" + coerce(text, Type.CHAR_TYPE, at) + ")"; + } + ClassModel.Info info = model.find(name); + if (info == null) { + throw new Failure(at, "\"" + text + "\" cannot be converted: the class " + java(target) + + " is not on the application's class path"); + } + if (info.isEnum()) { + String wanted = fold(t); + for (ClassModel.Field f : info.fields) { + if ((f.access & Opcodes.ACC_ENUM) != 0 && (f.name.equals(t) || fold(f.name).equals(wanted))) { + return info.sourceName() + "." + f.name; + } + } + throw new Failure(at, "\"" + text + "\" is not a constant of " + java(target)); + } + if ("javafx/geometry/Insets".equals(name)) { + List parts = numbers(t, target, at); + if (parts.size() == 1) { + return "new javafx.geometry.Insets(" + parts.get(0) + ")"; + } + if (parts.size() == 4) { + return "new javafx.geometry.Insets(" + parts.get(0) + ", " + parts.get(1) + ", " + parts.get(2) + + ", " + parts.get(3) + ")"; + } + throw new Failure(at, "\"" + text + "\" is not an Insets: one number, or top right bottom left"); + } + if ("javafx/scene/text/Font".equals(name)) { + CssValue font = values.parse("-fx-font", t); + if (font == null || font.type() != CssValue.FONT || font.unit(0) != CssValue.UNIT_PX) { + throw new Failure(at, "\"" + text + "\" is not a font: write it as in a style sheet," + + " such as \"bold 14px Arial\""); + } + int weight = font.flags() & 0xffff; + return "javafx.scene.text.Font.font(" + literal(font.text()) + ", " + + (weight == 0 ? "javafx.scene.text.FontWeight.NORMAL" + : "javafx.scene.text.FontWeight.findByWeight(" + weight + ")") + + ", javafx.scene.text.FontPosture." + ((font.flags() >> 16) == 2 ? "ITALIC" : "REGULAR") + ", " + + Double.toString(font.num(0)) + ")"; + } + if ("javafx/scene/paint/Color".equals(name) && values.constantColor(t) == null) { + throw new Failure(at, "\"" + text + "\" is not a colour"); + } + if ("javafx/scene/Cursor".equals(name)) { + return "javafx.scene.Cursor.cursor(" + literal(t) + ")"; + } + for (ClassModel.Method m : model.methods(name, "valueOf")) { + if (m.isStatic() && m.parameters.length == 1 && isObject(m.parameters[0]) + && "java/lang/String".equals(m.parameters[0].getInternalName()) + && isObject(m.returnType()) + && model.isAssignable(m.returnType().getInternalName(), name)) { + return info.sourceName() + ".valueOf(" + literal(t) + ")"; + } + } + throw new Failure(at, "\"" + text + "\" cannot be converted to a " + java(target) + + ": the class has no valueOf(String). Write the value as an element"); + } + + private List numbers(String t, Type target, Element at) throws Failure { + List out = new ArrayList(); + StringBuilder current = new StringBuilder(); + for (int i = 0; i <= t.length(); i++) { + char c = i < t.length() ? t.charAt(i) : ' '; + if (c == ' ' || c == ',' || c == '\t' || c == '\n' || c == '\r') { + if (current.length() > 0) { + out.add(number(current.toString(), target, at)); + current.setLength(0); + } + } else { + current.append(c); + } + } + return out; + } +} diff --git a/maven/fxml-compiler/src/main/java/com/codename1/fxml/FxmlDispatchGenerator.java b/maven/fxml-compiler/src/main/java/com/codename1/fxml/FxmlDispatchGenerator.java new file mode 100644 index 00000000000..a1a57caefc0 --- /dev/null +++ b/maven/fxml-compiler/src/main/java/com/codename1/fxml/FxmlDispatchGenerator.java @@ -0,0 +1,754 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.fxml; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; + +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +/// Generates, from the compiled classes of an application, what an FXML +/// loader does by reflection on a desktop: finding the class that builds a +/// document, creating a controller, setting its `@FXML` fields and calling +/// its handler methods. +/// +/// #### Why after javac, and why bytecode +/// +/// A controller's `@FXML` members are usually private. Source generated +/// before compilation could not name them, and only the class files say for +/// certain which members exist, of which types, and what a controller +/// inherits. So this runs on the compiled classes and does two things: +/// +/// - it widens what it needs to reach -- the controller class, its `@FXML` +/// fields and methods, its `initialize()` and its no-argument constructor +/// become public, in place. Nothing else about the class changes; +/// - it writes the registry class, whose methods are chains of string +/// comparisons ending in a direct field write or method call. +/// +/// #### The registry +/// +/// A subclass of the runtime's `FxmlDispatch` under the fixed name the +/// runtime instantiates (the names are parameters, because the application +/// sees the runtime relocated and the tests do not). It overrides +/// +/// - `has(path)` and `load(path, context)` from the `CN1_PATH` constant of +/// each compiled document; +/// - `create(className)` for every `fx:controller` class that has a +/// no-argument constructor, by `new`; +/// - `inject`, `invoke` and `init`, which find the controller's class with +/// `instanceof` -- the most derived class first -- and hand over to one +/// small class per controller, `$C`, so that the registry +/// itself declares nothing its placeholder does not. +/// +/// A field is only written after an `instanceof` check of the value: a +/// failed cast does not throw on every Codename One port, so the generated +/// code never relies on one. +/// +/// #### Which methods are handlers +/// +/// Every method marked `@FXML`, and every public method whose name some +/// document uses as `#name`, provided it takes no argument or one object. +/// A handler with both forms is called with the event when the event is of +/// the parameter's type. Members a controller inherits from another class +/// of the application are included. +public final class FxmlDispatchGenerator { + + /// The names the generated code refers to, which differ between an + /// application build -- where the runtime is relocated -- and a test. + public static final class Names { + final String registry; + final String dispatch; + final String context; + final String annotation; + + /// Creates a set of names. + /// + /// #### Parameters + /// + /// - `registry`: the internal name of the class to generate + /// + /// - `dispatch`: the internal name of the runtime's `FxmlDispatch` + /// + /// - `context`: the internal name of the runtime's `FxmlContext` + /// + /// - `fxmlAnnotation`: the internal name of the `FXML` annotation + public Names(String registry, String dispatch, String context, String fxmlAnnotation) { + this.registry = registry; + this.dispatch = dispatch; + this.context = context; + this.annotation = "L" + fxmlAnnotation + ";"; + } + } + + /// The internal name of the runtime's dispatcher, before relocation. + public static final String DISPATCH = "com/codename1/fxcompat/runtime/FxmlDispatch"; + /// The internal name of the runtime's registry placeholder, before + /// relocation. + public static final String REGISTRY = "com/codename1/fxcompat/runtime/FxmlRegistry"; + /// The internal name of the runtime's load context, before relocation. + public static final String CONTEXT = "com/codename1/fxcompat/runtime/FxmlContext"; + /// The internal name of the `FXML` annotation, before relocation. + public static final String ANNOTATION = "javafx/fxml/FXML"; + /// The internal name prefix of every compiled document. + public static final String LOADER_PREFIX = "com/codename1/generated/fxml/Fxml_"; + + private static final String PART = "$C"; + private static final int VISIBILITY = Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED | Opcodes.ACC_PUBLIC; + + private static final class Member { + final String owner; + final String name; + final String descriptor; + final int access; + final boolean annotated; + + Member(String owner, String name, String descriptor, int access, boolean annotated) { + this.owner = owner; + this.name = name; + this.descriptor = descriptor; + this.access = access; + this.annotated = annotated; + } + + String key() { + return name + descriptor; + } + } + + private static final class Scanned { + String name; + String superName; + int access; + boolean annotated; + final List fields = new ArrayList(); + final List methods = new ArrayList(); + String path; + String controller; + String handlers; + } + + /// What one controller class needs, its own and inherited. + private static final class Controller { + final Scanned type; + final int depth; + final List fields = new ArrayList(); + final List handlers = new ArrayList(); + Member initialize; + + Controller(Scanned type, int depth) { + this.type = type; + this.depth = depth; + } + } + + private final File classesDir; + private final Names names; + private int documents; + private int controllers; + + /// The order the registry asks the controllers in: a subclass before + /// the class it extends, so the first that matches is the nearest. + private static final Comparator MOST_DERIVED_FIRST = new Comparator() { + @Override + public int compare(Controller a, Controller b) { + return a.depth != b.depth ? b.depth - a.depth : a.type.name.compareTo(b.type.name); + } + }; + + /// Creates a generator over a directory of compiled classes. + public FxmlDispatchGenerator(File classesDir, Names names) { + this.classesDir = classesDir; + this.names = names; + } + + /// How many compiled documents the last run registered. + public int documents() { + return documents; + } + + /// How many controller classes the last run generated code for. + public int controllers() { + return controllers; + } + + /// Generates the registry for the given classes of the application -- + /// internal names, as they are in the directory -- and widens the + /// controllers among them. A second run over its own output changes no + /// file. + public void run(Collection appClasses) throws IOException { + Map all = new TreeMap(); + for (String name : appClasses) { + File f = new File(classesDir, name + ".class"); + if (f.isFile() && !name.equals(names.registry) && !name.startsWith(names.registry + PART)) { + Scanned s = scan(read(f), names.annotation); + all.put(s.name, s); + } + } + Map loaders = new TreeMap(); + Set handlerNames = new HashSet(); + Set named = new TreeSet(); + for (Scanned s : all.values()) { + if (s.name.startsWith(LOADER_PREFIX) && s.path != null) { + loaders.put(s.path, s); + if (s.handlers != null && s.handlers.length() > 0) { + handlerNames.addAll(Arrays.asList(s.handlers.split(","))); + } + if (s.controller != null && s.controller.length() > 0) { + named.add(s.controller); + } + } + } + List found = new ArrayList(); + for (Scanned s : all.values()) { + boolean isNamed = named.contains(s.name.replace('/', '.')); + if ((s.access & Opcodes.ACC_INTERFACE) != 0 || !(isNamed || s.annotated)) { + continue; + } + found.add(controller(s, all, handlerNames)); + } + Collections.sort(found, MOST_DERIVED_FIRST); + + // Widen first: the registry refers to the members as public ones. + Map> widen = new LinkedHashMap>(); + List creatable = new ArrayList(); + for (Controller c : found) { + need(widen, c.type.name, null); + for (Member m : c.fields) { + need(widen, m.owner, m.key()); + } + for (Member m : c.handlers) { + need(widen, m.owner, m.key()); + } + if (c.initialize != null) { + need(widen, c.initialize.owner, c.initialize.key()); + } + } + for (String binary : named) { + Scanned s = all.get(binary.replace('.', '/')); + if (s == null || (s.access & (Opcodes.ACC_ABSTRACT | Opcodes.ACC_INTERFACE)) != 0) { + continue; + } + for (Member m : s.methods) { + if ("".equals(m.name) && "()V".equals(m.descriptor)) { + need(widen, s.name, m.key()); + creatable.add(binary); + } + } + } + for (Map.Entry> e : widen.entrySet()) { + File f = new File(classesDir, e.getKey() + ".class"); + writeIfDifferent(f, widen(read(f), widen.keySet(), e.getValue())); + } + + for (int i = 0; i < found.size(); i++) { + writeIfDifferent(new File(classesDir, names.registry + PART + i + ".class"), + part(names.registry + PART + i, found.get(i))); + } + deleteStaleParts(found.size()); + writeIfDifferent(new File(classesDir, names.registry + ".class"), registry(loaders, creatable, found)); + documents = loaders.size(); + controllers = found.size(); + } + + private static void need(Map> widen, String owner, String member) { + Set members = widen.get(owner); + if (members == null) { + members = new HashSet(); + widen.put(owner, members); + } + if (member != null) { + members.add(member); + } + } + + private void deleteStaleParts(int count) throws IOException { + File registry = new File(classesDir, names.registry + ".class"); + File dir = registry.getParentFile(); + String prefix = names.registry.substring(names.registry.lastIndexOf('/') + 1) + PART; + File[] files = dir == null ? null : dir.listFiles(); + if (files == null) { + return; + } + for (File f : files) { + String n = f.getName(); + if (!n.startsWith(prefix) || !n.endsWith(".class")) { + continue; + } + String index = n.substring(prefix.length(), n.length() - 6); + boolean stale; + try { + stale = Integer.parseInt(index) >= count; + } catch (NumberFormatException e) { + stale = false; + } + if (stale && !f.delete()) { + throw new IOException("Could not delete the stale " + f); + } + } + } + + // ------------------------------------------------------------ reading + + private static byte[] read(File f) throws IOException { + InputStream in = new FileInputStream(f); + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buffer = new byte[8192]; + int n; + while ((n = in.read(buffer)) > 0) { + out.write(buffer, 0, n); + } + return out.toByteArray(); + } finally { + in.close(); + } + } + + /// Writes a file unless it already holds exactly these bytes, so that a + /// build that changes nothing touches nothing. + static boolean writeIfDifferent(File f, byte[] bytes) throws IOException { + if (f.isFile() && f.length() == bytes.length && Arrays.equals(read(f), bytes)) { + return false; + } + File dir = f.getParentFile(); + if (dir != null && !dir.isDirectory() && !dir.mkdirs() && !dir.isDirectory()) { + throw new IOException("Could not create " + dir); + } + OutputStream out = new FileOutputStream(f); + try { + out.write(bytes); + } finally { + out.close(); + } + return true; + } + + private static Scanned scan(byte[] bytes, final String fxml) { + final Scanned s = new Scanned(); + new ClassReader(bytes).accept(new ClassVisitor(Opcodes.ASM9) { + @Override + public void visit(int version, int access, String name, String signature, String superName, + String[] interfaces) { + s.name = name; + s.superName = superName; + s.access = access; + } + + @Override + public FieldVisitor visitField(final int access, final String name, final String descriptor, + String signature, Object value) { + if ((access & Opcodes.ACC_STATIC) != 0 && value instanceof String) { + if ("CN1_PATH".equals(name)) { + s.path = (String) value; + } else if ("CN1_CONTROLLER".equals(name)) { + s.controller = (String) value; + } else if ("CN1_HANDLERS".equals(name)) { + s.handlers = (String) value; + } + } + final int at = s.fields.size(); + s.fields.add(new Member(s.name, name, descriptor, access, false)); + return new FieldVisitor(Opcodes.ASM9) { + @Override + public AnnotationVisitor visitAnnotation(String annotation, boolean visible) { + if (fxml.equals(annotation)) { + s.annotated = true; + s.fields.set(at, new Member(s.name, name, descriptor, access, true)); + } + return null; + } + }; + } + + @Override + public MethodVisitor visitMethod(final int access, final String name, final String descriptor, + String signature, String[] exceptions) { + if ((access & (Opcodes.ACC_SYNTHETIC | Opcodes.ACC_BRIDGE)) != 0) { + return null; + } + final int at = s.methods.size(); + s.methods.add(new Member(s.name, name, descriptor, access, false)); + return new MethodVisitor(Opcodes.ASM9) { + @Override + public AnnotationVisitor visitAnnotation(String annotation, boolean visible) { + if (fxml.equals(annotation)) { + s.annotated = true; + s.methods.set(at, new Member(s.name, name, descriptor, access, true)); + } + return null; + } + }; + } + }, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + return s; + } + + private static Controller controller(Scanned type, Map all, Set handlerNames) { + int depth = 0; + for (Scanned s = all.get(type.superName); s != null; s = all.get(s.superName)) { + depth++; + } + Controller c = new Controller(type, depth); + Set fields = new HashSet(); + Set methods = new HashSet(); + for (Scanned s = type; s != null; s = all.get(s.superName)) { + for (Member f : s.fields) { + int sort = Type.getType(f.descriptor).getSort(); + // A public field is the document's to set without the + // annotation, as it is for a desktop loader. + boolean settable = (f.annotated || (f.access & Opcodes.ACC_PUBLIC) != 0) + && (f.access & (Opcodes.ACC_STATIC | Opcodes.ACC_FINAL)) == 0 + && (sort == Type.OBJECT || sort == Type.ARRAY); + // A field of a subclass hides the one it shadows. + if (settable && fields.add(f.name)) { + c.fields.add(f); + } + } + for (Member m : s.methods) { + if (m.name.startsWith("<") || (m.access & Opcodes.ACC_ABSTRACT) != 0) { + continue; + } + Type[] args = Type.getArgumentTypes(m.descriptor); + if ("initialize".equals(m.name) && args.length == 0 && (m.access & Opcodes.ACC_STATIC) == 0) { + if (c.initialize == null) { + c.initialize = m; + } + continue; + } + boolean wanted = m.annotated + || ((m.access & Opcodes.ACC_PUBLIC) != 0 && handlerNames.contains(m.name)); + boolean callable = args.length == 0 || (args.length == 1 && args[0].getSort() == Type.OBJECT); + if (wanted && callable && methods.add(m.key())) { + c.handlers.add(m); + } + } + } + return c; + } + + // ----------------------------------------------------------- widening + + private static int open(int access) { + return (access & ~VISIBILITY) | Opcodes.ACC_PUBLIC; + } + + private static byte[] widen(byte[] bytes, final Set classes, final Set members) { + ClassReader reader = new ClassReader(bytes); + ClassWriter writer = new ClassWriter(0); + final String[] self = new String[1]; + reader.accept(new ClassVisitor(Opcodes.ASM9, writer) { + @Override + public void visit(int version, int access, String name, String signature, String superName, + String[] interfaces) { + self[0] = name; + super.visit(version, open(access), name, signature, superName, interfaces); + } + + @Override + public void visitInnerClass(String name, String outerName, String innerName, int access) { + super.visitInnerClass(name, outerName, innerName, classes.contains(name) ? open(access) : access); + } + + @Override + public FieldVisitor visitField(int access, String name, String descriptor, String signature, + Object value) { + return super.visitField(members.contains(name + descriptor) ? open(access) : access, name, + descriptor, signature, value); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, + String[] exceptions) { + return super.visitMethod(members.contains(name + descriptor) ? open(access) : access, name, + descriptor, signature, exceptions); + } + }, 0); + return writer.toByteArray(); + } + + // --------------------------------------------------------- generating + + private static void ifNotNamed(MethodVisitor mv, String literal, int argument, Label otherwise) { + mv.visitLdcInsn(literal); + mv.visitVarInsn(Opcodes.ALOAD, argument); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z", false); + mv.visitJumpInsn(Opcodes.IFEQ, otherwise); + } + + private static void answer(MethodVisitor mv, boolean value) { + mv.visitInsn(value ? Opcodes.ICONST_1 : Opcodes.ICONST_0); + mv.visitInsn(Opcodes.IRETURN); + } + + private static void end(MethodVisitor mv) { + mv.visitMaxs(0, 0); + mv.visitEnd(); + } + + private byte[] registry(Map loaders, List creatable, List found) { + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); + cw.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_SUPER, names.registry, null, + names.dispatch, null); + MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "", "()V", null, null); + mv.visitCode(); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, names.dispatch, "", "()V", false); + mv.visitInsn(Opcodes.RETURN); + end(mv); + + mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "has", "(Ljava/lang/String;)Z", null, null); + mv.visitCode(); + for (String path : loaders.keySet()) { + Label next = new Label(); + ifNotNamed(mv, path, 1, next); + answer(mv, true); + mv.visitLabel(next); + } + answer(mv, false); + end(mv); + + String loadDescriptor = "(Ljava/lang/String;L" + names.context + ";)Ljava/lang/Object;"; + mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "load", loadDescriptor, null, new String[] {"java/lang/Exception"}); + mv.visitCode(); + for (Map.Entry e : loaders.entrySet()) { + Label next = new Label(); + ifNotNamed(mv, e.getKey(), 1, next); + mv.visitVarInsn(Opcodes.ALOAD, 2); + mv.visitMethodInsn(Opcodes.INVOKESTATIC, e.getValue().name, "load", + "(L" + names.context + ";)Ljava/lang/Object;", false); + mv.visitInsn(Opcodes.ARETURN); + mv.visitLabel(next); + } + mv.visitInsn(Opcodes.ACONST_NULL); + mv.visitInsn(Opcodes.ARETURN); + end(mv); + + mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "create", "(Ljava/lang/String;)Ljava/lang/Object;", null, null); + mv.visitCode(); + for (String binary : creatable) { + Label next = new Label(); + ifNotNamed(mv, binary, 1, next); + String internal = binary.replace('.', '/'); + mv.visitTypeInsn(Opcodes.NEW, internal); + mv.visitInsn(Opcodes.DUP); + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, internal, "", "()V", false); + mv.visitInsn(Opcodes.ARETURN); + mv.visitLabel(next); + } + mv.visitInsn(Opcodes.ACONST_NULL); + mv.visitInsn(Opcodes.ARETURN); + end(mv); + + delegate(cw, found, "inject", "Ljava/lang/String;Ljava/lang/Object;", null); + delegate(cw, found, "invoke", "Ljava/lang/String;Ljava/lang/Object;", new String[] {"java/lang/Exception"}); + delegate(cw, found, "init", "", new String[] {"java/lang/Exception"}); + cw.visitEnd(); + return cw.toByteArray(); + } + + /// One of the three methods that take a controller: the first class the + /// controller is an instance of answers, through its part class. + private void delegate(ClassWriter cw, List found, String method, String rest, String[] throwing) { + MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, method, "(Ljava/lang/Object;" + rest + ")Z", null, + throwing); + mv.visitCode(); + int extra = rest.length() == 0 ? 0 : 2; + for (int i = 0; i < found.size(); i++) { + String type = found.get(i).type.name; + Label next = new Label(); + mv.visitVarInsn(Opcodes.ALOAD, 1); + mv.visitTypeInsn(Opcodes.INSTANCEOF, type); + mv.visitJumpInsn(Opcodes.IFEQ, next); + mv.visitVarInsn(Opcodes.ALOAD, 1); + mv.visitTypeInsn(Opcodes.CHECKCAST, type); + for (int a = 0; a < extra; a++) { + mv.visitVarInsn(Opcodes.ALOAD, 2 + a); + } + mv.visitMethodInsn(Opcodes.INVOKESTATIC, names.registry + PART + i, method, "(L" + type + ";" + rest + + ")Z", false); + mv.visitInsn(Opcodes.IRETURN); + mv.visitLabel(next); + } + answer(mv, false); + end(mv); + } + + private byte[] part(String name, Controller c) { + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); + cw.visit(Opcodes.V1_5, Opcodes.ACC_FINAL | Opcodes.ACC_SUPER, name, null, "java/lang/Object", null); + MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PRIVATE, "", "()V", null, null); + mv.visitCode(); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "", "()V", false); + mv.visitInsn(Opcodes.RETURN); + end(mv); + String self = "L" + c.type.name + ";"; + + mv = cw.visitMethod(Opcodes.ACC_STATIC, "inject", "(" + self + "Ljava/lang/String;Ljava/lang/Object;)Z", + null, null); + mv.visitCode(); + for (Member f : c.fields) { + Type type = Type.getType(f.descriptor); + Label next = new Label(); + Label set = new Label(); + ifNotNamed(mv, f.name, 1, next); + mv.visitVarInsn(Opcodes.ALOAD, 2); + mv.visitJumpInsn(Opcodes.IFNULL, set); + mv.visitVarInsn(Opcodes.ALOAD, 2); + mv.visitTypeInsn(Opcodes.INSTANCEOF, type.getInternalName()); + mv.visitJumpInsn(Opcodes.IFNE, set); + // Not a value of the field's type: say so rather than cast. + mv.visitVarInsn(Opcodes.ALOAD, 1); + mv.visitLdcInsn(type.getClassName()); + mv.visitVarInsn(Opcodes.ALOAD, 2); + mv.visitMethodInsn(Opcodes.INVOKESTATIC, names.dispatch, "mismatch", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V", false); + answer(mv, true); + mv.visitLabel(set); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitVarInsn(Opcodes.ALOAD, 2); + mv.visitTypeInsn(Opcodes.CHECKCAST, type.getInternalName()); + mv.visitFieldInsn(Opcodes.PUTFIELD, f.owner, f.name, f.descriptor); + answer(mv, true); + mv.visitLabel(next); + } + answer(mv, false); + end(mv); + + mv = cw.visitMethod(Opcodes.ACC_STATIC, "invoke", "(" + self + "Ljava/lang/String;Ljava/lang/Object;)Z", + null, new String[] {"java/lang/Exception"}); + mv.visitCode(); + // The form that takes the event first, so that it is preferred + // whenever the event is of its type. + for (int pass = 1; pass >= 0; pass--) { + for (Member m : c.handlers) { + Type[] args = Type.getArgumentTypes(m.descriptor); + if (args.length != pass) { + continue; + } + Label next = new Label(); + ifNotNamed(mv, m.name, 1, next); + boolean isStatic = (m.access & Opcodes.ACC_STATIC) != 0; + if (pass == 1) { + mv.visitVarInsn(Opcodes.ALOAD, 2); + mv.visitTypeInsn(Opcodes.INSTANCEOF, args[0].getInternalName()); + mv.visitJumpInsn(Opcodes.IFEQ, next); + } + if (!isStatic) { + mv.visitVarInsn(Opcodes.ALOAD, 0); + } + if (pass == 1) { + mv.visitVarInsn(Opcodes.ALOAD, 2); + mv.visitTypeInsn(Opcodes.CHECKCAST, args[0].getInternalName()); + } + mv.visitMethodInsn(isStatic ? Opcodes.INVOKESTATIC : Opcodes.INVOKEVIRTUAL, m.owner, m.name, + m.descriptor, false); + int size = Type.getReturnType(m.descriptor).getSize(); + if (size == 1) { + mv.visitInsn(Opcodes.POP); + } else if (size == 2) { + mv.visitInsn(Opcodes.POP2); + } + answer(mv, true); + mv.visitLabel(next); + } + } + answer(mv, false); + end(mv); + + mv = cw.visitMethod(Opcodes.ACC_STATIC, "init", "(" + self + ")Z", null, + new String[] {"java/lang/Exception"}); + mv.visitCode(); + if (c.initialize != null) { + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, c.initialize.owner, "initialize", c.initialize.descriptor, + false); + int size = Type.getReturnType(c.initialize.descriptor).getSize(); + if (size == 1) { + mv.visitInsn(Opcodes.POP); + } else if (size == 2) { + mv.visitInsn(Opcodes.POP2); + } + answer(mv, true); + } else { + answer(mv, false); + } + end(mv); + cw.visitEnd(); + return cw.toByteArray(); + } + + /// The classes of a directory as internal names, for a caller that has + /// no list of its own. + public static List classesIn(File dir) { + List out = new ArrayList(); + collect(dir, "", out); + Collections.sort(out); + return out; + } + + private static void collect(File dir, String prefix, List out) { + File[] files = dir.listFiles(); + if (files == null) { + return; + } + for (File f : files) { + if (f.isDirectory()) { + collect(f, prefix + f.getName() + "/", out); + } else if (f.getName().endsWith(".class")) { + out.add(prefix + f.getName().substring(0, f.getName().length() - 6)); + } + } + } + + /// The names of an unrelocated runtime, as a test of this module or of + /// the layer sees it, with the registry under a name of the caller's. + public static Names unrelocated(String registry) { + return new Names(registry, DISPATCH, CONTEXT, ANNOTATION); + } +} diff --git a/maven/fxml-compiler/src/main/java/com/codename1/fxml/FxmlDocument.java b/maven/fxml-compiler/src/main/java/com/codename1/fxml/FxmlDocument.java new file mode 100644 index 00000000000..81f0f5b33d0 --- /dev/null +++ b/maven/fxml-compiler/src/main/java/com/codename1/fxml/FxmlDocument.java @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.fxml; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import javax.xml.XMLConstants; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; +import org.xml.sax.helpers.DefaultHandler; + +/// An FXML file as the compiler reads it: the element tree with the line +/// and column of every element, and the `` instructions. +/// +/// The position of an element is where its start tag ends, which is what +/// the XML parser reports; a message says which attribute it is about. +final class FxmlDocument { + + /// What an `fx:` name is in: the FXML namespace, whatever its version. + private static final String FX = "http://javafx.com/fxml"; + + /// One attribute. + static final class Attribute { + final String name; + final boolean fx; + final String value; + + Attribute(String name, boolean fx, String value) { + this.name = name; + this.fx = fx; + this.value = value; + } + + /// The attribute as it was written, for a message. + String text() { + return (fx ? "fx:" : "") + name + "=\"" + value + "\""; + } + } + + /// One element. A child is an [Element] or a `String` of text. + static final class Element { + final String name; + final boolean fx; + final int line; + final int column; + final List attributes = new ArrayList(); + final List children = new ArrayList(); + + Element(String name, boolean fx, int line, int column) { + this.name = name; + this.fx = fx; + this.line = line; + this.column = column; + } + + /// The value of an `fx:` attribute, or `null`. + String fx(String attribute) { + for (Attribute a : attributes) { + if (a.fx && a.name.equals(attribute)) { + return a.value; + } + } + return null; + } + + /// The value of a plain attribute, or `null`. + String plain(String attribute) { + for (Attribute a : attributes) { + if (!a.fx && a.name.equals(attribute)) { + return a.value; + } + } + return null; + } + + /// The start tag as it was written, for a message. + String text() { + StringBuilder s = new StringBuilder("<").append(fx ? "fx:" : "").append(name); + for (Attribute a : attributes) { + s.append(' ').append(a.text()); + } + return s.append('>').toString(); + } + + /// The text directly inside the element, trimmed; empty when there + /// is none. + String content() { + StringBuilder s = new StringBuilder(); + for (Object child : children) { + if (child instanceof String) { + s.append((String) child); + } + } + return s.toString().trim(); + } + + /// The child elements. + List elements() { + List out = new ArrayList(); + for (Object child : children) { + if (child instanceof Element) { + out.add((Element) child); + } + } + return out; + } + } + + /// The file, as messages name it. + final String file; + /// The resource path, without a leading slash. + final String path; + /// The imports in the order written: a class name, or a package + /// followed by `.*`. + final List imports = new ArrayList(); + Element root; + + private FxmlDocument(String file, String path) { + this.file = file; + this.path = path; + } + + /// Reads a document. Answers `null`, with the reason recorded as an + /// error, for a file that is not well formed XML or that uses a + /// scripting language. + static FxmlDocument parse(String file, String path, InputStream in, final Messages messages) + throws IOException { + final FxmlDocument doc = new FxmlDocument(file, path); + final boolean[] failed = new boolean[1]; + try { + SAXParserFactory factory = SAXParserFactory.newInstance(); + factory.setNamespaceAware(true); + factory.setValidating(false); + // A document is the application's own file, but nothing in FXML + // needs a DTD or an external entity, and a build must not fetch + // one. + factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + factory.setFeature("http://xml.org/sax/features/external-general-entities", false); + factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + SAXParser parser = factory.newSAXParser(); + InputSource source = new InputSource(in); + source.setSystemId(file); + parser.parse(source, new DefaultHandler() { + private Locator locator; + private final List stack = new ArrayList(); + + @Override + public void setDocumentLocator(Locator l) { + locator = l; + } + + @Override + public void processingInstruction(String target, String data) { + String value = data == null ? "" : data.trim(); + if ("import".equals(target)) { + doc.imports.add(value); + } else if ("language".equals(target)) { + failed[0] = true; + doc.error(messages, locator, ": scripts are not supported. A" + + " document is compiled to Java; write the handler as a controller method and" + + " name it with onAction=\"#method\""); + } + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) { + Element e = new Element(localName, isFx(uri), locator.getLineNumber(), + locator.getColumnNumber()); + for (int i = 0; i < attributes.getLength(); i++) { + e.attributes.add(new Attribute(attributes.getLocalName(i), isFx(attributes.getURI(i)), + attributes.getValue(i))); + } + if (stack.isEmpty()) { + doc.root = e; + } else { + stack.get(stack.size() - 1).children.add(e); + } + stack.add(e); + } + + @Override + public void endElement(String uri, String localName, String qName) { + stack.remove(stack.size() - 1); + } + + @Override + public void characters(char[] ch, int start, int length) { + if (!stack.isEmpty()) { + stack.get(stack.size() - 1).children.add(new String(ch, start, length)); + } + } + + @Override + public void error(SAXParseException e) throws SAXException { + throw e; + } + }); + } catch (SAXParseException e) { + messages.error(file, Math.max(1, e.getLineNumber()), Math.max(1, e.getColumnNumber()), + "not well formed XML: " + e.getMessage()); + return null; + } catch (SAXException e) { + messages.error(file, 1, 1, "not well formed XML: " + e.getMessage()); + return null; + } catch (ParserConfigurationException e) { + throw new IOException("No usable XML parser in this JDK: " + e.getMessage(), e); + } + if (failed[0] || doc.root == null) { + return null; + } + return doc; + } + + private static boolean isFx(String uri) { + return uri != null && uri.startsWith(FX); + } + + private void error(Messages messages, Locator locator, String message) { + messages.error(file, locator == null ? 1 : locator.getLineNumber(), + locator == null ? 1 : locator.getColumnNumber(), message); + } +} diff --git a/maven/fxml-compiler/src/main/java/com/codename1/fxml/Messages.java b/maven/fxml-compiler/src/main/java/com/codename1/fxml/Messages.java new file mode 100644 index 00000000000..5872b6b01c5 --- /dev/null +++ b/maven/fxml-compiler/src/main/java/com/codename1/fxml/Messages.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.fxml; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/// What the compilers found, each entry in the form an IDE and a build log +/// link back to the source: `file:line:column: message`. +/// +/// An error fails the build once everything has been compiled -- every +/// document is still compiled, so that one run reports all of them. A +/// warning is printed and the build goes on: it is what a style sheet gets +/// for a property or a value this layer does not have, which a style sheet +/// written for the desktop is full of. +public final class Messages { + + private final List errors = new ArrayList(); + private final List warnings = new ArrayList(); + + /// Creates an empty collection. + public Messages() { + // Nothing found yet. + } + + /// The position of `offset` in `text` as `line:column`, both from 1. + static String position(String text, int offset) { + int line = 1; + int column = 1; + int end = Math.min(offset, text.length()); + for (int i = 0; i < end; i++) { + if (text.charAt(i) == '\n') { + line++; + column = 1; + } else { + column++; + } + } + return line + ":" + column; + } + + /// Records an error at a position. + public void error(String file, int line, int column, String message) { + errors.add(file + ":" + line + ":" + column + ": " + message); + } + + /// Records an error at an offset in the text of a file. + void error(String file, String text, int offset, String message) { + errors.add(file + ":" + position(text, offset) + ": " + message); + } + + /// Records a warning at an offset in the text of a file. + void warning(String file, String text, int offset, String message) { + warnings.add(file + ":" + position(text, offset) + ": " + message); + } + + /// Records a warning about a file as a whole. + void warning(String file, String message) { + warnings.add(file + ": " + message); + } + + /// The errors, in the order found. + public List errors() { + return Collections.unmodifiableList(errors); + } + + /// The warnings, in the order found. + public List warnings() { + return Collections.unmodifiableList(warnings); + } + + /// Whether anything was found that fails the build. + public boolean hasErrors() { + return !errors.isEmpty(); + } +} diff --git a/maven/fxml-compiler/src/main/java/com/codename1/fxml/css/package-info.java b/maven/fxml-compiler/src/main/java/com/codename1/fxml/css/package-info.java new file mode 100644 index 00000000000..6aad3c87110 --- /dev/null +++ b/maven/fxml-compiler/src/main/java/com/codename1/fxml/css/package-info.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +/// The style value parser and the compiled style sheet format, as the +/// build runs them. +/// +/// The classes of this package are not written here. They are the sources +/// of `com.codename1.fxcompat.runtime.css` in the JavaFX compatibility +/// layer, copied under this package name when the module is built; see the +/// description of that package for what they are and the `pom.xml` of this +/// module for why they are copied rather than depended on. Change them +/// there. +package com.codename1.fxml.css; diff --git a/maven/fxml-compiler/src/main/java/com/codename1/fxml/package-info.java b/maven/fxml-compiler/src/main/java/com/codename1/fxml/package-info.java new file mode 100644 index 00000000000..af459024c41 --- /dev/null +++ b/maven/fxml-compiler/src/main/java/com/codename1/fxml/package-info.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +/// The build-time half of FXML and style sheet support in the JavaFX +/// compatibility layer. +/// +/// A device has no XML parser to spare, no reflection and no CSS parser, so +/// everything a desktop `FXMLLoader` and style manager work out while the +/// application runs is worked out here, while it is built: +/// +/// - [com.codename1.fxml.DesktopResourceCompiler] is what a build plugin +/// calls before javac: it turns every FXML document into Java source and +/// every style sheet into a binary table. +/// - [com.codename1.fxml.FxmlDispatchGenerator] is what the remap step +/// calls after javac: it generates the code that reaches a controller's +/// `@FXML` members. +/// +/// The module depends on the JDK, on ASM and on the resource naming rule, +/// and on nothing of the layer itself: the layer's classes are read as +/// class files from the application's class path. +package com.codename1.fxml; diff --git a/maven/fxml-compiler/src/test/java/com/codename1/fxml/CssTest.java b/maven/fxml-compiler/src/test/java/com/codename1/fxml/CssTest.java new file mode 100644 index 00000000000..eb617931595 --- /dev/null +++ b/maven/fxml-compiler/src/test/java/com/codename1/fxml/CssTest.java @@ -0,0 +1,731 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.fxml; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import com.codename1.compat.testing.HeadlessImplementation; +import com.codename1.compat.testing.MainThreadRule; +import com.codename1.fxcompat.runtime.StyleEngine; +import com.codename1.fxcompat.runtime.Units; +import com.codename1.fxcompat.runtime.css.CssSheetData; +import com.codename1.fxcompat.runtime.css.CssValue; + +import javafx.css.PseudoClass; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Cursor; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; +import javafx.scene.paint.CycleMethod; +import javafx.scene.paint.LinearGradient; +import javafx.scene.paint.Paint; +import javafx.scene.paint.RadialGradient; +import javafx.scene.text.FontPosture; +import javafx.scene.text.FontWeight; + +/// Style sheets from their text to the nodes they style: the build's +/// compiler writes the binary table, and the engine of the layer reads it +/// back from the application's resources and runs the cascade. +public class CssTest { + + @Rule + public final MainThreadRule mainThread = new MainThreadRule(); + + private FxmlHarness app; + private VBox root; + private HBox bar; + private Label title; + private Button ok; + private Label plain; + private Scene scene; + + @Before + public void setUp() { + HeadlessImplementation.install(); + Units.setScale(2); + StyleEngine.setInstance(null); + } + + @After + public void tearDown() throws IOException { + if (app != null) { + app.close(); + } + Units.setScale(0); + StyleEngine.setInstance(null); + } + + /// Builds an application whose only resource is a style sheet, and a + /// scene that uses it: + /// + /// ``` + /// VBox.root + /// HBox.bar + /// Label#title.title + /// Button.primary + /// Label + /// ``` + private void styled(String name, String css) throws Exception { + app = new FxmlHarness(name).resource("style/app.css", css); + app.build(); + tree(); + scene.getStylesheets().add("file:/work/app/target/classes/style/app.css"); + } + + private void tree() { + root = new VBox(); + bar = new HBox(); + bar.getStyleClass().add("bar"); + title = new Label("title"); + title.setId("title"); + title.getStyleClass().add("title"); + ok = new Button("ok"); + ok.getStyleClass().add("primary"); + plain = new Label("plain"); + bar.getChildren().addAll(title, ok); + root.getChildren().addAll(bar, plain); + scene = new Scene(root, 200, 200); + } + + private static Paint fill(Region r) { + return r.getBackground() == null ? null : r.getBackground().getFills().get(0).getFill(); + } + + // ---------------------------------------------------------- matching + + @Test + public void selectorsMatchByTypeClassIdAndPosition() throws Exception { + styled("match", "Label { -fx-text-fill: blue; }\n" + + ".bar { -fx-spacing: 7; }\n" + + "#title { -fx-underline: true; }\n" + + ".bar > .primary { -fx-text-fill: red; }\n" + + ".root Button { -fx-wrap-text: true; }\n" + + ".root > Button { -fx-underline: true; }\n" + + "VBox > Label { -fx-wrap-text: true; }\n" + + "Label.title#title { -fx-opacity: 0.5; }\n" + + "* { -fx-cursor: hand; }\n" + + ".nothing .bar, .root .bar { -fx-alignment: center-right; }\n"); + assertEquals(Color.BLUE, title.getTextFill()); + assertEquals(Color.BLUE, plain.getTextFill()); + assertEquals(7, bar.getSpacing(), 0); + assertTrue(title.isUnderline()); + assertFalse(plain.isUnderline()); + assertEquals(Color.RED, ok.getTextFill()); + // A descendant at any depth matches the descendant selector, only a + // direct child matches '>'. + assertTrue(ok.isWrapText()); + assertFalse(ok.isUnderline()); + assertTrue(plain.isWrapText()); + assertFalse(title.isWrapText()); + assertEquals(0.5, title.getOpacity(), 0); + assertEquals(1, plain.getOpacity(), 0); + assertEquals(Cursor.HAND, plain.getCursor()); + assertEquals(Cursor.HAND, root.getCursor()); + assertEquals(Pos.CENTER_RIGHT, bar.getAlignment()); + } + + @Test + public void theCascadeOrdersBySpecificityThenSourceOrderThenImportance() throws Exception { + styled("cascade", "#title { -fx-text-fill: green; }\n" + + ".title { -fx-text-fill: red; -fx-opacity: 0.2; }\n" + + "Label { -fx-text-fill: blue; -fx-opacity: 0.9 !important; -fx-underline: true; }\n" + + "Label { -fx-underline: false; }\n" + + ".bar .title { -fx-wrap-text: true; }\n" + + ".title { -fx-wrap-text: false; }\n"); + // The id beats the class beats the type, whatever the order. + assertEquals(Color.GREEN, title.getTextFill()); + assertEquals(Color.BLUE, plain.getTextFill()); + // !important beats a more specific rule. + assertEquals(0.9, title.getOpacity(), 0); + // Of two rules of one specificity the later wins. + assertFalse(plain.isUnderline()); + // Two classes beat one that comes later. + assertTrue(title.isWrapText()); + } + + @Test + public void inlineStyleBeatsTheSheetsAndIsWithdrawn() throws Exception { + styled("inline", ".title { -fx-text-fill: red; -fx-opacity: 0.5 !important; }\n"); + title.setStyle("-fx-text-fill: #00ff00; -fx-opacity: 0.25; -fx-underline: true"); + assertEquals(Color.LIME, title.getTextFill()); + assertTrue(title.isUnderline()); + // An important declaration of a sheet beats the inline style. + assertEquals(0.5, title.getOpacity(), 0); + title.setStyle(null); + assertEquals(Color.RED, title.getTextFill()); + assertFalse(title.isUnderline()); + } + + @Test + public void pseudoClassesRestyleAndRestore() throws Exception { + styled("pseudo", ".primary { -fx-text-fill: blue; }\n" + + ".primary:hover { -fx-text-fill: red; -fx-underline: true; }\n" + + ".primary:hover:armed { -fx-text-fill: green; }\n" + + ".bar:selected > Label { -fx-opacity: 0.5; }\n" + + ".bar:selected .primary { -fx-wrap-text: true; }\n"); + PseudoClass hover = PseudoClass.getPseudoClass("hover"); + PseudoClass armed = PseudoClass.getPseudoClass("armed"); + PseudoClass selected = PseudoClass.getPseudoClass("selected"); + assertEquals(Color.BLUE, ok.getTextFill()); + ok.pseudoClassStateChanged(hover, true); + assertEquals(Color.RED, ok.getTextFill()); + assertTrue(ok.isUnderline()); + ok.pseudoClassStateChanged(armed, true); + assertEquals(Color.GREEN, ok.getTextFill()); + ok.pseudoClassStateChanged(armed, false); + assertEquals(Color.RED, ok.getTextFill()); + ok.pseudoClassStateChanged(hover, false); + assertEquals(Color.BLUE, ok.getTextFill()); + // What only the state set is gone, back to what the node had. + assertFalse(ok.isUnderline()); + // A state of an ancestor restyles the descendants it selects. + bar.pseudoClassStateChanged(selected, true); + assertEquals(0.5, title.getOpacity(), 0); + assertTrue(ok.isWrapText()); + assertEquals(1, plain.getOpacity(), 0); + bar.pseudoClassStateChanged(selected, false); + assertEquals(1, title.getOpacity(), 0); + assertFalse(ok.isWrapText()); + } + + @Test + public void aValueTheApplicationSetIsRestoredWhenTheRuleStopsMatching() throws Exception { + styled("restore", ".loud { -fx-text-fill: red; -fx-padding: 9; }\n"); + plain.setTextFill(Color.NAVY); + plain.setPadding(new Insets(1)); + plain.getStyleClass().add("loud"); + assertEquals(Color.RED, plain.getTextFill()); + assertEquals(new Insets(9), plain.getPadding()); + plain.getStyleClass().remove("loud"); + assertEquals(Color.NAVY, plain.getTextFill()); + assertEquals(new Insets(1), plain.getPadding()); + // Removing the sheet does the same for every node. + plain.getStyleClass().add("loud"); + assertEquals(Color.RED, plain.getTextFill()); + scene.getStylesheets().clear(); + assertEquals(Color.NAVY, plain.getTextFill()); + } + + @Test + public void nodesAddedLaterAndParentSheetsAreStyled() throws Exception { + app = new FxmlHarness("parent").resource("a.css", "Label { -fx-text-fill: red; -fx-underline: true; }\n") + .resource("b.css", "Label { -fx-text-fill: blue; }\n"); + app.build(); + tree(); + scene.getStylesheets().add("cn1res:/a.css"); + assertEquals(Color.RED, title.getTextFill()); + // A parent's sheet styles its subtree only, and after the scene's. + bar.getStylesheets().add("cn1res:/b.css"); + assertEquals(Color.BLUE, title.getTextFill()); + assertTrue(title.isUnderline()); + assertEquals(Color.RED, plain.getTextFill()); + Label late = new Label("late"); + bar.getChildren().add(late); + assertEquals(Color.BLUE, late.getTextFill()); + root.getChildren().add(late); + assertEquals(Color.RED, late.getTextFill()); + } + + // ------------------------------------------------------------- values + + @Test + public void lookedUpColoursResolveThroughTheAncestors() throws Exception { + styled("lookup", ".root { -brand: #336699; -accent: -brand; -soft: derive(-brand, 100%); -fx-base: #101010; }\n" + + ".bar { -brand: red; }\n" + + "Label { -fx-text-fill: -accent; }\n" + + "Button { -fx-text-fill: -brand; -fx-background-color: -soft; }\n" + + "#title { -fx-background-color: linear-gradient(to right, -brand, white); }\n"); + // -accent is resolved where it is used: under .bar, -brand is red. + assertEquals(Color.RED, title.getTextFill()); + assertEquals(Color.web("#336699"), plain.getTextFill()); + assertEquals(Color.RED, ok.getTextFill()); + // derive() is the desktop's: a dark colour is lightened by less than was asked. + assertEquals(Color.web("#ff9999"), fill(ok)); + LinearGradient g = (LinearGradient) fill(title); + assertEquals(Color.RED, g.getStops().get(0).getColor()); + assertEquals(Color.WHITE, g.getStops().get(1).getColor()); + // Redefining the colour restyles what looks it up. + bar.setStyle("-brand: lime"); + assertEquals(Color.LIME, ok.getTextFill()); + assertEquals(Color.LIME, title.getTextFill()); + } + + @Test + public void fontsInheritAndEmFollowsTheFontSize() throws Exception { + styled("font", ".root { -fx-font-size: 20px; }\n" + + ".bar { -fx-font-weight: bold; -fx-font-style: italic; }\n" + + "#title { -fx-padding: 0.5em 1em; -fx-font-family: \"Courier New\"; }\n" + + ".primary { -fx-font-size: 1.5em; -fx-padding: 1em; }\n" + + "Label { -fx-pref-width: 2em; }\n"); + assertEquals(20, plain.getFont().getSize(), 0); + assertEquals(40, plain.getPrefWidth(), 0); + assertEquals(20, title.getFont().getSize(), 0); + assertEquals("Courier New", title.getFont().getFamily()); + assertEquals(new Insets(10, 20, 10, 20), title.getPadding()); + assertTrue(title.getFont().getStyle(), title.getFont().getStyle().contains("Bold")); + assertTrue(title.getFont().getStyle(), title.getFont().getStyle().contains("Italic")); + assertFalse(plain.getFont().getStyle(), plain.getFont().getStyle().contains("Bold")); + // An em font size is relative to the inherited size, and the node's + // other em lengths to the result. + assertEquals(30, ok.getFont().getSize(), 0); + assertEquals(new Insets(30), ok.getPadding()); + root.setStyle("-fx-font-size: 10px"); + assertEquals(10, plain.getFont().getSize(), 0); + assertEquals(15, ok.getFont().getSize(), 0); + assertEquals(new Insets(15), ok.getPadding()); + assertEquals(new Insets(5, 10, 5, 10), title.getPadding()); + } + + @Test + public void fontShorthand() throws Exception { + styled("shorthand", "#title { -fx-font: italic bold 18px \"Helvetica Neue\"; }\n" + + ".primary { -fx-font: 12pt serif; }\n"); + assertEquals(18, title.getFont().getSize(), 0); + assertEquals("Helvetica Neue", title.getFont().getFamily()); + assertTrue(title.getFont().getStyle(), title.getFont().getStyle().contains("Bold")); + assertTrue(title.getFont().getStyle(), title.getFont().getStyle().contains("Italic")); + assertEquals(16, ok.getFont().getSize(), 0); + assertNotNull(FontWeight.BOLD); + assertNotNull(FontPosture.ITALIC); + } + + @Test + public void everyKindOfColour() throws Exception { + String[][] cases = { + {"#f00", "#ff0000"}, {"#f008", "#ff000088"}, {"#00ff0080", "#00ff0080"}, {"navy", "#000080"}, + {"transparent", "#00000000"}, {"rgb(255, 0, 128)", "#ff0080"}, {"rgb(100%, 0%, 50%)", "#ff0080"}, + {"rgba(0, 0, 255, 0.5)", "#0000ff80"}, {"hsb(120, 100%, 100%)", "#00ff00"}, + {"hsba(0, 100%, 100%, 0.5)", "#ff000080"}, {"hsl(240, 100%, 50%)", "#0000ff"}, + {"derive(#808080, -100%)", "#000000"}, {"derive(black, 100%)", "#999999"}, + {"RED", "#ff0000"}, + }; + StringBuilder css = new StringBuilder(); + for (int i = 0; i < cases.length; i++) { + css.append(".c").append(i).append(" { -fx-text-fill: ").append(cases[i][0]).append("; }\n"); + } + styled("colours", css.toString()); + for (int i = 0; i < cases.length; i++) { + plain.getStyleClass().setAll("c" + i); + Color want = Color.web(cases[i][1]); + Color got = (Color) plain.getTextFill(); + assertEquals(cases[i][0], want.getRed(), got.getRed(), 0.005); + assertEquals(cases[i][0], want.getGreen(), got.getGreen(), 0.005); + assertEquals(cases[i][0], want.getBlue(), got.getBlue(), 0.005); + assertEquals(cases[i][0], want.getOpacity(), got.getOpacity(), 0.005); + } + } + + @Test + public void gradientsLengthsInsetsRadiiAndKeywords() throws Exception { + styled("values", ".bar { -fx-background-color: linear-gradient(to bottom right, red 0%, blue 100%);" + + " -fx-background-radius: 4 8; -fx-background-insets: 1 2 3 4; -fx-padding: 1px 2pt 1pc 1in;" + + " -fx-border-color: black; -fx-border-width: 2; -fx-border-radius: 3;" + + " -fx-alignment: bottom-left; -fx-spacing: 1.5em; -fx-min-width: 10mm; -fx-max-height: 1cm;" + + " -fx-fill-height: false; }\n" + + ".primary { -fx-background-color: radial-gradient(center 50% 50%, radius 50%, reflect, white, black 80%);" + + " -fx-content-display: right; -fx-text-alignment: center; -fx-cursor: crosshair;" + + " -fx-graphic-text-gap: 6; -fx-rotate: 45; -fx-scale-x: 2; -fx-translate-y: -3;" + + " -fx-background-color: red, radial-gradient(center 50% 50%, radius 50%, reflect, white, black 80%); }\n"); + LinearGradient lg = (LinearGradient) fill(bar); + assertTrue(lg.isProportional()); + assertEquals(0, lg.getStartX(), 0); + assertEquals(1, lg.getEndX(), 0); + assertEquals(1, lg.getEndY(), 0); + assertEquals(Color.BLUE, lg.getStops().get(1).getColor()); + assertEquals(4, bar.getBackground().getFills().get(0).getRadii().getTopLeftHorizontalRadius(), 0); + assertEquals(8, bar.getBackground().getFills().get(0).getRadii().getTopRightHorizontalRadius(), 0); + assertEquals(new Insets(1, 2, 3, 4), bar.getBackground().getFills().get(0).getInsets()); + assertEquals(1, bar.getPadding().getTop(), 0); + assertEquals(2 * 96.0 / 72, bar.getPadding().getRight(), 0.001); + assertEquals(16, bar.getPadding().getBottom(), 0.001); + assertEquals(96, bar.getPadding().getLeft(), 0.001); + assertNotNull(bar.getBorder()); + assertEquals(Pos.BOTTOM_LEFT, bar.getAlignment()); + assertEquals(1.5 * bar.getChildren().size() * 0 + 1.5 * javafx.scene.text.Font.getDefault().getSize(), + bar.getSpacing(), 0.001); + assertEquals(10 * 96 / 25.4, bar.getMinWidth(), 0.001); + assertEquals(96 / 2.54, bar.getMaxHeight(), 0.001); + assertFalse(bar.isFillHeight()); + // Two layers are two fills, the first written at the bottom. + assertEquals(2, ok.getBackground().getFills().size()); + assertEquals(Color.RED, fill(ok)); + RadialGradient rg = (RadialGradient) ok.getBackground().getFills().get(1).getFill(); + assertEquals(CycleMethod.REFLECT, rg.getCycleMethod()); + assertEquals(0.5, rg.getRadius(), 0); + assertEquals(0.8, rg.getStops().get(1).getOffset(), 0.0001); + assertEquals(javafx.scene.control.ContentDisplay.RIGHT, ok.getContentDisplay()); + assertEquals(javafx.scene.text.TextAlignment.CENTER, ok.getTextAlignment()); + assertEquals(Cursor.CROSSHAIR, ok.getCursor()); + assertEquals(6, ok.getGraphicTextGap(), 0); + assertEquals(45, ok.getRotate(), 0); + assertEquals(2, ok.getScaleX(), 0); + assertEquals(-3, ok.getTranslateY(), 0); + assertTrue(app.warnings.toString(), app.warnings.isEmpty()); + } + + // ---------------------------------------------------- the compiler + + @Test + public void unknownPropertiesAndBadValuesAreWarningsWithALine() throws Exception { + app = new FxmlHarness("warn").resource("app.css", "/* a comment\n over two lines */\n" + + ".a { -fx-text-fill: red; }\n" + + ".b {\n -fx-text-fil: red;\n -fx-opacity: lots;\n -fx-text-fill: blue;\n}\n" + + ".c:nth-child(2) { -fx-opacity: 1; }\n" + + "@media print { .d { -fx-opacity: 0; } }\n"); + List errors = app.compile(); + assertTrue(errors.toString(), errors.isEmpty()); + String file = app.file("app.css"); + String all = app.warnings.toString(); + assertEquals(all, 4, app.warnings.size()); + assertTrue(all, app.warnings.get(0).startsWith(file + ":5:")); + assertTrue(all, app.warnings.get(0).contains("-fx-text-fil")); + assertTrue(all, app.warnings.get(1).startsWith(file + ":6:")); + assertTrue(all, app.warnings.get(1).contains("lots")); + assertTrue(all, app.warnings.get(2).startsWith(file + ":9:")); + assertTrue(all, app.warnings.get(2).contains("nth-child")); + assertTrue(all, app.warnings.get(3).startsWith(file + ":10:")); + assertTrue(all, app.warnings.get(3).contains("@media")); + // The sheet is compiled with what was understood. + CssSheetData data = read(app); + assertEquals(2, data.ruleCount()); + assertEquals(1, data.declarationCount(data.blockOf(1))); + assertEquals("-fx-text-fill", data.declaration(data.blockOf(1), 0).name()); + } + + @Test + public void aSheetBrokenInItsStructureIsAnError() throws Exception { + app = new FxmlHarness("broken").resource("a.css", ".a { -fx-opacity: 1; }\n.b {\n -fx-opacity: 0;\n") + .resource("b.css", ".a { -fx-opacity: 1; }\n}\n") + .resource("c.css", ".a { -fx-opacity: 1; }\n.dangling\n") + .resource("ok.css", ".a { -fx-opacity: 1; }\n"); + List errors = app.compile(); + assertEquals(errors.toString(), 3, errors.size()); + assertTrue(errors.toString(), errors.get(0).startsWith(app.file("a.css") + ":2:")); + assertTrue(errors.toString(), errors.get(0).contains("never closed")); + assertTrue(errors.toString(), errors.get(1).startsWith(app.file("b.css") + ":2:")); + assertTrue(errors.toString(), errors.get(2).startsWith(app.file("c.css") + ":2:")); + assertFalse(app.compiledSheet("a.css").exists()); + assertTrue(app.compiledSheet("ok.css").exists()); + } + + private static CssSheetData read(FxmlHarness app) throws IOException { + InputStream in = new FileInputStream(app.compiledSheet("app.css")); + try { + return CssSheetData.read(in); + } finally { + in.close(); + } + } + + @Test + public void theBinaryTableRoundTrips() throws Exception { + app = new FxmlHarness("binary").resource("app.css", "@import \"base.css\";\n" + + "@font-face { font-family: \"Brand\"; src: url(\"fonts/brand.ttf\"); }\n" + + ".root { -brand: #123456; }\n" + + ".a, VBox > #b:hover .c.d { -fx-text-fill: -brand !important; -fx-padding: 1em 2px;" + + " -fx-background-color: linear-gradient(to right, red, derive(-brand, 20%) 50%, blue);" + + " -fx-font: bold 12px \"Sans\"; -fx-alignment: center; -fx-wrap-text: true;" + + " -fx-background-radius: 50%; }\n") + .resource("base.css", "Label { -fx-opacity: 0.5; }\n") + .resource("fonts/brand.ttf", "not a font"); + assertTrue(app.compile().isEmpty()); + CssSheetData first = read(app); + assertEquals(1, first.importCount()); + assertEquals("base.css", first.importPath(0)); + assertEquals(1, first.fontCount()); + assertEquals("Brand", first.fontFamily(0)); + assertEquals("fonts/brand.ttf", first.fontSource(0)); + assertEquals(3, first.ruleCount()); + assertEquals(2, first.blockCount()); + // Two selectors of one rule share its block. + assertEquals(first.blockOf(1), first.blockOf(2)); + assertEquals(".a", first.selector(1).toString()); + assertEquals("VBox > #b:hover .c.d", first.selector(2).toString()); + assertEquals(7, first.declarationCount(first.blockOf(1))); + assertTrue(first.declaration(first.blockOf(1), 0).important()); + assertFalse(first.declaration(first.blockOf(1), 1).important()); + // Written again, the table is the same bytes: nothing is lost. + ByteArrayOutputStream again = new ByteArrayOutputStream(); + first.write(again); + ByteArrayOutputStream third = new ByteArrayOutputStream(); + CssSheetData.read(new ByteArrayInputStream(again.toByteArray())).write(third); + assertEquals(again.size(), app.compiledSheet("app.css").length()); + assertTrue(java.util.Arrays.equals(again.toByteArray(), third.toByteArray())); + CssValue gradient = first.declaration(first.blockOf(1), 2).value(); + assertNotNull(gradient); + } + + @Test + public void importsComeBeforeTheSheetThatImportsThem() throws Exception { + app = new FxmlHarness("import").resource("css/app.css", "@import url(\"base.css\");\n" + + "Label { -fx-text-fill: red; }\n") + .resource("css/base.css", "Label { -fx-text-fill: blue; -fx-underline: true; }\n"); + app.build(); + tree(); + scene.getStylesheets().add("cn1res:/css/app.css"); + assertEquals(Color.RED, plain.getTextFill()); + assertTrue(plain.isUnderline()); + } + + @Test + public void aSheetThatWasNotCompiledStylesNothing() throws Exception { + app = new FxmlHarness("absent"); + app.build(); + tree(); + scene.getStylesheets().add("cn1res:/none.css"); + scene.getStylesheets().add("cn1res:/none.css"); + assertEquals(Color.BLACK, plain.getTextFill()); + } + + /// JavaFX reads a comment that runs to the end of its line, which CSS + /// has not; a sheet written for it has them, and the declaration on + /// the next line was swallowed into the comment's "property name". + /// The two slashes of an address inside `url()` are no comment. + @Test + public void aLineCommentEndsWithItsLineAndAnEffectIsCompiled() throws Exception { + app = new FxmlHarness("linecomment").resource("app.css", ".t {\n" + + " -fx-text-fill: #f2b179; // f9f6f2\n" + + " -fx-effect: dropshadow( three-pass-box, rgba(243, 215, 116, 1), 10, 0.5, 0, 0 );\n" + + "}\n"); + assertTrue(app.compile().isEmpty()); + CssSheetData data = read(app); + assertEquals(2, data.declarationCount(data.blockOf(0))); + assertEquals("-fx-effect", data.declaration(data.blockOf(0), 1).name()); + CssValue effect = data.declaration(data.blockOf(0), 1).value(); + assertEquals(CssValue.EFFECT, effect.type()); + assertEquals(CssValue.EFFECT_DROP | (3 << 4), effect.flags()); + assertEquals(10, effect.num(0), 0); + assertEquals(0.5, effect.num(1), 0); + assertEquals(CssValue.COLOR, effect.part(0).type()); + assertEquals("a(b://c) d", com.codename1.fxml.css.CssDeclarations.stripComments("a(b://c) d// e").trim()); + } + + /// A background is as many fills as it has colours and a border as + /// many strokes, each with insets, radii, widths and a style of its + /// own -- the last given standing in for a layer without one -- and a + /// stroke has a paint per side. The standard themes and every + /// "flat" restyling of a control are written that way. + @Test + public void layersSidesBorderInsetsAndALadder() throws Exception { + styled("layers", ".bar { -fx-base: #202020;" + + " -fx-background-color: red, derive(-fx-base, 20%), blue;" + + " -fx-background-insets: 0 0 0 0, 1, 2 3; -fx-background-radius: 5, 4;" + + " -fx-border-color: transparent transparent derive(-fx-base, 80%) transparent, white green;" + + " -fx-border-width: 0 0 1 0, 2; -fx-border-insets: 0 10 1 0, 4;" + + " -fx-border-style: solid, segments(1, 1) inside; }\n" + + ".primary { -fx-background-color: #101010; -fx-background-insets: 0 0 0 0, 0, 1, 2;" + + " -fx-border-color: #e2e2e2; -fx-border-width: 2; }\n" + + "#title { -fx-text-fill: ladder(#101010, white 49%, black 50%); }\n" + + ".root > .label { -fx-text-fill: ladder(#f0f0f0, white 49%, black 50%);" + + " -fx-background-color: ladder(gray, black 0%, white 100%); }\n"); + assertTrue(app.warnings.toString(), app.warnings.isEmpty()); + java.util.List fills = bar.getBackground().getFills(); + assertEquals(3, fills.size()); + assertEquals(Color.RED, fills.get(0).getFill()); + assertEquals(Color.BLUE, fills.get(2).getFill()); + assertTrue(fills.get(1).getFill() instanceof Color); + assertEquals(new javafx.geometry.Insets(0), fills.get(0).getInsets()); + assertEquals(new javafx.geometry.Insets(1), fills.get(1).getInsets()); + assertEquals(new javafx.geometry.Insets(2, 3, 2, 3), fills.get(2).getInsets()); + assertEquals(5, fills.get(0).getRadii().getTopLeftHorizontalRadius(), 0); + assertEquals(4, fills.get(1).getRadii().getTopLeftHorizontalRadius(), 0); + assertEquals("the last radius stands in", 4, fills.get(2).getRadii().getTopLeftHorizontalRadius(), 0); + + java.util.List strokes = bar.getBorder().getStrokes(); + assertEquals(2, strokes.size()); + javafx.scene.layout.BorderStroke under = strokes.get(0); + assertEquals(Color.TRANSPARENT, under.getTopStroke()); + assertEquals(Color.TRANSPARENT, under.getLeftStroke()); + assertTrue(under.getBottomStroke() instanceof Color); + assertTrue(((Color) under.getBottomStroke()).getBrightness() > 0.5); + assertEquals(1, under.getWidths().getBottom(), 0); + assertEquals(0, under.getWidths().getTop(), 0); + assertEquals(new javafx.geometry.Insets(0, 10, 1, 0), under.getInsets()); + assertEquals(javafx.scene.layout.BorderStrokeStyle.SOLID, under.getTopStyle()); + javafx.scene.layout.BorderStroke over = strokes.get(1); + assertEquals(Color.WHITE, over.getTopStroke()); + assertEquals(Color.GREEN, over.getRightStroke()); + assertEquals(Color.WHITE, over.getBottomStroke()); + assertEquals(Color.GREEN, over.getLeftStroke()); + assertEquals(2, over.getWidths().getLeft(), 0); + assertEquals(new javafx.geometry.Insets(4), over.getInsets()); + assertEquals(javafx.scene.layout.BorderStrokeStyle.DASHED, over.getTopStyle()); + + // One colour is one fill, whatever number of insets is written. + assertEquals(1, ok.getBackground().getFills().size()); + assertEquals(new javafx.geometry.Insets(0), ok.getBackground().getFills().get(0).getInsets()); + assertEquals(2, ok.getBorder().getStrokes().get(0).getWidths().getTop(), 0); + + assertEquals("dark picks the stop below", Color.WHITE, title.getTextFill()); + assertEquals("light the stop above", Color.BLACK, plain.getTextFill()); + Color between = (Color) fill(plain); + assertEquals(Color.GRAY.getBrightness(), between.getRed(), 0.01); + + // Withdrawn, the region has what it had. + bar.getStyleClass().clear(); + assertNull(bar.getBackground()); + assertNull(bar.getBorder()); + } + + private static javafx.scene.Node find(javafx.scene.Parent in, String styleClass) { + java.util.List children = in.getChildrenUnmodifiable(); + for (int i = 0; i < children.size(); i++) { + javafx.scene.Node n = children.get(i); + if (n.getStyleClass().contains(styleClass)) { + return n; + } + if (n instanceof javafx.scene.Parent) { + javafx.scene.Node deeper = find((javafx.scene.Parent) n, styleClass); + if (deeper != null) { + return deeper; + } + } + } + return null; + } + + /// The colours of the standard theme are defined without any sheet, + /// in terms of `-fx-base`, and the controls drawn by this layer take + /// theirs from them: a sheet that redefines the base recolours a + /// table, and one that names `-fx-focus-color` finds it. A header is + /// a `.column-header` around a `.label`, and the text of a menu in a + /// bar is a `.label` too. + @Test + public void theThemeColoursAreDefinedAndRecolourATableItsHeadersAndAMenuBar() throws Exception { + styled("theme", ".primary { -fx-background-color: -fx-focus-color; -fx-text-fill: -fx-text-base-color; }\n" + + ".dark { -fx-base: #1d1d1d; -fx-control-inner-background: #1d1d1d; -fx-padding: 5;" + + " -fx-table-header-border-color: transparent; }\n" + + ".dark .column-header { -fx-size: 35; -fx-border-width: 0 0 1 0;" + + " -fx-border-color: transparent transparent derive(-fx-base, 80%) transparent;" + + " -fx-border-insets: 0 10 1 0; }\n" + + ".dark .column-header .label { -fx-font-size: 20pt; -fx-alignment: center-left; }\n" + + ".menu-bar .label { -fx-text-fill: white; }\n"); + assertTrue(app.warnings.toString(), app.warnings.isEmpty()); + assertEquals(Color.web("#039ed3"), fill(ok)); + assertEquals("the base is light, its text dark", Color.web("#333333"), ok.getTextFill()); + + javafx.scene.control.TableView plainTable = new javafx.scene.control.TableView(); + plainTable.getColumns().add(new javafx.scene.control.TableColumn("One")); + javafx.scene.control.TableView dark = new javafx.scene.control.TableView(); + dark.getStyleClass().add("dark"); + dark.getColumns().add(new javafx.scene.control.TableColumn("One")); + javafx.scene.control.MenuBar menus = new javafx.scene.control.MenuBar(new javafx.scene.control.Menu("File")); + root.getChildren().addAll(plainTable, dark, menus); + root.applyCss(); + root.layout(); + + // The standard look: a border that is the fill under the inner one. + assertEquals(2, plainTable.getBackground().getFills().size()); + Color border = (Color) plainTable.getBackground().getFills().get(0).getFill(); + Color inner = (Color) plainTable.getBackground().getFills().get(1).getFill(); + assertTrue("a light table: " + inner, inner.getBrightness() > 0.95); + assertTrue("with a grey edge: " + border, border.getBrightness() > 0.6 && border.getBrightness() < 0.9); + assertEquals(new javafx.geometry.Insets(1), plainTable.getPadding()); + + Color darkInner = (Color) dark.getBackground().getFills().get(1).getFill(); + assertEquals(Color.web("#1d1d1d"), darkInner); + assertEquals("the edge of a dark base is black", Color.BLACK, dark.getBackground().getFills().get(0).getFill()); + assertEquals(new javafx.geometry.Insets(5), dark.getPadding()); + + Region header = (Region) find(dark, "column-header"); + assertNotNull(header); + assertTrue("-fx-size is the least height: " + header.prefHeight(-1), header.prefHeight(-1) >= 35); + javafx.scene.layout.BorderStroke line = header.getBorder().getStrokes().get(0); + assertEquals(Color.TRANSPARENT, line.getRightStroke()); + assertTrue(((Color) line.getBottomStroke()).getBrightness() > 0.5); + assertEquals(new javafx.geometry.Insets(0, 10, 1, 0), line.getInsets()); + Label text = (Label) find(header, "label"); + assertNotNull("the header holds a label", text); + assertEquals(20 * 96.0 / 72, text.getFont().getSize(), 0.01); + assertEquals(Pos.CENTER_LEFT, text.getAlignment()); + Label plainText = (Label) find((Region) find(plainTable, "column-header"), "label"); + assertEquals(Pos.CENTER, plainText.getAlignment()); + + Label file = (Label) find(menus, "label"); + assertNotNull("a menu's text is a label", file); + assertEquals(Color.WHITE, file.getTextFill()); + assertTrue(file.getStyleClass().contains("menu")); + } + + /// A button a sheet draws -- a fill, a border and a padding of its + /// own -- is that padding around its text: the padding the native + /// theme gives a button does not come on top, which made every + /// restyled button a third taller than in JavaFX. `:default` and + /// `:cancel` are states a sheet can name. + @Test + public void aRestyledButtonIsItsOwnPaddingAndKnowsDefaultAndCancel() throws Exception { + styled("button", ".primary { -fx-background-color: #101010; -fx-border-color: #e2e2e2;" + + " -fx-border-width: 2; -fx-padding: 5 22 5 22; }\n" + + ".primary:default { -fx-background-color: -fx-focus-color; }\n" + + ".primary:cancel { -fx-text-fill: red; }\n"); + com.codename1.ui.Component c = ok.cn1Native(); + // What the theme would add, made visible whatever the theme is. + root.applyCss(); + com.codename1.ui.plaf.Style style = c.getStyle(); + assertEquals(0, style.getPaddingTop() + style.getPaddingBottom() + style.getPaddingLeftNoRTL() + + style.getPaddingRightNoRTL()); + double text = com.codename1.fxcompat.runtime.Units.toLogical(c.getPreferredSize().getHeight()); + assertEquals(2 + 5 + text + 5 + 2, ok.prefHeight(-1), 1.0); + double wide = com.codename1.fxcompat.runtime.Units.toLogical(c.getPreferredSize().getWidth()); + assertEquals(2 + 22 + wide + 22 + 2, ok.prefWidth(-1), 1.0); + + assertEquals(Color.web("#101010"), fill(ok)); + ok.setDefaultButton(true); + root.applyCss(); + assertEquals(Color.web("#039ed3"), fill(ok)); + ok.setDefaultButton(false); + ok.setCancelButton(true); + root.applyCss(); + assertEquals(Color.web("#101010"), fill(ok)); + assertEquals(Color.RED, ok.getTextFill()); + + // Without a padding of its own it keeps the theme's. + ok.getStyleClass().clear(); + ok.setStyle("-fx-background-color: blue;"); + root.applyCss(); + com.codename1.ui.plaf.Style themed = com.codename1.ui.plaf.UIManager.getInstance().getComponentStyle(c.getUIID()); + assertEquals(themed.getPaddingTop(), c.getStyle().getPaddingTop()); + } +} diff --git a/maven/fxml-compiler/src/test/java/com/codename1/fxml/FxmlCompilerTest.java b/maven/fxml-compiler/src/test/java/com/codename1/fxml/FxmlCompilerTest.java new file mode 100644 index 00000000000..193d3c778b2 --- /dev/null +++ b/maven/fxml-compiler/src/test/java/com/codename1/fxml/FxmlCompilerTest.java @@ -0,0 +1,791 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codename1.fxml; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.ListResourceBundle; +import java.util.ResourceBundle; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import com.codename1.compat.testing.HeadlessImplementation; +import com.codename1.compat.testing.MainThreadRule; +import com.codename1.fxcompat.runtime.StyleEngine; +import com.codename1.fxcompat.runtime.Units; + +import javafx.collections.ObservableList; +import javafx.fxml.FXMLLoader; +import javafx.fxml.LoadException; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Label; +import javafx.scene.control.RadioButton; +import javafx.scene.control.TextField; +import javafx.scene.control.ToggleGroup; +import javafx.scene.image.ImageView; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; +import javafx.scene.shape.Rectangle; +import javafx.util.Callback; + +/// Every FXML feature the compiler supports, through the whole of what a +/// build does: document to Java source, javac, the generated dispatcher, +/// and the tree the document builds on a headless device. +public class FxmlCompilerTest { + + private static final String HEAD = "\n" + + "\n" + + "\n" + + "\n"; + private static final String NS = " xmlns=\"http://javafx.com/javafx/17\" xmlns:fx=\"http://javafx.com/fxml/1\""; + + @Rule + public final MainThreadRule mainThread = new MainThreadRule(); + + private FxmlHarness app; + + @Before + public void setUp() { + HeadlessImplementation.install(); + Units.setScale(2); + } + + @After + public void tearDown() throws IOException { + if (app != null) { + app.close(); + } + Units.setScale(0); + StyleEngine.setInstance(null); + } + + private FxmlHarness app(String name) throws IOException { + app = new FxmlHarness(name); + return app; + } + + /// The single error of a document that must not compile. + private String errorOf(String name, String document) throws IOException { + List errors = app(name).resource("bad.fxml", document).compile(); + assertEquals(errors.toString(), 1, errors.size()); + return errors.get(0); + } + + // ------------------------------------------------- the object graph + + @Test + public void attributesAreCoercedToThePropertyType() throws Exception { + app("coerce").resource("main.fxml", HEAD + "\n" + + "\n" + + "