├── .gitattributes ├── .gitignore ├── Chapter02 ├── .gitignore ├── pom.xml └── src │ └── main │ ├── java │ ├── com │ │ └── steeplesoft │ │ │ └── processmanager │ │ │ ├── Controller.java │ │ │ └── ProcessManager.java │ └── module-info.java │ └── resources │ ├── fxml │ └── procman.fxml │ └── styles │ └── Styles.css ├── Chapter03 ├── .gitignore ├── cli │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ ├── com │ │ │ └── steeplesoft │ │ │ │ └── dupefinder │ │ │ │ └── cli │ │ │ │ └── DupeFinderCommands.java │ │ └── module-info.java │ │ └── resources │ │ ├── com │ │ └── steeplesoft │ │ │ └── dupefinder │ │ │ └── cli │ │ │ └── OptionDescriptions.properties │ │ └── crest-commands.txt ├── gui │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ ├── com │ │ │ └── steeplesoft │ │ │ │ └── dupefinder │ │ │ │ ├── FXMLController.java │ │ │ │ └── Main.java │ │ └── module-info.java │ │ └── resources │ │ ├── fxml │ │ └── Scene.fxml │ │ └── styles │ │ └── Styles.css ├── lib │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ ├── com │ │ │ │ └── steeplesoft │ │ │ │ │ └── dupefinder │ │ │ │ │ └── lib │ │ │ │ │ ├── FileFinder.java │ │ │ │ │ ├── model │ │ │ │ │ └── FileInfo.java │ │ │ │ │ └── util │ │ │ │ │ └── FindFileTask.java │ │ │ └── module-info.java │ │ └── resources │ │ │ └── META-INF │ │ │ └── persistence.xml │ │ └── test │ │ ├── java │ │ └── com │ │ │ └── steeplesoft │ │ │ └── dupefinder │ │ │ └── lib │ │ │ └── FileFinderTest.java │ │ └── resources │ │ └── log4j2.yaml ├── pom.xml └── test-data │ ├── set1 │ ├── file1.txt │ ├── file11.txt │ ├── file13.txt │ ├── file15.txt │ ├── file3-1.txt │ ├── file3.txt │ ├── file5.txt │ ├── file7.txt │ └── file9.txt │ └── set2 │ ├── file1.txt │ ├── file10.txt │ ├── file11.txt │ ├── file12.txt │ ├── file13.txt │ ├── file14.txt │ ├── file15.txt │ ├── file2.txt │ ├── file3.txt │ ├── file4.txt │ ├── file5.txt │ ├── file6.txt │ ├── file7.txt │ ├── file8.txt │ └── file9.txt ├── Chapter04 ├── datecalc-cli │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ ├── com │ │ │ └── steeplesoft │ │ │ │ └── datecalc │ │ │ │ └── cli │ │ │ │ └── DateCalc.java │ │ └── module-info.java │ │ └── resources │ │ └── crest-commands.txt ├── datecalc-lib │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ ├── com │ │ │ └── steeplesoft │ │ │ │ └── datecalc │ │ │ │ ├── DateCalcException.java │ │ │ │ ├── DateCalculator.java │ │ │ │ ├── DateCalculatorResult.java │ │ │ │ └── parser │ │ │ │ ├── DateCalcExpressionParser.java │ │ │ │ └── token │ │ │ │ ├── DateToken.java │ │ │ │ ├── IntegerToken.java │ │ │ │ ├── OperatorToken.java │ │ │ │ ├── TimeToken.java │ │ │ │ ├── TimeZoneToken.java │ │ │ │ ├── Token.java │ │ │ │ └── UnitOfMeasureToken.java │ │ │ └── module-info.java │ │ └── test │ │ └── java │ │ └── com │ │ └── steeplesoft │ │ └── datecalc │ │ ├── DateCalculatorTest.java │ │ └── parser │ │ ├── DateCalcExpressionParserTest.java │ │ └── RegexTest.java └── pom.xml ├── Chapter05 ├── .gitignore ├── api │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── steeplesoft │ │ └── sunago │ │ ├── SunagoPrefsKeys.java │ │ ├── SunagoUtil.java │ │ └── api │ │ ├── SocialMediaClient.java │ │ ├── SocialMediaItem.java │ │ ├── SunagoPreferences.java │ │ └── fx │ │ ├── LoginController.java │ │ ├── SelectableItem.java │ │ └── SocialMediaPreferencesController.java ├── app │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── steeplesoft │ │ │ └── sunago │ │ │ └── app │ │ │ ├── PreferencesController.java │ │ │ ├── Sunago.java │ │ │ ├── SunagoController.java │ │ │ ├── SunagoPreferencesImpl.java │ │ │ ├── SunagoProperties.java │ │ │ └── javafx │ │ │ └── SocialMediaItemViewCell.java │ │ └── resources │ │ ├── META-INF │ │ └── services │ │ │ └── com.steeplesoft.sunago.api.SunagoPreferences │ │ ├── fxml │ │ ├── login.fxml │ │ ├── prefs.fxml │ │ └── sunago.fxml │ │ ├── images │ │ ├── reload.png │ │ └── settings.png │ │ └── styles │ │ └── styles.css ├── instagram │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── steeplesoft │ │ │ └── sunago │ │ │ └── instagram │ │ │ ├── InstagramClient.java │ │ │ ├── InstagramPrefsKeys.java │ │ │ ├── Photo.java │ │ │ └── fx │ │ │ └── InstagramPreferencesController.java │ │ └── resources │ │ ├── META-INF │ │ └── services │ │ │ ├── com.steeplesoft.sunago.api.SocialMediaClient │ │ │ └── com.steeplesoft.sunago.api.fx.SocialMediaPreferencesController │ │ └── com │ │ └── steeplesoft │ │ └── j9bp │ │ └── sunago │ │ └── instagram │ │ └── icon.png ├── pom.xml └── twitter │ ├── pom.xml │ └── src │ └── main │ ├── java │ └── com │ │ └── steeplesoft │ │ └── sunago │ │ └── twitter │ │ ├── MessageBundle.java │ │ ├── Tweet.java │ │ ├── TwitterClient.java │ │ ├── TwitterPrefsKeys.java │ │ └── fx │ │ ├── SelectableUserList.java │ │ └── TwitterPreferencesController.java │ └── resources │ ├── META-INF │ └── services │ │ ├── com.steeplesoft.sunago.api.SocialMediaClient │ │ └── com.steeplesoft.sunago.api.fx.SocialMediaPreferencesController │ ├── Messages.properties │ └── com │ └── steeplesoft │ └── sunago │ └── twitter │ └── icon.png ├── Chapter06 ├── .gitignore ├── app │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── steeplesoft │ │ │ └── sunago │ │ │ └── ExampleInstrumentedTest.java │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── steeplesoft │ │ │ └── sunago │ │ │ ├── MainActivity.java │ │ │ ├── PluginServiceConnection.java │ │ │ ├── PreferencesActivity.java │ │ │ ├── Sunago.java │ │ │ ├── SunagoCursorAdapter.java │ │ │ ├── SunagoUtil.java │ │ │ ├── WebLoginActivity.java │ │ │ ├── api │ │ │ ├── MessageHandler.java │ │ │ ├── SocialMediaClient.java │ │ │ └── SocialMediaItem.java │ │ │ ├── data │ │ │ ├── SunagoContentProvider.java │ │ │ └── SunagoOpenHelper.java │ │ │ ├── instagram │ │ │ ├── InstagramClient.java │ │ │ ├── InstagramPreferencesFragment.java │ │ │ ├── InstagramService.java │ │ │ └── Photo.java │ │ │ └── twitter │ │ │ ├── DataLoadAsyncTask.java │ │ │ ├── Tweet.java │ │ │ ├── TwitterClient.java │ │ │ ├── TwitterPreferencesFragment.java │ │ │ ├── TwitterService.java │ │ │ └── UserListAdapter.java │ │ └── res │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── activity_preferences.xml │ │ ├── activity_web_view.xml │ │ ├── content_main.xml │ │ ├── content_web_view.xml │ │ ├── fragment_instagram_preferences.xml │ │ ├── fragment_preferences.xml │ │ ├── fragment_twitter_preferences.xml │ │ ├── social_media_item.xml │ │ └── user_list_info.xml │ │ ├── menu │ │ ├── menu_main.xml │ │ └── menu_preferences.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-v21 │ │ └── styles.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradlew ├── gradlew.bat └── settings.gradle ├── Chapter07 ├── .gitignore ├── cli │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── steeplesoft │ │ │ └── mailfilter │ │ │ ├── AccountProcessor.java │ │ │ ├── AccountService.java │ │ │ ├── MailFilter.java │ │ │ ├── exceptions │ │ │ ├── AccountValidationException.java │ │ │ ├── MailFilterException.java │ │ │ └── RuleValidationException.java │ │ │ └── model │ │ │ ├── Account.java │ │ │ ├── Rule.java │ │ │ ├── RuleType.java │ │ │ └── validation │ │ │ ├── ValidRule.java │ │ │ └── ValidRuleValidator.java │ │ └── test │ │ ├── java │ │ └── com │ │ │ └── steeplesoft │ │ │ └── mailfilter │ │ │ └── test │ │ │ ├── AccountServiceTest.java │ │ │ ├── RuleTest.java │ │ │ ├── TestMailUtil.java │ │ │ └── ValidRuleValidatorTest.java │ │ └── resources │ │ └── rules.json ├── gui │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ ├── com │ │ │ └── steeplesoft │ │ │ │ └── mailfilter │ │ │ │ ├── gui │ │ │ │ ├── Controller.java │ │ │ │ ├── MailFilter.java │ │ │ │ └── RuleDescriptionFactory.java │ │ │ │ └── service │ │ │ │ ├── MailFilterJob.java │ │ │ │ └── MailFilterService.java │ │ └── jfxtras │ │ │ └── labs │ │ │ └── scene │ │ │ └── control │ │ │ └── BeanPathAdapter.java │ │ └── resources │ │ ├── fxml │ │ └── mailfilter.fxml │ │ └── styles │ │ └── Styles.css └── pom.xml ├── Chapter08 ├── .gitignore ├── application │ ├── pom.xml │ └── src │ │ ├── main │ │ └── build │ │ │ └── launcher.conf │ │ └── test │ │ └── java │ │ └── com │ │ └── steeplesoft │ │ └── photobeans │ │ └── ApplicationTest.java ├── branding │ ├── pom.xml │ └── src │ │ └── main │ │ ├── nbm-branding │ │ ├── core │ │ │ └── core.jar │ │ │ │ └── org │ │ │ │ └── netbeans │ │ │ │ └── core │ │ │ │ └── startup │ │ │ │ ├── Bundle.properties │ │ │ │ ├── frame.gif │ │ │ │ ├── frame32.gif │ │ │ │ ├── frame48.gif │ │ │ │ └── splash.gif │ │ └── modules │ │ │ ├── org-netbeans-core-windows.jar │ │ │ └── org │ │ │ │ └── netbeans │ │ │ │ └── core │ │ │ │ └── windows │ │ │ │ └── view │ │ │ │ └── ui │ │ │ │ ├── Bundle.properties │ │ │ │ └── Bundle_en_US.properties │ │ │ └── org-netbeans-core.jar │ │ │ └── org │ │ │ └── netbeans │ │ │ └── core │ │ │ └── ui │ │ │ └── Bundle.properties │ │ ├── nbm │ │ └── manifest.mf │ │ └── resources │ │ ├── camera-icon-16x16.png │ │ ├── camera-icon-32x32.png │ │ ├── camera-icon-48x48.png │ │ └── com │ │ └── steeplesoft │ │ └── photobeans │ │ └── branding │ │ └── Bundle.properties ├── photobeans-main │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── steeplesoft │ │ │ └── photobeans │ │ │ ├── main │ │ │ ├── PhotoListTopComponent.form │ │ │ ├── PhotoListTopComponent.java │ │ │ ├── PhotoViewerController.java │ │ │ ├── PhotoViewerTopComponent.form │ │ │ ├── PhotoViewerTopComponent.java │ │ │ ├── factories │ │ │ │ ├── MonthNodeFactory.java │ │ │ │ ├── PhotoNodeFactory.java │ │ │ │ └── YearChildFactory.java │ │ │ ├── nodes │ │ │ │ ├── MonthNode.java │ │ │ │ ├── PhotoNode.java │ │ │ │ ├── RootNode.java │ │ │ │ └── YearNode.java │ │ │ └── options │ │ │ │ ├── SourceDirectoriesOptionsPanelController.java │ │ │ │ ├── SourceDirectoriesPanel.form │ │ │ │ ├── SourceDirectoriesPanel.java │ │ │ │ └── package-info.java │ │ │ └── manager │ │ │ ├── PhotoManager.java │ │ │ ├── impl │ │ │ ├── Photo.java │ │ │ ├── PhotoManagerImpl.java │ │ │ └── SourceDirScanner.java │ │ │ └── reload │ │ │ ├── ReloadCookie.java │ │ │ └── ReloadImagesAction.java │ │ ├── nbm │ │ └── manifest.mf │ │ └── resources │ │ ├── com │ │ └── steeplesoft │ │ │ └── photobeans │ │ │ └── main │ │ │ ├── Bundle.properties │ │ │ └── options │ │ │ └── Bundle.properties │ │ └── fxml │ │ └── photoviewer.fxml └── pom.xml ├── Chapter09 ├── .gitignore ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── steeplesoft │ │ └── monumentum │ │ ├── model │ │ ├── Note.java │ │ ├── User.java │ │ └── package-info.java │ │ ├── mongo │ │ ├── Collection.java │ │ └── Producers.java │ │ ├── rest │ │ ├── LocalDateTimeAdapter.java │ │ ├── Monumentum.java │ │ └── resource │ │ │ ├── AuthenticationResource.java │ │ │ └── NoteResource.java │ │ └── security │ │ ├── KeyGenerator.java │ │ ├── Secure.java │ │ ├── SecureFilter.java │ │ └── UserProducer.java │ ├── resources │ └── logging.properties │ └── webapp │ ├── WEB-INF │ └── beans.xml │ ├── images │ ├── plus-225x225.png │ └── x-225x225.png │ ├── index.html │ ├── loginsuccess.html │ ├── monumentum.css │ └── monumentum.js ├── Chapter10 ├── .gitignore ├── api │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── steeplesoft │ │ │ └── cloudnotice │ │ │ └── api │ │ │ ├── CloudNoticeDAO.java │ │ │ ├── Recipient.java │ │ │ ├── SesClient.java │ │ │ └── SnsClient.java │ │ └── test │ │ └── java │ │ └── com │ │ └── steeplesoft │ │ └── cloudnotice │ │ └── api │ │ └── CloudNoticeDaoTest.java ├── function │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── steeplesoft │ │ │ └── cloudnotice │ │ │ └── function │ │ │ └── SnsEventHandler.java │ │ └── test │ │ └── java │ │ └── com │ │ └── steeplesoft │ │ └── cloudnotice │ │ └── function │ │ ├── SnsEventHandlerTest.java │ │ └── TestContext.java ├── manager │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── steeplesoft │ │ │ └── cloudnotice │ │ │ └── manager │ │ │ ├── CloudNoticeManager.java │ │ │ └── CloudNoticeManagerController.java │ │ └── resources │ │ ├── fxml │ │ └── manager.fxml │ │ └── styles │ │ └── styles.css └── pom.xml ├── Chapter11 ├── .gitignore ├── deskdroid-android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ ├── proguard-rules.pro │ │ └── src │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── steeplesoft │ │ │ │ └── deskdroid │ │ │ │ ├── AuthorizeClientActivity.java │ │ │ │ ├── BootReceiver.java │ │ │ │ ├── MainActivity.java │ │ │ │ ├── Permissions.java │ │ │ │ ├── WifiReceiver.java │ │ │ │ └── service │ │ │ │ ├── DeskDroidResource.java │ │ │ │ ├── DeskDroidService.java │ │ │ │ ├── KeyGenerator.java │ │ │ │ ├── Secure.java │ │ │ │ └── SecureFilter.java │ │ │ └── res │ │ │ ├── layout │ │ │ ├── activity_authorize_client.xml │ │ │ ├── activity_main.xml │ │ │ └── content_main.xml │ │ │ ├── menu │ │ │ └── menu_main.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── green_check.png │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_round.png │ │ │ └── red_x.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ └── values │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradlew │ ├── gradlew.bat │ └── settings.gradle ├── deskdroid-desktop │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── steeplesoft │ │ │ └── deskdroid │ │ │ └── desktop │ │ │ ├── ConnectToPhoneController.java │ │ │ ├── ConversationCell.java │ │ │ ├── ConversationService.java │ │ │ ├── DeskDroidController.java │ │ │ ├── DeskDroidPreferences.java │ │ │ ├── DragResizerXY.java │ │ │ ├── MainApp.java │ │ │ ├── MessageCell.java │ │ │ └── SendMessageDialogController.java │ │ └── resources │ │ ├── fxml │ │ ├── connect.fxml │ │ ├── deskdroid.fxml │ │ └── message_dialog.fxml │ │ ├── styles │ │ └── deskdroid.css │ │ └── unknown.png ├── deskdroid-shared │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ ├── com │ │ └── steeplesoft │ │ │ └── deskdroid │ │ │ └── model │ │ │ ├── Conversation.java │ │ │ ├── ConversationComparator.java │ │ │ ├── Message.java │ │ │ ├── MessageComparator.java │ │ │ └── Participant.java │ │ └── module-info.java └── pom.xml ├── LICENSE └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear in the root of a volume 35 | .DocumentRevisions-V100 36 | .fseventsd 37 | .Spotlight-V100 38 | .TemporaryItems 39 | .Trashes 40 | .VolumeIcon.icns 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | -------------------------------------------------------------------------------- /Chapter02/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /Chapter02/src/main/java/com/steeplesoft/processmanager/ProcessManager.java: -------------------------------------------------------------------------------- 1 | package com.steeplesoft.processmanager; 2 | 3 | import javafx.application.Application; 4 | import static javafx.application.Application.launch; 5 | import javafx.fxml.FXMLLoader; 6 | import javafx.scene.Parent; 7 | import javafx.scene.Scene; 8 | import javafx.stage.Stage; 9 | 10 | public class ProcessManager extends Application { 11 | 12 | @Override 13 | public void start(Stage stage) throws Exception { 14 | Parent root = FXMLLoader.load(getClass().getResource("/fxml/procman.fxml")); 15 | 16 | Scene scene = new Scene(root); 17 | scene.getStylesheets().add("/styles/Styles.css"); 18 | 19 | stage.setTitle("Process Manager"); 20 | stage.setScene(scene); 21 | stage.show(); 22 | } 23 | 24 | public static void main(String[] args) { 25 | launch(args); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Chapter02/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module procman.app { 2 | requires javafx.controls; 3 | requires javafx.fxml; 4 | } -------------------------------------------------------------------------------- /Chapter02/src/main/resources/fxml/procman.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 |
43 |
44 | -------------------------------------------------------------------------------- /Chapter02/src/main/resources/styles/Styles.css: -------------------------------------------------------------------------------- 1 | .button { 2 | -fx-font-weight: bold; 3 | } 4 | -------------------------------------------------------------------------------- /Chapter03/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /Chapter03/cli/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module dupefind.cli { 2 | requires dupefind.lib; 3 | requires tomitribe.crest; 4 | requires tomitribe.crest.api; 5 | } -------------------------------------------------------------------------------- /Chapter03/cli/src/main/resources/com/steeplesoft/dupefinder/cli/OptionDescriptions.properties: -------------------------------------------------------------------------------- 1 | path = Adds a path to be searched. Can be specified multiple times. 2 | pattern = Adds a pattern to match against the file names (e.g., "*.png"). Can be specified multiple times. 3 | show-timings = Show how long the scan took 4 | verbose = Show summary of duplicate scan configuration -------------------------------------------------------------------------------- /Chapter03/cli/src/main/resources/crest-commands.txt: -------------------------------------------------------------------------------- 1 | com.steeplesoft.dupefinder.cli.DupeFinderCommands 2 | -------------------------------------------------------------------------------- /Chapter03/gui/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | com.steeplesoft 7 | dupefinder-master 8 | 1.0-SNAPSHOT 9 | 10 | 11 | dupefinder-gui 12 | jar 13 | 14 | Duplicate Finder - GUI 15 | 16 | 17 | com.steeplesoft.dupefinder.Main 18 | 19 | 20 | 21 | 22 | ${project.groupId} 23 | dupefinder-lib 24 | ${project.version} 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Chapter03/gui/src/main/java/com/steeplesoft/dupefinder/Main.java: -------------------------------------------------------------------------------- 1 | package com.steeplesoft.dupefinder; 2 | 3 | import javafx.application.Application; 4 | import static javafx.application.Application.launch; 5 | import javafx.fxml.FXMLLoader; 6 | import javafx.scene.Parent; 7 | import javafx.scene.Scene; 8 | import javafx.stage.Stage; 9 | 10 | public class Main extends Application { 11 | @Override 12 | public void start(Stage stage) throws Exception { 13 | Parent root = FXMLLoader.load(getClass().getResource("/fxml/Scene.fxml")); 14 | 15 | Scene scene = new Scene(root); 16 | scene.getStylesheets().add("/styles/Styles.css"); 17 | 18 | stage.setTitle("Duplicate File Finder"); 19 | stage.setScene(scene); 20 | stage.show(); 21 | } 22 | 23 | /** 24 | * The main() method is ignored in correctly deployed JavaFX application. main() serves only as fallback in case the application 25 | * can not be launched through deployment artifacts, e.g., in IDEs with limited FX support. NetBeans ignores main(). 26 | * 27 | * @param args the command line arguments 28 | */ 29 | public static void main(String[] args) { 30 | launch(args); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Chapter03/gui/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module dupefind.gui { 2 | requires dupefind.lib; 3 | requires java.logging; 4 | requires javafx.controls; 5 | requires javafx.fxml; 6 | requires java.desktop; 7 | } -------------------------------------------------------------------------------- /Chapter03/gui/src/main/resources/styles/Styles.css: -------------------------------------------------------------------------------- 1 | .button { 2 | -fx-font-weight: bold; 3 | } 4 | -------------------------------------------------------------------------------- /Chapter03/lib/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | com.steeplesoft 7 | dupefinder-master 8 | 1.0-SNAPSHOT 9 | 10 | 11 | dupefinder-lib 12 | jar 13 | 14 | Duplicate Finder - Library 15 | 16 | 17 | org.xerial 18 | sqlite-jdbc 19 | ${sqlite.version} 20 | 21 | 22 | 23 | org.eclipse.persistence 24 | javax.persistence 25 | ${jpa.version} 26 | 27 | 28 | 29 | org.eclipse.persistence 30 | eclipselink 31 | ${eclipse.version} 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Chapter03/lib/src/main/java/com/steeplesoft/dupefinder/lib/model/FileInfo.java: -------------------------------------------------------------------------------- 1 | package com.steeplesoft.dupefinder.lib.model; 2 | 3 | import java.io.Serializable; 4 | import javax.persistence.*; 5 | 6 | /** 7 | * 8 | * @author jason 9 | */ 10 | @Entity 11 | public class FileInfo implements Serializable { 12 | @GeneratedValue 13 | @Id 14 | private int id; 15 | private String fileName; 16 | private String path; 17 | private long size; 18 | private String hash; 19 | 20 | public int getId() { 21 | return id; 22 | } 23 | 24 | public void setId(int id) { 25 | this.id = id; 26 | } 27 | 28 | public String getFileName() { 29 | return fileName; 30 | } 31 | 32 | public void setFileName(String fileName) { 33 | this.fileName = fileName; 34 | } 35 | 36 | public String getPath() { 37 | return path; 38 | } 39 | 40 | public void setPath(String path) { 41 | this.path = path; 42 | } 43 | 44 | public long getSize() { 45 | return size; 46 | } 47 | 48 | public void setSize(long size) { 49 | this.size = size; 50 | } 51 | 52 | public String getHash() { 53 | return hash; 54 | } 55 | 56 | public void setHash(String hash) { 57 | this.hash = hash; 58 | } 59 | 60 | @Override 61 | public boolean equals(Object o) { 62 | if (this == o) return true; 63 | if (o == null || getClass() != o.getClass()) return false; 64 | 65 | FileInfo fileInfo = (FileInfo) o; 66 | 67 | if (id != fileInfo.id) return false; 68 | if (size != fileInfo.size) return false; 69 | if (!fileName.equals(fileInfo.fileName)) return false; 70 | if (!path.equals(fileInfo.path)) return false; 71 | return hash != null ? hash.equals(fileInfo.hash) : fileInfo.hash == null; 72 | 73 | } 74 | 75 | @Override 76 | public int hashCode() { 77 | int result = id; 78 | result = 31 * result + fileName.hashCode(); 79 | result = 31 * result + path.hashCode(); 80 | result = 31 * result + (int) (size ^ (size >>> 32)); 81 | result = 31 * result + (hash != null ? hash.hashCode() : 0); 82 | return result; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Chapter03/lib/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module dupefind.lib { 2 | exports com.steeplesoft.dupefinder.lib; 3 | exports com.steeplesoft.dupefinder.lib.model; 4 | 5 | requires java.logging; 6 | requires javax.persistence; 7 | } -------------------------------------------------------------------------------- /Chapter03/lib/src/main/resources/META-INF/persistence.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | org.eclipse.persistence.jpa.PersistenceProvider 8 | com.steeplesoft.dupefinder.lib.FileInfo 9 | false 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Chapter03/lib/src/test/java/com/steeplesoft/dupefinder/lib/FileFinderTest.java: -------------------------------------------------------------------------------- 1 | package com.steeplesoft.dupefinder.lib; 2 | 3 | import com.steeplesoft.dupefinder.lib.FileFinder; 4 | import com.steeplesoft.dupefinder.lib.model.FileInfo; 5 | import java.util.List; 6 | import java.util.Map; 7 | import org.junit.Assert; 8 | import org.testng.annotations.Test; 9 | 10 | /** 11 | * 12 | * @author jason 13 | */ 14 | public class FileFinderTest { 15 | @Test 16 | public void findDuplicates() { 17 | FileFinder ff = new FileFinder(); 18 | ff.addPath("..\\test-data\\set1"); 19 | ff.addPath("..\\test-data\\set2"); 20 | ff.addPattern("*.txt"); 21 | ff.find(); 22 | final Map> duplicates = ff.getDuplicates(); 23 | Assert.assertFalse(duplicates.isEmpty()); 24 | duplicates.forEach((k, v) -> System.out.println(k + ": " + v.size())); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Chapter03/lib/src/test/resources/log4j2.yaml: -------------------------------------------------------------------------------- 1 | Configuration: 2 | status: info 3 | 4 | Appenders: 5 | Console: 6 | name: CONSOLE 7 | target: SYSTEM_OUT 8 | PatternLayout: 9 | Pattern: "%d{ISO8601} %-5p [%c{3}] [%t] %m%n" 10 | # RollingFile: 11 | # - name: APPLICATION 12 | # fileName: ../logs/my-app.log 13 | # filePattern: "../logs/$${date:yyyy-MM}/my-app-%d{yyyy-MM-dd}-%i.log.gz" 14 | # PatternLayout: 15 | # Pattern: "%d{ISO8601} %-5p [%c{3}] [%t] %m%n" 16 | # policies: 17 | # TimeBasedTriggeringPolicy: 18 | # interval: 1 19 | # modulate: true 20 | Loggers: 21 | Root: 22 | level: debug 23 | AppenderRef: 24 | - ref: CONSOLE 25 | Logger: 26 | - name: com.steeplesoft.dupefinder.lib 27 | additivity: false 28 | level: debug 29 | AppenderRef: 30 | - ref: CONSOLE -------------------------------------------------------------------------------- /Chapter03/test-data/set1/file1.txt: -------------------------------------------------------------------------------- 1 |

Hoc est non modo cor non habere, sed ne palatum quidem.

2 | 3 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit. An vero displicuit ea, quae tributa est animi virtutibus tanta praestantia? Qua tu etiam inprudens utebare non numquam. Duo Reges: constructio interrete. Quae duo sunt, unum facit. Nec enim, omnes avaritias si aeque avaritias esse dixerimus, sequetur ut etiam aequas esse dicamus. Et adhuc quidem ita nobis progresso ratio est, ut ea duceretur omnis a prima commendatione naturae. Haeret in salebra. Parvi enim primo ortu sic iacent, tamquam omnino sine animo sint.

4 | 5 |
6 | Utrum enim sit voluptas in iis rebus, quas primas secundum naturam esse diximus, necne sit ad id, quod agimus, nihil interest. 7 |
8 | 9 | 10 |
11 | Res enim fortasse verae, certe graves, non ita tractantur,
12 | ut debent, sed aliquanto minutius.
13 | 
14 | Sed haec in pueris;
15 | 
16 | 17 | 18 |
19 |
Memini vero, inquam;
20 |
Nec tamen ullo modo summum pecudis bonum et hominis idem mihi videri potest.
21 |
Tenent mordicus.
22 |
Idcirco enim non desideraret, quia, quod dolore caret, id in voluptate est.
23 |
Peccata paria.
24 |
Dulce amarum, leve asperum, prope longe, stare movere, quadratum rotundum.
25 |
Nihil illinc huc pervenit.
26 |
Itaque eos id agere, ut a se dolores, morbos, debilitates repellant.
27 |
At certe gravius.
28 |
Itaque si aut requietem natura non quaereret aut eam posset alia quadam ratione consequi.
29 |
30 | 31 | 32 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /Chapter03/test-data/set2/file1.txt: -------------------------------------------------------------------------------- 1 |

Hoc est non modo cor non habere, sed ne palatum quidem.

2 | 3 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit. An vero displicuit ea, quae tributa est animi virtutibus tanta praestantia? Qua tu etiam inprudens utebare non numquam. Duo Reges: constructio interrete. Quae duo sunt, unum facit. Nec enim, omnes avaritias si aeque avaritias esse dixerimus, sequetur ut etiam aequas esse dicamus. Et adhuc quidem ita nobis progresso ratio est, ut ea duceretur omnis a prima commendatione naturae. Haeret in salebra. Parvi enim primo ortu sic iacent, tamquam omnino sine animo sint.

4 | 5 |
6 | Utrum enim sit voluptas in iis rebus, quas primas secundum naturam esse diximus, necne sit ad id, quod agimus, nihil interest. 7 |
8 | 9 | 10 |
11 | Res enim fortasse verae, certe graves, non ita tractantur,
12 | ut debent, sed aliquanto minutius.
13 | 
14 | Sed haec in pueris;
15 | 
16 | 17 | 18 |
19 |
Memini vero, inquam;
20 |
Nec tamen ullo modo summum pecudis bonum et hominis idem mihi videri potest.
21 |
Tenent mordicus.
22 |
Idcirco enim non desideraret, quia, quod dolore caret, id in voluptate est.
23 |
Peccata paria.
24 |
Dulce amarum, leve asperum, prope longe, stare movere, quadratum rotundum.
25 |
Nihil illinc huc pervenit.
26 |
Itaque eos id agere, ut a se dolores, morbos, debilitates repellant.
27 |
At certe gravius.
28 |
Itaque si aut requietem natura non quaereret aut eam posset alia quadam ratione consequi.
29 |
30 | 31 | 32 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /Chapter04/datecalc-cli/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.steeplesoft 6 | datecalc-master 7 | 1.0-SNAPSHOT 8 | 9 | datecalc-cli 10 | Date Calculator - CLI 11 | jar 12 | 13 | 14 | ${project.groupId} 15 | datecalc-lib 16 | ${project.version} 17 | 18 | 19 | org.tomitribe 20 | tomitribe-crest 21 | 22 | 23 | 24 | 25 | install 26 | 27 | 28 | maven-shade-plugin 29 | ${plugin.shade} 30 | 31 | 32 | package 33 | 34 | shade 35 | 36 | 37 | 38 | 39 | org.tomitribe.crest.Main 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /Chapter04/datecalc-cli/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module datecalc.cli { 2 | requires datecalc.lib; 3 | requires tomitribe.crest; 4 | requires tomitribe.crest.api; 5 | 6 | exports com.steeplesoft.datecalc.cli; 7 | } 8 | -------------------------------------------------------------------------------- /Chapter04/datecalc-cli/src/main/resources/crest-commands.txt: -------------------------------------------------------------------------------- 1 | com.steeplesoft.j9blueprints.datecalc.cli.DateCalc -------------------------------------------------------------------------------- /Chapter04/datecalc-lib/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.steeplesoft 6 | datecalc-master 7 | 1.0-SNAPSHOT 8 | 9 | datecalc-lib 10 | Date Calculator - Library 11 | jar 12 | 13 | 14 | 2.5.0 15 | 2.1.1 16 | 17 | 18 | 19 | 20 | org.eclipse.persistence 21 | javax.persistence 22 | ${jpa.version} 23 | 24 | 25 | 26 | org.eclipse.persistence 27 | eclipselink 28 | ${eclipse.version} 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Chapter04/datecalc-lib/src/main/java/com/steeplesoft/datecalc/DateCalcException.java: -------------------------------------------------------------------------------- 1 | package com.steeplesoft.datecalc; 2 | 3 | /** 4 | * 5 | * @author jason 6 | */ 7 | public class DateCalcException extends RuntimeException { 8 | public DateCalcException(String message) { 9 | super(message); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /Chapter04/datecalc-lib/src/main/java/com/steeplesoft/datecalc/DateCalculatorResult.java: -------------------------------------------------------------------------------- 1 | package com.steeplesoft.datecalc; 2 | 3 | import java.time.Duration; 4 | import java.time.LocalDate; 5 | import java.time.LocalTime; 6 | import java.time.Period; 7 | import java.util.Optional; 8 | 9 | /** 10 | * 11 | * @author jason 12 | */ 13 | public class DateCalculatorResult { 14 | private Period period; 15 | private Duration duration; 16 | private LocalDate date; 17 | private LocalTime time; 18 | private String expression; 19 | 20 | public DateCalculatorResult(Period period) { 21 | this.period = period; 22 | } 23 | 24 | public DateCalculatorResult(Duration duration) { 25 | this.duration = duration; 26 | } 27 | 28 | public DateCalculatorResult(LocalDate date) { 29 | this.date = date; 30 | } 31 | 32 | public DateCalculatorResult(LocalTime time) { 33 | this.time = time; 34 | } 35 | 36 | public Optional getPeriod() { 37 | return Optional.ofNullable(period); 38 | } 39 | 40 | public Optional getDuration() { 41 | return Optional.ofNullable(duration); 42 | } 43 | 44 | public Optional getDate() { 45 | return Optional.ofNullable(date); 46 | } 47 | 48 | public Optional getTime() { 49 | return Optional.ofNullable(time); 50 | } 51 | 52 | public String getExpression() { 53 | return expression; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Chapter04/datecalc-lib/src/main/java/com/steeplesoft/datecalc/parser/token/DateToken.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | package com.steeplesoft.datecalc.parser.token; 7 | 8 | import com.steeplesoft.datecalc.DateCalcException; 9 | import java.time.LocalDate; 10 | import java.time.format.DateTimeParseException; 11 | import java.util.Optional; 12 | 13 | /** 14 | * 15 | * @author jason 16 | */ 17 | public class DateToken extends Token { 18 | private static final String TODAY = "today"; 19 | public static String REGEX = "\\d{4}[-/][01]\\d[-/][0123]\\d|today"; // YYYY-MM-DD or YYYY/MM/DD 20 | 21 | public static class Info implements Token.Info { 22 | @Override 23 | public String getRegex() { 24 | return REGEX; 25 | } 26 | 27 | @Override 28 | public DateToken getToken(String text) { 29 | return of(text); 30 | } 31 | } 32 | 33 | private DateToken(LocalDate value) { 34 | this.value = value; 35 | } 36 | 37 | public static DateToken of(String text) { 38 | try { 39 | return TODAY.equals(text.toLowerCase()) 40 | ? new DateToken(LocalDate.now()) 41 | : new DateToken(LocalDate.parse(text.replace("/", "-"))); 42 | } catch (DateTimeParseException ex) { 43 | throw new DateCalcException("Invalid date format: " + text); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Chapter04/datecalc-lib/src/main/java/com/steeplesoft/datecalc/parser/token/IntegerToken.java: -------------------------------------------------------------------------------- 1 | package com.steeplesoft.datecalc.parser.token; 2 | 3 | import com.steeplesoft.datecalc.DateCalcException; 4 | 5 | /** 6 | * 7 | * @author jason 8 | */ 9 | public class IntegerToken extends Token { 10 | 11 | public static final String REGEX = "\\d+"; 12 | 13 | public static class Info implements Token.Info { 14 | 15 | @Override 16 | public String getRegex() { 17 | return REGEX; 18 | } 19 | 20 | @Override 21 | public IntegerToken getToken(String text) { 22 | return of(text); 23 | } 24 | } 25 | 26 | private IntegerToken(Integer value) { 27 | this.value = value; 28 | } 29 | 30 | public static IntegerToken of(String text) { 31 | try { 32 | return new IntegerToken(Integer.valueOf(text)); 33 | } catch (NumberFormatException ex) { 34 | throw new DateCalcException("Invalid number: " + text); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Chapter04/datecalc-lib/src/main/java/com/steeplesoft/datecalc/parser/token/OperatorToken.java: -------------------------------------------------------------------------------- 1 | package com.steeplesoft.datecalc.parser.token; 2 | 3 | import com.steeplesoft.datecalc.DateCalcException; 4 | 5 | /** 6 | * 7 | * @author jason 8 | */ 9 | public class OperatorToken extends Token { 10 | 11 | public static final String MINUS = "-"; 12 | public static final String PLUS = "+"; 13 | public static final String REGEX = "\\+|-|to"; 14 | 15 | public static class Info implements Token.Info { 16 | @Override 17 | public String getRegex() { 18 | return REGEX; 19 | } 20 | 21 | @Override 22 | public OperatorToken getToken(String text) { 23 | return of(text); 24 | } 25 | } 26 | 27 | private OperatorToken(String value) { 28 | this.value = value; 29 | } 30 | 31 | public boolean isAddition() { 32 | return PLUS.equals(value); 33 | } 34 | 35 | public static OperatorToken of(String text) { 36 | if (PLUS.equals(text) || MINUS.equals(text)) { 37 | return new OperatorToken(text); 38 | } else { 39 | throw new DateCalcException("Invalid operator specified: " + text); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Chapter04/datecalc-lib/src/main/java/com/steeplesoft/datecalc/parser/token/TimeZoneToken.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | package com.steeplesoft.datecalc.parser.token; 7 | 8 | import com.steeplesoft.datecalc.DateCalcException; 9 | import java.time.ZoneId; 10 | 11 | /** 12 | * 13 | * @author jason 14 | */ 15 | public class TimeZoneToken extends Token { 16 | public static final String REGEX = "(?:UTC|GMT)?(?:[\\+-][01]*[0-9]+)"; 17 | public static class Info implements Token.Info { 18 | @Override 19 | public String getRegex() { 20 | return REGEX; 21 | } 22 | 23 | @Override 24 | public TimeZoneToken getToken(String text) { 25 | return of(text); 26 | } 27 | } 28 | 29 | public TimeZoneToken(ZoneId value) { 30 | this.value = value; 31 | } 32 | 33 | public static TimeZoneToken of(String text) { 34 | TimeZoneToken token; 35 | 36 | try { 37 | token = new TimeZoneToken(ZoneId.of(text)); 38 | } catch (Exception e) { 39 | throw new DateCalcException("Invalid time zone specified: " + text); 40 | } 41 | 42 | return token; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Chapter04/datecalc-lib/src/main/java/com/steeplesoft/datecalc/parser/token/Token.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | package com.steeplesoft.datecalc.parser.token; 7 | 8 | /** 9 | * 10 | * @author jason 11 | * @param 12 | */ 13 | public abstract class Token { 14 | protected T value; 15 | 16 | public interface Info { 17 | String getRegex(); 18 | Token getToken(String text); 19 | } 20 | 21 | public T getValue() { 22 | return value; 23 | } 24 | 25 | @Override 26 | public String toString() { 27 | return this.getClass().getSimpleName() + ": " + getValue(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Chapter04/datecalc-lib/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module datecalc.lib { 2 | exports com.steeplesoft.datecalc; 3 | } -------------------------------------------------------------------------------- /Chapter04/datecalc-lib/src/test/java/com/steeplesoft/datecalc/DateCalculatorTest.java: -------------------------------------------------------------------------------- 1 | package com.steeplesoft.datecalc; 2 | 3 | import com.steeplesoft.datecalc.DateCalculator; 4 | import com.steeplesoft.datecalc.DateCalculatorResult; 5 | import java.time.LocalTime; 6 | import java.time.Period; 7 | import org.testng.Assert; 8 | import org.testng.annotations.Test; 9 | 10 | /** 11 | * 12 | * @author jason 13 | */ 14 | public class DateCalculatorTest { 15 | private final DateCalculator dc = new DateCalculator(); 16 | 17 | @Test 18 | public void testDateMath() { 19 | final String expression = "today + 2 weeks 3 days"; 20 | DateCalculatorResult result = dc.calculate(expression); 21 | Assert.assertNotNull(result.getDate().get(), "'" + expression + "' should have returned a result."); 22 | } 23 | 24 | @Test 25 | public void testDateDiff() { 26 | final String expression = "2016/07/04 - 1776/07/04"; 27 | DateCalculatorResult result = dc.calculate(expression); 28 | Assert.assertEquals(result.getPeriod().get(), Period.of(240,0,0), "'" + expression + "' should..."); 29 | } 30 | 31 | @Test 32 | public void timeMath() { 33 | final String expression = "12:37 + 42 m"; 34 | DateCalculatorResult result = dc.calculate(expression); 35 | Assert.assertEquals(result.getTime().get(), LocalTime.parse("13:19")); 36 | } 37 | 38 | @Test 39 | public void timeDiff() { 40 | final String expression = "12:37 - 7:15"; 41 | DateCalculatorResult result = dc.calculate(expression); 42 | Assert.assertEquals(result.getDuration().get().toHoursPart(), 5); 43 | Assert.assertEquals(result.getDuration().get().toMinutesPart(), 22); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Chapter05/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /Chapter05/api/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.steeplesoft 6 | sunago-master 7 | 1.0-SNAPSHOT 8 | 9 | sunago-api 10 | Sunago - API 11 | jar 12 | -------------------------------------------------------------------------------- /Chapter05/api/src/main/java/com/steeplesoft/sunago/SunagoPrefsKeys.java: -------------------------------------------------------------------------------- 1 | package com.steeplesoft.sunago; 2 | 3 | /** 4 | * 5 | * @author jason 6 | */ 7 | public enum SunagoPrefsKeys { 8 | ITEM_COUNT("itemCount"); 9 | private final String key; 10 | 11 | SunagoPrefsKeys(String key) { 12 | this.key = key; 13 | } 14 | 15 | public String getKey() { 16 | return "sunago." + key; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /Chapter05/api/src/main/java/com/steeplesoft/sunago/SunagoUtil.java: -------------------------------------------------------------------------------- 1 | package com.steeplesoft.sunago; 2 | 3 | import com.steeplesoft.sunago.api.SunagoPreferences; 4 | import java.util.Iterator; 5 | import java.util.ServiceLoader; 6 | 7 | /** 8 | * 9 | * @author jason 10 | */ 11 | public class SunagoUtil { 12 | private static SunagoPreferences preferences; 13 | 14 | public static synchronized SunagoPreferences getSunagoPreferences() { 15 | if (preferences == null) { 16 | ServiceLoader spLoader = ServiceLoader.load(SunagoPreferences.class); 17 | Iterator iterator = spLoader.iterator(); 18 | preferences = iterator.hasNext() ? iterator.next() : null; 19 | } 20 | return preferences; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Chapter05/api/src/main/java/com/steeplesoft/sunago/api/SocialMediaClient.java: -------------------------------------------------------------------------------- 1 | package com.steeplesoft.sunago.api; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * 7 | * @author jason 8 | */ 9 | public interface SocialMediaClient { 10 | void authenticateUser(String token, String tokenSecret); 11 | String getAuthorizationUrl(); 12 | List getItems(); 13 | boolean isAuthenticated(); 14 | } 15 | -------------------------------------------------------------------------------- /Chapter05/api/src/main/java/com/steeplesoft/sunago/api/SocialMediaItem.java: -------------------------------------------------------------------------------- 1 | package com.steeplesoft.sunago.api; 2 | 3 | import java.io.Serializable; 4 | import java.util.Date; 5 | 6 | public interface SocialMediaItem extends Serializable { 7 | String getProvider(); 8 | String getTitle(); 9 | String getBody(); 10 | String getUrl(); 11 | String getImage(); 12 | Date getTimestamp(); 13 | } 14 | -------------------------------------------------------------------------------- /Chapter05/api/src/main/java/com/steeplesoft/sunago/api/SunagoPreferences.java: -------------------------------------------------------------------------------- 1 | package com.steeplesoft.sunago.api; 2 | 3 | /** 4 | * 5 | * @author jason 6 | */ 7 | public interface SunagoPreferences { 8 | String getPreference(String key); 9 | String getPreference(String key, String defaultValue); 10 | Integer getPreference(String key, Integer defaultValue); 11 | void putPreference(String key, String value); 12 | void putPreference(String key, Integer value); 13 | } 14 | -------------------------------------------------------------------------------- /Chapter05/api/src/main/java/com/steeplesoft/sunago/api/fx/SelectableItem.java: -------------------------------------------------------------------------------- 1 | package com.steeplesoft.sunago.api.fx; 2 | 3 | import javafx.beans.property.SimpleBooleanProperty; 4 | 5 | /** 6 | * 7 | * @author jason 8 | */ 9 | public abstract class SelectableItem { 10 | private final SimpleBooleanProperty selected = new SimpleBooleanProperty(false); 11 | private final T item; 12 | public SelectableItem(T item) { 13 | this.item = item; 14 | } 15 | public T getItem() { 16 | return item; 17 | } 18 | public SimpleBooleanProperty getSelected() { 19 | return selected; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Chapter05/api/src/main/java/com/steeplesoft/sunago/api/fx/SocialMediaPreferencesController.java: -------------------------------------------------------------------------------- 1 | package com.steeplesoft.sunago.api.fx; 2 | 3 | import javafx.scene.control.Tab; 4 | 5 | /** 6 | * 7 | * @author jason 8 | */ 9 | public abstract class SocialMediaPreferencesController { 10 | public abstract Tab getTab(); 11 | public abstract void savePreferences(); 12 | } -------------------------------------------------------------------------------- /Chapter05/app/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.steeplesoft 6 | sunago-master 7 | 1.0-SNAPSHOT 8 | 9 | sunago-app 10 | Sunago - App 11 | jar 12 | 13 | 14 | 15 | ${project.groupId} 16 | sunago-api 17 | ${project.version} 18 | 19 | 20 | 21 | 22 | 23 | maven-shade-plugin 24 | ${plugin.shade} 25 | 26 | 27 | package 28 | 29 | shade 30 | 31 | 32 | 33 | 34 | com.steeplesoft.sunago.app.Sunago 35 | 36 | 37 | 38 | 39 | ${project.groupId}:twitter 40 | ${project.groupId}:instagram 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /Chapter05/app/src/main/java/com/steeplesoft/sunago/app/Sunago.java: -------------------------------------------------------------------------------- 1 | package com.steeplesoft.sunago.app; 2 | 3 | import java.io.File; 4 | import java.net.MalformedURLException; 5 | import java.net.URL; 6 | import java.net.URLClassLoader; 7 | import java.util.Arrays; 8 | import java.util.logging.Level; 9 | import java.util.logging.Logger; 10 | import javafx.application.Application; 11 | import static javafx.application.Application.launch; 12 | import javafx.fxml.FXMLLoader; 13 | import javafx.scene.Parent; 14 | import javafx.scene.Scene; 15 | import javafx.stage.Stage; 16 | 17 | /** 18 | * 19 | * @author jason 20 | */ 21 | public class Sunago extends Application { 22 | 23 | public Sunago() throws Exception { 24 | super(); 25 | updateClassLoader(); 26 | } 27 | 28 | @Override 29 | public void start(Stage stage) throws Exception { 30 | Parent root = FXMLLoader.load(getClass().getResource("/fxml/sunago.fxml")); 31 | 32 | Scene scene = new Scene(root); 33 | scene.getStylesheets().add("/styles/styles.css"); 34 | 35 | stage.setTitle("Sunago"); 36 | stage.setScene(scene); 37 | stage.show(); 38 | } 39 | 40 | public static void main(String[] args) { 41 | launch(args); 42 | } 43 | 44 | private void updateClassLoader() { 45 | final File[] jars = getFiles(); 46 | if (jars != null) { 47 | URL[] urls = new URL[jars.length]; 48 | int index = 0; 49 | for (File jar : jars) { 50 | try { 51 | urls[index++] = jar.toURI().toURL(); 52 | } catch (MalformedURLException ex) { 53 | Logger.getLogger(Sunago.class.getName()).log(Level.SEVERE, null, ex); 54 | } 55 | } 56 | Thread.currentThread().setContextClassLoader(URLClassLoader.newInstance(urls)); 57 | } 58 | } 59 | 60 | private File[] getFiles() { 61 | String pluginDir = System.getProperty("user.home") + "/.sunago"; 62 | return new File(pluginDir).listFiles(file -> file.isFile() && file.getName().toLowerCase().endsWith(".jar")); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Chapter05/app/src/main/java/com/steeplesoft/sunago/app/SunagoPreferencesImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | package com.steeplesoft.sunago.app; 7 | 8 | import com.steeplesoft.sunago.api.SunagoPreferences; 9 | import java.util.prefs.Preferences; 10 | 11 | /** 12 | * 13 | * @author jason 14 | */ 15 | public class SunagoPreferencesImpl implements SunagoPreferences { 16 | private final Preferences prefs = Preferences.userRoot() 17 | .node(SunagoPreferencesImpl.class.getPackage().getName()); 18 | 19 | @Override 20 | public String getPreference(String key) { 21 | return prefs.get(key, null); 22 | } 23 | 24 | @Override 25 | public String getPreference(String key, String defaultValue) { 26 | return prefs.get(key, defaultValue); 27 | } 28 | 29 | @Override 30 | public Integer getPreference(String key, Integer defaultValue) { 31 | return prefs.getInt(key, defaultValue); 32 | } 33 | 34 | @Override 35 | public void putPreference(String key, String value) { 36 | prefs.put(key, value); 37 | } 38 | 39 | @Override 40 | public void putPreference(String key, Integer value) { 41 | prefs.putInt(key, value); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Chapter05/app/src/main/java/com/steeplesoft/sunago/app/SunagoProperties.java: -------------------------------------------------------------------------------- 1 | package com.steeplesoft.sunago.app; 2 | 3 | import com.steeplesoft.sunago.api.SunagoPreferences; 4 | import java.io.File; 5 | import java.io.FileInputStream; 6 | import java.io.FileOutputStream; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.util.Properties; 10 | import java.util.logging.Level; 11 | import java.util.logging.Logger; 12 | 13 | /** 14 | * 15 | * @author jason 16 | */ 17 | public class SunagoProperties implements SunagoPreferences { 18 | private Properties props = new Properties(); 19 | private final String FILE = System.getProperty("user.home") + File.separator + ".sunago.properties"; 20 | 21 | public SunagoProperties() { 22 | try (InputStream input = new FileInputStream(FILE)) { 23 | props.load(input); 24 | } catch (IOException ex) { 25 | } 26 | } 27 | 28 | @Override 29 | public String getPreference(String key) { 30 | return props.getProperty(key); 31 | } 32 | 33 | @Override 34 | public String getPreference(String key, String defaultValue) { 35 | String value = props.getProperty(key); 36 | return (value == null) ? defaultValue : value; 37 | } 38 | 39 | @Override 40 | public Integer getPreference(String key, Integer defaultValue) { 41 | String value = props.getProperty(key); 42 | return (value == null) ? defaultValue : Integer.parseInt(value); 43 | } 44 | 45 | @Override 46 | public void putPreference(String key, String value) { 47 | props.put(key, value); 48 | store(); 49 | } 50 | 51 | @Override 52 | public void putPreference(String key, Integer value) { 53 | if (value != null) { 54 | putPreference(key, value.toString()); 55 | } 56 | } 57 | 58 | private void store() { 59 | try (FileOutputStream output = new FileOutputStream(FILE)) { 60 | props.store(output, null); 61 | } catch (IOException e) { 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Chapter05/app/src/main/resources/META-INF/services/com.steeplesoft.sunago.api.SunagoPreferences: -------------------------------------------------------------------------------- 1 | #com.steeplesoft.sunago.app.SunagoPreferencesImpl 2 | com.steeplesoft.sunago.app.SunagoProperties 3 | -------------------------------------------------------------------------------- /Chapter05/app/src/main/resources/fxml/login.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Chapter05/app/src/main/resources/fxml/prefs.fxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 28 | 29 | 30 | 31 | 32 | 33 |
34 | 35 | 36 | 37 |