From 76e4ecac0b5b1f82060326b2f876ca18fc70fb35 Mon Sep 17 00:00:00 2001 From: Akash Yadav Date: Wed, 18 May 2022 18:30:57 +0530 Subject: [PATCH] [Gradle] Format java source code --- CONTRIBUTING.md | 4 +- .../itsaky/androidide/actions/ActionData.java | 36 +- .../com/itsaky/androidide/AboutActivity.java | 310 +- .../androidide/CrashHandlerActivity.java | 50 +- .../itsaky/androidide/DesignerActivity.java | 770 +- .../itsaky/androidide/DownloadActivity.java | 498 +- .../androidide/PreferencesActivity.java | 52 +- .../com/itsaky/androidide/SplashActivity.java | 98 +- .../itsaky/androidide/TerminalActivity.java | 863 +- .../androidide/adapters/AttrListAdapter.java | 68 +- .../adapters/AttrValueCompletionAdapter.java | 153 +- .../adapters/AttrValueFormatTabAdapter.java | 132 +- .../adapters/CompletionListAdapter.java | 329 +- .../adapters/DiagnosticItemAdapter.java | 120 +- .../adapters/DiagnosticsAdapter.java | 103 +- .../adapters/EditorBottomSheetTabAdapter.java | 254 +- .../androidide/adapters/IconTextAdapter.java | 132 +- .../adapters/OptionsSheetAdapter.java | 88 +- .../adapters/SearchListAdapter.java | 198 +- .../adapters/SimpleIconTextAdapter.java | 44 +- .../adapters/SymbolInputAdapter.java | 165 +- .../adapters/WidgetGroupItemAdapter.java | 68 +- .../adapters/WidgetItemAdapter.java | 72 +- .../adapters/XMLAttributeListAdapter.java | 121 +- .../viewholders/FileTreeViewHolder.java | 128 +- .../itsaky/androidide/app/StudioActivity.java | 184 +- .../com/itsaky/androidide/app/StudioApp.java | 180 +- .../filepicker/IDEDocumentsProvider.java | 496 +- .../androidide/fragments/BaseFragment.java | 6 +- .../fragments/CrashReportFragment.java | 202 +- .../fragments/DiagnosticsListFragment.java | 82 +- .../fragments/FileTreeFragment.java | 505 +- .../androidide/fragments/IDELogFragment.java | 42 +- .../androidide/fragments/LogViewFragment.java | 72 +- .../fragments/NonEditableEditorFragment.java | 62 +- .../fragments/SearchResultFragment.java | 80 +- .../fragments/SimpleOutputFragment.java | 41 +- .../attr/AbstractReferenceEditor.java | 103 +- .../attr/BaseValueEditorFragment.java | 70 +- .../fragments/attr/BooleanEditor.java | 94 +- .../fragments/attr/ColorEditor.java | 128 +- .../fragments/attr/DimensionEditor.java | 152 +- .../androidide/fragments/attr/EnumEditor.java | 52 +- .../fragments/attr/FixedValueEditor.java | 107 +- .../androidide/fragments/attr/FlagEditor.java | 82 +- .../fragments/attr/ReferenceEditor.java | 72 +- .../fragments/attr/StringEditor.java | 90 +- .../preferences/BasePreferenceFragment.java | 35 +- .../preferences/BuildPreferences.java | 217 +- .../preferences/EditorPreferences.java | 507 +- .../preferences/GeneralPreferences.java | 98 +- .../fragments/preferences/IDEPreferences.java | 216 +- .../fragments/sheets/AttrEditorSheet.java | 534 +- .../sheets/AttrValueEditorSheet.java | 215 +- .../fragments/sheets/AttributeListSheet.java | 147 +- .../sheets/BaseBottomSheetFragment.java | 194 +- .../fragments/sheets/OptionsListFragment.java | 102 +- .../fragments/sheets/ProgressSheet.java | 160 +- .../fragments/sheets/TextSheetFragment.java | 163 +- .../handlers/FileOptionsHandler.java | 697 +- .../androidide/handlers/IDEHandler.java | 38 +- .../interfaces/DiagnosticClickListener.java | 4 +- .../interfaces/EditorActivityProvider.java | 11 +- .../language/CommonCompletionProvider.java | 152 +- .../androidide/language/IAnalyzeManager.java | 26 +- .../androidide/language/IDELanguage.java | 84 +- .../language/groovy/GroovyAnalyzer.java | 426 +- .../language/groovy/GroovyAutoComplete.java | 664 +- .../language/groovy/GroovyLanguage.java | 220 +- .../language/java/JavaAnalyzer.java | 620 +- .../language/java/JavaLanguage.java | 254 +- .../language/logs/LogLanguageImpl.java | 140 +- .../androidide/language/xml/XMLAnalyzer.java | 205 +- .../androidide/language/xml/XMLLanguage.java | 177 +- .../androidide/lsp/IDELanguageClientImpl.java | 633 +- .../itsaky/androidide/models/ApkMetadata.java | 514 +- .../androidide/models/DiagnosticGroup.java | 20 +- .../androidide/models/IconTextListItem.java | 28 +- .../com/itsaky/androidide/models/License.java | 22 +- .../androidide/models/NewProjectDetails.java | 78 +- .../androidide/models/ProjectTemplate.java | 90 +- .../androidide/models/SearchResult.java | 18 +- .../itsaky/androidide/models/UIWidget.java | 148 +- .../androidide/models/UIWidgetGroup.java | 224 +- .../androidide/models/XMLAttribute.java | 250 +- .../projects/ProjectResourceTable.java | 542 +- .../services/GradleBuildService.java | 918 +- .../androidide/services/LogReceiver.java | 56 +- .../syntax/colorschemes/SchemeAndroidIDE.java | 336 +- .../syntax/highlighters/Highlighter.java | 2 +- .../syntax/highlighters/JavaHighlighter.java | 340 +- .../itsaky/androidide/tasks/TaskExecutor.java | 84 +- .../tasks/callables/FileTreeCallable.java | 80 +- .../tasks/callables/LicenseReader.java | 30 +- .../callables/ListDirectoryCallable.java | 32 +- .../callables/ProjectCreatorCallable.java | 28 +- .../tasks/callables/UnzipCallable.java | 20 +- .../itsaky/androidide/ui/WidgetDragData.java | 42 +- .../androidide/ui/WidgetDragListener.java | 315 +- .../ui/WidgetDragShadowBuilder.java | 28 +- .../androidide/ui/WidgetTouchListener.java | 118 +- .../ui/edit/BaseSuggestionProvider.java | 36 +- .../ui/edit/DimensionSuggestionProvider.java | 84 +- .../ui/edit/IValueSuggestionProvider.java | 34 +- .../itsaky/androidide/utils/DialogUtils.java | 122 +- .../utils/EditorBottomSheetBehavior.java | 54 +- .../utils/HighlightRangeHelper.java | 212 +- .../itsaky/androidide/utils/JSONUtility.java | 16 +- .../com/itsaky/androidide/utils/LSPUtils.java | 214 +- .../androidide/utils/ProjectWriter.java | 328 +- .../utils/RecursiveFileSearcher.java | 218 +- .../com/itsaky/androidide/utils/Symbols.java | 146 +- .../androidide/utils/TextWatcherAdapter.java | 12 +- .../androidide/utils/TransformUtils.java | 30 +- .../androidide/viewmodel/EditorViewModel.java | 190 +- .../views/DesignerLayoutContainer.java | 90 +- .../itsaky/androidide/views/EmptyView.java | 50 +- .../androidide/views/MaterialBanner.java | 288 +- .../androidide/views/SymbolInputView.java | 86 +- .../views/editor/BaseEditorWindow.java | 128 +- .../views/editor/CodeEditorView.java | 672 +- .../views/editor/DiagnosticWindow.java | 56 +- .../androidide/views/editor/IDEEditor.java | 1460 +- .../views/editor/SignatureHelpWindow.java | 248 +- .../views/transition/ArcMotionPlus.java | 158 +- .../views/transition/CubicBezierArc.java | 268 +- .../views/transition/ProgressTransition.java | 82 +- .../androidide/diffutils/DiffUtils.java | 1422 +- .../androidide/diffutils/structs/EditOp.java | 15 +- .../diffutils/structs/EditType.java | 12 +- .../diffutils/structs/MatchingBlock.java | 14 +- .../androidide/diffutils/structs/OpCode.java | 15 +- .../androidide/fuzzysearch/Applicable.java | 21 +- .../androidide/fuzzysearch/Extractor.java | 333 +- .../androidide/fuzzysearch/FuzzySearch.java | 1184 +- .../itsaky/androidide/fuzzysearch/Ratio.java | 39 +- .../fuzzysearch/StringProcessor.java | 28 +- .../fuzzysearch/ToStringFunction.java | 29 +- .../algorithms/BasicAlgorithm.java | 51 +- .../algorithms/DefaultStringFunction.java | 99 +- .../algorithms/DefaultStringProcessor.java | 99 +- .../fuzzysearch/algorithms/NoProcess.java | 11 +- .../algorithms/PrimitiveUtils.java | 43 +- .../algorithms/RatioAlgorithm.java | 70 +- .../fuzzysearch/algorithms/SetUtils.java | 23 +- .../fuzzysearch/algorithms/TokenSet.java | 38 +- .../fuzzysearch/algorithms/TokenSort.java | 29 +- .../fuzzysearch/algorithms/Utils.java | 137 +- .../fuzzysearch/algorithms/WeightedRatio.java | 85 +- .../model/BoundExtractedResult.java | 68 +- .../fuzzysearch/model/ExtractedResult.java | 58 +- .../fuzzysearch/ratios/PartialRatio.java | 101 +- .../fuzzysearch/ratios/SimpleRatio.java | 31 +- .../com/google/googlejavaformat/CloseOp.java | 34 +- .../googlejavaformat/CommentsHelper.java | 18 +- .../java/com/google/googlejavaformat/Doc.java | 1403 +- .../google/googlejavaformat/DocBuilder.java | 164 +- .../googlejavaformat/FormatterDiagnostic.java | 100 +- .../googlejavaformat/FormattingError.java | 22 +- .../com/google/googlejavaformat/Indent.java | 102 +- .../com/google/googlejavaformat/Input.java | 212 +- .../google/googlejavaformat/InputOutput.java | 162 +- .../com/google/googlejavaformat/Newlines.java | 308 +- .../java/com/google/googlejavaformat/Op.java | 12 +- .../com/google/googlejavaformat/OpenOp.java | 42 +- .../google/googlejavaformat/OpsBuilder.java | 1147 +- .../com/google/googlejavaformat/Output.java | 102 +- ...aInputAstVisitor_AnnotationOrModifier.java | 199 +- ...eclarationModifiersAndTypeAnnotations.java | 106 +- .../java/CommandLineOptions.java | 474 +- .../java/CommandLineOptionsParser.java | 337 +- .../java/DimensionHelpers.java | 158 +- .../java/FormatFileCallable.java | 101 +- .../googlejavaformat/java/Formatter.java | 400 +- .../java/FormatterException.java | 48 +- .../java/GoogleJavaFormatVersion.java | 6 +- .../googlejavaformat/java/ImportOrderer.java | 837 +- .../java/JavaCommentsHelper.java | 270 +- .../java/JavaFormatterOptions.java | 120 +- .../googlejavaformat/java/JavaInput.java | 1176 +- .../java/JavaInputAstVisitor.java | 7314 +++-- .../googlejavaformat/java/JavaOutput.java | 663 +- .../googlejavaformat/java/JavacTokens.java | 370 +- .../google/googlejavaformat/java/Main.java | 418 +- .../java/ModifierOrderer.java | 248 +- .../java/RemoveUnusedImports.java | 521 +- .../googlejavaformat/java/Replacement.java | 68 +- .../java/SnippetFormatter.java | 333 +- .../googlejavaformat/java/StringWrapper.java | 701 +- .../google/googlejavaformat/java/Trees.java | 145 +- .../java/TypeNameClassifier.java | 274 +- .../googlejavaformat/java/UsageException.java | 154 +- .../java/filer/FormattingFiler.java | 94 +- .../java/filer/FormattingJavaFileObject.java | 113 +- .../java/javadoc/CharStream.java | 62 +- .../java/javadoc/JavadocFormatter.java | 292 +- .../java/javadoc/JavadocLexer.java | 936 +- .../java/javadoc/JavadocWriter.java | 720 +- .../java/javadoc/NestingCounter.java | 42 +- .../googlejavaformat/java/javadoc/Token.java | 201 +- .../eclipse/lsp4j/jsonrpc/CancelChecker.java | 36 +- .../lsp4j/jsonrpc/CompletableFutures.java | 100 +- .../org/eclipse/lsp4j/jsonrpc/Endpoint.java | 15 +- .../lsp4j/jsonrpc/JsonRpcException.java | 43 +- .../org/eclipse/lsp4j/jsonrpc/Launcher.java | 816 +- .../lsp4j/jsonrpc/MessageConsumer.java | 22 +- .../lsp4j/jsonrpc/MessageIssueException.java | 69 +- .../lsp4j/jsonrpc/MessageIssueHandler.java | 14 +- .../lsp4j/jsonrpc/MessageProducer.java | 21 +- .../eclipse/lsp4j/jsonrpc/MessageTracer.java | 22 +- .../lsp4j/jsonrpc/ResponseErrorException.java | 34 +- .../lsp4j/jsonrpc/StandardLauncher.java | 75 +- .../lsp4j/jsonrpc/json/JsonRpcMethod.java | 139 +- .../jsonrpc/json/JsonRpcMethodProvider.java | 11 +- .../lsp4j/jsonrpc/json/MessageConstants.java | 15 +- .../jsonrpc/json/MessageJsonHandler.java | 244 +- .../lsp4j/jsonrpc/json/MethodProvider.java | 19 +- .../jsonrpc/json/ResponseJsonAdapter.java | 7 +- .../jsonrpc/json/StreamMessageConsumer.java | 124 +- .../json/adapters/CollectionTypeAdapter.java | 202 +- .../CollectionTypeAdapterFactory.java | 31 +- .../json/adapters/EitherTypeAdapter.java | 459 +- .../adapters/EitherTypeAdapterFactory.java | 26 +- .../json/adapters/EnumTypeAdapter.java | 144 +- .../json/adapters/EnumTypeAdapterFactory.java | 8 +- .../json/adapters/JsonElementTypeAdapter.java | 79 +- .../json/adapters/MessageTypeAdapter.java | 784 +- .../adapters/MessageTypeAdapterFactory.java | 11 +- .../json/adapters/ThrowableTypeAdapter.java | 204 +- .../json/adapters/TupleTypeAdapters.java | 100 +- .../jsonrpc/json/adapters/TypeUtils.java | 403 +- .../lsp4j/jsonrpc/messages/CancelParams.java | 116 +- .../lsp4j/jsonrpc/messages/Either.java | 364 +- .../lsp4j/jsonrpc/messages/Either3.java | 170 +- .../jsonrpc/messages/IdentifiableMessage.java | 107 +- .../lsp4j/jsonrpc/messages/Message.java | 75 +- .../lsp4j/jsonrpc/messages/MessageIssue.java | 78 +- .../jsonrpc/messages/NotificationMessage.java | 102 +- .../jsonrpc/messages/RequestMessage.java | 103 +- .../lsp4j/jsonrpc/messages/ResponseError.java | 160 +- .../jsonrpc/messages/ResponseErrorCode.java | 217 +- .../jsonrpc/messages/ResponseMessage.java | 109 +- .../eclipse/lsp4j/jsonrpc/messages/Tuple.java | 110 +- .../jsonrpc/services/AnnotationUtil.java | 214 +- .../lsp4j/jsonrpc/services/EndpointProxy.java | 193 +- .../lsp4j/jsonrpc/services/JsonDelegate.java | 14 +- .../jsonrpc/services/JsonNotification.java | 42 +- .../lsp4j/jsonrpc/services/JsonRequest.java | 47 +- .../lsp4j/jsonrpc/services/JsonSegment.java | 9 +- .../jsonrpc/services/ServiceEndpoints.java | 212 +- .../lsp4j/jsonrpc/validation/NonNull.java | 21 +- .../com/itsaky/androidide/models/LogLine.java | 305 +- .../androidide/utils/AndroidLogger.java | 46 +- .../com/itsaky/androidide/utils/ILogger.java | 401 +- .../itsaky/androidide/utils/JvmLogger.java | 30 +- .../com/itsaky/androidide/utils/LogUtils.java | 146 +- .../itsaky/androidide/utils/LoggerTest.java | 12 +- .../tooling/impl/util/ProjectReader.java | 2 +- .../api/util/RuntimeTypeAdapterFactory.java | 294 +- .../tooling/api/util/ToolingApiLauncher.java | 280 +- .../main/java/com/android/SdkConstants.java | 2554 +- .../ide/common/xml/XmlAttributeSortOrder.java | 304 +- .../ide/common/xml/XmlFormatPreferences.java | 114 +- .../ide/common/xml/XmlFormatStyle.java | 96 +- .../ide/common/xml/XmlPrettyPrinter.java | 2214 +- .../android/resources/ResourceConstants.java | 64 +- .../android/resources/ResourceFolderType.java | 116 +- .../main/java/com/android/utils/SdkUtils.java | 862 +- .../main/java/com/android/utils/XmlUtils.java | 979 +- .../java/com/android/xml/AndroidManifest.java | 4 +- build.gradle | 2 +- .../androidide/app/BaseApplication.java | 380 +- .../colorpicker/ColorPickerView.java | 343 +- .../itsaky/androidide/interfaces/Filter.java | 2 +- .../interfaces/ProjectWriterCallback.java | 8 +- .../managers/PreferenceManager.java | 294 +- .../androidide/managers/ToolsManager.java | 248 +- .../managers/VersionedFileManager.java | 30 +- .../androidide/models/ConstantsBridge.java | 75 +- .../com/itsaky/androidide/models/DexPair.java | 60 +- .../itsaky/androidide/models/SaveResult.java | 18 +- .../itsaky/androidide/models/SheetOption.java | 52 +- .../shell/CommonProcessExecutor.java | 179 +- .../androidide/shell/IProcessExecutor.java | 109 +- .../shell/IProcessExitListener.java | 2 +- .../shell/ProcessExecutorFactory.java | 8 +- .../shell/ProcessStreamsHolder.java | 6 +- .../itsaky/androidide/shell/ShellServer.java | 212 +- .../androidide/utils/BootstrapInstaller.java | 252 +- .../utils/CharSequenceInputStream.java | 473 +- .../androidide/utils/CharSequenceReader.java | 481 +- .../itsaky/androidide/utils/Environment.java | 424 +- .../com/itsaky/androidide/utils/FileUtil.java | 964 +- .../utils/InputStreamLineReader.java | 40 +- .../androidide/utils/InputStreamWriter.java | 46 +- .../itsaky/androidide/utils/IntProperty.java | 24 +- .../androidide/utils/JavaCharacter.java | 114 +- .../androidide/utils/ReaderInputStream.java | 484 +- .../itsaky/androidide/utils/StudioUtils.java | 70 +- .../androidide/utils/TypefaceUtils.java | 24 +- .../java/com/itsaky/toaster/ToastWrapper.java | 93 +- .../main/java/com/itsaky/toaster/Toaster.java | 246 +- .../views/virtualkeys/SpecialButton.java | 70 +- .../views/virtualkeys/SpecialButtonState.java | 78 +- .../views/virtualkeys/VirtualKeyButton.java | 266 +- .../virtualkeys/VirtualKeysConstants.java | 444 +- .../views/virtualkeys/VirtualKeysInfo.java | 259 +- .../views/virtualkeys/VirtualKeysView.java | 1089 +- .../java/com/itsaky/terminal/ByteQueue.java | 197 +- .../main/java/com/itsaky/terminal/JNI.java | 86 +- .../java/com/itsaky/terminal/KeyHandler.java | 669 +- .../com/itsaky/terminal/TerminalBuffer.java | 1006 +- .../itsaky/terminal/TerminalColorScheme.java | 612 +- .../com/itsaky/terminal/TerminalColors.java | 134 +- .../com/itsaky/terminal/TerminalEmulator.java | 5521 ++-- .../com/itsaky/terminal/TerminalOutput.java | 40 +- .../java/com/itsaky/terminal/TerminalRow.java | 537 +- .../com/itsaky/terminal/TerminalSession.java | 637 +- .../terminal/TerminalSessionClient.java | 32 +- .../java/com/itsaky/terminal/TextStyle.java | 127 +- .../java/com/itsaky/terminal/WcWidth.java | 982 +- .../view/GestureAndScaleRecognizer.java | 184 +- .../terminal/view/TerminalRenderer.java | 571 +- .../itsaky/terminal/view/TerminalView.java | 2496 +- .../terminal/view/TerminalViewClient.java | 54 +- .../view/textselection/CursorController.java | 56 +- .../TextSelectionCursorController.java | 687 +- .../TextSelectionHandleView.java | 595 +- .../androidide/lexers/groovy/GroovyLexer.java | 1789 +- .../androidide/lexers/java/JavaLexer.java | 1775 +- .../androidide/lexers/java/JavaParser.java | 26351 ++++++++-------- .../lexers/java/JavaParserBaseListener.java | 3024 +- .../lexers/java/JavaParserBaseVisitor.java | 2113 +- .../lexers/java/JavaParserListener.java | 2532 +- .../lexers/java/JavaParserVisitor.java | 1472 +- .../lexers/javadoc/JavadocLexer.java | 375 +- .../lexers/javadoc/JavadocParser.java | 4530 ++- .../javadoc/JavadocParserBaseListener.java | 675 +- .../javadoc/JavadocParserBaseVisitor.java | 444 +- .../lexers/javadoc/JavadocParserListener.java | 528 +- .../lexers/javadoc/JavadocParserVisitor.java | 308 +- .../androidide/lexers/xml/XMLLexer.java | 503 +- .../androidide/lexers/xml/XMLParser.java | 2097 +- .../lexers/xml/XMLParserBaseListener.java | 364 +- .../lexers/xml/XMLParserBaseVisitor.java | 222 +- .../lexers/xml/XMLParserListener.java | 264 +- .../lexers/xml/XMLParserVisitor.java | 154 +- .../lsp/api/AbstractServiceProvider.java | 28 +- .../lsp/api/ConfigurableServiceProvider.java | 14 +- .../itsaky/lsp/api/ICompletionProvider.java | 24 +- .../com/itsaky/lsp/api/IDocumentHandler.java | 88 +- .../com/itsaky/lsp/api/ILanguageClient.java | 32 +- .../com/itsaky/lsp/api/ILanguageServer.java | 254 +- .../com/itsaky/lsp/api/IServerSettings.java | 101 +- .../com/itsaky/lsp/util/DiagnosticUtil.java | 325 +- .../com/itsaky/lsp/util/LSPEditorActions.java | 30 +- .../itsaky/lsp/util/NoDocumentHandler.java | 28 +- .../lsp/util/PrefBasedServerSettings.java | 38 +- .../java/com/itsaky/lsp/util/StringUtils.java | 96 +- .../itsaky/androidide/utils/ClassBuilder.java | 84 +- .../java/com/itsaky/lsp/java/FileStore.java | 556 +- .../itsaky/lsp/java/JavaLanguageServer.java | 439 +- .../lsp/java/compiler/CompileBatch.java | 293 +- .../itsaky/lsp/java/compiler/CompileTask.java | 71 +- .../lsp/java/compiler/CompilerProvider.java | 26 +- .../java/compiler/JavaCompilerService.java | 575 +- .../lsp/java/compiler/ReusableCompiler.java | 486 +- .../lsp/java/compiler/SourceFileManager.java | 178 +- .../lsp/java/compiler/SourceFileObject.java | 196 +- .../lsp/java/compiler/SynchronizedTask.java | 86 +- .../java/models/JavaServerConfiguration.java | 80 +- .../lsp/java/models/JavaServerSettings.java | 57 +- .../com/itsaky/lsp/java/parser/ParseTask.java | 12 +- .../com/itsaky/lsp/java/parser/Parser.java | 777 +- .../java/providers/CodeFormatProvider.java | 84 +- .../java/providers/CompletionProvider.java | 2074 +- .../java/providers/DefinitionProvider.java | 259 +- .../providers/JavaDiagnosticProvider.java | 38 +- .../java/providers/JavaSelectionProvider.java | 44 +- .../lsp/java/providers/ReferenceProvider.java | 138 +- .../lsp/java/providers/SignatureProvider.java | 528 +- .../itsaky/lsp/java/rewrite/AddException.java | 97 +- .../itsaky/lsp/java/rewrite/AddImport.java | 87 +- .../rewrite/AddSuppressWarningAnnotation.java | 69 +- .../lsp/java/rewrite/ConvertFieldToBlock.java | 82 +- .../rewrite/ConvertVariableToStatement.java | 106 +- .../lsp/java/rewrite/CreateMissingMethod.java | 412 +- .../rewrite/GenerateRecordConstructor.java | 184 +- .../rewrite/ImplementAbstractMethods.java | 197 +- .../itsaky/lsp/java/rewrite/RemoveClass.java | 27 +- .../lsp/java/rewrite/RemoveException.java | 253 +- .../itsaky/lsp/java/rewrite/RemoveMethod.java | 49 +- .../com/itsaky/lsp/java/rewrite/Rewrite.java | 72 +- .../lsp/java/rewrite/RewriteNotSupported.java | 8 +- .../itsaky/lsp/java/utils/AnalyzeTimer.java | 110 +- .../java/com/itsaky/lsp/java/utils/Cache.java | 102 +- .../lsp/java/utils/CodeActionUtils.java | 270 +- .../com/itsaky/lsp/java/utils/EditHelper.java | 265 +- .../com/itsaky/lsp/java/utils/Extractors.java | 34 +- .../com/itsaky/lsp/java/utils/FindHelper.java | 250 +- .../itsaky/lsp/java/utils/MarkdownHelper.java | 304 +- .../com/itsaky/lsp/java/utils/MethodPtr.java | 115 +- .../lsp/java/utils/NavigationHelper.java | 90 +- .../itsaky/lsp/java/utils/ScanClassPath.java | 157 +- .../itsaky/lsp/java/utils/ScopeHelper.java | 112 +- .../lsp/java/utils/ShortTypePrinter.java | 259 +- .../itsaky/lsp/java/utils/StringSearch.java | 855 +- .../com/itsaky/lsp/java/utils/TypeUtils.java | 422 +- .../FindAnonymousTypeDeclaration.java | 64 +- .../lsp/java/visitors/FindBiggerRange.java | 130 +- .../lsp/java/visitors/FindCompletionsAt.java | 188 +- .../lsp/java/visitors/FindInvocationAt.java | 68 +- .../lsp/java/visitors/FindMethodCallAt.java | 402 +- .../visitors/FindMethodDeclarationAt.java | 50 +- .../itsaky/lsp/java/visitors/FindNameAt.java | 158 +- .../lsp/java/visitors/FindReferences.java | 68 +- .../java/visitors/FindTypeDeclarationAt.java | 62 +- .../visitors/FindTypeDeclarationNamed.java | 42 +- .../java/visitors/FindTypeDeclarations.java | 30 +- .../java/visitors/FindVariableAtCursor.java | 50 +- .../java/visitors/FindVariablesBetween.java | 66 +- .../java/visitors/PrettyPrintingVisitor.java | 200 +- .../lsp/java/visitors/PruneMethodBodies.java | 80 +- .../java/visitors/SemanticHighlighter.java | 331 +- .../com/squareup/javapoet/AnnotationSpec.java | 431 +- .../com/squareup/javapoet/ArrayTypeName.java | 140 +- .../java/com/squareup/javapoet/ClassName.java | 561 +- .../java/com/squareup/javapoet/CodeBlock.java | 794 +- .../com/squareup/javapoet/CodeWriter.java | 913 +- .../java/com/squareup/javapoet/FieldSpec.java | 246 +- .../javapoet/ImportCollectingCodeWriter.java | 126 +- .../java/com/squareup/javapoet/JavaFile.java | 542 +- .../com/squareup/javapoet/LineWrapper.java | 256 +- .../com/squareup/javapoet/MethodSpec.java | 937 +- .../com/squareup/javapoet/NameAllocator.java | 156 +- .../com/squareup/javapoet/ParameterSpec.java | 293 +- .../javapoet/ParameterizedTypeName.java | 208 +- .../java/com/squareup/javapoet/TypeName.java | 613 +- .../java/com/squareup/javapoet/TypeSpec.java | 1602 +- .../squareup/javapoet/TypeVariableName.java | 269 +- .../main/java/com/squareup/javapoet/Util.java | 180 +- .../squareup/javapoet/WildcardTypeName.java | 172 +- .../lib/nbjavac/services/CancelAbort.java | 5 +- .../lib/nbjavac/services/CancelService.java | 44 +- .../netbeans/lib/nbjavac/services/NBAttr.java | 202 +- .../lib/nbjavac/services/NBClassFinder.java | 147 +- .../lib/nbjavac/services/NBClassReader.java | 160 +- .../lib/nbjavac/services/NBClassWriter.java | 30 +- .../lib/nbjavac/services/NBEnter.java | 82 +- .../lib/nbjavac/services/NBJavaCompiler.java | 104 +- .../lib/nbjavac/services/NBJavacTrees.java | 68 +- .../netbeans/lib/nbjavac/services/NBLog.java | 162 +- .../lib/nbjavac/services/NBMemberEnter.java | 94 +- .../lib/nbjavac/services/NBNames.java | 55 +- .../lib/nbjavac/services/NBParserFactory.java | 306 +- .../lib/nbjavac/services/NBResolve.java | 79 +- .../lib/nbjavac/services/NBTreeMaker.java | 54 +- .../com/itsaky/lsp/xml/XMLLanguageServer.java | 218 +- .../lsp/xml/models/XMLServerSettings.java | 14 +- .../lsp/xml/providers/CodeFormatProvider.java | 36 +- .../lsp/xml/providers/CompletionProvider.java | 504 +- .../main/java/com/itsaky/apiinfo/ApiInfo.java | 254 +- .../com/itsaky/apiinfo/models/ClassInfo.java | 107 +- .../java/com/itsaky/apiinfo/models/Info.java | 16 +- .../com/itsaky/apiinfo/models/MethodInfo.java | 14 +- .../java/com/itsaky/attrinfo/AttrInfo.java | 337 +- .../java/com/itsaky/attrinfo/models/Attr.java | 364 +- .../com/itsaky/attrinfo/models/Styleable.java | 55 +- .../src/main/java/com/itsaky/sdk/SDKInfo.java | 42 +- .../java/com/itsaky/widgets/WidgetInfo.java | 106 +- .../com/itsaky/widgets/models/Widget.java | 32 +- .../main/java/com/itsaky/xml/INamespace.java | 108 +- .../java/com/itsaky/xml/impl/UiNamespace.java | 154 +- .../java/org/eclipse/jdt/core/Signature.java | 5579 ++-- .../jdt/core/compiler/CharOperation.java | 7725 +++-- .../jdt/internal/compiler/ast/ASTNode.java | 252 +- .../compiler/classfmt/ClassFileConstants.java | 358 +- .../compiler/parser/ScannerHelper.java | 1317 +- .../jdt/internal/compiler/util/Util.java | 1206 +- .../b/atv/holder/SimpleViewHolder.java | 22 +- .../com/unnamed/b/atv/model/TreeNode.java | 548 +- .../unnamed/b/atv/view/AndroidTreeView.java | 849 +- .../b/atv/view/TreeNodeWrapperView.java | 60 +- .../unnamed/b/atv/view/TwoDScrollView.java | 2041 +- .../java/com/itsaky/inflater/AttrType.java | 148 +- .../itsaky/inflater/BaseLayoutInflater.java | 74 +- .../java/com/itsaky/inflater/IAttribute.java | 126 +- .../itsaky/inflater/IAttributeAdapter.java | 46 +- .../java/com/itsaky/inflater/IDTable.java | 88 +- .../java/com/itsaky/inflater/IFixedValue.java | 36 +- .../com/itsaky/inflater/IInflateListener.java | 44 +- .../com/itsaky/inflater/ILayoutInflater.java | 266 +- .../com/itsaky/inflater/IResourceTable.java | 148 +- .../main/java/com/itsaky/inflater/IView.java | 318 +- .../java/com/itsaky/inflater/IViewGroup.java | 211 +- .../com/itsaky/inflater/InflateException.java | 18 +- .../inflater/LayoutInflaterConfiguration.java | 108 +- .../inflater/NotSupportedException.java | 20 +- .../itsaky/inflater/XMLLayoutInflater.java | 753 +- .../GestureOverlayViewAttrAdapter.java | 148 +- .../android/view/ViewAttrAdapter.java | 1068 +- .../android/view/ViewGroupAttrAdapter.java | 128 +- .../android/widget/AbsSeekBarAttrAdapter.java | 84 +- .../android/widget/AbsSpinnerAttrAdapter.java | 74 +- .../widget/AdapterViewAttrAdapter.java | 50 +- .../android/widget/ButtonAttrAdapter.java | 25 +- .../android/widget/CheckBoxAttrAdapter.java | 26 +- .../widget/CheckedTextViewAttrAdapter.java | 78 +- .../widget/CompondButtonAttrAdapter.java | 78 +- .../android/widget/EditTextAttrAdapter.java | 26 +- .../widget/FrameLayoutAttrAdapter.java | 68 +- .../android/widget/GridLayoutAttrAdapter.java | 118 +- .../widget/ImageButtonAttrAdapter.java | 26 +- .../android/widget/ImageViewAttrAdapter.java | 174 +- .../widget/LinearLayoutAttrAdapter.java | 110 +- .../widget/ProgressBarAttrAdapter.java | 192 +- .../widget/RadioButtonAttrAdapter.java | 26 +- .../widget/RelativeLayoutAttrAdapter.java | 72 +- .../android/widget/SeekBarAttrAdapter.java | 63 +- .../android/widget/SpinnerAttrAdapter.java | 93 +- .../android/widget/SwitchAttrAdapter.java | 147 +- .../android/widget/TextViewAttrAdapter.java | 376 +- .../widget/ToggleButtonAttrAdapter.java | 74 +- .../drawable/BitmapDrawableParser.java | 204 +- .../inflater/drawable/ClipDrawableParser.java | 82 +- .../drawable/DrawableParserFactory.java | 287 +- .../inflater/drawable/IDrawableParser.java | 219 +- .../drawable/InsetDrawableParser.java | 68 +- .../inflater/drawable/LayerListParser.java | 175 +- .../drawable/ScaleDrawableParser.java | 124 +- .../drawable/ShapeDrawableParser.java | 566 +- .../inflater/drawable/StateListParser.java | 159 +- .../com/itsaky/inflater/impl/BaseView.java | 560 +- .../itsaky/inflater/impl/BaseViewGroup.java | 142 +- .../com/itsaky/inflater/impl/ErrorUiView.java | 112 +- .../itsaky/inflater/impl/IncludeLayout.java | 20 +- .../com/itsaky/inflater/impl/UiAttribute.java | 112 +- .../java/com/itsaky/inflater/impl/UiView.java | 12 +- .../com/itsaky/inflater/impl/UiViewGroup.java | 138 +- .../inflater/util/CommonParseUtils.java | 666 +- .../itsaky/inflater/util/Preconditions.java | 42 +- .../itsaky/inflater/util/TextDrawable.java | 85 +- .../inflater/values/FrameworkValues.java | 92 +- .../com/itsaky/inflater/values/IResource.java | 30 +- .../itsaky/inflater/values/ValuesTable.java | 689 +- .../inflater/values/ValuesTableFactory.java | 183 +- .../values/models/AbstractResource.java | 93 +- .../inflater/values/models/ArrayResource.java | 66 +- .../values/models/BooleanResource.java | 40 +- .../inflater/values/models/ColorResource.java | 28 +- .../values/models/DimensionResource.java | 6 +- .../values/models/IntegerResource.java | 28 +- .../values/models/StringResource.java | 6 +- .../harjot/vectormaster/DefaultValues.java | 78 +- .../vectormaster/VectorMasterDrawable.java | 948 +- .../harjot/vectormaster/VectorMasterView.java | 807 +- .../harjot/vectormaster/enums/TintMode.java | 12 +- .../vectormaster/models/ClipPathModel.java | 124 +- .../vectormaster/models/GroupModel.java | 452 +- .../harjot/vectormaster/models/PathModel.java | 490 +- .../vectormaster/models/VectorModel.java | 316 +- .../harjot/vectormaster/utilities/Utils.java | 154 +- .../utilities/legacyparser/PathParser.java | 1411 +- .../utilities/parser/ParserHelper.java | 636 +- .../utilities/parser/PathParser.java | 645 +- 565 files changed, 100448 insertions(+), 102253 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 71eb0e04c..71a4d7510 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,8 +17,8 @@ Building AndroidIDE in AndroidIDE itself is also possible. But that might requir If you face `AAPT2` issue while building AndroidIDE in the IDE itself, specify the `android.aapt2FromMavenOverride` property in `gradle.properties` with location of `aapt2` binary from Android SDK. However, this is an **experimental** property. ## Source code format -The source code must have `4-space` indentation (soft tab). -Java source code is formatted using `google-java-format` with `AOSP` style formatting. +The source code must have `2-space` indentation (soft tab). +Java source code is formatted using `google-java-format` with `GOOGLE` style formatting. You can execute `./gradlew formatJavaSources` in terminal to format all Java source files using Google Java Format. **This will commit the changes as well**. diff --git a/actions/src/main/java/com/itsaky/androidide/actions/ActionData.java b/actions/src/main/java/com/itsaky/androidide/actions/ActionData.java index f305c5256..a92a73dfa 100644 --- a/actions/src/main/java/com/itsaky/androidide/actions/ActionData.java +++ b/actions/src/main/java/com/itsaky/androidide/actions/ActionData.java @@ -29,26 +29,26 @@ import java.util.Objects; */ public class ActionData { - private final Map, Object> instances = new HashMap<>(); + private final Map, Object> instances = new HashMap<>(); - public void put(@NonNull Class type, @Nullable T object) { - Objects.requireNonNull(type); - this.instances.put(type, object); + public void put(@NonNull Class type, @Nullable T object) { + Objects.requireNonNull(type); + this.instances.put(type, object); + } + + @Nullable + public T get(@NonNull Class type) { + Objects.requireNonNull(type); + + final var object = this.instances.get(type); + if (object == null) { + return null; } - @Nullable - public T get(@NonNull Class type) { - Objects.requireNonNull(type); + return type.cast(object); + } - final var object = this.instances.get(type); - if (object == null) { - return null; - } - - return type.cast(object); - } - - public void clear() { - this.instances.clear(); - } + public void clear() { + this.instances.clear(); + } } diff --git a/app/src/main/java/com/itsaky/androidide/AboutActivity.java b/app/src/main/java/com/itsaky/androidide/AboutActivity.java index 9c6ee2f9a..6124ae4c1 100755 --- a/app/src/main/java/com/itsaky/androidide/AboutActivity.java +++ b/app/src/main/java/com/itsaky/androidide/AboutActivity.java @@ -35,183 +35,153 @@ import java.util.List; public class AboutActivity extends StudioActivity { - public static final String JDK_SOURCE = "https://github.com/itsaky/OpenJDK-Android"; - private ActivityAboutBinding binding; + public static final String JDK_SOURCE = "https://github.com/itsaky/OpenJDK-Android"; + private ActivityAboutBinding binding; - @Override - protected View bindLayout() { - binding = ActivityAboutBinding.inflate(getLayoutInflater()); - return binding.getRoot(); + @Override + protected View bindLayout() { + binding = ActivityAboutBinding.inflate(getLayoutInflater()); + return binding.getRoot(); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + binding.items.footerText.setText(getFooter()); + + setupTranslations(); + setupLicenses(); + + LayoutAboutItemsBinding items = binding.items; + TooltipCompat.setTooltipText(items.discuss, getString(R.string.discussions_on_telegram)); + TooltipCompat.setTooltipText(items.github, getString(R.string.title_github)); + TooltipCompat.setTooltipText(items.email, getString(R.string.about_option_email)); + TooltipCompat.setTooltipText(items.website, getString(R.string.about_option_website)); + items.github.setOnClickListener(v -> getApp().openGitHub()); + items.discuss.setOnClickListener(v -> getApp().openTelegramGroup()); + items.email.setOnClickListener(v -> getApp().emailUs()); + items.website.setOnClickListener(v -> getApp().openWebsite()); + } + + private void setupTranslations() { + final var translations = createTranslationsList(); + final var sb = new StringBuilder(); + + for (var translation : translations) { + final var author = translation.first; + final var language = translation.second; + + sb.append(author); + sb.append(" - "); + sb.append(language); + sb.append("\n"); } - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + binding.items.translations.setText(sb); + } - binding.items.footerText.setText(getFooter()); + /** + * Create the list of translations. The list items are pairs. The first item of the pair is the + * name of the language. The second item is the name of the person who made the translations. + * + *

If you're adding translations, make sure you add an item to this list. You can choose to + * display the language in a localized version. For example : Language: हिन्दी - < name of author + * > + * + *

Prefer using unicode escapes for language names. For example, हिन्दी can be written as : + * Language: \u0939\u093f\u0928\u094d\u0926\u0940 - < name of author > + * + * @return The translations list. + */ + private List> createTranslationsList() { + final var list = new ArrayList>(); - setupTranslations(); - setupLicenses(); + list.add(Pair.create("Bahasa Indonesia", "Fitrah Nuno Syahbani")); + list.add(Pair.create("\u4e2d\u6587", "Rosemoe and @mikofe")); // 中文 + list.add(Pair.create("Deutsch", "Marvin Stelter")); + list.add(Pair.create("\u0939\u093f\u0928\u094d\u0926\u0940", "Premjit Chowdhury")); // हिन्दी + list.add(Pair.create("Russian", "Smooth-E")); + list.add(Pair.create("French", "Se-Lyan")); + list.add(Pair.create("Portuguese-Brazilian", "Frederick'XS")); + return list; + } - LayoutAboutItemsBinding items = binding.items; - TooltipCompat.setTooltipText(items.discuss, getString(R.string.discussions_on_telegram)); - TooltipCompat.setTooltipText(items.github, getString(R.string.title_github)); - TooltipCompat.setTooltipText(items.email, getString(R.string.about_option_email)); - TooltipCompat.setTooltipText(items.website, getString(R.string.about_option_website)); - items.github.setOnClickListener(v -> getApp().openGitHub()); - items.discuss.setOnClickListener(v -> getApp().openTelegramGroup()); - items.email.setOnClickListener(v -> getApp().emailUs()); - items.website.setOnClickListener(v -> getApp().openWebsite()); + private void setupLicenses() { + final var licenses = new ArrayList(); + licenses.add( + new License( + "AndroidX Libraries", "Apache License 2.0", "https://github.com/androidx/androidx")); + licenses.add( + new License("Gradle Build Tool", "Apache License 2.0", "https://github.com/gradle/gradle")); + licenses.add(new License("Gson", "Apache License 2.0", "https://github.com/google/gson")); + licenses.add( + new License( + "Material Components for Android", + "Apache License 2.0", + "https://github.com/material-components/material-components-android")); + licenses.add( + new License("ANTLR4 Runtime", "BSD 3-clause License", "https://github.com/antlr/antlr4")); + licenses.add( + new License("Glide", "BSD, part MIT and Apache 2.0", "https://github.com/bumptech/glide")); + licenses.add( + new License( + "Android UtilCode", "Apache License 2.0", "https://github.com/Blankj/AndroidUtilCode")); + licenses.add( + new License( + "UnicornFilePicker", + "Apache License 2.0", + "https://github.com/abhishekti7/UnicornFilePicker")); + licenses.add( + new License("QuickAction", "Apache License 2.0", "https://github.com/piruin/quickaction")); + licenses.add( + new License("XmlToJson", "Apache License 2.0", "https://github.com/smart-fun/XmlToJson")); + licenses.add( + new License( + "AndroidTreeView", + "Apache License 2.0", + "https://github.com/bmelnychuk/AndroidTreeView")); + licenses.add( + new License( + "CodeEditor v0.5.2", "Apache License 2.0", "https://github.com/Rosemoe/CodeEditor")); + licenses.add( + new License("Guava-Android", "Apache License 2.0", "https://github.com/google/guava")); + licenses.add(new License("Jsoup", "MIT License", "https://github.com/jhy/jsoup")); + licenses.add( + new License("Termux [Terminal Emulator]", "GPL 3", "https://github.com/termux/termux-app")); + licenses.add( + new License("JavaPoet", "Apache License 2.0", "https://github.com/square/JavaPoet")); + licenses.add( + new License("Zip4j", "Apache License 2.0", "https://github.com/srikanth-lingala/zip4j")); + + StringBuilder sb = new StringBuilder(); + for (License license : licenses) { + sb.append("\u2022 "); + sb.append(license.name); + sb.append("
"); + sb.append(getString(R.string.msg_about_licensed_under)); + sb.append(license.license); + sb.append("
"); + sb.append(license.url); + sb.append(""); + sb.append("

"); } + sb.append(getString(R.string.license_jdk)); + sb.append(String.format("
%1$s", JDK_SOURCE)); + sb.append("

"); + binding.items.licenses.setText(Html.fromHtml(sb.toString(), Html.FROM_HTML_MODE_LEGACY)); + binding.items.licenses.setMovementMethod(LinkMovementMethod.getInstance()); + } - private void setupTranslations() { - final var translations = createTranslationsList(); - final var sb = new StringBuilder(); - - for (var translation : translations) { - final var author = translation.first; - final var language = translation.second; - - sb.append(author); - sb.append(" - "); - sb.append(language); - sb.append("\n"); - } - - binding.items.translations.setText(sb); - } - - /** - * Create the list of translations. The list items are pairs. The first item of the pair is the - * name of the language. The second item is the name of the person who made the translations. - * - *

If you're adding translations, make sure you add an item to this list. You can choose to - * display the language in a localized version. For example : Language: हिन्दी - < name of - * author > - * - *

Prefer using unicode escapes for language names. For example, हिन्दी can be written as : - * Language: \u0939\u093f\u0928\u094d\u0926\u0940 - < name of author > - * - * @return The translations list. - */ - private List> createTranslationsList() { - final var list = new ArrayList>(); - - list.add(Pair.create("Bahasa Indonesia", "Fitrah Nuno Syahbani")); - list.add(Pair.create("\u4e2d\u6587", "Rosemoe and @mikofe")); // 中文 - list.add(Pair.create("Deutsch", "Marvin Stelter")); - list.add( - Pair.create("\u0939\u093f\u0928\u094d\u0926\u0940", "Premjit Chowdhury")); // हिन्दी - list.add(Pair.create("Russian", "Smooth-E")); - list.add(Pair.create("French", "Se-Lyan")); - list.add(Pair.create("Portuguese-Brazilian", "Frederick'XS")); - return list; - } - - private void setupLicenses() { - final var licenses = new ArrayList(); - licenses.add( - new License( - "AndroidX Libraries", - "Apache License 2.0", - "https://github.com/androidx/androidx")); - licenses.add( - new License( - "Gradle Build Tool", - "Apache License 2.0", - "https://github.com/gradle/gradle")); - licenses.add(new License("Gson", "Apache License 2.0", "https://github.com/google/gson")); - licenses.add( - new License( - "Material Components for Android", - "Apache License 2.0", - "https://github.com/material-components/material-components-android")); - licenses.add( - new License( - "ANTLR4 Runtime", - "BSD 3-clause License", - "https://github.com/antlr/antlr4")); - licenses.add( - new License( - "Glide", - "BSD, part MIT and Apache 2.0", - "https://github.com/bumptech/glide")); - licenses.add( - new License( - "Android UtilCode", - "Apache License 2.0", - "https://github.com/Blankj/AndroidUtilCode")); - licenses.add( - new License( - "UnicornFilePicker", - "Apache License 2.0", - "https://github.com/abhishekti7/UnicornFilePicker")); - licenses.add( - new License( - "QuickAction", - "Apache License 2.0", - "https://github.com/piruin/quickaction")); - licenses.add( - new License( - "XmlToJson", - "Apache License 2.0", - "https://github.com/smart-fun/XmlToJson")); - licenses.add( - new License( - "AndroidTreeView", - "Apache License 2.0", - "https://github.com/bmelnychuk/AndroidTreeView")); - licenses.add( - new License( - "CodeEditor v0.5.2", - "Apache License 2.0", - "https://github.com/Rosemoe/CodeEditor")); - licenses.add( - new License( - "Guava-Android", "Apache License 2.0", "https://github.com/google/guava")); - licenses.add(new License("Jsoup", "MIT License", "https://github.com/jhy/jsoup")); - licenses.add( - new License( - "Termux [Terminal Emulator]", - "GPL 3", - "https://github.com/termux/termux-app")); - licenses.add( - new License( - "JavaPoet", "Apache License 2.0", "https://github.com/square/JavaPoet")); - licenses.add( - new License( - "Zip4j", - "Apache License 2.0", - "https://github.com/srikanth-lingala/zip4j")); - - StringBuilder sb = new StringBuilder(); - for (License license : licenses) { - sb.append("\u2022 "); - sb.append(license.name); - sb.append("
"); - sb.append(getString(R.string.msg_about_licensed_under)); - sb.append(license.license); - sb.append("
"); - sb.append(license.url); - sb.append(""); - sb.append("

"); - } - sb.append(getString(R.string.license_jdk)); - sb.append(String.format("
%1$s", JDK_SOURCE)); - sb.append("

"); - binding.items.licenses.setText(Html.fromHtml(sb.toString(), Html.FROM_HTML_MODE_LEGACY)); - binding.items.licenses.setMovementMethod(LinkMovementMethod.getInstance()); - } - - private String getFooter() { - final String arch = android.os.Build.SUPPORTED_ABIS[0]; - try { - final String version = getPackageManager().getPackageInfo(getPackageName(), 0).versionName; - return getString(R.string.about_footer, version, arch); - } catch (Throwable th) { - return getString(R.string.about_footer_alternate, arch); - } + private String getFooter() { + final String arch = android.os.Build.SUPPORTED_ABIS[0]; + try { + final String version = getPackageManager().getPackageInfo(getPackageName(), 0).versionName; + return getString(R.string.about_footer, version, arch); + } catch (Throwable th) { + return getString(R.string.about_footer_alternate, arch); } + } } diff --git a/app/src/main/java/com/itsaky/androidide/CrashHandlerActivity.java b/app/src/main/java/com/itsaky/androidide/CrashHandlerActivity.java index 4e88ad6dc..225bdcc7d 100644 --- a/app/src/main/java/com/itsaky/androidide/CrashHandlerActivity.java +++ b/app/src/main/java/com/itsaky/androidide/CrashHandlerActivity.java @@ -26,34 +26,34 @@ import com.itsaky.androidide.fragments.CrashReportFragment; public class CrashHandlerActivity extends StudioActivity { - private ActivityCrashHandlerBinding binding; + private ActivityCrashHandlerBinding binding; - public static final String REPORT_ACTION = "com.itsaky.androidide.REPORT_CRASH"; - public static final String TRACE_KEY = "crash_trace"; + public static final String REPORT_ACTION = "com.itsaky.androidide.REPORT_CRASH"; + public static final String TRACE_KEY = "crash_trace"; - @Override - protected View bindLayout() { - binding = ActivityCrashHandlerBinding.inflate(getLayoutInflater()); - return binding.getRoot(); + @Override + protected View bindLayout() { + binding = ActivityCrashHandlerBinding.inflate(getLayoutInflater()); + return binding.getRoot(); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + final var extra = getIntent().getExtras(); + if (extra == null) { + finishAffinity(); + return; } - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + final var report = extra.getString(TRACE_KEY, "Unable to get logs."); + final var fragment = CrashReportFragment.newInstance(report); - final var extra = getIntent().getExtras(); - if (extra == null) { - finishAffinity(); - return; - } - - final var report = extra.getString(TRACE_KEY, "Unable to get logs."); - final var fragment = CrashReportFragment.newInstance(report); - - getSupportFragmentManager() - .beginTransaction() - .replace(binding.getRoot().getId(), fragment, "crash_report_fragment") - .addToBackStack(null) - .commit(); - } + getSupportFragmentManager() + .beginTransaction() + .replace(binding.getRoot().getId(), fragment, "crash_report_fragment") + .addToBackStack(null) + .commit(); + } } diff --git a/app/src/main/java/com/itsaky/androidide/DesignerActivity.java b/app/src/main/java/com/itsaky/androidide/DesignerActivity.java index b81dd2e22..8dae8c9a7 100755 --- a/app/src/main/java/com/itsaky/androidide/DesignerActivity.java +++ b/app/src/main/java/com/itsaky/androidide/DesignerActivity.java @@ -88,450 +88,424 @@ import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; public class DesignerActivity extends StudioActivity - implements WidgetItemAdapter.OnDragStartListener, - AttrEditorSheet.OnViewDeletionFailedListener, - ILayoutInflater.ContextProvider { + implements WidgetItemAdapter.OnDragStartListener, + AttrEditorSheet.OnViewDeletionFailedListener, + ILayoutInflater.ContextProvider { - public static final String KEY_LAYOUT_PATH = "designer_layoutPath"; - public static final String KEY_RES_DIRS = "designer_resDirs"; - public static final String KEY_GENERATED_CODE = "designer_xmlCode"; - public static final String DRAGGING_WIDGET_TAG = "DRAGGING_WIDGET"; - public static final String DRAGGING_WIDGET_MIME = "application/ide_widget"; - private static final ILogger LOG = ILogger.newInstance("DesignerActivity"); - public final String EDITOR_SHEET_DIALOG_TAG = "attribute_editor_dialog"; - private final boolean isTablet = DeviceUtils.isTablet(); - private final List widgetGroups = new ArrayList<>(); - private ActivityDesignerBinding mBinding; - private UIWidgetGroup checkedWidgetCategory; - private ILayoutInflater layoutInflater; - private IViewGroup inflatedRoot; - private File layout; - private boolean inflationFailed = false; - private AttrEditorSheet mEditorSheet; - // This will make sure to apply listeners, background and data to view that are inflated from - // XML. - private final IInflateListener mInflateListener = - new IInflateListener() { + public static final String KEY_LAYOUT_PATH = "designer_layoutPath"; + public static final String KEY_RES_DIRS = "designer_resDirs"; + public static final String KEY_GENERATED_CODE = "designer_xmlCode"; + public static final String DRAGGING_WIDGET_TAG = "DRAGGING_WIDGET"; + public static final String DRAGGING_WIDGET_MIME = "application/ide_widget"; + private static final ILogger LOG = ILogger.newInstance("DesignerActivity"); + public final String EDITOR_SHEET_DIALOG_TAG = "attribute_editor_dialog"; + private final boolean isTablet = DeviceUtils.isTablet(); + private final List widgetGroups = new ArrayList<>(); + private ActivityDesignerBinding mBinding; + private UIWidgetGroup checkedWidgetCategory; + private ILayoutInflater layoutInflater; + private IViewGroup inflatedRoot; + private File layout; + private boolean inflationFailed = false; + private AttrEditorSheet mEditorSheet; + // This will make sure to apply listeners, background and data to view that are inflated from + // XML. + private final IInflateListener mInflateListener = + new IInflateListener() { - @Override - public void onBeginInflate() {} + @Override + public void onBeginInflate() {} - @Override - public void onInflateView(IView view, IViewGroup parent) { - if (view instanceof IViewGroup) { - View v = view.asView(); - v.setBackgroundResource(R.drawable.bg_design_container_background); - v.setOnDragListener(getOnDragListener((IViewGroup) view)); - } + @Override + public void onInflateView(IView view, IViewGroup parent) { + if (view instanceof IViewGroup) { + View v = view.asView(); + v.setBackgroundResource(R.drawable.bg_design_container_background); + v.setOnDragListener(getOnDragListener((IViewGroup) view)); + } - setupInflatedView(view); - setDragDataToInflatedView(view); - } + setupInflatedView(view); + setDragDataToInflatedView(view); + } - @Override - public void onApplyAttribute(IAttribute attr, IView view) {} + @Override + public void onApplyAttribute(IAttribute attr, IView view) {} - @Override - public void onFinishInflate(IView rootView) { - if (rootView instanceof IViewGroup) { - inflatedRoot = (IViewGroup) rootView; - } - } - }; + @Override + public void onFinishInflate(IView rootView) { + if (rootView instanceof IViewGroup) { + inflatedRoot = (IViewGroup) rootView; + } + } + }; - @Override - public void onBackPressed() { + @Override + public void onBackPressed() { - if (inflationFailed) { - super.onBackPressed(); + if (inflationFailed) { + super.onBackPressed(); + return; + } + + // When the user presses the back button, set the activity result and finish this activity + + final var progress = new ProgressSheet(); + progress.setSubMessageEnabled(true); + progress.setShowTitle(false); + progress.setMessage(getString(R.string.title_generating_xml)); + progress.setSubMessage(getString(R.string.please_wait)); + progress.show(getSupportFragmentManager(), "generate_code_progress"); + + final var future = + CompletableFuture.supplyAsync( + () -> BaseView.XML_HEADER.concat("\n").concat(inflatedRoot.generateCode())); + future.whenComplete( + (s, throwable) -> { + progress.dismiss(); + + if (throwable != null) { + notifyXmlGenerationFailed(throwable); return; - } + } - // When the user presses the back button, set the activity result and finish this activity + try { + finishWithResult(future.get()); + } catch (ExecutionException | InterruptedException e) { + notifyXmlGenerationFailed(e); + } + }); + } - final var progress = new ProgressSheet(); - progress.setSubMessageEnabled(true); - progress.setShowTitle(false); - progress.setMessage(getString(R.string.title_generating_xml)); - progress.setSubMessage(getString(R.string.please_wait)); - progress.show(getSupportFragmentManager(), "generate_code_progress"); + private void notifyXmlGenerationFailed(Throwable error) { + final var errorMessage = getString(R.string.msg_generate_xml_failed); + LOG.error(errorMessage, error); - final var future = - CompletableFuture.supplyAsync( - () -> BaseView.XML_HEADER.concat("\n").concat(inflatedRoot.generateCode())); - future.whenComplete( - (s, throwable) -> { - progress.dismiss(); + DialogUtils.newYesNoDialog( + this, + getString(R.string.title_code_generation_failed), + getString(R.string.msg_code_generation_failed), + (dialog1, which) -> finishWithError(), + (dialog1, which) -> dialog1.dismiss()) + .show(); + } - if (throwable != null) { - notifyXmlGenerationFailed(throwable); - return; - } + private void finishWithResult(String result) { + final var intent = new Intent(); + intent.putExtra(KEY_GENERATED_CODE, result); + setResult(RESULT_OK, intent); + finish(); + } - try { - finishWithResult(future.get()); - } catch (ExecutionException | InterruptedException e) { - notifyXmlGenerationFailed(e); - } - }); + private void finishWithError() { + final var intent = new Intent(); + setResult(-123, intent); + finish(); + } + + @Override + public void onDragStarted(View view) { + mBinding.getRoot().closeDrawer(GravityCompat.START); + } + + @Override + public boolean onDeletionFailed(@NonNull IView view) { + final var v = view.asView(); + if (mBinding.layoutContainer.indexOfChild(v) >= 0) { + mBinding.layoutContainer.removeView(v); + return true; } + return false; + } - private void notifyXmlGenerationFailed(Throwable error) { - final var errorMessage = getString(R.string.msg_generate_xml_failed); - LOG.error(errorMessage, error); + @Override + public Context getContext() { + return this; + } - DialogUtils.newYesNoDialog( - this, - getString(R.string.title_code_generation_failed), - getString(R.string.msg_code_generation_failed), - (dialog1, which) -> finishWithError(), - (dialog1, which) -> dialog1.dismiss()) - .show(); - } + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); - private void finishWithResult(String result) { - final var intent = new Intent(); - intent.putExtra(KEY_GENERATED_CODE, result); - setResult(RESULT_OK, intent); - finish(); - } + setSupportActionBar(mBinding.toolbar); + Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true); - private void finishWithError() { - final var intent = new Intent(); - setResult(-123, intent); - finish(); - } + final var toggle = + new ActionBarDrawerToggle( + this, mBinding.getRoot(), mBinding.toolbar, R.string.app_name, R.string.app_name) { - @Override - public void onDragStarted(View view) { - mBinding.getRoot().closeDrawer(GravityCompat.START); - } - - @Override - public boolean onDeletionFailed(@NonNull IView view) { - final var v = view.asView(); - if (mBinding.layoutContainer.indexOfChild(v) >= 0) { - mBinding.layoutContainer.removeView(v); - return true; - } - return false; - } - - @Override - public Context getContext() { - return this; - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setSupportActionBar(mBinding.toolbar); - Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true); - - final var toggle = - new ActionBarDrawerToggle( - this, - mBinding.getRoot(), - mBinding.toolbar, - R.string.app_name, - R.string.app_name) { - - @Override - public void onDrawerSlide(View drawerView, float slideOffset) { - super.onDrawerSlide(drawerView, slideOffset); - if (isTablet) { - mBinding.mainContainer.setTranslationX( - drawerView.getWidth() * slideOffset); - } - } - }; - mBinding.getRoot().addDrawerListener(toggle); - toggle.syncState(); - - final var extras = getIntent().getExtras(); - final var path = extras.getString(KEY_LAYOUT_PATH, null); - final var name = path.substring(path.lastIndexOf(File.separator) + 1); - final var resDirs = extras.getStringArrayList(KEY_RES_DIRS); - getSupportActionBar().setTitle(name); - - try { - this.layout = new File(path); - LOG.debug("Inflating layout file:", this.layout); - LOG.debug("Resource directories:", TextUtils.join(",\n", resDirs)); - - //noinspection deprecation - final var pd = - ProgressDialog.show(this, null, getString(R.string.please_wait), true, false); - getApp().createInflaterConfig( - this, resDirs.stream().map(File::new).collect(Collectors.toSet())) - .whenComplete( - (config, throwable) -> { - if (config == null || throwable != null) { - LOG.error("Unable to create inflater configuration", throwable); - return; - } - - ThreadUtils.runOnUiThread( - () -> { - pd.dismiss(); - inflatePath(path, config); - }); - }); - } catch (Throwable th) { - onLayoutInflationFailed(th); - } - - setupWidgets(); - - // Always open the drawer if this device is a tablet. - if (isTablet) { - mBinding.getRoot().setScrimColor(Color.TRANSPARENT); - mBinding.getRoot().openDrawer(GravityCompat.START); - mBinding.getRoot().setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN); - } - } - - @Override - protected View bindLayout() { - this.mBinding = ActivityDesignerBinding.inflate(getLayoutInflater()); - return mBinding.getRoot(); - } - - @Override - protected void onDestroy() { - LOG.logThis(); - if (isAttrEditorShowing()) { - getAttrEditorSheet().dismiss(); - } - - this.mEditorSheet = null; - - // Release the reference to inflate listener and context from the layout inflater - // Failing to do so will lead getSupportFragmentManager() to return a destroyed fragment - // manager - try { - if (layoutInflater == null) { - LOG.debug("Skipping layout inflater context reference release. Inflater is null"); - return; + @Override + public void onDrawerSlide(View drawerView, float slideOffset) { + super.onDrawerSlide(drawerView, slideOffset); + if (isTablet) { + mBinding.mainContainer.setTranslationX(drawerView.getWidth() * slideOffset); } + } + }; + mBinding.getRoot().addDrawerListener(toggle); + toggle.syncState(); - this.layoutInflater.unregisterListener(mInflateListener); - this.layoutInflater.resetContextProvider(null); - } catch (Throwable e) { - LOG.error("Unable to release activity references from ILayoutInflater", e); - } + final var extras = getIntent().getExtras(); + final var path = extras.getString(KEY_LAYOUT_PATH, null); + final var name = path.substring(path.lastIndexOf(File.separator) + 1); + final var resDirs = extras.getStringArrayList(KEY_RES_DIRS); + getSupportActionBar().setTitle(name); - super.onDestroy(); - } - - private boolean isAttrEditorShowing() { - return getAttrEditorSheet().getDialog() != null - && getAttrEditorSheet().getDialog().isShowing(); + try { + this.layout = new File(path); + LOG.debug("Inflating layout file:", this.layout); + LOG.debug("Resource directories:", TextUtils.join(",\n", resDirs)); + + //noinspection deprecation + final var pd = ProgressDialog.show(this, null, getString(R.string.please_wait), true, false); + getApp() + .createInflaterConfig(this, resDirs.stream().map(File::new).collect(Collectors.toSet())) + .whenComplete( + (config, throwable) -> { + if (config == null || throwable != null) { + LOG.error("Unable to create inflater configuration", throwable); + return; + } + + ThreadUtils.runOnUiThread( + () -> { + pd.dismiss(); + inflatePath(path, config); + }); + }); + } catch (Throwable th) { + onLayoutInflationFailed(th); } - private AttrEditorSheet getAttrEditorSheet() { - return this.mEditorSheet == null - ? mEditorSheet = new AttrEditorSheet().setLayout(this.layout) - : mEditorSheet; + setupWidgets(); + + // Always open the drawer if this device is a tablet. + if (isTablet) { + mBinding.getRoot().setScrimColor(Color.TRANSPARENT); + mBinding.getRoot().openDrawer(GravityCompat.START); + mBinding.getRoot().setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN); + } + } + + @Override + protected View bindLayout() { + this.mBinding = ActivityDesignerBinding.inflate(getLayoutInflater()); + return mBinding.getRoot(); + } + + @Override + protected void onDestroy() { + LOG.logThis(); + if (isAttrEditorShowing()) { + getAttrEditorSheet().dismiss(); } - protected void onLayoutInflationFailed(Throwable th) { - mBinding.layoutContainer.removeAllViews(); - mBinding.layoutContainer.addView(createErrorText(th)); - LOG.error(getString(R.string.err_cannot_inflate_layout), th); - inflationFailed = true; + this.mEditorSheet = null; + + // Release the reference to inflate listener and context from the layout inflater + // Failing to do so will lead getSupportFragmentManager() to return a destroyed fragment + // manager + try { + if (layoutInflater == null) { + LOG.debug("Skipping layout inflater context reference release. Inflater is null"); + return; + } + + this.layoutInflater.unregisterListener(mInflateListener); + this.layoutInflater.resetContextProvider(null); + } catch (Throwable e) { + LOG.error("Unable to release activity references from ILayoutInflater", e); } - private void inflatePath(String path, LayoutInflaterConfiguration config) { - try { - this.layoutInflater = ILayoutInflater.newInstance(config); - this.layoutInflater.resetContextProvider(this); - this.layoutInflater.registerInflateListener(this.mInflateListener); - final IView view = this.layoutInflater.inflatePath(path, mBinding.layoutContainer); + super.onDestroy(); + } - if (this.inflatedRoot != null) { - this.inflatedRoot.asView().setOnDragListener(getOnDragListener(this.inflatedRoot)); - } + private boolean isAttrEditorShowing() { + return getAttrEditorSheet().getDialog() != null && getAttrEditorSheet().getDialog().isShowing(); + } - mBinding.layoutContainer.addView(view.asView()); - mBinding.layoutContainer.setOnClickListener( - v -> mBinding.getRoot().openDrawer(GravityCompat.START)); + private AttrEditorSheet getAttrEditorSheet() { + return this.mEditorSheet == null + ? mEditorSheet = new AttrEditorSheet().setLayout(this.layout) + : mEditorSheet; + } - tryDismiss(); - } catch (Throwable error) { - onLayoutInflationFailed(error); - } + protected void onLayoutInflationFailed(Throwable th) { + mBinding.layoutContainer.removeAllViews(); + mBinding.layoutContainer.addView(createErrorText(th)); + LOG.error(getString(R.string.err_cannot_inflate_layout), th); + inflationFailed = true; + } + + private void inflatePath(String path, LayoutInflaterConfiguration config) { + try { + this.layoutInflater = ILayoutInflater.newInstance(config); + this.layoutInflater.resetContextProvider(this); + this.layoutInflater.registerInflateListener(this.mInflateListener); + final IView view = this.layoutInflater.inflatePath(path, mBinding.layoutContainer); + + if (this.inflatedRoot != null) { + this.inflatedRoot.asView().setOnDragListener(getOnDragListener(this.inflatedRoot)); + } + + mBinding.layoutContainer.addView(view.asView()); + mBinding.layoutContainer.setOnClickListener( + v -> mBinding.getRoot().openDrawer(GravityCompat.START)); + + tryDismiss(); + } catch (Throwable error) { + onLayoutInflationFailed(error); + } + } + + private void tryDismiss() { + final var manager = getSupportFragmentManager(); + final var frag = manager.findFragmentByTag(EDITOR_SHEET_DIALOG_TAG); + if (frag != null) { + final var transaction = manager.beginTransaction(); + transaction.remove(frag); + transaction.commit(); + } + } + + private void setupInflatedView(@NonNull IView view) { + final var listener = + new WidgetTouchListener(view, this::onLayoutViewClick, this::onLayoutViewLongClick); + view.asView().setOnTouchListener(listener); + + setDragDataToInflatedView(view); + } + + private boolean onLayoutViewLongClick(@NonNull IView view) { + final var shadow = new WidgetDragShadowBuilder(view.asView()); + final var item = new ClipData.Item(DesignerActivity.DRAGGING_WIDGET_TAG); + final var clip = + new ClipData( + DesignerActivity.DRAGGING_WIDGET_TAG, + new String[] {DesignerActivity.DRAGGING_WIDGET_MIME}, + item); + final var data = view.getExtraData(); + + view.asView().startDragAndDrop(clip, shadow, data, 0); + view.removeFromParent(); + + return true; + } + + private void onLayoutViewClick(@NonNull IView view) { + if (isAttrEditorShowing()) { + getAttrEditorSheet().dismiss(); } - private void tryDismiss() { - final var manager = getSupportFragmentManager(); - final var frag = manager.findFragmentByTag(EDITOR_SHEET_DIALOG_TAG); - if (frag != null) { - final var transaction = manager.beginTransaction(); - transaction.remove(frag); - transaction.commit(); - } + getAttrEditorSheet().setSelectedView(view); + + if (!getSupportFragmentManager().isDestroyed()) { + getAttrEditorSheet().show(getSupportFragmentManager(), EDITOR_SHEET_DIALOG_TAG); } + } - private void setupInflatedView(@NonNull IView view) { - final var listener = - new WidgetTouchListener(view, this::onLayoutViewClick, this::onLayoutViewLongClick); - view.asView().setOnTouchListener(listener); + private void setupWidgets() { + final var android = new UIWidgetGroup(getString(R.string.widget_group_android)); + android.addChild( + new UIWidget(getString(R.string.widget_button), R.drawable.ic_widget_button, Button.class)); + android.addChild( + new UIWidget( + getString(R.string.widget_checkbox), R.drawable.ic_widget_checkbox, CheckBox.class)); + android.addChild( + new UIWidget( + getString(R.string.widget_checked_textview), + R.drawable.ic_widget_checked_textview, + CheckedTextView.class)); + android.addChild( + new UIWidget( + getString(R.string.widget_edittext), R.drawable.ic_widget_edittext, EditText.class)); + android.addChild( + new UIWidget( + getString(R.string.widget_image_button), + R.drawable.ic_widget_image, + ImageButton.class)); + android.addChild( + new UIWidget( + getString(R.string.widget_image_view), R.drawable.ic_widget_image, ImageView.class)); + android.addChild( + new UIWidget( + getString(R.string.widget_progressbar), + R.drawable.ic_widget_progress, + ProgressBar.class)); + android.addChild( + new UIWidget( + getString(R.string.widget_radio_button), + R.drawable.ic_widget_radio_button, + RadioButton.class)); + android.addChild( + new UIWidget( + getString(R.string.widget_seekbar), R.drawable.ic_widget_seekbar, SeekBar.class)); + android.addChild( + new UIWidget( + getString(R.string.widget_spinner), R.drawable.ic_widget_spinner, Spinner.class)); + android.addChild( + new UIWidget( + getString(R.string.widget_textview), R.drawable.ic_widget_textview, TextView.class)); - setDragDataToInflatedView(view); - } + // IMPORTANT: Do not select any other groups here + android.setSelected(true); + this.checkedWidgetCategory = android; + widgetGroups.add(android); - private boolean onLayoutViewLongClick(@NonNull IView view) { - final var shadow = new WidgetDragShadowBuilder(view.asView()); - final var item = new ClipData.Item(DesignerActivity.DRAGGING_WIDGET_TAG); - final var clip = - new ClipData( - DesignerActivity.DRAGGING_WIDGET_TAG, - new String[] {DesignerActivity.DRAGGING_WIDGET_MIME}, - item); - final var data = view.getExtraData(); + final var layouts = new UIWidgetGroup(getString(R.string.widget_group_layouts)); + layouts.addChild( + new UIWidget( + getString(R.string.layout_linear), R.drawable.ic_widget_linear, LinearLayout.class)); + layouts.addChild( + new UIWidget( + getString(R.string.layout_relative), + R.drawable.ic_widget_relative, + RelativeLayout.class)); - view.asView().startDragAndDrop(clip, shadow, data, 0); - view.removeFromParent(); + widgetGroups.add(layouts); - return true; - } + final var adapter = new WidgetGroupItemAdapter(widgetGroups, this::showWidgetGroup); + mBinding.groupItems.setAdapter(adapter); - private void onLayoutViewClick(@NonNull IView view) { - if (isAttrEditorShowing()) { - getAttrEditorSheet().dismiss(); - } + showWidgetGroup(android); + } - getAttrEditorSheet().setSelectedView(view); + @SuppressLint("NotifyDataSetChanged") + private void showWidgetGroup(@NonNull UIWidgetGroup group) { + final var children = group.getChildren(); + this.mBinding.widgetItems.setAdapter(new WidgetItemAdapter(children, this)); + this.checkedWidgetCategory.setSelected(false); + this.checkedWidgetCategory = group; + this.checkedWidgetCategory.setSelected(true); - if (!getSupportFragmentManager().isDestroyed()) { - getAttrEditorSheet().show(getSupportFragmentManager(), EDITOR_SHEET_DIALOG_TAG); - } - } + TransitionManager.beginDelayedTransition( + mBinding.navigation, + new TransitionSet().addTransition(new Slide(Gravity.END)).addTransition(new Fade())); - private void setupWidgets() { - final var android = new UIWidgetGroup(getString(R.string.widget_group_android)); - android.addChild( - new UIWidget( - getString(R.string.widget_button), - R.drawable.ic_widget_button, - Button.class)); - android.addChild( - new UIWidget( - getString(R.string.widget_checkbox), - R.drawable.ic_widget_checkbox, - CheckBox.class)); - android.addChild( - new UIWidget( - getString(R.string.widget_checked_textview), - R.drawable.ic_widget_checked_textview, - CheckedTextView.class)); - android.addChild( - new UIWidget( - getString(R.string.widget_edittext), - R.drawable.ic_widget_edittext, - EditText.class)); - android.addChild( - new UIWidget( - getString(R.string.widget_image_button), - R.drawable.ic_widget_image, - ImageButton.class)); - android.addChild( - new UIWidget( - getString(R.string.widget_image_view), - R.drawable.ic_widget_image, - ImageView.class)); - android.addChild( - new UIWidget( - getString(R.string.widget_progressbar), - R.drawable.ic_widget_progress, - ProgressBar.class)); - android.addChild( - new UIWidget( - getString(R.string.widget_radio_button), - R.drawable.ic_widget_radio_button, - RadioButton.class)); - android.addChild( - new UIWidget( - getString(R.string.widget_seekbar), - R.drawable.ic_widget_seekbar, - SeekBar.class)); - android.addChild( - new UIWidget( - getString(R.string.widget_spinner), - R.drawable.ic_widget_spinner, - Spinner.class)); - android.addChild( - new UIWidget( - getString(R.string.widget_textview), - R.drawable.ic_widget_textview, - TextView.class)); + //noinspection ConstantConditions + this.mBinding.groupItems.getAdapter().notifyDataSetChanged(); + } - // IMPORTANT: Do not select any other groups here - android.setSelected(true); - this.checkedWidgetCategory = android; - widgetGroups.add(android); + @NonNull + private TextView createErrorText(Throwable th) { + final TextView error = new TextView(this); + error.setText(ThrowableUtils.getFullStackTrace(th)); + error.setLayoutParams(new ViewGroup.LayoutParams(-2, -2)); + error.setTextColor(ContextCompat.getColor(this, android.R.color.black)); + error.setBackgroundColor(ContextCompat.getColor(this, android.R.color.white)); + return error; + } - final var layouts = new UIWidgetGroup(getString(R.string.widget_group_layouts)); - layouts.addChild( - new UIWidget( - getString(R.string.layout_linear), - R.drawable.ic_widget_linear, - LinearLayout.class)); - layouts.addChild( - new UIWidget( - getString(R.string.layout_relative), - R.drawable.ic_widget_relative, - RelativeLayout.class)); + @NonNull + @Contract("_ -> new") + private View.OnDragListener getOnDragListener(IViewGroup group) { + // WidgetDragListener cannot be reused + // This is because they keep a reference to the view group in which dragged view will be + // added - widgetGroups.add(layouts); + return new WidgetDragListener(this, group, this::setupInflatedView); + } - final var adapter = new WidgetGroupItemAdapter(widgetGroups, this::showWidgetGroup); - mBinding.groupItems.setAdapter(adapter); - - showWidgetGroup(android); - } - - @SuppressLint("NotifyDataSetChanged") - private void showWidgetGroup(@NonNull UIWidgetGroup group) { - final var children = group.getChildren(); - this.mBinding.widgetItems.setAdapter(new WidgetItemAdapter(children, this)); - this.checkedWidgetCategory.setSelected(false); - this.checkedWidgetCategory = group; - this.checkedWidgetCategory.setSelected(true); - - TransitionManager.beginDelayedTransition( - mBinding.navigation, - new TransitionSet() - .addTransition(new Slide(Gravity.END)) - .addTransition(new Fade())); - - //noinspection ConstantConditions - this.mBinding.groupItems.getAdapter().notifyDataSetChanged(); - } - - @NonNull - private TextView createErrorText(Throwable th) { - final TextView error = new TextView(this); - error.setText(ThrowableUtils.getFullStackTrace(th)); - error.setLayoutParams(new ViewGroup.LayoutParams(-2, -2)); - error.setTextColor(ContextCompat.getColor(this, android.R.color.black)); - error.setBackgroundColor(ContextCompat.getColor(this, android.R.color.white)); - return error; - } - - @NonNull - @Contract("_ -> new") - private View.OnDragListener getOnDragListener(IViewGroup group) { - // WidgetDragListener cannot be reused - // This is because they keep a reference to the view group in which dragged view will be - // added - - return new WidgetDragListener(this, group, this::setupInflatedView); - } - - private void setDragDataToInflatedView(@NonNull IView view) { - view.setExtraData(new WidgetDragData(true, view, null)); - } + private void setDragDataToInflatedView(@NonNull IView view) { + view.setExtraData(new WidgetDragData(true, view, null)); + } } diff --git a/app/src/main/java/com/itsaky/androidide/DownloadActivity.java b/app/src/main/java/com/itsaky/androidide/DownloadActivity.java index 4d128ad61..58505755c 100755 --- a/app/src/main/java/com/itsaky/androidide/DownloadActivity.java +++ b/app/src/main/java/com/itsaky/androidide/DownloadActivity.java @@ -56,274 +56,266 @@ import abhishekti7.unicorn.filepicker.UnicornFilePicker; public class DownloadActivity extends StudioActivity { - private ActivityDownloadBinding binding; - private ProgressSheet progressSheet; - private File choosenDir = null; + private ActivityDownloadBinding binding; + private ProgressSheet progressSheet; + private File choosenDir = null; - @Override - protected View bindLayout() { - binding = ActivityDownloadBinding.inflate(getLayoutInflater()); - return binding.getRoot(); - } + @Override + protected View bindLayout() { + binding = ActivityDownloadBinding.inflate(getLayoutInflater()); + return binding.getRoot(); + } - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - final OnClickListener clickListener = this::handleClick; - binding.dismiss.setOnClickListener(clickListener); - binding.choose.setOnClickListener(clickListener); - binding.install.setOnClickListener(clickListener); + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + final OnClickListener clickListener = this::handleClick; + binding.dismiss.setOnClickListener(clickListener); + binding.choose.setOnClickListener(clickListener); + binding.install.setOnClickListener(clickListener); + binding.install.setEnabled(false); + } + + private void handleClick(View v) { + final int id = v.getId(); + if (id == binding.dismiss.getId()) { + TransitionManager.beginDelayedTransition(binding.getRoot()); + binding.card.setVisibility(View.GONE); + } else if (id == binding.choose.getId()) { + try { binding.install.setEnabled(false); - } + UnicornFilePicker.from(this) + .addConfigBuilder() + .addItemDivider(false) + .selectMultipleFiles(false) + .setRootDirectory(FileUtil.getExternalStorageDir()) + .showHiddenFiles(true) + .showOnlyDirectory(true) + .theme(R.style.AppTheme_FilePicker) + .build() + .forResult(abhishekti7.unicorn.filepicker.utils.Constants.REQ_UNICORN_FILE); + } catch (Throwable th) { + LOG.error(getString(R.string.err_cannot_start_file_picker), th); + } + } else if (id == binding.install.getId()) { + if (choosenDir == null) { + getApp().toast(R.string.msg_pick_files, Toaster.Type.ERROR); + return; + } - private void handleClick(View v) { - final int id = v.getId(); - if (id == binding.dismiss.getId()) { - TransitionManager.beginDelayedTransition(binding.getRoot()); - binding.card.setVisibility(View.GONE); - } else if (id == binding.choose.getId()) { - try { - binding.install.setEnabled(false); - UnicornFilePicker.from(this) - .addConfigBuilder() - .addItemDivider(false) - .selectMultipleFiles(false) - .setRootDirectory(FileUtil.getExternalStorageDir()) - .showHiddenFiles(true) - .showOnlyDirectory(true) - .theme(R.style.AppTheme_FilePicker) - .build() - .forResult(abhishekti7.unicorn.filepicker.utils.Constants.REQ_UNICORN_FILE); - } catch (Throwable th) { - LOG.error(getString(R.string.err_cannot_start_file_picker), th); - } - } else if (id == binding.install.getId()) { - if (choosenDir == null) { - getApp().toast(R.string.msg_pick_files, Toaster.Type.ERROR); - return; + if (!choosenDir.exists()) { + getApp().toast(R.string.msg_file_doesnt_exist, Toaster.Type.ERROR); + return; + } + + installAll(); + } + } + + private void installAll() { + showProgress(); + getApp().getPrefManager().putBoolean(PreferenceManager.KEY_FRAMEWORK_DOWNLOADED, true); + + try { + final File script = createExtractScript(); + final ProcessStreamsHolder holder = new ProcessStreamsHolder(); + final IProcessExecutor executor = ProcessExecutorFactory.commonExecutor(); + + executor.execAsync( + holder, + this::onInstallProcessExit, + true, + Environment.BUSYBOX.getAbsolutePath(), + "sh", // We use busybox's sh, because Environment.SHELL is not installed yet... + script.getAbsolutePath()); + + this.output = new StringBuilder(); + final InputStreamLineReader reader = + new InputStreamLineReader(holder.in, this::onInstallationOutput); + new Thread(reader).start(); + + } catch (DownloadActivity.InstallationException e) { + LOG.error(getString(R.string.err_installation), e); + onInstallationFailed(e.exitCode); + } catch (IOException e) { + LOG.error(getString(R.string.err_installation), e); + onInstallationFailed(5); // Exit code 5 : I/O Error + } + } + + private void onInstallationOutput(final String line) { + ThreadUtils.runOnUiThread(() -> this.appendOut(line)); + } + + private void onInstallProcessExit(final int code) { + ThreadUtils.runOnUiThread( + () -> { + if (code == 0) { // 0 = normal execution + getApp().getPrefManager().putBoolean(PreferenceManager.KEY_FRAMEWORK_INSTALLED, true); + showRestartNeeded(); + if (getProgressSheet().isShowing()) { + getProgressSheet().dismiss(); } + } else { + onInstallationFailed(code); + } + }); + } - if (!choosenDir.exists()) { - getApp().toast(R.string.msg_file_doesnt_exist, Toaster.Type.ERROR); - return; - } + private void onInstallationFailed(int code) { + // TODO Can this be improved by adding some animations? - installAll(); + if (getProgressSheet().isShowing()) { + getProgressSheet().dismiss(); + } + + final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this); + builder.setTitle(R.string.title_installation_failed); + builder.setMessage(getString(R.string.msg_installation_failed, code, this.output.toString())); + builder.setPositiveButton( + android.R.string.ok, + (d, w) -> { + d.dismiss(); + finishAffinity(); + }); + builder.setNegativeButton(android.R.string.cancel, null); + builder.show(); + } + + private File createExtractScript() throws DownloadActivity.InstallationException { + final StringBuilder sb = new StringBuilder(); + sb.append("cd"); + joiner(sb); + sb.append("echo 'Installing...'"); + joiner(sb); + + File[] files = choosenDir.listFiles(ARCHIVE_FILTER); + + if (files == null || files.length <= 0) { + throw new InstallationException(2); + } + + for (File f : files) { + if (f.getName().endsWith(".tar.xz")) { + if (f.getName().startsWith("androidide-sysroot")) { + sb.append("cd $SYSROOT/.."); + joiner(sb); } - } - - private void installAll() { - showProgress(); - getApp().getPrefManager().putBoolean(PreferenceManager.KEY_FRAMEWORK_DOWNLOADED, true); - - try { - final File script = createExtractScript(); - final ProcessStreamsHolder holder = new ProcessStreamsHolder(); - final IProcessExecutor executor = ProcessExecutorFactory.commonExecutor(); - - executor.execAsync( - holder, - this::onInstallProcessExit, - true, - Environment.BUSYBOX.getAbsolutePath(), - "sh", // We use busybox's sh, because Environment.SHELL is not installed yet... - script.getAbsolutePath()); - - this.output = new StringBuilder(); - final InputStreamLineReader reader = - new InputStreamLineReader(holder.in, this::onInstallationOutput); - new Thread(reader).start(); - - } catch (DownloadActivity.InstallationException e) { - LOG.error(getString(R.string.err_installation), e); - onInstallationFailed(e.exitCode); - } catch (IOException e) { - LOG.error(getString(R.string.err_installation), e); - onInstallationFailed(5); // Exit code 5 : I/O Error - } - } - - private void onInstallationOutput(final String line) { - ThreadUtils.runOnUiThread(() -> this.appendOut(line)); - } - - private void onInstallProcessExit(final int code) { - ThreadUtils.runOnUiThread( - () -> { - if (code == 0) { // 0 = normal execution - getApp().getPrefManager() - .putBoolean(PreferenceManager.KEY_FRAMEWORK_INSTALLED, true); - showRestartNeeded(); - if (getProgressSheet().isShowing()) { - getProgressSheet().dismiss(); - } - } else { - onInstallationFailed(code); - } - }); - } - - private void onInstallationFailed(int code) { - // TODO Can this be improved by adding some animations? - - if (getProgressSheet().isShowing()) { - getProgressSheet().dismiss(); - } - - final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this); - builder.setTitle(R.string.title_installation_failed); - builder.setMessage( - getString(R.string.msg_installation_failed, code, this.output.toString())); - builder.setPositiveButton( - android.R.string.ok, - (d, w) -> { - d.dismiss(); - finishAffinity(); - }); - builder.setNegativeButton(android.R.string.cancel, null); - builder.show(); - } - - private File createExtractScript() throws DownloadActivity.InstallationException { - final StringBuilder sb = new StringBuilder(); - sb.append("cd"); + sb.append("$BUSYBOX tar xvJf '").append(f.getAbsolutePath()).append("'"); joiner(sb); - sb.append("echo 'Installing...'"); + sb.append("cd $HOME"); joiner(sb); - - File[] files = choosenDir.listFiles(ARCHIVE_FILTER); - - if (files == null || files.length <= 0) { - throw new InstallationException(2); - } - - for (File f : files) { - if (f.getName().endsWith(".tar.xz")) { - if (f.getName().startsWith("androidide-sysroot")) { - sb.append("cd $SYSROOT/.."); - joiner(sb); - } - sb.append("$BUSYBOX tar xvJf '").append(f.getAbsolutePath()).append("'"); - joiner(sb); - sb.append("cd $HOME"); - joiner(sb); - } else if (f.getName().endsWith(".zip")) { - sb.append("$BUSYBOX unzip '").append(f.getAbsolutePath()).append("'"); - joiner(sb); - } - } - - sb.append("echo 'Cleaning unsupported flags in binaries...'"); + } else if (f.getName().endsWith(".zip")) { + sb.append("$BUSYBOX unzip '").append(f.getAbsolutePath()).append("'"); joiner(sb); - String DONE = "DONE"; - sb.append("echo ").append(DONE); + } + } - final File script = new File(Environment.TMP_DIR, "extract_tools.sh"); - if (!FileIOUtils.writeFileFromString(script, sb.toString())) { - throw new InstallationException(2); + sb.append("echo 'Cleaning unsupported flags in binaries...'"); + joiner(sb); + String DONE = "DONE"; + sb.append("echo ").append(DONE); + + final File script = new File(Environment.TMP_DIR, "extract_tools.sh"); + if (!FileIOUtils.writeFileFromString(script, sb.toString())) { + throw new InstallationException(2); + } + + return script; + } + + private void joiner(StringBuilder sb) { + sb.append(" && "); + } + + private StringBuilder output = new StringBuilder(); + + private void appendOut(String line) { + LOG.debug("Installation:", line); + output.append(line.trim()); + output.append("\n"); + + getProgressSheet().setSubMessage(line); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == abhishekti7.unicorn.filepicker.utils.Constants.REQ_UNICORN_FILE + && resultCode == RESULT_OK) { + ArrayList files = data.getStringArrayListExtra("filePaths"); + if (files != null) { + if (files.size() == 1) { + File choosenDir = new File(files.get(0)); + if (choosenDir.exists() && choosenDir.isDirectory()) { + this.choosenDir = choosenDir; + setDone(binding.choose); + binding.install.setEnabled(true); + new TaskExecutor() + .executeAsync( + new ListDirectoryCallable(choosenDir), + __ -> binding.filesText.setText(getString(R.string.msg_installable_files, __))); + } else { + getApp().toast(R.string.msg_picked_isnt_dir, Toaster.Type.ERROR); + } + } else { + getApp().toast(R.string.msg_pick_single_file, Toaster.Type.ERROR); } - - return script; + } } + } - private void joiner(StringBuilder sb) { - sb.append(" && "); - } - - private StringBuilder output = new StringBuilder(); - - private void appendOut(String line) { - LOG.debug("Installation:", line); - output.append(line.trim()); - output.append("\n"); - - getProgressSheet().setSubMessage(line); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (requestCode == abhishekti7.unicorn.filepicker.utils.Constants.REQ_UNICORN_FILE - && resultCode == RESULT_OK) { - ArrayList files = data.getStringArrayListExtra("filePaths"); - if (files != null) { - if (files.size() == 1) { - File choosenDir = new File(files.get(0)); - if (choosenDir.exists() && choosenDir.isDirectory()) { - this.choosenDir = choosenDir; - setDone(binding.choose); - binding.install.setEnabled(true); - new TaskExecutor() - .executeAsync( - new ListDirectoryCallable(choosenDir), - __ -> - binding.filesText.setText( - getString( - R.string.msg_installable_files, - __))); - } else { - getApp().toast(R.string.msg_picked_isnt_dir, Toaster.Type.ERROR); - } - } else { - getApp().toast(R.string.msg_pick_single_file, Toaster.Type.ERROR); - } - } - } - } - - private void setDone(MaterialButton button) { - final int dp8 = SizeUtils.dp2px(8); - final int dp4 = SizeUtils.dp2px(4); - final int dp24 = SizeUtils.dp2px(24); - button.setText(""); - button.setIconResource(R.drawable.ic_ok); - button.setIconTint(ColorStateList.valueOf(ContextCompat.getColor(this, R.color.bg_green))); - button.setBackgroundColor(android.graphics.Color.TRANSPARENT); - button.setPaddingRelative(dp8, dp4, dp8, dp4); - button.setStrokeWidth(0); - button.setGravity(Gravity.CENTER); - button.setIconPadding(0); - button.setInsetTop(0); - button.setInsetBottom(0); - button.setPaddingRelative(dp24, dp4, dp24, dp4); - } - - private void showProgress() { - getProgressSheet().setCancelable(false); - getProgressSheet().setShowShadow(false); - getProgressSheet() - .setSubMessageEnabled(true) - .setWelcomeTextEnabled(true) - .show(getSupportFragmentManager(), "progress_sheet"); - } - - private void showRestartNeeded() { - setDone(binding.install); - final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this); - builder.setTitle(R.string.title_restart); - builder.setMessage(R.string.msg_restart); - builder.setCancelable(false); - builder.setPositiveButton(android.R.string.ok, (p1, p2) -> finishAffinity()); - builder.create().show(); - } - - private ProgressSheet getProgressSheet() { - return progressSheet == null - ? progressSheet = new ProgressSheet().setMessage(getString(R.string.please_wait)) - : progressSheet; - } - - private final FileFilter ARCHIVE_FILTER = - p1 -> - p1.isFile() - && (p1.getName().endsWith(".tar.xz") || p1.getName().endsWith(".zip")); - - private static class InstallationException extends Exception { - private final int exitCode; - - public InstallationException(int exitCode) { - this.exitCode = exitCode; - } + private void setDone(MaterialButton button) { + final int dp8 = SizeUtils.dp2px(8); + final int dp4 = SizeUtils.dp2px(4); + final int dp24 = SizeUtils.dp2px(24); + button.setText(""); + button.setIconResource(R.drawable.ic_ok); + button.setIconTint(ColorStateList.valueOf(ContextCompat.getColor(this, R.color.bg_green))); + button.setBackgroundColor(android.graphics.Color.TRANSPARENT); + button.setPaddingRelative(dp8, dp4, dp8, dp4); + button.setStrokeWidth(0); + button.setGravity(Gravity.CENTER); + button.setIconPadding(0); + button.setInsetTop(0); + button.setInsetBottom(0); + button.setPaddingRelative(dp24, dp4, dp24, dp4); + } + + private void showProgress() { + getProgressSheet().setCancelable(false); + getProgressSheet().setShowShadow(false); + getProgressSheet() + .setSubMessageEnabled(true) + .setWelcomeTextEnabled(true) + .show(getSupportFragmentManager(), "progress_sheet"); + } + + private void showRestartNeeded() { + setDone(binding.install); + final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this); + builder.setTitle(R.string.title_restart); + builder.setMessage(R.string.msg_restart); + builder.setCancelable(false); + builder.setPositiveButton(android.R.string.ok, (p1, p2) -> finishAffinity()); + builder.create().show(); + } + + private ProgressSheet getProgressSheet() { + return progressSheet == null + ? progressSheet = new ProgressSheet().setMessage(getString(R.string.please_wait)) + : progressSheet; + } + + private final FileFilter ARCHIVE_FILTER = + p1 -> p1.isFile() && (p1.getName().endsWith(".tar.xz") || p1.getName().endsWith(".zip")); + + private static class InstallationException extends Exception { + private final int exitCode; + + public InstallationException(int exitCode) { + this.exitCode = exitCode; } + } } diff --git a/app/src/main/java/com/itsaky/androidide/PreferencesActivity.java b/app/src/main/java/com/itsaky/androidide/PreferencesActivity.java index a3e99d7a1..d0721053a 100755 --- a/app/src/main/java/com/itsaky/androidide/PreferencesActivity.java +++ b/app/src/main/java/com/itsaky/androidide/PreferencesActivity.java @@ -31,38 +31,38 @@ import com.itsaky.androidide.fragments.preferences.IDEPreferences; public class PreferencesActivity extends StudioActivity { - private ActivityPreferencesBinding binding; - private IDEPreferences mPref; + private ActivityPreferencesBinding binding; + private IDEPreferences mPref; - @Override - protected View bindLayout() { - binding = ActivityPreferencesBinding.inflate(getLayoutInflater()); - return binding.getRoot(); - } + @Override + protected View bindLayout() { + binding = ActivityPreferencesBinding.inflate(getLayoutInflater()); + return binding.getRoot(); + } - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); - setSupportActionBar(binding.toolbar); - getSupportActionBar().setTitle(R.string.ide_preferences); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); + setSupportActionBar(binding.toolbar); + getSupportActionBar().setTitle(R.string.ide_preferences); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); - binding.toolbar.setNavigationOnClickListener(v -> onBackPressed()); + binding.toolbar.setNavigationOnClickListener(v -> onBackPressed()); - loadFragment(getPrefsFragment()); - } + loadFragment(getPrefsFragment()); + } - private IDEPreferences getPrefsFragment() { - return mPref == null ? mPref = new IDEPreferences() : mPref; - } + private IDEPreferences getPrefsFragment() { + return mPref == null ? mPref = new IDEPreferences() : mPref; + } - private void loadFragment(Fragment frag) { - super.loadFragment(frag, binding.fragmentContainer.getId()); - } + private void loadFragment(Fragment frag) { + super.loadFragment(frag, binding.fragmentContainer.getId()); + } - @Override - public void onBackPressed() { - super.onBackPressed(); - } + @Override + public void onBackPressed() { + super.onBackPressed(); + } } diff --git a/app/src/main/java/com/itsaky/androidide/SplashActivity.java b/app/src/main/java/com/itsaky/androidide/SplashActivity.java index f26c132cb..d1aa25400 100755 --- a/app/src/main/java/com/itsaky/androidide/SplashActivity.java +++ b/app/src/main/java/com/itsaky/androidide/SplashActivity.java @@ -33,60 +33,60 @@ import com.itsaky.androidide.models.ConstantsBridge; import com.itsaky.androidide.utils.DialogUtils; public class SplashActivity extends StudioActivity { - private static final Handler mHandler = new Handler(); - private final Runnable mRunnable = - () -> { - ConstantsBridge.SPLASH_TO_MAIN = true; - startActivity(new Intent(SplashActivity.this, MainActivity.class)); - finish(); - }; - private ActivitySplashBinding binding; + private static final Handler mHandler = new Handler(); + private final Runnable mRunnable = + () -> { + ConstantsBridge.SPLASH_TO_MAIN = true; + startActivity(new Intent(SplashActivity.this, MainActivity.class)); + finish(); + }; + private ActivitySplashBinding binding; - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - binding.splashText.setText(getString(R.string.msg_checking_storage_permissions)); - } + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + binding.splashText.setText(getString(R.string.msg_checking_storage_permissions)); + } - @Override - protected void onStorageAlreadyGranted() { - super.onStorageAlreadyGranted(); - proceed(); - } + @Override + protected void onStorageAlreadyGranted() { + super.onStorageAlreadyGranted(); + proceed(); + } - @Override - protected void onStorageGranted() { - super.onStorageGranted(); - proceed(); - } + @Override + protected void onStorageGranted() { + super.onStorageGranted(); + proceed(); + } - @Override - protected View bindLayout() { - binding = ActivitySplashBinding.inflate(getLayoutInflater()); - return binding.getRoot(); - } + @Override + protected View bindLayout() { + binding = ActivitySplashBinding.inflate(getLayoutInflater()); + return binding.getRoot(); + } - private void proceed() { - if (!StudioApp.isAbiSupported()) { - final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this); - builder.setTitle(R.string.title_device_not_supported); - builder.setMessage(R.string.msg_device_not_supported); - builder.setCancelable(false); - builder.setPositiveButton(android.R.string.ok, (p1, p2) -> finishAffinity()); - builder.create().show(); - } else { - binding.splashText.setText(getString(R.string.msg_storage_granted)); - if (getApp().isFrameworkInstalled()) { - goToMain(); - } else { - startActivity(new Intent(this, DownloadActivity.class)); - finish(); - } - } + private void proceed() { + if (!StudioApp.isAbiSupported()) { + final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(this); + builder.setTitle(R.string.title_device_not_supported); + builder.setMessage(R.string.msg_device_not_supported); + builder.setCancelable(false); + builder.setPositiveButton(android.R.string.ok, (p1, p2) -> finishAffinity()); + builder.create().show(); + } else { + binding.splashText.setText(getString(R.string.msg_storage_granted)); + if (getApp().isFrameworkInstalled()) { + goToMain(); + } else { + startActivity(new Intent(this, DownloadActivity.class)); + finish(); + } } + } - private void goToMain() { - mHandler.removeCallbacks(mRunnable); - mHandler.postDelayed(mRunnable, 1000); - } + private void goToMain() { + mHandler.removeCallbacks(mRunnable); + mHandler.postDelayed(mRunnable, 1000); + } } diff --git a/app/src/main/java/com/itsaky/androidide/TerminalActivity.java b/app/src/main/java/com/itsaky/androidide/TerminalActivity.java index c6018e795..81db28a47 100755 --- a/app/src/main/java/com/itsaky/androidide/TerminalActivity.java +++ b/app/src/main/java/com/itsaky/androidide/TerminalActivity.java @@ -65,453 +65,448 @@ import java.util.Map; public class TerminalActivity extends StudioActivity { - public static final String KEY_WORKING_DIRECTORY = "terminal_workingDirectory"; - private static final ILogger LOG = ILogger.newInstance("TerminalActivity"); - private static final byte[] SOURCES_LIST_CONTENT = - "deb https://androidide.com/packages/ stable main".getBytes(); - private final Client client = new Client(); - private ActivityTerminalBinding binding; - private TerminalView terminal; - private TerminalSession session; - private boolean isVisible = false; - private KeyListener listener; + public static final String KEY_WORKING_DIRECTORY = "terminal_workingDirectory"; + private static final ILogger LOG = ILogger.newInstance("TerminalActivity"); + private static final byte[] SOURCES_LIST_CONTENT = + "deb https://androidide.com/packages/ stable main".getBytes(); + private final Client client = new Client(); + private ActivityTerminalBinding binding; + private TerminalView terminal; + private TerminalSession session; + private boolean isVisible = false; + private KeyListener listener; - @Override - protected View bindLayout() { - binding = ActivityTerminalBinding.inflate(getLayoutInflater()); - return binding.getRoot(); - } + @Override + protected View bindLayout() { + binding = ActivityTerminalBinding.inflate(getLayoutInflater()); + return binding.getRoot(); + } - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); - final var bash = new File(BIN_DIR, "bash"); - final var useSystemShell = - getApp().getPrefManager() - .getBoolean(PreferenceManager.KEY_TERMINAL_USE_SYSTEM_SHELL); - if ((SYSROOT.exists() - && SYSROOT.isDirectory() - && bash.exists() - && bash.isFile() - && bash.canExecute()) - || useSystemShell) { - setupTerminalView(); - } else { - LOG.info("Bootstrap is not installed."); + final var bash = new File(BIN_DIR, "bash"); + final var useSystemShell = + getApp().getPrefManager().getBoolean(PreferenceManager.KEY_TERMINAL_USE_SYSTEM_SHELL); + if ((SYSROOT.exists() + && SYSROOT.isDirectory() + && bash.exists() + && bash.isFile() + && bash.canExecute()) + || useSystemShell) { + setupTerminalView(); + } else { + LOG.info("Bootstrap is not installed."); - // Show the progress sheet - final var progress = new ProgressSheet(); - progress.setShowShadow(false); - progress.setSubMessageEnabled(true); - progress.setShowTitle(false); - progress.setMessage(getString(R.string.please_wait)); - progress.setSubMessage(getString(R.string.msg_reading_bootstrap)); - progress.setCancelable(false); - progress.show(getSupportFragmentManager(), "extract_bootstrap_progress"); + // Show the progress sheet + final var progress = new ProgressSheet(); + progress.setShowShadow(false); + progress.setSubMessageEnabled(true); + progress.setShowTitle(false); + progress.setMessage(getString(R.string.please_wait)); + progress.setSubMessage(getString(R.string.msg_reading_bootstrap)); + progress.setCancelable(false); + progress.show(getSupportFragmentManager(), "extract_bootstrap_progress"); - LOG.debug("Starting installation..."); - // Install bootstrap asynchronously - final var future = - BootstrapInstaller.doInstall( - this, message -> runOnUiThread(() -> progress.setSubMessage(message))); + LOG.debug("Starting installation..."); + // Install bootstrap asynchronously + final var future = + BootstrapInstaller.doInstall( + this, message -> runOnUiThread(() -> progress.setSubMessage(message))); - future.whenComplete( - (voidResult, throwable) -> { - LOG.debug("Completable future has been complete.", throwable); + future.whenComplete( + (voidResult, throwable) -> { + LOG.debug("Completable future has been complete.", throwable); - runOnUiThread( - () -> { - progress.dismissAllowingStateLoss(); + runOnUiThread( + () -> { + progress.dismissAllowingStateLoss(); - if (future.isCompletedExceptionally() || throwable != null) { - LOG.error("Future has been completed exceptionally."); - showInstallationError(throwable); - return; - } - - setupTerminalView(); - }); - }); - } - } - - private void showInstallationError(Throwable throwable) { - - binding.virtualKeyTable.setVisibility(View.GONE); - - final var frag = - CrashReportFragment.newInstance( - getString(R.string.title_installation_failed), - getString(R.string.msg_bootstrap_installation_error), - ThrowableUtils.getFullStackTrace(throwable), - false); - - getSupportFragmentManager() - .beginTransaction() - .replace(binding.root.getId(), frag, "bootstrap_install_error_fragment") - .addToBackStack(null) - .commit(); - } - - private void setupTerminalView() { - terminal = new TerminalView(this, null); - terminal.setTerminalViewClient(client); - terminal.attachSession(createSession(getWorkingDirectory())); - terminal.setKeepScreenOn(true); - terminal.setTextSize(SizeUtils.dp2px(10)); - terminal.setTypeface(TypefaceUtils.jetbrainsMono()); - - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(-1, 0); - params.weight = 1f; - - binding.getRoot().addView(terminal, 0, params); - try { - binding.virtualKeyTable.setVirtualKeysViewClient(getKeyListener()); - binding.virtualKeyTable.reload( - new VirtualKeysInfo( - ConstantsBridge.VIRTUAL_KEYS, - "", - VirtualKeysConstants.CONTROL_CHARS_ALIASES)); - } catch (JSONException e) { - LOG.error("Unable to parse terminal virtual keys json data", e); - } - } - - @NonNull - private String getWorkingDirectory() { - final Bundle extras = getIntent().getExtras(); - if (extras != null && extras.containsKey(KEY_WORKING_DIRECTORY)) { - String directory = extras.getString(KEY_WORKING_DIRECTORY, null); - if (directory == null || directory.trim().length() <= 0) { - directory = HOME.getAbsolutePath(); - } - return directory; - } - return HOME.getAbsolutePath(); - } - - private KeyListener getKeyListener() { - return listener == null ? listener = new KeyListener(terminal) : listener; - } - - @Override - protected void onResume() { - super.onResume(); - setTerminalCursorBlinkingState(true); - } - - @Override - protected void onStart() { - super.onStart(); - isVisible = true; - } - - @Override - protected void onStop() { - super.onStop(); - isVisible = false; - setTerminalCursorBlinkingState(false); - } - - public boolean isVisible() { - return isVisible; - } - - private void setTerminalCursorBlinkingState(boolean start) { - if (terminal != null && terminal.mEmulator != null) { - terminal.setTerminalCursorBlinkerState(start, true); - } - } - - private TerminalSession createSession(final String workingDirectory) { - final Map envs = getEnvironment(); - final String[] env = new String[envs.size()]; - int i = 0; - for (Map.Entry entry : envs.entrySet()) { - env[i] = entry.getKey() + "=" + entry.getValue(); - i++; - } - - session = - new TerminalSession( - getShellPath(), // Shell command - workingDirectory, // Working directory - new String[] {}, // Arguments - env, // Environment variables - TerminalEmulator.DEFAULT_TERMINAL_TRANSCRIPT_ROWS, // Transcript rows - client // TerminalSessionClient - ); - - try { - final var file = new File(SYSROOT, "etc/apt/sources.list"); - final var out = new FileOutputStream(file); - out.write(SOURCES_LIST_CONTENT); - out.flush(); - out.close(); - } catch (Throwable th) { - LOG.error("Unable to update sources.list", th); - } - - return session; - } - - @NonNull - private String getShellPath() { - final var useSystemShell = - getApp().getPrefManager() - .getBoolean(PreferenceManager.KEY_TERMINAL_USE_SYSTEM_SHELL); - if (!useSystemShell && LOGIN_SHELL.exists() && LOGIN_SHELL.isFile()) { - return LOGIN_SHELL.getAbsolutePath(); - } - - if (!useSystemShell) { - LOG.error( - "Default shell does not exist. Falling back to '/system/bin/sh'.", - "This should not happen in normal circumstances."); - } - - return "/system/bin/sh"; - } - - private static final class KeyListener implements VirtualKeysView.IVirtualKeysView { - - private final TerminalView terminal; - - public KeyListener(TerminalView terminal) { - this.terminal = terminal; - } - - @Override - public void onVirtualKeyButtonClick(View view, VirtualKeyButton buttonInfo, Button button) { - if (terminal == null) { - return; - } - if (buttonInfo.isMacro()) { - String[] keys = buttonInfo.getKey().split(" "); - boolean ctrlDown = false; - boolean altDown = false; - boolean shiftDown = false; - boolean fnDown = false; - for (String key : keys) { - if (SpecialButton.CTRL.getKey().equals(key)) { - ctrlDown = true; - } else if (SpecialButton.ALT.getKey().equals(key)) { - altDown = true; - } else if (SpecialButton.SHIFT.getKey().equals(key)) { - shiftDown = true; - } else if (SpecialButton.FN.getKey().equals(key)) { - fnDown = true; - } else { - onTerminalExtraKeyButtonClick(key, ctrlDown, altDown, shiftDown, fnDown); - ctrlDown = false; - altDown = false; - shiftDown = false; - fnDown = false; - } - } - } else { - onTerminalExtraKeyButtonClick(buttonInfo.getKey(), false, false, false, false); - } - } - - protected void onTerminalExtraKeyButtonClick( - String key, boolean ctrlDown, boolean altDown, boolean shiftDown, boolean fnDown) { - if (VirtualKeysConstants.PRIMARY_KEY_CODES_FOR_STRINGS.containsKey(key)) { - Integer keyCode = VirtualKeysConstants.PRIMARY_KEY_CODES_FOR_STRINGS.get(key); - if (keyCode == null) { + if (future.isCompletedExceptionally() || throwable != null) { + LOG.error("Future has been completed exceptionally."); + showInstallationError(throwable); return; - } - int metaState = 0; - if (ctrlDown) { - metaState |= KeyEvent.META_CTRL_ON | KeyEvent.META_CTRL_LEFT_ON; - } - if (altDown) { - metaState |= KeyEvent.META_ALT_ON | KeyEvent.META_ALT_LEFT_ON; - } - if (shiftDown) { - metaState |= KeyEvent.META_SHIFT_ON | KeyEvent.META_SHIFT_LEFT_ON; - } - if (fnDown) { - metaState |= KeyEvent.META_FUNCTION_ON; - } + } - KeyEvent keyEvent = new KeyEvent(0, 0, KeyEvent.ACTION_UP, keyCode, 0, metaState); - terminal.onKeyDown(keyCode, keyEvent); - } else { - // not a control char - for (int off = 0; off < key.length(); ) { - int codePoint = key.codePointAt(off); - terminal.inputCodePoint(codePoint, ctrlDown, altDown); - off += Character.charCount(codePoint); - } - } - } + setupTerminalView(); + }); + }); + } + } - @Override - public boolean performVirtualKeyButtonHapticFeedback( - View view, VirtualKeyButton buttonInfo, Button button) { - // No need to handle this - // VirtualKeysView will take care of performing haptic feedback - return false; - } + private void showInstallationError(Throwable throwable) { + + binding.virtualKeyTable.setVisibility(View.GONE); + + final var frag = + CrashReportFragment.newInstance( + getString(R.string.title_installation_failed), + getString(R.string.msg_bootstrap_installation_error), + ThrowableUtils.getFullStackTrace(throwable), + false); + + getSupportFragmentManager() + .beginTransaction() + .replace(binding.root.getId(), frag, "bootstrap_install_error_fragment") + .addToBackStack(null) + .commit(); + } + + private void setupTerminalView() { + terminal = new TerminalView(this, null); + terminal.setTerminalViewClient(client); + terminal.attachSession(createSession(getWorkingDirectory())); + terminal.setKeepScreenOn(true); + terminal.setTextSize(SizeUtils.dp2px(10)); + terminal.setTypeface(TypefaceUtils.jetbrainsMono()); + + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(-1, 0); + params.weight = 1f; + + binding.getRoot().addView(terminal, 0, params); + try { + binding.virtualKeyTable.setVirtualKeysViewClient(getKeyListener()); + binding.virtualKeyTable.reload( + new VirtualKeysInfo( + ConstantsBridge.VIRTUAL_KEYS, "", VirtualKeysConstants.CONTROL_CHARS_ALIASES)); + } catch (JSONException e) { + LOG.error("Unable to parse terminal virtual keys json data", e); + } + } + + @NonNull + private String getWorkingDirectory() { + final Bundle extras = getIntent().getExtras(); + if (extras != null && extras.containsKey(KEY_WORKING_DIRECTORY)) { + String directory = extras.getString(KEY_WORKING_DIRECTORY, null); + if (directory == null || directory.trim().length() <= 0) { + directory = HOME.getAbsolutePath(); + } + return directory; + } + return HOME.getAbsolutePath(); + } + + private KeyListener getKeyListener() { + return listener == null ? listener = new KeyListener(terminal) : listener; + } + + @Override + protected void onResume() { + super.onResume(); + setTerminalCursorBlinkingState(true); + } + + @Override + protected void onStart() { + super.onStart(); + isVisible = true; + } + + @Override + protected void onStop() { + super.onStop(); + isVisible = false; + setTerminalCursorBlinkingState(false); + } + + public boolean isVisible() { + return isVisible; + } + + private void setTerminalCursorBlinkingState(boolean start) { + if (terminal != null && terminal.mEmulator != null) { + terminal.setTerminalCursorBlinkerState(start, true); + } + } + + private TerminalSession createSession(final String workingDirectory) { + final Map envs = getEnvironment(); + final String[] env = new String[envs.size()]; + int i = 0; + for (Map.Entry entry : envs.entrySet()) { + env[i] = entry.getKey() + "=" + entry.getValue(); + i++; } - private final class Client implements TerminalViewClient, TerminalSessionClient { + session = + new TerminalSession( + getShellPath(), // Shell command + workingDirectory, // Working directory + new String[] {}, // Arguments + env, // Environment variables + TerminalEmulator.DEFAULT_TERMINAL_TRANSCRIPT_ROWS, // Transcript rows + client // TerminalSessionClient + ); - @Override - public boolean onKeyUp(int keyCode, KeyEvent e) { - return false; - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent e, TerminalSession session) { - if (keyCode == KeyEvent.KEYCODE_ENTER && !session.isRunning()) { - finish(); - return true; - } - return false; - } - - @Override - public boolean onCodePoint(int codePoint, boolean ctrlDown, TerminalSession session) { - return false; - } - - @Override - public void onTextChanged(TerminalSession changedSession) { - terminal.onScreenUpdated(); - } - - @Override - public void onTitleChanged(TerminalSession changedSession) {} - - @Override - public void onSessionFinished(TerminalSession finishedSession) { - finish(); - } - - @Override - public void onCopyTextToClipboard(TerminalSession session, String text) { - ClipboardUtils.copyText("AndroidIDE Terminal", text); - } - - @Override - public void onPasteTextFromClipboard(TerminalSession session) { - String clip = ClipboardUtils.getText().toString(); - if (clip.trim().length() > 0 && terminal != null && terminal.mEmulator != null) { - terminal.mEmulator.paste(clip); - } - } - - @Override - public void onBell(TerminalSession session) {} - - @Override - public void onColorsChanged(TerminalSession session) {} - - @Override - public void onTerminalCursorStateChange(boolean state) {} - - @Override - public Integer getTerminalCursorStyle() { - return TerminalEmulator.DEFAULT_TERMINAL_CURSOR_STYLE; - } - - @Override - public float onScale(float scale) { - return 1.0f; - } - - @Override - public void onSingleTapUp(MotionEvent e) { - KeyboardUtils.showSoftInput(terminal); - } - - @Override - public boolean shouldBackButtonBeMappedToEscape() { - return false; - } - - @Override - public boolean shouldEnforceCharBasedInput() { - return true; - } - - @Override - public boolean shouldUseCtrlSpaceWorkaround() { - return false; - } - - @Override - public boolean isTerminalViewSelected() { - return true; - } - - @Override - public void copyModeChanged(boolean copyMode) {} - - @Override - public boolean onLongPress(MotionEvent event) { - return false; - } - - @Override - public boolean readControlKey() { - Boolean state = binding.virtualKeyTable.readSpecialButton(SpecialButton.CTRL, true); - return state != null && state; - } - - @Override - public boolean readAltKey() { - Boolean state = binding.virtualKeyTable.readSpecialButton(SpecialButton.ALT, true); - return state != null && state; - } - - @Override - public boolean readFnKey() { - return false; - } - - @Override - public boolean readShiftKey() { - return false; - } - - @Override - public void onEmulatorSet() { - setTerminalCursorBlinkingState(true); - - if (session != null) { - binding.getRoot() - .setBackgroundColor( - session.getEmulator() - .mColors - .mCurrentColors[TextStyle.COLOR_INDEX_BACKGROUND]); - } - } - - @Override - public void logError(String tag, String message) { - // No logs - } - - @Override - public void logWarn(String tag, String message) {} - - @Override - public void logInfo(String tag, String message) {} - - @Override - public void logDebug(String tag, String message) {} - - @Override - public void logVerbose(String tag, String message) {} - - @Override - public void logStackTraceWithMessage(String tag, String message, Exception e) {} - - @Override - public void logStackTrace(String tag, Exception e) {} + try { + final var file = new File(SYSROOT, "etc/apt/sources.list"); + final var out = new FileOutputStream(file); + out.write(SOURCES_LIST_CONTENT); + out.flush(); + out.close(); + } catch (Throwable th) { + LOG.error("Unable to update sources.list", th); } + + return session; + } + + @NonNull + private String getShellPath() { + final var useSystemShell = + getApp().getPrefManager().getBoolean(PreferenceManager.KEY_TERMINAL_USE_SYSTEM_SHELL); + if (!useSystemShell && LOGIN_SHELL.exists() && LOGIN_SHELL.isFile()) { + return LOGIN_SHELL.getAbsolutePath(); + } + + if (!useSystemShell) { + LOG.error( + "Default shell does not exist. Falling back to '/system/bin/sh'.", + "This should not happen in normal circumstances."); + } + + return "/system/bin/sh"; + } + + private static final class KeyListener implements VirtualKeysView.IVirtualKeysView { + + private final TerminalView terminal; + + public KeyListener(TerminalView terminal) { + this.terminal = terminal; + } + + @Override + public void onVirtualKeyButtonClick(View view, VirtualKeyButton buttonInfo, Button button) { + if (terminal == null) { + return; + } + if (buttonInfo.isMacro()) { + String[] keys = buttonInfo.getKey().split(" "); + boolean ctrlDown = false; + boolean altDown = false; + boolean shiftDown = false; + boolean fnDown = false; + for (String key : keys) { + if (SpecialButton.CTRL.getKey().equals(key)) { + ctrlDown = true; + } else if (SpecialButton.ALT.getKey().equals(key)) { + altDown = true; + } else if (SpecialButton.SHIFT.getKey().equals(key)) { + shiftDown = true; + } else if (SpecialButton.FN.getKey().equals(key)) { + fnDown = true; + } else { + onTerminalExtraKeyButtonClick(key, ctrlDown, altDown, shiftDown, fnDown); + ctrlDown = false; + altDown = false; + shiftDown = false; + fnDown = false; + } + } + } else { + onTerminalExtraKeyButtonClick(buttonInfo.getKey(), false, false, false, false); + } + } + + protected void onTerminalExtraKeyButtonClick( + String key, boolean ctrlDown, boolean altDown, boolean shiftDown, boolean fnDown) { + if (VirtualKeysConstants.PRIMARY_KEY_CODES_FOR_STRINGS.containsKey(key)) { + Integer keyCode = VirtualKeysConstants.PRIMARY_KEY_CODES_FOR_STRINGS.get(key); + if (keyCode == null) { + return; + } + int metaState = 0; + if (ctrlDown) { + metaState |= KeyEvent.META_CTRL_ON | KeyEvent.META_CTRL_LEFT_ON; + } + if (altDown) { + metaState |= KeyEvent.META_ALT_ON | KeyEvent.META_ALT_LEFT_ON; + } + if (shiftDown) { + metaState |= KeyEvent.META_SHIFT_ON | KeyEvent.META_SHIFT_LEFT_ON; + } + if (fnDown) { + metaState |= KeyEvent.META_FUNCTION_ON; + } + + KeyEvent keyEvent = new KeyEvent(0, 0, KeyEvent.ACTION_UP, keyCode, 0, metaState); + terminal.onKeyDown(keyCode, keyEvent); + } else { + // not a control char + for (int off = 0; off < key.length(); ) { + int codePoint = key.codePointAt(off); + terminal.inputCodePoint(codePoint, ctrlDown, altDown); + off += Character.charCount(codePoint); + } + } + } + + @Override + public boolean performVirtualKeyButtonHapticFeedback( + View view, VirtualKeyButton buttonInfo, Button button) { + // No need to handle this + // VirtualKeysView will take care of performing haptic feedback + return false; + } + } + + private final class Client implements TerminalViewClient, TerminalSessionClient { + + @Override + public boolean onKeyUp(int keyCode, KeyEvent e) { + return false; + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent e, TerminalSession session) { + if (keyCode == KeyEvent.KEYCODE_ENTER && !session.isRunning()) { + finish(); + return true; + } + return false; + } + + @Override + public boolean onCodePoint(int codePoint, boolean ctrlDown, TerminalSession session) { + return false; + } + + @Override + public void onTextChanged(TerminalSession changedSession) { + terminal.onScreenUpdated(); + } + + @Override + public void onTitleChanged(TerminalSession changedSession) {} + + @Override + public void onSessionFinished(TerminalSession finishedSession) { + finish(); + } + + @Override + public void onCopyTextToClipboard(TerminalSession session, String text) { + ClipboardUtils.copyText("AndroidIDE Terminal", text); + } + + @Override + public void onPasteTextFromClipboard(TerminalSession session) { + String clip = ClipboardUtils.getText().toString(); + if (clip.trim().length() > 0 && terminal != null && terminal.mEmulator != null) { + terminal.mEmulator.paste(clip); + } + } + + @Override + public void onBell(TerminalSession session) {} + + @Override + public void onColorsChanged(TerminalSession session) {} + + @Override + public void onTerminalCursorStateChange(boolean state) {} + + @Override + public Integer getTerminalCursorStyle() { + return TerminalEmulator.DEFAULT_TERMINAL_CURSOR_STYLE; + } + + @Override + public float onScale(float scale) { + return 1.0f; + } + + @Override + public void onSingleTapUp(MotionEvent e) { + KeyboardUtils.showSoftInput(terminal); + } + + @Override + public boolean shouldBackButtonBeMappedToEscape() { + return false; + } + + @Override + public boolean shouldEnforceCharBasedInput() { + return true; + } + + @Override + public boolean shouldUseCtrlSpaceWorkaround() { + return false; + } + + @Override + public boolean isTerminalViewSelected() { + return true; + } + + @Override + public void copyModeChanged(boolean copyMode) {} + + @Override + public boolean onLongPress(MotionEvent event) { + return false; + } + + @Override + public boolean readControlKey() { + Boolean state = binding.virtualKeyTable.readSpecialButton(SpecialButton.CTRL, true); + return state != null && state; + } + + @Override + public boolean readAltKey() { + Boolean state = binding.virtualKeyTable.readSpecialButton(SpecialButton.ALT, true); + return state != null && state; + } + + @Override + public boolean readFnKey() { + return false; + } + + @Override + public boolean readShiftKey() { + return false; + } + + @Override + public void onEmulatorSet() { + setTerminalCursorBlinkingState(true); + + if (session != null) { + binding + .getRoot() + .setBackgroundColor( + session.getEmulator().mColors.mCurrentColors[TextStyle.COLOR_INDEX_BACKGROUND]); + } + } + + @Override + public void logError(String tag, String message) { + // No logs + } + + @Override + public void logWarn(String tag, String message) {} + + @Override + public void logInfo(String tag, String message) {} + + @Override + public void logDebug(String tag, String message) {} + + @Override + public void logVerbose(String tag, String message) {} + + @Override + public void logStackTraceWithMessage(String tag, String message, Exception e) {} + + @Override + public void logStackTrace(String tag, Exception e) {} + } } diff --git a/app/src/main/java/com/itsaky/androidide/adapters/AttrListAdapter.java b/app/src/main/java/com/itsaky/androidide/adapters/AttrListAdapter.java index de3f2c998..222d9f8f4 100644 --- a/app/src/main/java/com/itsaky/androidide/adapters/AttrListAdapter.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/AttrListAdapter.java @@ -34,48 +34,46 @@ import java.util.function.Consumer; */ public class AttrListAdapter extends IconTextAdapter { - private final Consumer clickConsumer; - private final List attrs; + private final Consumer clickConsumer; + private final List attrs; - public AttrListAdapter(Consumer clickConsumer, List attrs) { - this.clickConsumer = clickConsumer; - if (attrs == null) { - attrs = new ArrayList<>(); - } - - this.attrs = attrs; + public AttrListAdapter(Consumer clickConsumer, List attrs) { + this.clickConsumer = clickConsumer; + if (attrs == null) { + attrs = new ArrayList<>(); } - @NonNull - @Override - public Attr getItemAt(int index) { - return attrs.get(index); - } + this.attrs = attrs; + } - @Override - public int getIconResource(int index) { - return R.drawable.ic_xml_attribute; - } + @NonNull + @Override + public Attr getItemAt(int index) { + return attrs.get(index); + } - @NonNull - @Override - public String getItemText(int index) { - return getItemAt(index).name; - } + @Override + public int getIconResource(int index) { + return R.drawable.ic_xml_attribute; + } - @Override - public int getItemCount() { - return attrs.size(); - } + @NonNull + @Override + public String getItemText(int index) { + return getItemAt(index).name; + } - @Override - public void onBindViewHolder(@NonNull VH holder, int position) { - super.onBindViewHolder(holder, position); + @Override + public int getItemCount() { + return attrs.size(); + } - if (clickConsumer != null) { - holder.binding - .getRoot() - .setOnClickListener(v -> clickConsumer.accept(getItemAt(position))); - } + @Override + public void onBindViewHolder(@NonNull VH holder, int position) { + super.onBindViewHolder(holder, position); + + if (clickConsumer != null) { + holder.binding.getRoot().setOnClickListener(v -> clickConsumer.accept(getItemAt(position))); } + } } diff --git a/app/src/main/java/com/itsaky/androidide/adapters/AttrValueCompletionAdapter.java b/app/src/main/java/com/itsaky/androidide/adapters/AttrValueCompletionAdapter.java index 371ae4082..450bbf9e3 100644 --- a/app/src/main/java/com/itsaky/androidide/adapters/AttrValueCompletionAdapter.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/AttrValueCompletionAdapter.java @@ -40,88 +40,87 @@ import java.util.Objects; */ public class AttrValueCompletionAdapter extends BaseAdapter implements Filterable { - private final List items; - private final List filtered; - private final Context context; - private final Filter mFilter = - new Filter() { - @Override - protected FilterResults performFiltering(CharSequence constraint) { - final var result = new FilterResults(); - if (TextUtils.isEmpty(constraint)) { - final var list = new ArrayList<>(items); - result.values = list; - result.count = list.size(); - return result; - } + private final List items; + private final List filtered; + private final Context context; + private final Filter mFilter = + new Filter() { + @Override + protected FilterResults performFiltering(CharSequence constraint) { + final var result = new FilterResults(); + if (TextUtils.isEmpty(constraint)) { + final var list = new ArrayList<>(items); + result.values = list; + result.count = list.size(); + return result; + } - final var match = constraint.toString().trim().toLowerCase(Locale.ROOT); - final var list = new ArrayList(); - for (String item : items) { - if (item.contains(match)) { - list.add(item); - } - } + final var match = constraint.toString().trim().toLowerCase(Locale.ROOT); + final var list = new ArrayList(); + for (String item : items) { + if (item.contains(match)) { + list.add(item); + } + } - result.values = list; - result.count = list.size(); - return result; - } - - @Override - protected void publishResults( - CharSequence constraint, @NonNull FilterResults results) { - //noinspection unchecked - List items = (List) results.values; - Objects.requireNonNull(items); - - filtered.clear(); - filtered.addAll(items); - - if (items.isEmpty()) { - notifyDataSetInvalidated(); - } else { - notifyDataSetChanged(); - } - } - }; - - public AttrValueCompletionAdapter(Context context, List items) { - this.context = context; - this.items = items; - this.filtered = new ArrayList<>(items); - } - - @Override - public int getCount() { - return this.filtered.size(); - } - - @Override - public Object getItem(int position) { - return filtered.get(position); - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - if (convertView == null) { - convertView = - ((LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)) - .inflate(android.R.layout.simple_list_item_1, parent, false); + result.values = list; + result.count = list.size(); + return result; } - ((TextView) convertView).setText(getItem(position).toString()); + @Override + protected void publishResults(CharSequence constraint, @NonNull FilterResults results) { + //noinspection unchecked + List items = (List) results.values; + Objects.requireNonNull(items); - return convertView; + filtered.clear(); + filtered.addAll(items); + + if (items.isEmpty()) { + notifyDataSetInvalidated(); + } else { + notifyDataSetChanged(); + } + } + }; + + public AttrValueCompletionAdapter(Context context, List items) { + this.context = context; + this.items = items; + this.filtered = new ArrayList<>(items); + } + + @Override + public int getCount() { + return this.filtered.size(); + } + + @Override + public Object getItem(int position) { + return filtered.get(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = + ((LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)) + .inflate(android.R.layout.simple_list_item_1, parent, false); } - @Override - public Filter getFilter() { - return mFilter; - } + ((TextView) convertView).setText(getItem(position).toString()); + + return convertView; + } + + @Override + public Filter getFilter() { + return mFilter; + } } diff --git a/app/src/main/java/com/itsaky/androidide/adapters/AttrValueFormatTabAdapter.java b/app/src/main/java/com/itsaky/androidide/adapters/AttrValueFormatTabAdapter.java index 6a074db50..73774932d 100644 --- a/app/src/main/java/com/itsaky/androidide/adapters/AttrValueFormatTabAdapter.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/AttrValueFormatTabAdapter.java @@ -41,84 +41,84 @@ import java.util.Objects; @SuppressWarnings("deprecation") public class AttrValueFormatTabAdapter extends FragmentStatePagerAdapter { - private static final ILogger LOG = ILogger.newInstance("AttrValueEditorTabAdapter"); - private final List mFragments = new ArrayList<>(); - private final List mTitles = new ArrayList<>(); - private final XMLAttribute attribute; + private static final ILogger LOG = ILogger.newInstance("AttrValueEditorTabAdapter"); + private final List mFragments = new ArrayList<>(); + private final List mTitles = new ArrayList<>(); + private final XMLAttribute attribute; - public AttrValueFormatTabAdapter(@NonNull FragmentManager manager, XMLAttribute attribute) { - super(manager); - Objects.requireNonNull(attribute); + public AttrValueFormatTabAdapter(@NonNull FragmentManager manager, XMLAttribute attribute) { + super(manager); + Objects.requireNonNull(attribute); - this.attribute = attribute; + this.attribute = attribute; + } + + public void addFragment(final Class fragment, String title) { + Objects.requireNonNull(fragment); + Objects.requireNonNull(title); + if (TextUtils.isEmpty(title.trim())) { + throw new IllegalArgumentException("Invalid tab title"); } - public void addFragment(final Class fragment, String title) { - Objects.requireNonNull(fragment); - Objects.requireNonNull(title); - if (TextUtils.isEmpty(title.trim())) { - throw new IllegalArgumentException("Invalid tab title"); - } + mTitles.add(capitalize(title)); + mFragments.add(createFragment(fragment, title)); + } - mTitles.add(capitalize(title)); - mFragments.add(createFragment(fragment, title)); + @NonNull + private String capitalize(String title) { + title = title.trim(); + + final var sb = new StringBuilder(); + for (int i = 0; i < title.length(); i++) { + var ch = title.charAt(i); + if (i == 0) { + ch = Character.toUpperCase(ch); + } else { + ch = Character.toLowerCase(ch); + } + + sb.append(ch); } - @NonNull - private String capitalize(String title) { - title = title.trim(); + return sb.toString(); + } - final var sb = new StringBuilder(); - for (int i = 0; i < title.length(); i++) { - var ch = title.charAt(i); - if (i == 0) { - ch = Character.toUpperCase(ch); - } else { - ch = Character.toLowerCase(ch); - } + @NonNull + public Fragment createFragment( + Class clazz, @NonNull final String name) { + try { + final var frag = clazz.newInstance(); + final var args = new Bundle(); + args.putParcelable(BaseValueEditorFragment.KEY_ATTR, attribute); + args.putString(BaseValueEditorFragment.KEY_NAME, name); + frag.setArguments(args); - sb.append(ch); - } - - return sb.toString(); + return frag; + } catch (Throwable th) { + LOG.error("Unable to create value editor fragment", th); + throw new RuntimeException(th); } + } - @NonNull - public Fragment createFragment( - Class clazz, @NonNull final String name) { - try { - final var frag = clazz.newInstance(); - final var args = new Bundle(); - args.putParcelable(BaseValueEditorFragment.KEY_ATTR, attribute); - args.putString(BaseValueEditorFragment.KEY_NAME, name); - frag.setArguments(args); + public void removeAll() { + mFragments.clear(); + mTitles.clear(); + } - return frag; - } catch (Throwable th) { - LOG.error("Unable to create value editor fragment", th); - throw new RuntimeException(th); - } - } + @NonNull + @Override + public Fragment getItem(int position) { + return mFragments.get(position); + } - public void removeAll() { - mFragments.clear(); - mTitles.clear(); - } + @Override + public int getCount() { + return mFragments.size(); + } - @NonNull - @Override - public Fragment getItem(int position) { - return mFragments.get(position); - } - - @Override - public int getCount() { - return mFragments.size(); - } - - @Nullable - @Override - public CharSequence getPageTitle(int position) { - return mTitles.get(position); - } + @Nullable + @Override + public CharSequence getPageTitle(int position) { + return mTitles.get(position); + } } diff --git a/app/src/main/java/com/itsaky/androidide/adapters/CompletionListAdapter.java b/app/src/main/java/com/itsaky/androidide/adapters/CompletionListAdapter.java index a99e7ee90..af2b58696 100755 --- a/app/src/main/java/com/itsaky/androidide/adapters/CompletionListAdapter.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/CompletionListAdapter.java @@ -53,191 +53,178 @@ import io.github.rosemoe.sora.widget.component.EditorCompletionAdapter; public class CompletionListAdapter extends EditorCompletionAdapter { - private static final ILogger LOG = ILogger.newInstance("CompletionListAdapter"); + private static final ILogger LOG = ILogger.newInstance("CompletionListAdapter"); - @Override - public void attachValues( - EditorAutoCompletion window, - List items) { - super.attachValues(window, items); + @Override + public void attachValues( + EditorAutoCompletion window, + List items) { + super.attachValues(window, items); + } + + @Override + public int getItemHeight() { + return (int) + TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, 40, Resources.getSystem().getDisplayMetrics()); + } + + @Override + protected View getView( + int position, View convertView, ViewGroup parent, boolean isCurrentCursorPosition) { + final LayoutCompletionItemBinding binding = + LayoutCompletionItemBinding.inflate(LayoutInflater.from(getContext()), parent, false); + + var item = (CompletionItem) getItem(position); + String label = item.getLabel(), desc = item.getDetail(), type = item.getKind().toString(); + String header = type.length() <= 0 ? "O" : String.valueOf(type.charAt(0)); + + binding.completionIconText.setText(header); + binding.completionLabel.setText(label); + binding.completionType.setText(type); + binding.completionDetail.setText(desc); + binding.completionIconText.setTypeface(TypefaceUtils.jetbrainsMono(), Typeface.BOLD); + if (desc.isEmpty()) { + binding.completionDetail.setVisibility(View.GONE); } - @Override - public int getItemHeight() { - return (int) - TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, 40, Resources.getSystem().getDisplayMetrics()); + if (isCurrentCursorPosition) { + binding + .getRoot() + .setBackgroundColor( + ContextCompat.getColor(getContext(), R.color.completionList_backgroundSelected)); + } else { + binding + .getRoot() + .setBackgroundColor( + ContextCompat.getColor(getContext(), R.color.completionList_background)); } - @Override - protected View getView( - int position, View convertView, ViewGroup parent, boolean isCurrentCursorPosition) { - final LayoutCompletionItemBinding binding = - LayoutCompletionItemBinding.inflate( - LayoutInflater.from(getContext()), parent, false); + binding.completionApiInfo.setVisibility(View.GONE); - var item = (CompletionItem) getItem(position); - String label = item.getLabel(), desc = item.getDetail(), type = item.getKind().toString(); - String header = type.length() <= 0 ? "O" : String.valueOf(type.charAt(0)); + showApiInfoIfNeeded(item, binding.completionApiInfo); - binding.completionIconText.setText(header); - binding.completionLabel.setText(label); - binding.completionType.setText(type); - binding.completionDetail.setText(desc); - binding.completionIconText.setTypeface(TypefaceUtils.jetbrainsMono(), Typeface.BOLD); - if (desc.isEmpty()) { - binding.completionDetail.setVisibility(View.GONE); - } + return binding.getRoot(); + } - if (isCurrentCursorPosition) { - binding.getRoot() - .setBackgroundColor( - ContextCompat.getColor( - getContext(), R.color.completionList_backgroundSelected)); - } else { - binding.getRoot() - .setBackgroundColor( - ContextCompat.getColor( - getContext(), R.color.completionList_background)); - } + private void showApiInfoIfNeeded(final CompletionItem item, final TextView completionApiInfo) { - binding.completionApiInfo.setVisibility(View.GONE); + new Thread( + () -> { + final ApiInfo apiInfo = StudioApp.getInstance().apiInfo(); + boolean hasRead = apiInfo != null && apiInfo.hasRead(); + boolean isValid = isValidForApiVersion(item); - showApiInfoIfNeeded(item, binding.completionApiInfo); + if (hasRead && isValid) { - return binding.getRoot(); + JsonElement element = new Gson().toJsonTree(item.getData()); + if (element == null || !element.isJsonObject()) { + return; + } + JsonObject data = element.getAsJsonObject(); + if (!data.has("className")) { + return; + } + + final String className = data.get("className").getAsString(); + CompletionItemKind kind = item.getKind(); + + ClassInfo clazz = apiInfo.getClassByName(className); + if (clazz == null) { + return; + } + + Info info = clazz; + + // If this Info is not a class info, find the right member + if (kind == CompletionItemKind.METHOD + && data.has("erasedParameterTypes") + && data.has("memberName")) { + JsonElement erasedParameterTypesElement = data.get("erasedParameterTypes"); + if (erasedParameterTypesElement.isJsonArray()) { + String simpleName = data.get("memberName").getAsString(); + JsonArray erasedParameterTypes = erasedParameterTypesElement.getAsJsonArray(); + String[] paramTypes = new String[erasedParameterTypes.size()]; + for (int i = 0; i < erasedParameterTypes.size(); i++) { + paramTypes[i] = erasedParameterTypes.get(i).getAsString(); + } + + MethodInfo method = clazz.getMethod(simpleName, paramTypes); + + if (method != null) { + info = method; + } + } + } else if (kind == CompletionItemKind.FIELD && data.has("memberName")) { + String simpleName = data.get("memberName").getAsString(); + FieldInfo field = clazz.getFieldByName(simpleName); + + if (field != null) { + info = field; + } + } + + final StringBuilder infoBuilder = new StringBuilder(); + if (info.since > 1) { + infoBuilder.append( + completionApiInfo + .getContext() + .getString(R.string.msg_api_info_since, info.since)); + infoBuilder.append("\n"); + } + + if (info.removed > 0) { + infoBuilder.append( + completionApiInfo + .getContext() + .getString(R.string.msg_api_info_removed, info.removed)); + infoBuilder.append("\n"); + } + + if (info.deprecated > 0) { + infoBuilder.append( + completionApiInfo + .getContext() + .getString(R.string.msg_api_info_deprecated, info.deprecated)); + infoBuilder.append("\n"); + } + + ThreadUtils.runOnUiThread( + () -> { + if (infoBuilder.toString().trim().length() > 0) { + completionApiInfo.setText(infoBuilder.toString().trim()); + completionApiInfo.setVisibility(View.VISIBLE); + } else { + completionApiInfo.setVisibility(View.GONE); + } + }); + } + }) + .start(); + } + + private boolean isValidForApiVersion(CompletionItem item) { + if (item == null) { + return false; } + final CompletionItemKind type = item.getKind(); + final var data = item.getData(); + if ( - private void showApiInfoIfNeeded(final CompletionItem item, final TextView completionApiInfo) { + // These represent a class type + (type == CompletionItemKind.CLASS + || type == CompletionItemKind.INTERFACE + || type == CompletionItemKind.ENUM - new Thread( - () -> { - final ApiInfo apiInfo = StudioApp.getInstance().apiInfo(); - boolean hasRead = apiInfo != null && apiInfo.hasRead(); - boolean isValid = isValidForApiVersion(item); + // These represent a method type + || type == CompletionItemKind.METHOD + || type == CompletionItemKind.CONSTRUCTOR - if (hasRead && isValid) { - - JsonElement element = new Gson().toJsonTree(item.getData()); - if (element == null || !element.isJsonObject()) { - return; - } - JsonObject data = element.getAsJsonObject(); - if (!data.has("className")) { - return; - } - - final String className = data.get("className").getAsString(); - CompletionItemKind kind = item.getKind(); - - ClassInfo clazz = apiInfo.getClassByName(className); - if (clazz == null) { - return; - } - - Info info = clazz; - - // If this Info is not a class info, find the right member - if (kind == CompletionItemKind.METHOD - && data.has("erasedParameterTypes") - && data.has("memberName")) { - JsonElement erasedParameterTypesElement = - data.get("erasedParameterTypes"); - if (erasedParameterTypesElement.isJsonArray()) { - String simpleName = data.get("memberName").getAsString(); - JsonArray erasedParameterTypes = - erasedParameterTypesElement.getAsJsonArray(); - String[] paramTypes = - new String[erasedParameterTypes.size()]; - for (int i = 0; i < erasedParameterTypes.size(); i++) { - paramTypes[i] = - erasedParameterTypes.get(i).getAsString(); - } - - MethodInfo method = clazz.getMethod(simpleName, paramTypes); - - if (method != null) { - info = method; - } - } - } else if (kind == CompletionItemKind.FIELD - && data.has("memberName")) { - String simpleName = data.get("memberName").getAsString(); - FieldInfo field = clazz.getFieldByName(simpleName); - - if (field != null) { - info = field; - } - } - - final StringBuilder infoBuilder = new StringBuilder(); - if (info.since > 1) { - infoBuilder.append( - completionApiInfo - .getContext() - .getString( - R.string.msg_api_info_since, - info.since)); - infoBuilder.append("\n"); - } - - if (info.removed > 0) { - infoBuilder.append( - completionApiInfo - .getContext() - .getString( - R.string.msg_api_info_removed, - info.removed)); - infoBuilder.append("\n"); - } - - if (info.deprecated > 0) { - infoBuilder.append( - completionApiInfo - .getContext() - .getString( - R.string.msg_api_info_deprecated, - info.deprecated)); - infoBuilder.append("\n"); - } - - ThreadUtils.runOnUiThread( - () -> { - if (infoBuilder.toString().trim().length() > 0) { - completionApiInfo.setText( - infoBuilder.toString().trim()); - completionApiInfo.setVisibility(View.VISIBLE); - } else { - completionApiInfo.setVisibility(View.GONE); - } - }); - } - }) - .start(); - } - - private boolean isValidForApiVersion(CompletionItem item) { - if (item == null) { - return false; - } - final CompletionItemKind type = item.getKind(); - final var data = item.getData(); - if ( - - // These represent a class type - (type == CompletionItemKind.CLASS - || type == CompletionItemKind.INTERFACE - || type == CompletionItemKind.ENUM - - // These represent a method type - || type == CompletionItemKind.METHOD - || type == CompletionItemKind.CONSTRUCTOR - - // A field type - || type == CompletionItemKind.FIELD) - && data != null) { - return !TextUtils.isEmpty(data.getClassName()); - } - return false; + // A field type + || type == CompletionItemKind.FIELD) + && data != null) { + return !TextUtils.isEmpty(data.getClassName()); } + return false; + } } diff --git a/app/src/main/java/com/itsaky/androidide/adapters/DiagnosticItemAdapter.java b/app/src/main/java/com/itsaky/androidide/adapters/DiagnosticItemAdapter.java index d38bc038f..029dc4872 100644 --- a/app/src/main/java/com/itsaky/androidide/adapters/DiagnosticItemAdapter.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/DiagnosticItemAdapter.java @@ -36,66 +36,66 @@ import java.util.List; public class DiagnosticItemAdapter extends RecyclerView.Adapter { - private final List diags; - private final File file; - private final DiagnosticClickListener listener; + private final List diags; + private final File file; + private final DiagnosticClickListener listener; - public DiagnosticItemAdapter( - List diags, File file, DiagnosticClickListener listener) { - this.diags = diags; - this.file = file; - this.listener = listener; - } - - @NonNull - @Override - public DiagnosticItemAdapter.VH onCreateViewHolder(@NonNull ViewGroup p1, int p2) { - return new VH( - LayoutDiagnosticItemBinding.inflate( - LayoutInflater.from(p1.getContext()), p1, false)); - } - - @Override - public void onBindViewHolder(DiagnosticItemAdapter.VH p1, int p2) { - final DiagnosticItem diagnostic = diags.get(p2); - final LayoutDiagnosticItemBinding binding = p1.binding; - - binding.icon.setImageResource(getDiagnosticIconId(diagnostic)); - binding.icon.setColorFilter( - ContextCompat.getColor( - binding.icon.getContext(), - diagnostic.getSeverity() == DiagnosticSeverity.ERROR - ? R.color.diagnostic_error - : R.color.diagnostic_warning), - PorterDuff.Mode.SRC_ATOP); - binding.title.setText(diagnostic.getMessage()); - - binding.getRoot() - .setOnClickListener( - v -> { - if (listener != null) { - listener.onDiagnosticClick(file, diagnostic); - } - }); - } - - @Override - public int getItemCount() { - return diags.size(); - } - - private int getDiagnosticIconId(DiagnosticItem diagnostic) { - if (diagnostic.getSeverity() == DiagnosticSeverity.ERROR) - return R.drawable.ic_compilation_error; - return R.drawable.ic_info; - } - - public static class VH extends RecyclerView.ViewHolder { - private LayoutDiagnosticItemBinding binding; - - public VH(LayoutDiagnosticItemBinding binding) { - super(binding.getRoot()); - this.binding = binding; - } + public DiagnosticItemAdapter( + List diags, File file, DiagnosticClickListener listener) { + this.diags = diags; + this.file = file; + this.listener = listener; + } + + @NonNull + @Override + public DiagnosticItemAdapter.VH onCreateViewHolder(@NonNull ViewGroup p1, int p2) { + return new VH( + LayoutDiagnosticItemBinding.inflate(LayoutInflater.from(p1.getContext()), p1, false)); + } + + @Override + public void onBindViewHolder(DiagnosticItemAdapter.VH p1, int p2) { + final DiagnosticItem diagnostic = diags.get(p2); + final LayoutDiagnosticItemBinding binding = p1.binding; + + binding.icon.setImageResource(getDiagnosticIconId(diagnostic)); + binding.icon.setColorFilter( + ContextCompat.getColor( + binding.icon.getContext(), + diagnostic.getSeverity() == DiagnosticSeverity.ERROR + ? R.color.diagnostic_error + : R.color.diagnostic_warning), + PorterDuff.Mode.SRC_ATOP); + binding.title.setText(diagnostic.getMessage()); + + binding + .getRoot() + .setOnClickListener( + v -> { + if (listener != null) { + listener.onDiagnosticClick(file, diagnostic); + } + }); + } + + @Override + public int getItemCount() { + return diags.size(); + } + + private int getDiagnosticIconId(DiagnosticItem diagnostic) { + if (diagnostic.getSeverity() == DiagnosticSeverity.ERROR) + return R.drawable.ic_compilation_error; + return R.drawable.ic_info; + } + + public static class VH extends RecyclerView.ViewHolder { + private LayoutDiagnosticItemBinding binding; + + public VH(LayoutDiagnosticItemBinding binding) { + super(binding.getRoot()); + this.binding = binding; } + } } diff --git a/app/src/main/java/com/itsaky/androidide/adapters/DiagnosticsAdapter.java b/app/src/main/java/com/itsaky/androidide/adapters/DiagnosticsAdapter.java index 81a605593..a6f84d7ff 100755 --- a/app/src/main/java/com/itsaky/androidide/adapters/DiagnosticsAdapter.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/DiagnosticsAdapter.java @@ -35,58 +35,57 @@ import java.util.List; public class DiagnosticsAdapter extends RecyclerView.Adapter { - private final List diags; - private final DiagnosticClickListener listener; + private final List diags; + private final DiagnosticClickListener listener; - public DiagnosticsAdapter(List diags, DiagnosticClickListener listener) { - this.diags = diags; - this.listener = listener; - } - - @Override - public DiagnosticsAdapter.VH onCreateViewHolder(ViewGroup p1, int p2) { - return new VH( - LayoutDiagnosticGroupBinding.inflate( - LayoutInflater.from(p1.getContext()), p1, false)); - } - - @Override - public void onBindViewHolder(DiagnosticsAdapter.VH p1, int p2) { - final DiagnosticGroup group = diags.get(p2); - final LayoutDiagnosticGroupBinding binding = p1.binding; - - final int color = - ContextCompat.getColor(binding.info.icon.getContext(), R.color.secondaryColor); - binding.info.icon.setImageResource(group.icon); - binding.info.icon.setColorFilter(color, PorterDuff.Mode.SRC_ATOP); - binding.info.title.setText(group.text); - binding.info.title.setTextColor(color); - binding.diagnostics.setLayoutManager( - new LinearLayoutManager(binding.diagnostics.getContext())); - binding.diagnostics.setAdapter( - new DiagnosticItemAdapter(group.diagnostics, group.file, listener)); - - binding.info - .getRoot() - .setOnClickListener( - v -> { - if (listener != null) { - listener.onGroupClick(group); - } - }); - } - - @Override - public int getItemCount() { - return diags.size(); - } - - public class VH extends RecyclerView.ViewHolder { - private LayoutDiagnosticGroupBinding binding; - - public VH(LayoutDiagnosticGroupBinding binding) { - super(binding.getRoot()); - this.binding = binding; - } + public DiagnosticsAdapter(List diags, DiagnosticClickListener listener) { + this.diags = diags; + this.listener = listener; + } + + @Override + public DiagnosticsAdapter.VH onCreateViewHolder(ViewGroup p1, int p2) { + return new VH( + LayoutDiagnosticGroupBinding.inflate(LayoutInflater.from(p1.getContext()), p1, false)); + } + + @Override + public void onBindViewHolder(DiagnosticsAdapter.VH p1, int p2) { + final DiagnosticGroup group = diags.get(p2); + final LayoutDiagnosticGroupBinding binding = p1.binding; + + final int color = + ContextCompat.getColor(binding.info.icon.getContext(), R.color.secondaryColor); + binding.info.icon.setImageResource(group.icon); + binding.info.icon.setColorFilter(color, PorterDuff.Mode.SRC_ATOP); + binding.info.title.setText(group.text); + binding.info.title.setTextColor(color); + binding.diagnostics.setLayoutManager(new LinearLayoutManager(binding.diagnostics.getContext())); + binding.diagnostics.setAdapter( + new DiagnosticItemAdapter(group.diagnostics, group.file, listener)); + + binding + .info + .getRoot() + .setOnClickListener( + v -> { + if (listener != null) { + listener.onGroupClick(group); + } + }); + } + + @Override + public int getItemCount() { + return diags.size(); + } + + public class VH extends RecyclerView.ViewHolder { + private LayoutDiagnosticGroupBinding binding; + + public VH(LayoutDiagnosticGroupBinding binding) { + super(binding.getRoot()); + this.binding = binding; } + } } diff --git a/app/src/main/java/com/itsaky/androidide/adapters/EditorBottomSheetTabAdapter.java b/app/src/main/java/com/itsaky/androidide/adapters/EditorBottomSheetTabAdapter.java index be1fa25d8..dcb73e37d 100644 --- a/app/src/main/java/com/itsaky/androidide/adapters/EditorBottomSheetTabAdapter.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/EditorBottomSheetTabAdapter.java @@ -37,145 +37,139 @@ import java.util.List; public class EditorBottomSheetTabAdapter extends FragmentStateAdapter { - private static final ILogger LOG = ILogger.newInstance("EditorBottomSheetTabAdapter"); - private final List fragments; + private static final ILogger LOG = ILogger.newInstance("EditorBottomSheetTabAdapter"); + private final List fragments; - public EditorBottomSheetTabAdapter(@NonNull FragmentActivity fragmentActivity) { - super(fragmentActivity); + public EditorBottomSheetTabAdapter(@NonNull FragmentActivity fragmentActivity) { + super(fragmentActivity); - var index = -1; - this.fragments = new ArrayList<>(); - this.fragments.add( - new Tab( - fragmentActivity.getString(R.string.build_output), - SimpleOutputFragment.class, - ++index)); - this.fragments.add( - new Tab( - fragmentActivity.getString(R.string.app_logs), - LogViewFragment.class, - ++index)); - this.fragments.add( - new Tab( - fragmentActivity.getString(R.string.ide_logs), - IDELogFragment.class, - ++index)); - this.fragments.add( - new Tab( - fragmentActivity.getString(R.string.view_diags), - DiagnosticsListFragment.class, - ++index)); - this.fragments.add( - new Tab( - fragmentActivity.getString(R.string.view_search_results), - SearchResultFragment.class, - ++index)); + var index = -1; + this.fragments = new ArrayList<>(); + this.fragments.add( + new Tab( + fragmentActivity.getString(R.string.build_output), + SimpleOutputFragment.class, + ++index)); + this.fragments.add( + new Tab(fragmentActivity.getString(R.string.app_logs), LogViewFragment.class, ++index)); + this.fragments.add( + new Tab(fragmentActivity.getString(R.string.ide_logs), IDELogFragment.class, ++index)); + this.fragments.add( + new Tab( + fragmentActivity.getString(R.string.view_diags), + DiagnosticsListFragment.class, + ++index)); + this.fragments.add( + new Tab( + fragmentActivity.getString(R.string.view_search_results), + SearchResultFragment.class, + ++index)); + } + + public Fragment getFragmentAtIndex(int index) { + return getFragmentById(getItemId(index)); + } + + @NonNull + @Override + public Fragment createFragment(int position) { + try { + final var tab = fragments.get(position); + final var klass = Class.forName(tab.name).asSubclass(Fragment.class); + final var constructor = klass.getDeclaredConstructor(); + constructor.setAccessible(true); + return constructor.newInstance(); + } catch (Throwable th) { + throw new RuntimeException("Unable to create fragment", th); + } + } + + @Override + public int getItemCount() { + return fragments.size(); + } + + public String getTitle(int position) { + return fragments.get(position).title; + } + + @Nullable + public SimpleOutputFragment getBuildOutputFragment() { + return findFragmentByClass(SimpleOutputFragment.class); + } + + @Nullable + public LogViewFragment getLogFragment() { + return findFragmentByClass(LogViewFragment.class); + } + + @Nullable + public DiagnosticsListFragment getDiagnosticsFragment() { + return findFragmentByClass(DiagnosticsListFragment.class); + } + + @Nullable + public SearchResultFragment getSearchResultFragment() { + return findFragmentByClass(SearchResultFragment.class); + } + + @Nullable + private T findFragmentByClass(Class clazz) { + final var name = clazz.getName(); + for (final var tab : this.fragments) { + if (tab.name.equals(name)) { + return (T) getFragmentById(tab.itemId); + } } - public Fragment getFragmentAtIndex(int index) { - return getFragmentById(getItemId(index)); + return null; + } + + @Nullable + public Fragment getFragmentById(long itemId) { + final var fragments = getFragments(); + if (fragments != null) { + return fragments.get(itemId); } - @NonNull - @Override - public Fragment createFragment(int position) { - try { - final var tab = fragments.get(position); - final var klass = Class.forName(tab.name).asSubclass(Fragment.class); - final var constructor = klass.getDeclaredConstructor(); - constructor.setAccessible(true); - return constructor.newInstance(); - } catch (Throwable th) { - throw new RuntimeException("Unable to create fragment", th); - } + return null; + } + + @Nullable + private LongSparseArray getFragments() { + try { + final var field = FragmentStateAdapter.class.getDeclaredField("mFragments"); + field.setAccessible(true); + return (LongSparseArray) field.get(this); + } catch (Throwable th) { + LOG.error("Unable to reflect fragment list from adapter."); } - @Override - public int getItemCount() { - return fragments.size(); + return null; + } + + public int findIndexOfFragmentByClass(@NonNull Class tClass) { + final var name = tClass.getName(); + for (int i = 0; i < this.fragments.size(); i++) { + final var tab = this.fragments.get(i); + if (tab.name.equals(name)) { + return i; + } } - public String getTitle(int position) { - return fragments.get(position).title; - } - - @Nullable - public SimpleOutputFragment getBuildOutputFragment() { - return findFragmentByClass(SimpleOutputFragment.class); - } - - @Nullable - public LogViewFragment getLogFragment() { - return findFragmentByClass(LogViewFragment.class); - } - - @Nullable - public DiagnosticsListFragment getDiagnosticsFragment() { - return findFragmentByClass(DiagnosticsListFragment.class); - } - - @Nullable - public SearchResultFragment getSearchResultFragment() { - return findFragmentByClass(SearchResultFragment.class); - } - - @Nullable - private T findFragmentByClass(Class clazz) { - final var name = clazz.getName(); - for (final var tab : this.fragments) { - if (tab.name.equals(name)) { - return (T) getFragmentById(tab.itemId); - } - } - - return null; - } - - @Nullable - public Fragment getFragmentById(long itemId) { - final var fragments = getFragments(); - if (fragments != null) { - return fragments.get(itemId); - } - - return null; - } - - @Nullable - private LongSparseArray getFragments() { - try { - final var field = FragmentStateAdapter.class.getDeclaredField("mFragments"); - field.setAccessible(true); - return (LongSparseArray) field.get(this); - } catch (Throwable th) { - LOG.error("Unable to reflect fragment list from adapter."); - } - - return null; - } - - public int findIndexOfFragmentByClass(@NonNull Class tClass) { - final var name = tClass.getName(); - for (int i = 0; i < this.fragments.size(); i++) { - final var tab = this.fragments.get(i); - if (tab.name.equals(name)) { - return i; - } - } - - return -1; - } - - static class Tab { - - final String title; - final String name; - final long itemId; - - public Tab(String title, @NonNull Class fragment, long id) { - this.title = title; - this.name = fragment.getName(); - this.itemId = id; - } + return -1; + } + + static class Tab { + + final String title; + final String name; + final long itemId; + + public Tab(String title, @NonNull Class fragment, long id) { + this.title = title; + this.name = fragment.getName(); + this.itemId = id; } + } } diff --git a/app/src/main/java/com/itsaky/androidide/adapters/IconTextAdapter.java b/app/src/main/java/com/itsaky/androidide/adapters/IconTextAdapter.java index 0b1b92bde..8fc7b4bda 100644 --- a/app/src/main/java/com/itsaky/androidide/adapters/IconTextAdapter.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/IconTextAdapter.java @@ -33,86 +33,86 @@ import com.itsaky.androidide.databinding.LayoutSimpleIconTextBinding; */ public abstract class IconTextAdapter extends RecyclerView.Adapter { - private OnBindListener bindListener; + private OnBindListener bindListener; - public IconTextAdapter setOnBindListener(OnBindListener listener) { - this.bindListener = listener; - return this; + public IconTextAdapter setOnBindListener(OnBindListener listener) { + this.bindListener = listener; + return this; + } + + @NonNull + @Override + public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new VH( + LayoutSimpleIconTextBinding.inflate( + LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull VH holder, int position) { + final var binding = holder.binding; + final var item = getItemAt(position); + + if (this.bindListener != null && this.bindListener.onBind(item, holder, position)) { + return; } - @NonNull - @Override - public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - return new VH( - LayoutSimpleIconTextBinding.inflate( - LayoutInflater.from(parent.getContext()), parent, false)); + final var icon = getIconResource(position); + + if (icon == -1) { + binding.icon.setVisibility(View.GONE); + } else { + binding.icon.setImageResource(icon); } - @Override - public void onBindViewHolder(@NonNull VH holder, int position) { - final var binding = holder.binding; - final var item = getItemAt(position); + binding.text.setText(getItemText(position)); - if (this.bindListener != null && this.bindListener.onBind(item, holder, position)) { - return; - } + if (this.bindListener != null) { + this.bindListener.postBind(item, holder, position); + } + } - final var icon = getIconResource(position); + /** + * Get the list item at the given position. + * + * @param index The index of the item to retrieve. + * @return The item at the given index. Must not be null. + */ + @NonNull + public abstract E getItemAt(int index); - if (icon == -1) { - binding.icon.setVisibility(View.GONE); - } else { - binding.icon.setImageResource(icon); - } + /** + * Get the icon resource ID of the item the given index. + * + * @param index The index of the item. + * @return The icon resource id or -1 to hide the icon. + */ + public abstract int getIconResource(int index); - binding.text.setText(getItemText(position)); + /** + * Get the title of the item at the given index. + * + * @param index The index of the item. + * @return The title of the item. + */ + @NonNull + public abstract String getItemText(int index); - if (this.bindListener != null) { - this.bindListener.postBind(item, holder, position); - } + public interface OnBindListener { + default boolean onBind(T item, VH holder, int position) { + return false; } - /** - * Get the list item at the given position. - * - * @param index The index of the item to retrieve. - * @return The item at the given index. Must not be null. - */ - @NonNull - public abstract E getItemAt(int index); + default void postBind(T item, VH holder, int position) {} + } - /** - * Get the icon resource ID of the item the given index. - * - * @param index The index of the item. - * @return The icon resource id or -1 to hide the icon. - */ - public abstract int getIconResource(int index); + public static class VH extends RecyclerView.ViewHolder { - /** - * Get the title of the item at the given index. - * - * @param index The index of the item. - * @return The title of the item. - */ - @NonNull - public abstract String getItemText(int index); + public final LayoutSimpleIconTextBinding binding; - public interface OnBindListener { - default boolean onBind(T item, VH holder, int position) { - return false; - } - - default void postBind(T item, VH holder, int position) {} - } - - public static class VH extends RecyclerView.ViewHolder { - - public final LayoutSimpleIconTextBinding binding; - - public VH(@NonNull LayoutSimpleIconTextBinding binding) { - super(binding.getRoot()); - this.binding = binding; - } + public VH(@NonNull LayoutSimpleIconTextBinding binding) { + super(binding.getRoot()); + this.binding = binding; } + } } diff --git a/app/src/main/java/com/itsaky/androidide/adapters/OptionsSheetAdapter.java b/app/src/main/java/com/itsaky/androidide/adapters/OptionsSheetAdapter.java index a3425a86d..0675131c2 100755 --- a/app/src/main/java/com/itsaky/androidide/adapters/OptionsSheetAdapter.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/OptionsSheetAdapter.java @@ -32,51 +32,51 @@ import java.util.List; public class OptionsSheetAdapter extends RecyclerView.Adapter { - private OnOptionsClickListener listener; - private List options; + private OnOptionsClickListener listener; + private List options; - public OptionsSheetAdapter(List options, OnOptionsClickListener listener) { - this.options = options; - this.listener = listener; + public OptionsSheetAdapter(List options, OnOptionsClickListener listener) { + this.options = options; + this.listener = listener; + } + + @Override + public OptionsSheetAdapter.VH onCreateViewHolder(ViewGroup p1, int p2) { + return new VH( + LayoutOptionssheetItemBinding.inflate(LayoutInflater.from(p1.getContext()), p1, false)); + } + + @Override + public void onBindViewHolder(OptionsSheetAdapter.VH p1, int p2) { + final LayoutOptionssheetItemBinding binding = p1.binding; + final SheetOption option = options.get(p2); + + binding.text.setText(option.titleRes); + binding.icon.setImageResource(option.iconRes); + + binding + .getRoot() + .setOnClickListener( + v -> { + if (listener != null) listener.onOptionClick(option); + }); + } + + @Override + public int getItemCount() { + return options.size(); + } + + public class VH extends RecyclerView.ViewHolder { + private LayoutOptionssheetItemBinding binding; + + public VH(LayoutOptionssheetItemBinding binding) { + super(binding.getRoot()); + this.binding = binding; } + } - @Override - public OptionsSheetAdapter.VH onCreateViewHolder(ViewGroup p1, int p2) { - return new VH( - LayoutOptionssheetItemBinding.inflate( - LayoutInflater.from(p1.getContext()), p1, false)); - } - - @Override - public void onBindViewHolder(OptionsSheetAdapter.VH p1, int p2) { - final LayoutOptionssheetItemBinding binding = p1.binding; - final SheetOption option = options.get(p2); - - binding.text.setText(option.titleRes); - binding.icon.setImageResource(option.iconRes); - - binding.getRoot() - .setOnClickListener( - v -> { - if (listener != null) listener.onOptionClick(option); - }); - } - - @Override - public int getItemCount() { - return options.size(); - } - - public class VH extends RecyclerView.ViewHolder { - private LayoutOptionssheetItemBinding binding; - - public VH(LayoutOptionssheetItemBinding binding) { - super(binding.getRoot()); - this.binding = binding; - } - } - - public static interface OnOptionsClickListener { - public void onOptionClick(SheetOption option); - } + public static interface OnOptionsClickListener { + public void onOptionClick(SheetOption option); + } } diff --git a/app/src/main/java/com/itsaky/androidide/adapters/SearchListAdapter.java b/app/src/main/java/com/itsaky/androidide/adapters/SearchListAdapter.java index fd3868eed..aef80ac9a 100755 --- a/app/src/main/java/com/itsaky/androidide/adapters/SearchListAdapter.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/SearchListAdapter.java @@ -43,124 +43,124 @@ import java.util.Map; public class SearchListAdapter extends RecyclerView.Adapter { - private final Map> results; - private final List keys; + private final Map> results; + private final List keys; - private final OnFileClickListener fileListener; - private final OnMatchClickListener matchListener; + private final OnFileClickListener fileListener; + private final OnMatchClickListener matchListener; - public SearchListAdapter( - Map> results, - OnFileClickListener fileListener, - OnMatchClickListener matchListener) { - if (results == null) results = new HashMap<>(); - this.results = results; - this.keys = new ArrayList(results.keySet()); - this.fileListener = fileListener; - this.matchListener = matchListener; + public SearchListAdapter( + Map> results, + OnFileClickListener fileListener, + OnMatchClickListener matchListener) { + if (results == null) results = new HashMap<>(); + this.results = results; + this.keys = new ArrayList(results.keySet()); + this.fileListener = fileListener; + this.matchListener = matchListener; + } + + @Override + public SearchListAdapter.VH onCreateViewHolder(ViewGroup p1, int p2) { + return new VH(LayoutSearchResultGroupBinding.inflate(LayoutInflater.from(p1.getContext()))); + } + + @Override + public void onBindViewHolder(SearchListAdapter.VH p1, int p2) { + final LayoutSearchResultGroupBinding binding = p1.binding; + final File file = keys.get(p2); + final List matches = results.get(file); + final int color = + ContextCompat.getColor( + binding.icon.getContext(), com.itsaky.androidide.R.color.secondaryColor); + binding.title.setText(file.getName()); + binding.icon.setImageResource(com.itsaky.androidide.R.drawable.ic_language_java); + binding.icon.setColorFilter(color, PorterDuff.Mode.SRC_ATOP); + binding.items.setLayoutManager(new LinearLayoutManager(binding.items.getContext())); + binding.items.setAdapter(new ChildAdapter(matches)); + + binding + .getRoot() + .setOnClickListener( + v -> { + if (fileListener != null) fileListener.onClick(file); + }); + } + + @Override + public int getItemCount() { + return results.size(); + } + + public class ChildAdapter extends RecyclerView.Adapter { + + private final List matches; + + public ChildAdapter(List matches) { + if (matches == null) matches = new ArrayList<>(); + this.matches = matches; } @Override - public SearchListAdapter.VH onCreateViewHolder(ViewGroup p1, int p2) { - return new VH(LayoutSearchResultGroupBinding.inflate(LayoutInflater.from(p1.getContext()))); + public SearchListAdapter.ChildVH onCreateViewHolder(ViewGroup p1, int p2) { + return new ChildVH( + LayoutSearchResultItemBinding.inflate(LayoutInflater.from(p1.getContext()))); } @Override - public void onBindViewHolder(SearchListAdapter.VH p1, int p2) { - final LayoutSearchResultGroupBinding binding = p1.binding; - final File file = keys.get(p2); - final List matches = results.get(file); - final int color = - ContextCompat.getColor( - binding.icon.getContext(), com.itsaky.androidide.R.color.secondaryColor); - binding.title.setText(file.getName()); - binding.icon.setImageResource(com.itsaky.androidide.R.drawable.ic_language_java); - binding.icon.setColorFilter(color, PorterDuff.Mode.SRC_ATOP); - binding.items.setLayoutManager(new LinearLayoutManager(binding.items.getContext())); - binding.items.setAdapter(new ChildAdapter(matches)); - - binding.getRoot() - .setOnClickListener( - v -> { - if (fileListener != null) fileListener.onClick(file); - }); + public void onBindViewHolder(SearchListAdapter.ChildVH p1, int p2) { + final SearchResult match = matches.get(p2); + final LayoutSearchResultItemBinding binding = p1.binding; + new Thread( + () -> { + try { + final SpannableStringBuilder sb = + new JavaHighlighter().highlight(match.line, match.match); + ThreadUtils.runOnUiThread(() -> binding.text.setText(sb)); + } catch (Exception e) { + ThreadUtils.runOnUiThread(() -> binding.text.setText(match.match)); + } + }) + .start(); + binding + .getRoot() + .setOnClickListener( + v -> { + if (matchListener != null) { + matchListener.onClick(match); + } + }); } @Override public int getItemCount() { - return results.size(); + return matches.size(); } + } - public class ChildAdapter extends RecyclerView.Adapter { + public class VH extends RecyclerView.ViewHolder { + private LayoutSearchResultGroupBinding binding; - private final List matches; - - public ChildAdapter(List matches) { - if (matches == null) matches = new ArrayList<>(); - this.matches = matches; - } - - @Override - public SearchListAdapter.ChildVH onCreateViewHolder(ViewGroup p1, int p2) { - return new ChildVH( - LayoutSearchResultItemBinding.inflate(LayoutInflater.from(p1.getContext()))); - } - - @Override - public void onBindViewHolder(SearchListAdapter.ChildVH p1, int p2) { - final SearchResult match = matches.get(p2); - final LayoutSearchResultItemBinding binding = p1.binding; - new Thread( - () -> { - try { - final SpannableStringBuilder sb = - new JavaHighlighter() - .highlight(match.line, match.match); - ThreadUtils.runOnUiThread(() -> binding.text.setText(sb)); - } catch (Exception e) { - ThreadUtils.runOnUiThread( - () -> binding.text.setText(match.match)); - } - }) - .start(); - binding.getRoot() - .setOnClickListener( - v -> { - if (matchListener != null) { - matchListener.onClick(match); - } - }); - } - - @Override - public int getItemCount() { - return matches.size(); - } + public VH(LayoutSearchResultGroupBinding binding) { + super(binding.getRoot()); + this.binding = binding; } + } - public class VH extends RecyclerView.ViewHolder { - private LayoutSearchResultGroupBinding binding; + public class ChildVH extends RecyclerView.ViewHolder { + private LayoutSearchResultItemBinding binding; - public VH(LayoutSearchResultGroupBinding binding) { - super(binding.getRoot()); - this.binding = binding; - } + public ChildVH(LayoutSearchResultItemBinding binding) { + super(binding.getRoot()); + this.binding = binding; } + } - public class ChildVH extends RecyclerView.ViewHolder { - private LayoutSearchResultItemBinding binding; + public static interface OnFileClickListener { + void onClick(File file); + } - public ChildVH(LayoutSearchResultItemBinding binding) { - super(binding.getRoot()); - this.binding = binding; - } - } - - public static interface OnFileClickListener { - void onClick(File file); - } - - public static interface OnMatchClickListener { - void onClick(SearchResult match); - } + public static interface OnMatchClickListener { + void onClick(SearchResult match); + } } diff --git a/app/src/main/java/com/itsaky/androidide/adapters/SimpleIconTextAdapter.java b/app/src/main/java/com/itsaky/androidide/adapters/SimpleIconTextAdapter.java index be64d6d00..a6daf4f45 100755 --- a/app/src/main/java/com/itsaky/androidide/adapters/SimpleIconTextAdapter.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/SimpleIconTextAdapter.java @@ -30,31 +30,31 @@ import java.util.List; */ public class SimpleIconTextAdapter extends IconTextAdapter { - private final List items; + private final List items; - public SimpleIconTextAdapter(List items) { - this.items = items; - } + public SimpleIconTextAdapter(List items) { + this.items = items; + } - @NonNull - @Override - public IconTextListItem getItemAt(int index) { - return items.get(index); - } + @NonNull + @Override + public IconTextListItem getItemAt(int index) { + return items.get(index); + } - @Override - public int getIconResource(int index) { - return getItemAt(index).getIconResource(); - } + @Override + public int getIconResource(int index) { + return getItemAt(index).getIconResource(); + } - @NonNull - @Override - public String getItemText(int index) { - return getItemAt(index).getText(); - } + @NonNull + @Override + public String getItemText(int index) { + return getItemAt(index).getText(); + } - @Override - public int getItemCount() { - return items.size(); - } + @Override + public int getItemCount() { + return items.size(); + } } diff --git a/app/src/main/java/com/itsaky/androidide/adapters/SymbolInputAdapter.java b/app/src/main/java/com/itsaky/androidide/adapters/SymbolInputAdapter.java index b9a254ec6..efba874d5 100755 --- a/app/src/main/java/com/itsaky/androidide/adapters/SymbolInputAdapter.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/SymbolInputAdapter.java @@ -33,95 +33,90 @@ import java.util.List; public class SymbolInputAdapter extends RecyclerView.Adapter { - private final IDEEditor editor; - private Symbol[] symbols; + private final IDEEditor editor; + private Symbol[] symbols; - public SymbolInputAdapter(IDEEditor editor) { - this(editor, null); + public SymbolInputAdapter(IDEEditor editor) { + this(editor, null); + } + + public SymbolInputAdapter(IDEEditor editor, Symbol[] symbols) { + this.editor = editor; + this.symbols = symbols == null ? new Symbol[0] : symbols; + } + + @SuppressLint("NotifyDataSetChanged") + public void setSymbols(boolean notify, Symbol... symbols) { + this.symbols = symbols; + if (notify) { + notifyDataSetChanged(); + } + } + + @NonNull + @Override + public VH onCreateViewHolder(@NonNull ViewGroup parent, int itemType) { + return new VH( + LayoutSymbolItemBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull VH holder, int position) { + if (symbols == null || symbols[position] == null) return; + final Symbol symbol = symbols[position]; + holder.binding.symbol.setText(symbol.label); + holder.binding.symbol.setOnClickListener(__ -> insertSymbol(symbol.commit, symbol.offset)); + } + + @Override + public int getItemCount() { + return symbols == null ? 0 : symbols.length; + } + + public static class VH extends RecyclerView.ViewHolder { + LayoutSymbolItemBinding binding; + + public VH(LayoutSymbolItemBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } + + void insertSymbol(String text, int selectionOffset) { + if (selectionOffset < 0 || selectionOffset > text.length()) { + return; + } + var cur = editor.getText().getCursor(); + if (cur.isSelected()) { + editor + .getText() + .delete(cur.getLeftLine(), cur.getLeftColumn(), cur.getRightLine(), cur.getRightColumn()); + editor.notifyIMEExternalCursorChange(); } - public SymbolInputAdapter(IDEEditor editor, Symbol[] symbols) { - this.editor = editor; - this.symbols = symbols == null ? new Symbol[0] : symbols; + if (cur.getLeftColumn() < editor.getText().getColumnCount(cur.getLeftLine()) + && text.length() == 1 + && text.charAt(0) == editor.getText().charAt(cur.getLeftLine(), cur.getLeftColumn()) + && pairs.contains(text.charAt(0))) { + editor.moveSelectionRight(); + } else { + editor.commitText(text); + if (selectionOffset != text.length()) { + editor.setSelection( + cur.getRightLine(), cur.getRightColumn() - (text.length() - selectionOffset)); + } } + } - @SuppressLint("NotifyDataSetChanged") - public void setSymbols(boolean notify, Symbol... symbols) { - this.symbols = symbols; - if (notify) { - notifyDataSetChanged(); - } - } + private static final List pairs; - @NonNull - @Override - public VH onCreateViewHolder(@NonNull ViewGroup parent, int itemType) { - return new VH( - LayoutSymbolItemBinding.inflate( - LayoutInflater.from(parent.getContext()), parent, false)); - } - - @Override - public void onBindViewHolder(@NonNull VH holder, int position) { - if (symbols == null || symbols[position] == null) return; - final Symbol symbol = symbols[position]; - holder.binding.symbol.setText(symbol.label); - holder.binding.symbol.setOnClickListener(__ -> insertSymbol(symbol.commit, symbol.offset)); - } - - @Override - public int getItemCount() { - return symbols == null ? 0 : symbols.length; - } - - public static class VH extends RecyclerView.ViewHolder { - LayoutSymbolItemBinding binding; - - public VH(LayoutSymbolItemBinding binding) { - super(binding.getRoot()); - this.binding = binding; - } - } - - void insertSymbol(String text, int selectionOffset) { - if (selectionOffset < 0 || selectionOffset > text.length()) { - return; - } - var cur = editor.getText().getCursor(); - if (cur.isSelected()) { - editor.getText() - .delete( - cur.getLeftLine(), - cur.getLeftColumn(), - cur.getRightLine(), - cur.getRightColumn()); - editor.notifyIMEExternalCursorChange(); - } - - if (cur.getLeftColumn() < editor.getText().getColumnCount(cur.getLeftLine()) - && text.length() == 1 - && text.charAt(0) == editor.getText().charAt(cur.getLeftLine(), cur.getLeftColumn()) - && pairs.contains(text.charAt(0))) { - editor.moveSelectionRight(); - } else { - editor.commitText(text); - if (selectionOffset != text.length()) { - editor.setSelection( - cur.getRightLine(), - cur.getRightColumn() - (text.length() - selectionOffset)); - } - } - } - - private static final List pairs; - - static { - pairs = new ArrayList<>(); - pairs.add('}'); - pairs.add(')'); - pairs.add(']'); - pairs.add('"'); - pairs.add('\''); - pairs.add('>'); - } + static { + pairs = new ArrayList<>(); + pairs.add('}'); + pairs.add(')'); + pairs.add(']'); + pairs.add('"'); + pairs.add('\''); + pairs.add('>'); + } } diff --git a/app/src/main/java/com/itsaky/androidide/adapters/WidgetGroupItemAdapter.java b/app/src/main/java/com/itsaky/androidide/adapters/WidgetGroupItemAdapter.java index 5c1d85431..29fc31ff4 100755 --- a/app/src/main/java/com/itsaky/androidide/adapters/WidgetGroupItemAdapter.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/WidgetGroupItemAdapter.java @@ -30,45 +30,45 @@ import com.itsaky.androidide.models.UIWidgetGroup; import java.util.List; public class WidgetGroupItemAdapter extends SimpleIconTextAdapter - implements SimpleIconTextAdapter.OnBindListener { + implements SimpleIconTextAdapter.OnBindListener { - private final OnGroupClickListener clickListener; + private final OnGroupClickListener clickListener; - public WidgetGroupItemAdapter( - @NonNull List groups, @Nullable OnGroupClickListener clickListener) { - super(groups); - this.clickListener = clickListener; + public WidgetGroupItemAdapter( + @NonNull List groups, @Nullable OnGroupClickListener clickListener) { + super(groups); + this.clickListener = clickListener; - super.setOnBindListener(this); + super.setOnBindListener(this); + } + + @Override + public boolean onBind(IconTextListItem item, VH holder, int position) { + // Ignored + return false; + } + + @Override + public void postBind(IconTextListItem item, @NonNull VH holder, int position) { + final var binding = holder.binding; + final var group = (UIWidgetGroup) item; + + if (group.isSelected()) { + binding + .getRoot() + .setBackground( + ContextCompat.getDrawable( + binding.getRoot().getContext(), R.drawable.bg_widget_group_selected)); + } else { + binding.getRoot().setBackgroundColor(Color.TRANSPARENT); } - @Override - public boolean onBind(IconTextListItem item, VH holder, int position) { - // Ignored - return false; + if (this.clickListener != null) { + binding.getRoot().setOnClickListener(v -> this.clickListener.onGroupClick(group)); } + } - @Override - public void postBind(IconTextListItem item, @NonNull VH holder, int position) { - final var binding = holder.binding; - final var group = (UIWidgetGroup) item; - - if (group.isSelected()) { - binding.getRoot() - .setBackground( - ContextCompat.getDrawable( - binding.getRoot().getContext(), - R.drawable.bg_widget_group_selected)); - } else { - binding.getRoot().setBackgroundColor(Color.TRANSPARENT); - } - - if (this.clickListener != null) { - binding.getRoot().setOnClickListener(v -> this.clickListener.onGroupClick(group)); - } - } - - public interface OnGroupClickListener { - void onGroupClick(UIWidgetGroup group); - } + public interface OnGroupClickListener { + void onGroupClick(UIWidgetGroup group); + } } diff --git a/app/src/main/java/com/itsaky/androidide/adapters/WidgetItemAdapter.java b/app/src/main/java/com/itsaky/androidide/adapters/WidgetItemAdapter.java index 62502ed49..d5199ebf1 100755 --- a/app/src/main/java/com/itsaky/androidide/adapters/WidgetItemAdapter.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/WidgetItemAdapter.java @@ -31,49 +31,49 @@ import com.itsaky.androidide.ui.WidgetDragShadowBuilder; import java.util.List; public class WidgetItemAdapter extends SimpleIconTextAdapter - implements SimpleIconTextAdapter.OnBindListener { + implements SimpleIconTextAdapter.OnBindListener { - private final OnDragStartListener dragStartListener; + private final OnDragStartListener dragStartListener; - public WidgetItemAdapter(List widgets, OnDragStartListener dragStartListener) { - super(widgets); - this.dragStartListener = dragStartListener; + public WidgetItemAdapter(List widgets, OnDragStartListener dragStartListener) { + super(widgets); + this.dragStartListener = dragStartListener; - setOnBindListener(this); - } + setOnBindListener(this); + } - @Override - public boolean onBind(IconTextListItem item, @NonNull VH holder, int position) { - return false; // Returning true will not set the text and icon - } + @Override + public boolean onBind(IconTextListItem item, @NonNull VH holder, int position) { + return false; // Returning true will not set the text and icon + } - @Override - public void postBind(IconTextListItem item, VH holder, int position) { - final var binding = holder.binding; - final var root = binding.getRoot(); - final var widget = (UIWidget) getItemAt(position); + @Override + public void postBind(IconTextListItem item, VH holder, int position) { + final var binding = holder.binding; + final var root = binding.getRoot(); + final var widget = (UIWidget) getItemAt(position); - root.setOnLongClickListener( - v -> { - final var dragData = new WidgetDragData(false, null, widget); - final var shadow = new WidgetDragShadowBuilder(binding.icon); - final var dataItem = new ClipData.Item(DesignerActivity.DRAGGING_WIDGET_TAG); - final var data = - new ClipData( - DesignerActivity.DRAGGING_WIDGET_TAG, - new String[] {DesignerActivity.DRAGGING_WIDGET_MIME}, - dataItem); - binding.icon.startDragAndDrop(data, shadow, dragData, 0); + root.setOnLongClickListener( + v -> { + final var dragData = new WidgetDragData(false, null, widget); + final var shadow = new WidgetDragShadowBuilder(binding.icon); + final var dataItem = new ClipData.Item(DesignerActivity.DRAGGING_WIDGET_TAG); + final var data = + new ClipData( + DesignerActivity.DRAGGING_WIDGET_TAG, + new String[] {DesignerActivity.DRAGGING_WIDGET_MIME}, + dataItem); + binding.icon.startDragAndDrop(data, shadow, dragData, 0); - if (this.dragStartListener != null) { - this.dragStartListener.onDragStarted(v); - } + if (this.dragStartListener != null) { + this.dragStartListener.onDragStarted(v); + } - return true; - }); - } + return true; + }); + } - public interface OnDragStartListener { - void onDragStarted(View view); - } + public interface OnDragStartListener { + void onDragStarted(View view); + } } diff --git a/app/src/main/java/com/itsaky/androidide/adapters/XMLAttributeListAdapter.java b/app/src/main/java/com/itsaky/androidide/adapters/XMLAttributeListAdapter.java index 060504b47..38c7d4541 100644 --- a/app/src/main/java/com/itsaky/androidide/adapters/XMLAttributeListAdapter.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/XMLAttributeListAdapter.java @@ -37,70 +37,67 @@ import java.util.List; public class XMLAttributeListAdapter extends RecyclerView.Adapter { - private final List attributes; - private final OnClickListener clickListener; + private final List attributes; + private final OnClickListener clickListener; - public XMLAttributeListAdapter(List attributes, OnClickListener clickListener) { - this.attributes = attributes; - this.clickListener = clickListener; + public XMLAttributeListAdapter(List attributes, OnClickListener clickListener) { + this.attributes = attributes; + this.clickListener = clickListener; + } + + @NonNull + @Override + public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new VH( + LayoutAttrEditorSheetItemBinding.inflate( + LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull VH holder, int position) { + final var binding = holder.binding; + final var attr = attributes.get(position); + final var sb = new SpannableStringBuilder(); + sb.append(attr.getAttributeName()); + if (attr.isApplied()) { + sb.append(" = "); + sb.append(attr.getValue(), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + + final var length = attr.getValue().length(); + sb.setSpan( + new ForegroundColorSpan( + ContextCompat.getColor(binding.text.getContext(), R.color.primaryTextColor)), + sb.length() - length, + sb.length() - 1, + SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); } - @NonNull - @Override - public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - return new VH( - LayoutAttrEditorSheetItemBinding.inflate( - LayoutInflater.from(parent.getContext()), parent, false)); - } - - @Override - public void onBindViewHolder(@NonNull VH holder, int position) { - final var binding = holder.binding; - final var attr = attributes.get(position); - final var sb = new SpannableStringBuilder(); - sb.append(attr.getAttributeName()); - if (attr.isApplied()) { - sb.append(" = "); - sb.append( - attr.getValue(), - new StyleSpan(Typeface.BOLD), - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - - final var length = attr.getValue().length(); - sb.setSpan( - new ForegroundColorSpan( - ContextCompat.getColor( - binding.text.getContext(), R.color.primaryTextColor)), - sb.length() - length, - sb.length() - 1, - SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); - } - - binding.text.setText(sb); - binding.getRoot() - .setOnClickListener( - v -> { - if (this.clickListener != null) { - this.clickListener.onClick(binding, attr); - } - }); - } - - @Override - public int getItemCount() { - return attributes.size(); - } - - public interface OnClickListener { - void onClick(LayoutAttrEditorSheetItemBinding binding, XMLAttribute attribute); - } - - static class VH extends RecyclerView.ViewHolder { - LayoutAttrEditorSheetItemBinding binding; - - VH(@NonNull LayoutAttrEditorSheetItemBinding binding) { - super(binding.getRoot()); - this.binding = binding; - } + binding.text.setText(sb); + binding + .getRoot() + .setOnClickListener( + v -> { + if (this.clickListener != null) { + this.clickListener.onClick(binding, attr); + } + }); + } + + @Override + public int getItemCount() { + return attributes.size(); + } + + public interface OnClickListener { + void onClick(LayoutAttrEditorSheetItemBinding binding, XMLAttribute attribute); + } + + static class VH extends RecyclerView.ViewHolder { + LayoutAttrEditorSheetItemBinding binding; + + VH(@NonNull LayoutAttrEditorSheetItemBinding binding) { + super(binding.getRoot()); + this.binding = binding; } + } } diff --git a/app/src/main/java/com/itsaky/androidide/adapters/viewholders/FileTreeViewHolder.java b/app/src/main/java/com/itsaky/androidide/adapters/viewholders/FileTreeViewHolder.java index 8462d8183..7bc4da372 100755 --- a/app/src/main/java/com/itsaky/androidide/adapters/viewholders/FileTreeViewHolder.java +++ b/app/src/main/java/com/itsaky/androidide/adapters/viewholders/FileTreeViewHolder.java @@ -39,75 +39,73 @@ import java.io.File; public class FileTreeViewHolder extends TreeNode.BaseNodeViewHolder { - private Context context; - private ImageView chevron; - private LayoutFiletreeItemBinding binding; + private Context context; + private ImageView chevron; + private LayoutFiletreeItemBinding binding; - public FileTreeViewHolder(Context context) { - super(context); - this.context = context; - } + public FileTreeViewHolder(Context context) { + super(context); + this.context = context; + } - @Override - public View createNodeView(TreeNode node, File file) { - binding = LayoutFiletreeItemBinding.inflate(LayoutInflater.from(context)); - int dp15 = - context instanceof StudioActivity - ? ((StudioActivity) context).dpToPx(15) - : (int) - (TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, - 15, - context.getResources().getDisplayMetrics())); - int icon = 0; - if (file.isDirectory()) icon = R.drawable.ic_folder; - else if (file.getName().endsWith(".java")) icon = R.drawable.ic_language_java; - else if (file.getName().endsWith(".kt")) icon = R.drawable.ic_language_kotlin; - else if (file.getName().endsWith(".xml")) icon = R.drawable.ic_language_xml; - else if (file.getName().endsWith(".gradle")) icon = R.drawable.ic_language_gradle; - else if (file.getName().endsWith(".json")) icon = R.drawable.ic_language_json; - else icon = R.drawable.ic_file_unknown; + @Override + public View createNodeView(TreeNode node, File file) { + binding = LayoutFiletreeItemBinding.inflate(LayoutInflater.from(context)); + int dp15 = + context instanceof StudioActivity + ? ((StudioActivity) context).dpToPx(15) + : (int) + (TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, 15, context.getResources().getDisplayMetrics())); + int icon = 0; + if (file.isDirectory()) icon = R.drawable.ic_folder; + else if (file.getName().endsWith(".java")) icon = R.drawable.ic_language_java; + else if (file.getName().endsWith(".kt")) icon = R.drawable.ic_language_kotlin; + else if (file.getName().endsWith(".xml")) icon = R.drawable.ic_language_xml; + else if (file.getName().endsWith(".gradle")) icon = R.drawable.ic_language_gradle; + else if (file.getName().endsWith(".json")) icon = R.drawable.ic_language_json; + else icon = R.drawable.ic_file_unknown; - final boolean isGradle = - file.getAbsolutePath().equals(Environment.GRADLE_USER_HOME.getAbsolutePath()); - chevron = binding.getRoot().findViewById(R.id.filetree_chevron); - binding.filetreeName.setText(isGradle ? "GRADLE_HOME" : file.getName()); - binding.filetreeIcon.setImageResource(icon); - binding.getRoot() - .setPadding( - binding.getRoot().getPaddingLeft() + (dp15 * (node.getLevel() - 1)), - binding.getRoot().getPaddingTop(), - binding.getRoot().getPaddingRight(), - binding.getRoot().getPaddingBottom()); - chevron.setVisibility(file.isFile() ? View.INVISIBLE : View.VISIBLE); - chevron.setImageResource( - node.isExpanded() ? R.drawable.ic_chevron_down : R.drawable.ic_chevron_right); - chevron.getDrawable() - .setColorFilter( - ContextCompat.getColor(context, R.color.secondaryLightColor), - PorterDuff.Mode.SRC_ATOP); - binding.filetreeIcon - .getDrawable() - .setColorFilter( - ContextCompat.getColor(context, R.color.secondaryLightColor), - PorterDuff.Mode.SRC_ATOP); + final boolean isGradle = + file.getAbsolutePath().equals(Environment.GRADLE_USER_HOME.getAbsolutePath()); + chevron = binding.getRoot().findViewById(R.id.filetree_chevron); + binding.filetreeName.setText(isGradle ? "GRADLE_HOME" : file.getName()); + binding.filetreeIcon.setImageResource(icon); + binding + .getRoot() + .setPadding( + binding.getRoot().getPaddingLeft() + (dp15 * (node.getLevel() - 1)), + binding.getRoot().getPaddingTop(), + binding.getRoot().getPaddingRight(), + binding.getRoot().getPaddingBottom()); + chevron.setVisibility(file.isFile() ? View.INVISIBLE : View.VISIBLE); + chevron.setImageResource( + node.isExpanded() ? R.drawable.ic_chevron_down : R.drawable.ic_chevron_right); + chevron + .getDrawable() + .setColorFilter( + ContextCompat.getColor(context, R.color.secondaryLightColor), PorterDuff.Mode.SRC_ATOP); + binding + .filetreeIcon + .getDrawable() + .setColorFilter( + ContextCompat.getColor(context, R.color.secondaryLightColor), PorterDuff.Mode.SRC_ATOP); - return binding.getRoot(); - } + return binding.getRoot(); + } - public void setLoading() { - chevron.getDrawable() - .setColorFilter( - ContextCompat.getColor(context, R.color.secondaryLightColor), - PorterDuff.Mode.SRC_ATOP); - } + public void setLoading() { + chevron + .getDrawable() + .setColorFilter( + ContextCompat.getColor(context, R.color.secondaryLightColor), PorterDuff.Mode.SRC_ATOP); + } - public void updateChevron(boolean isExpanded) { - chevron.setImageResource( - isExpanded ? R.drawable.ic_chevron_down : R.drawable.ic_chevron_right); - chevron.getDrawable() - .setColorFilter( - ContextCompat.getColor(context, R.color.secondaryLightColor), - PorterDuff.Mode.SRC_ATOP); - } + public void updateChevron(boolean isExpanded) { + chevron.setImageResource(isExpanded ? R.drawable.ic_chevron_down : R.drawable.ic_chevron_right); + chevron + .getDrawable() + .setColorFilter( + ContextCompat.getColor(context, R.color.secondaryLightColor), PorterDuff.Mode.SRC_ATOP); + } } diff --git a/app/src/main/java/com/itsaky/androidide/app/StudioActivity.java b/app/src/main/java/com/itsaky/androidide/app/StudioActivity.java index bbf93c564..1f9322503 100755 --- a/app/src/main/java/com/itsaky/androidide/app/StudioActivity.java +++ b/app/src/main/java/com/itsaky/androidide/app/StudioActivity.java @@ -34,113 +34,111 @@ import com.itsaky.androidide.R; import com.itsaky.androidide.utils.ILogger; public abstract class StudioActivity extends AppCompatActivity { - private boolean toRequestStorage = true; + private boolean toRequestStorage = true; - public static final String TAG = "StudioActivity"; - public static final int REQCODE_STORAGE = 1009; + public static final String TAG = "StudioActivity"; + public static final int REQCODE_STORAGE = 1009; - protected static ILogger LOG = ILogger.newInstance("StudioActivity"); + protected static ILogger LOG = ILogger.newInstance("StudioActivity"); - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(bindLayout()); - onSetContentView(); + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(bindLayout()); + onSetContentView(); - if (toRequestStorage && !isStoragePermissionGranted()) { - requestStorage(); - } - - if (isStoragePermissionGranted()) { - onStorageAlreadyGranted(); - } else { - onStorageDenied(); - } + if (toRequestStorage && !isStoragePermissionGranted()) { + requestStorage(); } - public boolean isStoragePermissionGranted() { - return ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) - == PackageManager.PERMISSION_GRANTED - && ContextCompat.checkSelfPermission( - this, Manifest.permission.WRITE_EXTERNAL_STORAGE) - == PackageManager.PERMISSION_GRANTED; + if (isStoragePermissionGranted()) { + onStorageAlreadyGranted(); + } else { + onStorageDenied(); } + } - protected void doNotRequestStorage() { - this.toRequestStorage = false; + public boolean isStoragePermissionGranted() { + return ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) + == PackageManager.PERMISSION_GRANTED + && ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) + == PackageManager.PERMISSION_GRANTED; + } + + protected void doNotRequestStorage() { + this.toRequestStorage = false; + } + + protected void requestStorage() { + if (isStoragePermissionGranted()) { + onStorageGranted(); + return; } + ActivityCompat.requestPermissions( + this, + new String[] { + Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE + }, + REQCODE_STORAGE); + } - protected void requestStorage() { - if (isStoragePermissionGranted()) { - onStorageGranted(); - return; - } - ActivityCompat.requestPermissions( - this, - new String[] { - Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.READ_EXTERNAL_STORAGE - }, - REQCODE_STORAGE); + public void loadFrament(Fragment frag, View view) { + loadFragment(frag, view.getId()); + } + + public void loadFragment(Fragment fragment, int id) { + FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); + transaction.replace(id, fragment); + transaction.commit(); + } + + public StudioApp getApp() { + return (StudioApp) getApplication(); + } + + public TypedValue getTypedValueForAttr(@AttrRes int attrRes) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(attrRes, typedValue, true); + return typedValue; + } + + public int getColorPrimary() { + return getTypedValueForAttr(R.attr.colorPrimary).data; + } + + public int getColorPrimaryDark() { + return getTypedValueForAttr(R.attr.colorPrimaryDark).data; + } + + public int getColorAccent() { + return getTypedValueForAttr(R.attr.colorAccent).data; + } + + public int dpToPx(int dp) { + return (int) + TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics()); + } + + @Override + public void onRequestPermissionsResult( + int requestCode, String[] permissions, int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (requestCode == REQCODE_STORAGE) { + if (grantResults != null + && grantResults.length > 0 + && grantResults[0] == PackageManager.PERMISSION_GRANTED) onStorageGranted(); + else onStorageDenied(); } + } - public void loadFrament(Fragment frag, View view) { - loadFragment(frag, view.getId()); - } + protected void onStorageAlreadyGranted() {} - public void loadFragment(Fragment fragment, int id) { - FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); - transaction.replace(id, fragment); - transaction.commit(); - } + protected void onStorageGranted() {} - public StudioApp getApp() { - return (StudioApp) getApplication(); - } + protected void onStorageDenied() {} - public TypedValue getTypedValueForAttr(@AttrRes int attrRes) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(attrRes, typedValue, true); - return typedValue; - } + protected void onSetContentView() {} - public int getColorPrimary() { - return getTypedValueForAttr(R.attr.colorPrimary).data; - } - - public int getColorPrimaryDark() { - return getTypedValueForAttr(R.attr.colorPrimaryDark).data; - } - - public int getColorAccent() { - return getTypedValueForAttr(R.attr.colorAccent).data; - } - - public int dpToPx(int dp) { - return (int) - TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics()); - } - - @Override - public void onRequestPermissionsResult( - int requestCode, String[] permissions, int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - if (requestCode == REQCODE_STORAGE) { - if (grantResults != null - && grantResults.length > 0 - && grantResults[0] == PackageManager.PERMISSION_GRANTED) onStorageGranted(); - else onStorageDenied(); - } - } - - protected void onStorageAlreadyGranted() {} - - protected void onStorageGranted() {} - - protected void onStorageDenied() {} - - protected void onSetContentView() {} - - protected abstract View bindLayout(); + protected abstract View bindLayout(); } diff --git a/app/src/main/java/com/itsaky/androidide/app/StudioApp.java b/app/src/main/java/com/itsaky/androidide/app/StudioApp.java index 84f391c43..f64dc5165 100755 --- a/app/src/main/java/com/itsaky/androidide/app/StudioApp.java +++ b/app/src/main/java/com/itsaky/androidide/app/StudioApp.java @@ -44,107 +44,107 @@ import java.util.concurrent.CompletionException; public class StudioApp extends BaseApplication { - private static final ILogger LOG = ILogger.newInstance("StudioApp"); - private static StudioApp instance; - private static SDKInfo sdkInfo; - private final ILanguageServer mJavaLanguageServer = new JavaLanguageServer(); - private final ILanguageServer mXMLLanguageServer = new XMLLanguageServer(); - private IResourceTable mResTable; - private Thread.UncaughtExceptionHandler uncaughtExceptionHandler; + private static final ILogger LOG = ILogger.newInstance("StudioApp"); + private static StudioApp instance; + private static SDKInfo sdkInfo; + private final ILanguageServer mJavaLanguageServer = new JavaLanguageServer(); + private final ILanguageServer mXMLLanguageServer = new XMLLanguageServer(); + private IResourceTable mResTable; + private Thread.UncaughtExceptionHandler uncaughtExceptionHandler; - public static StudioApp getInstance() { - return instance; + public static StudioApp getInstance() { + return instance; + } + + @Override + public void onCreate() { + instance = this; + this.uncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler(); + Thread.setDefaultUncaughtExceptionHandler(this::handleCrash); + super.onCreate(); + + getApiInformation(); + } + + private void handleCrash(Thread thread, Throwable th) { + writeException(th); + + try { + final var intent = new Intent(); + intent.setAction(CrashHandlerActivity.REPORT_ACTION); + intent.putExtra(CrashHandlerActivity.TRACE_KEY, ThrowableUtils.getFullStackTrace(th)); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + + if (this.uncaughtExceptionHandler != null) { + this.uncaughtExceptionHandler.uncaughtException(thread, th); + } + System.exit(1); + } catch (Throwable error) { + LOG.error("Unable to show crash handler activity", error); } + } - @Override - public void onCreate() { - instance = this; - this.uncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler(); - Thread.setDefaultUncaughtExceptionHandler(this::handleCrash); - super.onCreate(); - - getApiInformation(); - } - - private void handleCrash(Thread thread, Throwable th) { - writeException(th); - - try { - final var intent = new Intent(); - intent.setAction(CrashHandlerActivity.REPORT_ACTION); - intent.putExtra(CrashHandlerActivity.TRACE_KEY, ThrowableUtils.getFullStackTrace(th)); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); - - if (this.uncaughtExceptionHandler != null) { - this.uncaughtExceptionHandler.uncaughtException(thread, th); + /** Reads API version information from api-versions.xml */ + public CompletableFuture getApiInformation() { + return CompletableFuture.supplyAsync( + () -> { + if (sdkInfo == null) { + try { + sdkInfo = new SDKInfo(StudioApp.this); + ((XMLLanguageServer) mXMLLanguageServer).setupSDK(sdkInfo); + } catch (Throwable th) { + LOG.error(getString(R.string.err_init_sdkinfo), th); } - System.exit(1); - } catch (Throwable error) { - LOG.error("Unable to show crash handler activity", error); - } - } + } - /** Reads API version information from api-versions.xml */ - public CompletableFuture getApiInformation() { - return CompletableFuture.supplyAsync( - () -> { - if (sdkInfo == null) { - try { - sdkInfo = new SDKInfo(StudioApp.this); - ((XMLLanguageServer) mXMLLanguageServer).setupSDK(sdkInfo); - } catch (Throwable th) { - LOG.error(getString(R.string.err_init_sdkinfo), th); - } - } + return sdkInfo; + }); + } - return sdkInfo; - }); - } + @NonNull + public ILanguageServer getJavaLanguageServer() { + return mJavaLanguageServer; + } - @NonNull - public ILanguageServer getJavaLanguageServer() { - return mJavaLanguageServer; - } + @NonNull + public ILanguageServer getXMLLanguageServer() { + return mXMLLanguageServer; + } - @NonNull - public ILanguageServer getXMLLanguageServer() { - return mXMLLanguageServer; - } + public CompletableFuture createInflaterConfig( + ILayoutInflater.ContextProvider contextProvider, Set resDirs) { - public CompletableFuture createInflaterConfig( - ILayoutInflater.ContextProvider contextProvider, Set resDirs) { + return CompletableFuture.supplyAsync( + () -> { + try { + final var sdkInfo = getApiInformation().get(); + return new LayoutInflaterConfiguration.Builder() + .setAttrInfo(sdkInfo.getAttrInfo()) + .setWidgetInfo(sdkInfo.getWidgetInfo()) + .setResourceFinder(getResourceTable()) + .setResourceDirectories(resDirs) + .setContextProvider(contextProvider) + .create(); + } catch (Throwable e) { + throw new CompletionException(e); + } + }); + } - return CompletableFuture.supplyAsync( - () -> { - try { - final var sdkInfo = getApiInformation().get(); - return new LayoutInflaterConfiguration.Builder() - .setAttrInfo(sdkInfo.getAttrInfo()) - .setWidgetInfo(sdkInfo.getWidgetInfo()) - .setResourceFinder(getResourceTable()) - .setResourceDirectories(resDirs) - .setContextProvider(contextProvider) - .create(); - } catch (Throwable e) { - throw new CompletionException(e); - } - }); - } + public IResourceTable getResourceTable() { + return mResTable == null ? mResTable = new ProjectResourceTable() : mResTable; + } - public IResourceTable getResourceTable() { - return mResTable == null ? mResTable = new ProjectResourceTable() : mResTable; - } + public ApiInfo apiInfo() { + return sdkInfo.getApiInfo(); + } - public ApiInfo apiInfo() { - return sdkInfo.getApiInfo(); - } + public AttrInfo attrInfo() { + return sdkInfo.getAttrInfo(); + } - public AttrInfo attrInfo() { - return sdkInfo.getAttrInfo(); - } - - public WidgetInfo widgetInfo() { - return sdkInfo.getWidgetInfo(); - } + public WidgetInfo widgetInfo() { + return sdkInfo.getWidgetInfo(); + } } diff --git a/app/src/main/java/com/itsaky/androidide/filepicker/IDEDocumentsProvider.java b/app/src/main/java/com/itsaky/androidide/filepicker/IDEDocumentsProvider.java index 5dba7781b..f6f0832c2 100644 --- a/app/src/main/java/com/itsaky/androidide/filepicker/IDEDocumentsProvider.java +++ b/app/src/main/java/com/itsaky/androidide/filepicker/IDEDocumentsProvider.java @@ -56,272 +56,264 @@ import java.util.LinkedList; */ public class IDEDocumentsProvider extends DocumentsProvider { - private static final String ALL_MIME_TYPES = "*/*"; - private static final ILogger LOG = ILogger.newInstance("IDEDocumentsProvider"); - private static final File BASE_DIR = getBaseDir(); - // The default columns to return information about a root if no specific - // columns are requested in a query. - private static final String[] DEFAULT_ROOT_PROJECTION = - new String[] { - Root.COLUMN_ROOT_ID, - Root.COLUMN_MIME_TYPES, - Root.COLUMN_FLAGS, - Root.COLUMN_ICON, - Root.COLUMN_TITLE, - Root.COLUMN_SUMMARY, - Root.COLUMN_DOCUMENT_ID, - Root.COLUMN_AVAILABLE_BYTES - }; - // The default columns to return information about a document if no specific - // columns are requested in a query. - private static final String[] DEFAULT_DOCUMENT_PROJECTION = - new String[] { - Document.COLUMN_DOCUMENT_ID, - Document.COLUMN_MIME_TYPE, - Document.COLUMN_DISPLAY_NAME, - Document.COLUMN_LAST_MODIFIED, - Document.COLUMN_FLAGS, - Document.COLUMN_SIZE - }; + private static final String ALL_MIME_TYPES = "*/*"; + private static final ILogger LOG = ILogger.newInstance("IDEDocumentsProvider"); + private static final File BASE_DIR = getBaseDir(); + // The default columns to return information about a root if no specific + // columns are requested in a query. + private static final String[] DEFAULT_ROOT_PROJECTION = + new String[] { + Root.COLUMN_ROOT_ID, + Root.COLUMN_MIME_TYPES, + Root.COLUMN_FLAGS, + Root.COLUMN_ICON, + Root.COLUMN_TITLE, + Root.COLUMN_SUMMARY, + Root.COLUMN_DOCUMENT_ID, + Root.COLUMN_AVAILABLE_BYTES + }; + // The default columns to return information about a document if no specific + // columns are requested in a query. + private static final String[] DEFAULT_DOCUMENT_PROJECTION = + new String[] { + Document.COLUMN_DOCUMENT_ID, + Document.COLUMN_MIME_TYPE, + Document.COLUMN_DISPLAY_NAME, + Document.COLUMN_LAST_MODIFIED, + Document.COLUMN_FLAGS, + Document.COLUMN_SIZE + }; - @NonNull - private static File getBaseDir() { - if (Environment.HOME != null) { - return Environment.HOME; - } - - return new File(Environment.DEFAULT_HOME); + @NonNull + private static File getBaseDir() { + if (Environment.HOME != null) { + return Environment.HOME; } - /** - * Get the document id given a file. This document id must be consistent across time as other - * applications may save the ID and use it to reference documents later. - * - *

The reverse of @{link #getFileForDocId}. - */ - private static String getDocIdForFile(File file) { - return file.getAbsolutePath(); - } + return new File(Environment.DEFAULT_HOME); + } - /** Get the file given a document id (the reverse of {@link #getDocIdForFile(File)}). */ - private static File getFileForDocId(String docId) throws FileNotFoundException { - final File f = new File(docId); - if (!f.exists()) throw new FileNotFoundException(f.getAbsolutePath() + " not found"); - return f; - } + /** + * Get the document id given a file. This document id must be consistent across time as other + * applications may save the ID and use it to reference documents later. + * + *

The reverse of @{link #getFileForDocId}. + */ + private static String getDocIdForFile(File file) { + return file.getAbsolutePath(); + } - private static String getMimeType(File file) { + /** Get the file given a document id (the reverse of {@link #getDocIdForFile(File)}). */ + private static File getFileForDocId(String docId) throws FileNotFoundException { + final File f = new File(docId); + if (!f.exists()) throw new FileNotFoundException(f.getAbsolutePath() + " not found"); + return f; + } + + private static String getMimeType(File file) { + if (file.isDirectory()) { + return Document.MIME_TYPE_DIR; + } else { + final String name = file.getName(); + final int lastDot = name.lastIndexOf('.'); + if (lastDot >= 0) { + final String extension = name.substring(lastDot + 1).toLowerCase(); + final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); + if (mime != null) return mime; + } + return "application/octet-stream"; + } + } + + @Override + public Cursor queryRoots(String[] projection) { + final MatrixCursor result = + new MatrixCursor(projection != null ? projection : DEFAULT_ROOT_PROJECTION); + final String applicationName = getContext().getString(R.string.app_name); + + final MatrixCursor.RowBuilder row = result.newRow(); + LOG.debug("queryRoots() before all add"); + row.add(Root.COLUMN_ROOT_ID, getDocIdForFile(BASE_DIR)); + LOG.debug("queryRoots() before all add, 1"); + row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(BASE_DIR)); + LOG.debug("queryRoots() before all add, 2"); + row.add(Root.COLUMN_SUMMARY, null); + LOG.debug("queryRoots() before all add, 3"); + row.add( + Root.COLUMN_FLAGS, + Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD); + LOG.debug("queryRoots() before all add, 4"); + row.add(Root.COLUMN_TITLE, applicationName); + LOG.debug("queryRoots() before all add, 5"); + row.add(Root.COLUMN_MIME_TYPES, ALL_MIME_TYPES); + LOG.debug("queryRoots() before all add, 6"); + row.add(Root.COLUMN_AVAILABLE_BYTES, BASE_DIR.getFreeSpace()); + LOG.debug("queryRoots() before all add, 7"); + row.add(Root.COLUMN_ICON, R.mipmap.ic_launcher); + LOG.debug("queryRoots() before all add"); + return result; + } + + @Override + public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException { + final MatrixCursor result = + new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION); + includeFile(result, documentId, null); + return result; + } + + @Override + public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder) + throws FileNotFoundException { + final MatrixCursor result = + new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION); + final File parent = getFileForDocId(parentDocumentId); + for (File file : parent.listFiles()) { + includeFile(result, null, file); + } + return result; + } + + @Override + public ParcelFileDescriptor openDocument( + final String documentId, String mode, CancellationSignal signal) + throws FileNotFoundException { + final File file = getFileForDocId(documentId); + final int accessMode = ParcelFileDescriptor.parseMode(mode); + return ParcelFileDescriptor.open(file, accessMode); + } + + @Override + public AssetFileDescriptor openDocumentThumbnail( + String documentId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException { + final File file = getFileForDocId(documentId); + final ParcelFileDescriptor pfd = + ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); + return new AssetFileDescriptor(pfd, 0, file.length()); + } + + @Override + public boolean onCreate() { + return true; + } + + @Override + public String createDocument(String parentDocumentId, String mimeType, String displayName) + throws FileNotFoundException { + File newFile = new File(parentDocumentId, displayName); + int noConflictId = 2; + while (newFile.exists()) { + newFile = new File(parentDocumentId, displayName + " (" + noConflictId++ + ")"); + } + try { + boolean succeeded; + if (Document.MIME_TYPE_DIR.equals(mimeType)) { + succeeded = newFile.mkdir(); + } else { + succeeded = newFile.createNewFile(); + } + if (!succeeded) { + throw new FileNotFoundException("Failed to create document with id " + newFile.getPath()); + } + } catch (IOException e) { + throw new FileNotFoundException("Failed to create document with id " + newFile.getPath()); + } + return newFile.getPath(); + } + + @Override + public void deleteDocument(String documentId) throws FileNotFoundException { + File file = getFileForDocId(documentId); + if (!file.delete()) { + throw new FileNotFoundException("Failed to delete document with id " + documentId); + } + } + + @Override + public String getDocumentType(String documentId) throws FileNotFoundException { + File file = getFileForDocId(documentId); + return getMimeType(file); + } + + @Override + public Cursor querySearchDocuments(String rootId, String query, String[] projection) + throws FileNotFoundException { + final MatrixCursor result = + new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION); + final File parent = getFileForDocId(rootId); + + // This example implementation searches file names for the query and doesn't rank search + // results, so we can stop as soon as we find a sufficient number of matches. Other + // implementations might rank results and use other data about files, rather than the file + // name, to produce a match. + final LinkedList pending = new LinkedList<>(); + pending.add(parent); + + final int MAX_SEARCH_RESULTS = 50; + while (!pending.isEmpty() && result.getCount() < MAX_SEARCH_RESULTS) { + final File file = pending.removeFirst(); + // Avoid directories outside the $HOME directory linked with symlinks (to avoid e.g. + // search + // through the whole SD card). + boolean isInsideHome; + try { + isInsideHome = file.getCanonicalPath().startsWith(Environment.HOME.getAbsolutePath()); + } catch (IOException e) { + isInsideHome = true; + } + if (isInsideHome) { if (file.isDirectory()) { - return Document.MIME_TYPE_DIR; + Collections.addAll(pending, file.listFiles()); } else { - final String name = file.getName(); - final int lastDot = name.lastIndexOf('.'); - if (lastDot >= 0) { - final String extension = name.substring(lastDot + 1).toLowerCase(); - final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); - if (mime != null) return mime; - } - return "application/octet-stream"; - } - } - - @Override - public Cursor queryRoots(String[] projection) { - final MatrixCursor result = - new MatrixCursor(projection != null ? projection : DEFAULT_ROOT_PROJECTION); - final String applicationName = getContext().getString(R.string.app_name); - - final MatrixCursor.RowBuilder row = result.newRow(); - LOG.debug("queryRoots() before all add"); - row.add(Root.COLUMN_ROOT_ID, getDocIdForFile(BASE_DIR)); - LOG.debug("queryRoots() before all add, 1"); - row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(BASE_DIR)); - LOG.debug("queryRoots() before all add, 2"); - row.add(Root.COLUMN_SUMMARY, null); - LOG.debug("queryRoots() before all add, 3"); - row.add( - Root.COLUMN_FLAGS, - Root.FLAG_SUPPORTS_CREATE - | Root.FLAG_SUPPORTS_SEARCH - | Root.FLAG_SUPPORTS_IS_CHILD); - LOG.debug("queryRoots() before all add, 4"); - row.add(Root.COLUMN_TITLE, applicationName); - LOG.debug("queryRoots() before all add, 5"); - row.add(Root.COLUMN_MIME_TYPES, ALL_MIME_TYPES); - LOG.debug("queryRoots() before all add, 6"); - row.add(Root.COLUMN_AVAILABLE_BYTES, BASE_DIR.getFreeSpace()); - LOG.debug("queryRoots() before all add, 7"); - row.add(Root.COLUMN_ICON, R.mipmap.ic_launcher); - LOG.debug("queryRoots() before all add"); - return result; - } - - @Override - public Cursor queryDocument(String documentId, String[] projection) - throws FileNotFoundException { - final MatrixCursor result = - new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION); - includeFile(result, documentId, null); - return result; - } - - @Override - public Cursor queryChildDocuments( - String parentDocumentId, String[] projection, String sortOrder) - throws FileNotFoundException { - final MatrixCursor result = - new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION); - final File parent = getFileForDocId(parentDocumentId); - for (File file : parent.listFiles()) { + if (file.getName().toLowerCase().contains(query)) { includeFile(result, null, file); + } } - return result; + } } - @Override - public ParcelFileDescriptor openDocument( - final String documentId, String mode, CancellationSignal signal) - throws FileNotFoundException { - final File file = getFileForDocId(documentId); - final int accessMode = ParcelFileDescriptor.parseMode(mode); - return ParcelFileDescriptor.open(file, accessMode); + return result; + } + + @Override + public boolean isChildDocument(String parentDocumentId, String documentId) { + return documentId.startsWith(parentDocumentId); + } + + /** + * Add a representation of a file to a cursor. + * + * @param result the cursor to modify + * @param docId the document ID representing the desired file (may be null if given file) + * @param file the File object representing the desired file (may be null if given docID) + */ + private void includeFile(MatrixCursor result, String docId, File file) + throws FileNotFoundException { + if (docId == null) { + docId = getDocIdForFile(file); + } else { + file = getFileForDocId(docId); } - @Override - public AssetFileDescriptor openDocumentThumbnail( - String documentId, Point sizeHint, CancellationSignal signal) - throws FileNotFoundException { - final File file = getFileForDocId(documentId); - final ParcelFileDescriptor pfd = - ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); - return new AssetFileDescriptor(pfd, 0, file.length()); + int flags = 0; + if (file.isDirectory()) { + if (file.canWrite()) flags |= Document.FLAG_DIR_SUPPORTS_CREATE; + } else if (file.canWrite()) { + flags |= Document.FLAG_SUPPORTS_WRITE; } + if (file.getParentFile().canWrite()) flags |= Document.FLAG_SUPPORTS_DELETE; - @Override - public boolean onCreate() { - return true; - } + final String displayName = file.getName(); + final String mimeType = getMimeType(file); + if (mimeType.startsWith("image/")) flags |= Document.FLAG_SUPPORTS_THUMBNAIL; - @Override - public String createDocument(String parentDocumentId, String mimeType, String displayName) - throws FileNotFoundException { - File newFile = new File(parentDocumentId, displayName); - int noConflictId = 2; - while (newFile.exists()) { - newFile = new File(parentDocumentId, displayName + " (" + noConflictId++ + ")"); - } - try { - boolean succeeded; - if (Document.MIME_TYPE_DIR.equals(mimeType)) { - succeeded = newFile.mkdir(); - } else { - succeeded = newFile.createNewFile(); - } - if (!succeeded) { - throw new FileNotFoundException( - "Failed to create document with id " + newFile.getPath()); - } - } catch (IOException e) { - throw new FileNotFoundException( - "Failed to create document with id " + newFile.getPath()); - } - return newFile.getPath(); - } - - @Override - public void deleteDocument(String documentId) throws FileNotFoundException { - File file = getFileForDocId(documentId); - if (!file.delete()) { - throw new FileNotFoundException("Failed to delete document with id " + documentId); - } - } - - @Override - public String getDocumentType(String documentId) throws FileNotFoundException { - File file = getFileForDocId(documentId); - return getMimeType(file); - } - - @Override - public Cursor querySearchDocuments(String rootId, String query, String[] projection) - throws FileNotFoundException { - final MatrixCursor result = - new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION); - final File parent = getFileForDocId(rootId); - - // This example implementation searches file names for the query and doesn't rank search - // results, so we can stop as soon as we find a sufficient number of matches. Other - // implementations might rank results and use other data about files, rather than the file - // name, to produce a match. - final LinkedList pending = new LinkedList<>(); - pending.add(parent); - - final int MAX_SEARCH_RESULTS = 50; - while (!pending.isEmpty() && result.getCount() < MAX_SEARCH_RESULTS) { - final File file = pending.removeFirst(); - // Avoid directories outside the $HOME directory linked with symlinks (to avoid e.g. - // search - // through the whole SD card). - boolean isInsideHome; - try { - isInsideHome = - file.getCanonicalPath().startsWith(Environment.HOME.getAbsolutePath()); - } catch (IOException e) { - isInsideHome = true; - } - if (isInsideHome) { - if (file.isDirectory()) { - Collections.addAll(pending, file.listFiles()); - } else { - if (file.getName().toLowerCase().contains(query)) { - includeFile(result, null, file); - } - } - } - } - - return result; - } - - @Override - public boolean isChildDocument(String parentDocumentId, String documentId) { - return documentId.startsWith(parentDocumentId); - } - - /** - * Add a representation of a file to a cursor. - * - * @param result the cursor to modify - * @param docId the document ID representing the desired file (may be null if given file) - * @param file the File object representing the desired file (may be null if given docID) - */ - private void includeFile(MatrixCursor result, String docId, File file) - throws FileNotFoundException { - if (docId == null) { - docId = getDocIdForFile(file); - } else { - file = getFileForDocId(docId); - } - - int flags = 0; - if (file.isDirectory()) { - if (file.canWrite()) flags |= Document.FLAG_DIR_SUPPORTS_CREATE; - } else if (file.canWrite()) { - flags |= Document.FLAG_SUPPORTS_WRITE; - } - if (file.getParentFile().canWrite()) flags |= Document.FLAG_SUPPORTS_DELETE; - - final String displayName = file.getName(); - final String mimeType = getMimeType(file); - if (mimeType.startsWith("image/")) flags |= Document.FLAG_SUPPORTS_THUMBNAIL; - - final MatrixCursor.RowBuilder row = result.newRow(); - row.add(Document.COLUMN_DOCUMENT_ID, docId); - row.add(Document.COLUMN_DISPLAY_NAME, displayName); - row.add(Document.COLUMN_SIZE, file.length()); - row.add(Document.COLUMN_MIME_TYPE, mimeType); - row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified()); - row.add(Document.COLUMN_FLAGS, flags); - row.add(Document.COLUMN_ICON, R.mipmap.ic_launcher); - } + final MatrixCursor.RowBuilder row = result.newRow(); + row.add(Document.COLUMN_DOCUMENT_ID, docId); + row.add(Document.COLUMN_DISPLAY_NAME, displayName); + row.add(Document.COLUMN_SIZE, file.length()); + row.add(Document.COLUMN_MIME_TYPE, mimeType); + row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified()); + row.add(Document.COLUMN_FLAGS, flags); + row.add(Document.COLUMN_ICON, R.mipmap.ic_launcher); + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/BaseFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/BaseFragment.java index 7319e9287..f2f7f0f76 100755 --- a/app/src/main/java/com/itsaky/androidide/fragments/BaseFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/BaseFragment.java @@ -25,7 +25,7 @@ import androidx.fragment.app.Fragment; import com.itsaky.androidide.app.StudioActivity; public class BaseFragment extends Fragment { - protected StudioActivity getStudioActivity() { - return (StudioActivity) getActivity(); - } + protected StudioActivity getStudioActivity() { + return (StudioActivity) getActivity(); + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/CrashReportFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/CrashReportFragment.java index 1d5ed9524..e94aa4149 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/CrashReportFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/CrashReportFragment.java @@ -39,116 +39,116 @@ import com.itsaky.androidide.databinding.LayoutCrashReportBinding; public class CrashReportFragment extends Fragment { - public static final String KEY_TITLE = "crash_title"; - public static final String KEY_MESSAGE = "crash_message"; - public static final String KEY_TRACE = "crash_trace"; - public static final String KEY_CLOSE_APP_ON_CLICK = "close_on_app_click"; - private LayoutCrashReportBinding binding; - private boolean closeAppOnClick = true; + public static final String KEY_TITLE = "crash_title"; + public static final String KEY_MESSAGE = "crash_message"; + public static final String KEY_TRACE = "crash_trace"; + public static final String KEY_CLOSE_APP_ON_CLICK = "close_on_app_click"; + private LayoutCrashReportBinding binding; + private boolean closeAppOnClick = true; - @NonNull - public static CrashReportFragment newInstance(@NonNull final String trace) { - return CrashReportFragment.newInstance(null, null, trace, true); + @NonNull + public static CrashReportFragment newInstance(@NonNull final String trace) { + return CrashReportFragment.newInstance(null, null, trace, true); + } + + @NonNull + public static CrashReportFragment newInstance( + @Nullable final String title, + @Nullable final String message, + @NonNull final String trace, + boolean closeAppOnClick) { + final var frag = new CrashReportFragment(); + final var args = new Bundle(); + + args.putString(KEY_TRACE, trace); + args.putBoolean(KEY_CLOSE_APP_ON_CLICK, closeAppOnClick); + + if (title != null) { + args.putString(KEY_TITLE, title); } - @NonNull - public static CrashReportFragment newInstance( - @Nullable final String title, - @Nullable final String message, - @NonNull final String trace, - boolean closeAppOnClick) { - final var frag = new CrashReportFragment(); - final var args = new Bundle(); - - args.putString(KEY_TRACE, trace); - args.putBoolean(KEY_CLOSE_APP_ON_CLICK, closeAppOnClick); - - if (title != null) { - args.putString(KEY_TITLE, title); - } - - if (message != null) { - args.putString(KEY_MESSAGE, message); - } - - frag.setArguments(args); - return frag; + if (message != null) { + args.putString(KEY_MESSAGE, message); } - @Nullable - @Override - public View onCreateView( - @NonNull LayoutInflater inflater, - @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - return (binding = LayoutCrashReportBinding.inflate(inflater, container, false)).getRoot(); + frag.setArguments(args); + return frag; + } + + @Nullable + @Override + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + return (binding = LayoutCrashReportBinding.inflate(inflater, container, false)).getRoot(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + final var args = requireArguments(); + this.closeAppOnClick = args.getBoolean(KEY_CLOSE_APP_ON_CLICK); + var title = getString(R.string.msg_ide_crashed); + var message = getString(R.string.msg_report_crash); + var trace = ""; + if (args.containsKey(KEY_TITLE)) { + title = args.getString(KEY_TITLE); } - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - final var args = requireArguments(); - this.closeAppOnClick = args.getBoolean(KEY_CLOSE_APP_ON_CLICK); - var title = getString(R.string.msg_ide_crashed); - var message = getString(R.string.msg_report_crash); - var trace = ""; - if (args.containsKey(KEY_TITLE)) { - title = args.getString(KEY_TITLE); - } - - if (args.containsKey(KEY_MESSAGE)) { - message = args.getString(KEY_MESSAGE); - } - - if (args.containsKey(KEY_TRACE)) { - trace = args.getString(KEY_TRACE); - trace = buildReportText(trace); - } else { - trace = "No stack strace was provided for the report"; - } - - binding.crashTitle.setText(title); - binding.crashSubtitle.setText(message); - binding.logText.setText(trace); - - final var report = trace; - binding.closeButton.setOnClickListener( - v -> { - if (closeAppOnClick) { - requireActivity().finishAffinity(); - } else { - requireActivity().finish(); - } - }); - binding.reportButton.setOnClickListener(v -> reportTrace(report)); + if (args.containsKey(KEY_MESSAGE)) { + message = args.getString(KEY_MESSAGE); } - private void reportTrace(String report) { - ClipboardUtils.copyText("AndroidIDE CrashLog", report); - - final var url = BaseApplication.GITHUB_URL.concat("/issues"); - final var intent = new Intent(); - intent.setAction(Intent.ACTION_VIEW); - intent.setData(Uri.parse(url)); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); + if (args.containsKey(KEY_TRACE)) { + trace = args.getString(KEY_TRACE); + trace = buildReportText(trace); + } else { + trace = "No stack strace was provided for the report"; } - @NonNull - private String buildReportText(String trace) { - return "AndroidIDE crash report\n" - + "Manufacturer: " - + getManufacturer() - + "\n" - + "Device: " - + getModel() - + "\n" - + "App version: " - + getAppVersionName() - + " (" - + getAppVersionCode() - + ")\n\n Stacktrace: \n" - + trace; - } + binding.crashTitle.setText(title); + binding.crashSubtitle.setText(message); + binding.logText.setText(trace); + + final var report = trace; + binding.closeButton.setOnClickListener( + v -> { + if (closeAppOnClick) { + requireActivity().finishAffinity(); + } else { + requireActivity().finish(); + } + }); + binding.reportButton.setOnClickListener(v -> reportTrace(report)); + } + + private void reportTrace(String report) { + ClipboardUtils.copyText("AndroidIDE CrashLog", report); + + final var url = BaseApplication.GITHUB_URL.concat("/issues"); + final var intent = new Intent(); + intent.setAction(Intent.ACTION_VIEW); + intent.setData(Uri.parse(url)); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + } + + @NonNull + private String buildReportText(String trace) { + return "AndroidIDE crash report\n" + + "Manufacturer: " + + getManufacturer() + + "\n" + + "Device: " + + getModel() + + "\n" + + "App version: " + + getAppVersionName() + + " (" + + getAppVersionCode() + + ")\n\n Stacktrace: \n" + + trace; + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/DiagnosticsListFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/DiagnosticsListFragment.java index 58e7cb9b4..257fe0c8d 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/DiagnosticsListFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/DiagnosticsListFragment.java @@ -36,54 +36,52 @@ import java.util.ArrayList; public class DiagnosticsListFragment extends Fragment { - private RecyclerView list; - private EmptyView emptyView; + private RecyclerView list; + private EmptyView emptyView; - private DiagnosticsAdapter unsavedAdapter; + private DiagnosticsAdapter unsavedAdapter; - @Nullable - @Override - public View onCreateView( - @NonNull LayoutInflater inflater, - @Nullable ViewGroup parent, - @Nullable Bundle savedInstanceState) { - final var container = new LinearLayout(inflater.getContext()); - container.setOrientation(LinearLayout.VERTICAL); - container.setLayoutParams(new ViewGroup.LayoutParams(-1, -1)); + @Nullable + @Override + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup parent, + @Nullable Bundle savedInstanceState) { + final var container = new LinearLayout(inflater.getContext()); + container.setOrientation(LinearLayout.VERTICAL); + container.setLayoutParams(new ViewGroup.LayoutParams(-1, -1)); - this.list = new RecyclerView(inflater.getContext()); - this.emptyView = new EmptyView(inflater.getContext()); + this.list = new RecyclerView(inflater.getContext()); + this.emptyView = new EmptyView(inflater.getContext()); - container.addView(list, new LinearLayout.LayoutParams(-1, -1)); - container.addView(emptyView, new LinearLayout.LayoutParams(-1, -1)); + container.addView(list, new LinearLayout.LayoutParams(-1, -1)); + container.addView(emptyView, new LinearLayout.LayoutParams(-1, -1)); - return container; + return container; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + list.setLayoutManager(new LinearLayoutManager(list.getContext())); + list.setAdapter( + unsavedAdapter != null ? unsavedAdapter : new DiagnosticsAdapter(new ArrayList<>(), null)); + unsavedAdapter = null; + } + + public void setAdapter(@NonNull DiagnosticsAdapter adapter) { + if (list != null) { + list.setAdapter(adapter); + } else { + unsavedAdapter = adapter; } + } - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - list.setLayoutManager(new LinearLayoutManager(list.getContext())); - list.setAdapter( - unsavedAdapter != null - ? unsavedAdapter - : new DiagnosticsAdapter(new ArrayList<>(), null)); - unsavedAdapter = null; - } - - public void setAdapter(@NonNull DiagnosticsAdapter adapter) { - if (list != null) { - list.setAdapter(adapter); - } else { - unsavedAdapter = adapter; - } - } - - public void handleResultVisibility(boolean error) { - if (emptyView != null && list != null) { - emptyView.setVisibility(error ? View.VISIBLE : View.GONE); - list.setVisibility(error ? View.GONE : View.VISIBLE); - } + public void handleResultVisibility(boolean error) { + if (emptyView != null && list != null) { + emptyView.setVisibility(error ? View.VISIBLE : View.GONE); + list.setVisibility(error ? View.GONE : View.VISIBLE); } + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/FileTreeFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/FileTreeFragment.java index 70779bd1b..f9adfc2e3 100755 --- a/app/src/main/java/com/itsaky/androidide/fragments/FileTreeFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/FileTreeFragment.java @@ -56,290 +56,285 @@ import java.util.HashSet; import java.util.Set; public class FileTreeFragment extends BottomSheetDialogFragment - implements TreeNode.TreeNodeClickListener, TreeNode.TreeNodeLongClickListener { + implements TreeNode.TreeNodeClickListener, TreeNode.TreeNodeLongClickListener { - private static final String KEY_STORED_TREE_STATE = "fileTree_state"; - private static final ILogger LOG = ILogger.newInstance("FileTreeFragment"); - private LayoutEditorFileTreeBinding binding; - private AndroidTreeView mFileTreeView; - private FileActionListener mFileActionListener; - private TreeNode mRoot; - private String mTreeState; + private static final String KEY_STORED_TREE_STATE = "fileTree_state"; + private static final ILogger LOG = ILogger.newInstance("FileTreeFragment"); + private LayoutEditorFileTreeBinding binding; + private AndroidTreeView mFileTreeView; + private FileActionListener mFileActionListener; + private TreeNode mRoot; + private String mTreeState; - public FileTreeFragment() {} + public FileTreeFragment() {} - @NonNull - public static FileTreeFragment newInstance() { - return new FileTreeFragment(); + @NonNull + public static FileTreeFragment newInstance() { + return new FileTreeFragment(); + } + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + mFileActionListener = (FileActionListener) context; + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + saveTreeState(); + outState.putString(KEY_STORED_TREE_STATE, mTreeState); + } + + public void saveTreeState() { + if (mFileTreeView != null) { + mTreeState = mFileTreeView.getSaveState(); + } else { + LOG.error("Unable to save tree state. TreeView is null."); + mTreeState = null; + } + } + + @Override + public View onCreateView( + @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + binding = LayoutEditorFileTreeBinding.inflate(inflater, container, false); + return binding.getRoot(); + } + + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + if (savedInstanceState != null && savedInstanceState.containsKey(KEY_STORED_TREE_STATE)) { + mTreeState = savedInstanceState.getString(KEY_STORED_TREE_STATE, null); } - @Override - public void onAttach(@NonNull Context context) { - super.onAttach(context); - mFileActionListener = (FileActionListener) context; + listProjectFiles(); + } + + @Override + public void onClick(TreeNode node, Object p2) { + final File f = (File) p2; + if (!f.exists()) { + return; } - @Override - public void onSaveInstanceState(@NonNull Bundle outState) { - super.onSaveInstanceState(outState); - saveTreeState(); - outState.putString(KEY_STORED_TREE_STATE, mTreeState); - } - - public void saveTreeState() { - if (mFileTreeView != null) { - mTreeState = mFileTreeView.getSaveState(); + if (f.isFile()) { + if (f.getName().endsWith(".apk")) { + final var intent = IntentUtils.getInstallAppIntent(f); + if (intent != null) { + requireContext().startActivity(intent); } else { - LOG.error("Unable to save tree state. TreeView is null."); - mTreeState = null; + StudioApp.getInstance() + .toast(getString(R.string.msg_apk_install_intent_failed), Toaster.Type.ERROR); } + } else if (mFileActionListener != null && (FileUtils.isUtf8(f) || f.length() == 0)) { + mFileActionListener.openFile(f); + } + } else if (f.isDirectory() && f.exists()) { + if (node.isExpanded()) { + collapseNode(node); + } else if (f.getAbsolutePath().equals(Environment.GRADLE_USER_HOME.getAbsolutePath()) + && !node.isExpanded()) { + expandNode(node); + } else { + setLoading(node); + listNode( + node, + () -> { + updateChevron(node); + expandNode(node); + }); + } + } + } + + public void collapseNode(TreeNode node) { + if (mFileTreeView == null) { + return; } - @Override - public View onCreateView( - @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - binding = LayoutEditorFileTreeBinding.inflate(inflater, container, false); - return binding.getRoot(); + TransitionManager.beginDelayedTransition(binding.getRoot(), new ChangeBounds()); + mFileTreeView.collapseNode(node); + } + + public void expandNode(TreeNode node) { + if (mFileTreeView == null) { + return; } - @Override - public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); + TransitionManager.beginDelayedTransition(binding.getRoot(), new ChangeBounds()); + mFileTreeView.expandNode(node); + } - if (savedInstanceState != null && savedInstanceState.containsKey(KEY_STORED_TREE_STATE)) { - mTreeState = savedInstanceState.getString(KEY_STORED_TREE_STATE, null); - } + private void setLoading(@NonNull TreeNode node) { + if (node.getViewHolder() instanceof FileTreeViewHolder) { + ((FileTreeViewHolder) node.getViewHolder()).setLoading(); + } + } - listProjectFiles(); + private void listNode(@NonNull TreeNode node, Runnable whenDone) { + + if (whenDone == null) { + whenDone = () -> {}; } - @Override - public void onClick(TreeNode node, Object p2) { - final File f = (File) p2; - if (!f.exists()) { - return; - } + node.getChildren().clear(); + node.setExpanded(false); - if (f.isFile()) { - if (f.getName().endsWith(".apk")) { - final var intent = IntentUtils.getInstallAppIntent(f); - if (intent != null) { - requireContext().startActivity(intent); - } else { - StudioApp.getInstance() - .toast( - getString(R.string.msg_apk_install_intent_failed), - Toaster.Type.ERROR); - } - } else if (mFileActionListener != null && (FileUtils.isUtf8(f) || f.length() == 0)) { - mFileActionListener.openFile(f); + final var finalWhenDone = whenDone; + TaskExecutor.execAsync( + () -> { + getNodeFromFiles(node.getValue().listFiles(), node); + TreeNode temp = node; + while (temp.size() == 1) { + temp = temp.childAt(0); + if (!temp.getValue().isDirectory()) { + break; } - } else if (f.isDirectory() && f.exists()) { - if (node.isExpanded()) { - collapseNode(node); - } else if (f.getAbsolutePath().equals(Environment.GRADLE_USER_HOME.getAbsolutePath()) - && !node.isExpanded()) { - expandNode(node); - } else { - setLoading(node); - listNode( - node, - () -> { - updateChevron(node); - expandNode(node); - }); - } - } + getNodeFromFiles(temp.getValue().listFiles(), temp); + temp.setExpanded(true); + } + return null; + }, + __ -> finalWhenDone.run()); + } + + private void updateChevron(@NonNull TreeNode node) { + if (node.getViewHolder() instanceof FileTreeViewHolder) { + ((FileTreeViewHolder) node.getViewHolder()).updateChevron(!node.isExpanded()); + } + } + + private void getNodeFromFiles(File[] files, TreeNode parent) { + Arrays.sort(files, new FileTreeCallable.SortFileName()); + Arrays.sort(files, new FileTreeCallable.SortFolder()); + for (File file : files) { + TreeNode node = new TreeNode(file); + node.setViewHolder(new FileTreeViewHolder(getContext())); + parent.addChild(node); + } + } + + @Override + public boolean onLongClick(TreeNode node, Object value) { + if (mFileActionListener != null) { + mFileActionListener.showFileOptions((File) value, node); + } + return true; + } + + public void listProjectFiles() { + final var projectDirPath = ProjectManager.INSTANCE.getProjectDirPath(); + if (projectDirPath == null) { + return; } - public void collapseNode(TreeNode node) { - if (mFileTreeView == null) { - return; - } + final File gradleProps = Environment.GRADLE_PROPS; + final File gradleHome = Environment.GRADLE_USER_HOME; + File projectDir = new File(projectDirPath); + mRoot = TreeNode.root(projectDir); + if (gradleHome.exists() && gradleHome.isDirectory()) { + if (!gradleProps.exists()) { + FileIOUtils.writeFileFromString( + gradleProps, + "# Specify global Gradle properties in this file\n" + + "# These properties will be applicable for every project you build" + + " with Gradle."); + } + TreeNode home = new TreeNode(gradleHome); + home.setViewHolder(new FileTreeViewHolder(getContext())); + TreeNode prop = new TreeNode(gradleProps); + prop.setViewHolder(new FileTreeViewHolder(getContext())); + home.addChild(prop); + mRoot.addChild(home); + } + mRoot.setViewHolder(new FileTreeViewHolder(getContext())); - TransitionManager.beginDelayedTransition(binding.getRoot(), new ChangeBounds()); - mFileTreeView.collapseNode(node); + getScrollView().setVisibility(View.GONE); + getLoadingProgress().setVisibility(View.VISIBLE); + new TaskExecutor() + .executeAsync( + new FileTreeCallable(getContext(), mRoot, projectDir), + (result) -> { + getScrollView().setVisibility(View.VISIBLE); + getLoadingProgress().setVisibility(View.GONE); + AndroidTreeView tree = createTreeView(mRoot); + if (tree != null) { + tree.setUseAutoToggle(false); + tree.setDefaultNodeClickListener(FileTreeFragment.this); + tree.setDefaultNodeLongClickListener(FileTreeFragment.this); + getScrollView().removeAllViews(); + + final var view = tree.getView(); + getScrollView().addView(view); + + view.post(this::tryRestoreState); + } + }); + } + + public AndroidTreeView createTreeView(TreeNode node) { + Context ctx = null; + if (getActivity() != null) { + ctx = getActivity(); + } else if (getContext() != null) { + ctx = getContext(); } - public void expandNode(TreeNode node) { - if (mFileTreeView == null) { - return; - } - - TransitionManager.beginDelayedTransition(binding.getRoot(), new ChangeBounds()); - mFileTreeView.expandNode(node); + if (ctx == null) { + return null; } - private void setLoading(@NonNull TreeNode node) { - if (node.getViewHolder() instanceof FileTreeViewHolder) { - ((FileTreeViewHolder) node.getViewHolder()).setLoading(); - } + return mFileTreeView = new AndroidTreeView(ctx, node, R.drawable.bg_ripple); + } + + public HorizontalScrollView getScrollView() { + return binding.filetreeHorizontalScrollView; + } + + public ProgressBar getLoadingProgress() { + return binding.fileTreeLoadingProgress; + } + + private void tryRestoreState() { + tryRestoreState(mTreeState); + } + + private void tryRestoreState(String state) { + if (!TextUtils.isEmpty(state) && mFileTreeView != null) { + + LOG.debug("Restoring tree view state:", "'" + state + "'"); + + mFileTreeView.collapseAll(); + final var openNodesArray = state.split(NODES_PATH_SEPARATOR); + final var openNodes = new HashSet<>(Arrays.asList(openNodesArray)); + restoreNodeState(mRoot, openNodes); + } else { + LOG.error("Unable to restore tree state", "treeState=" + state, "treeView=" + mFileTreeView); } + } - private void listNode(@NonNull TreeNode node, Runnable whenDone) { - - if (whenDone == null) { - whenDone = () -> {}; - } - - node.getChildren().clear(); - node.setExpanded(false); - - final var finalWhenDone = whenDone; - TaskExecutor.execAsync( - () -> { - getNodeFromFiles(node.getValue().listFiles(), node); - TreeNode temp = node; - while (temp.size() == 1) { - temp = temp.childAt(0); - if (!temp.getValue().isDirectory()) { - break; - } - getNodeFromFiles(temp.getValue().listFiles(), temp); - temp.setExpanded(true); - } - return null; - }, - __ -> finalWhenDone.run()); + private void restoreNodeState(@NonNull TreeNode root, Set openNodes) { + final var children = root.getChildren(); + for (int i = 0, childrenSize = children.size(); i < childrenSize; i++) { + final var node = children.get(i); + if (openNodes.contains(node.getPath())) { + listNode( + node, + () -> { + updateChevron(node); + expandNode(node); + restoreNodeState(node, openNodes); + }); + } } + } - private void updateChevron(@NonNull TreeNode node) { - if (node.getViewHolder() instanceof FileTreeViewHolder) { - ((FileTreeViewHolder) node.getViewHolder()).updateChevron(!node.isExpanded()); - } - } + public interface FileActionListener { + CodeEditorView openFile(File file); - private void getNodeFromFiles(File[] files, TreeNode parent) { - Arrays.sort(files, new FileTreeCallable.SortFileName()); - Arrays.sort(files, new FileTreeCallable.SortFolder()); - for (File file : files) { - TreeNode node = new TreeNode(file); - node.setViewHolder(new FileTreeViewHolder(getContext())); - parent.addChild(node); - } - } - - @Override - public boolean onLongClick(TreeNode node, Object value) { - if (mFileActionListener != null) { - mFileActionListener.showFileOptions((File) value, node); - } - return true; - } - - public void listProjectFiles() { - final var projectDirPath = ProjectManager.INSTANCE.getProjectDirPath (); - if (projectDirPath == null) { - return; - } - - final File gradleProps = Environment.GRADLE_PROPS; - final File gradleHome = Environment.GRADLE_USER_HOME; - File projectDir = new File(projectDirPath); - mRoot = TreeNode.root(projectDir); - if (gradleHome.exists() && gradleHome.isDirectory()) { - if (!gradleProps.exists()) { - FileIOUtils.writeFileFromString( - gradleProps, - "# Specify global Gradle properties in this file\n" - + "# These properties will be applicable for every project you build" - + " with Gradle."); - } - TreeNode home = new TreeNode(gradleHome); - home.setViewHolder(new FileTreeViewHolder(getContext())); - TreeNode prop = new TreeNode(gradleProps); - prop.setViewHolder(new FileTreeViewHolder(getContext())); - home.addChild(prop); - mRoot.addChild(home); - } - mRoot.setViewHolder(new FileTreeViewHolder(getContext())); - - getScrollView().setVisibility(View.GONE); - getLoadingProgress().setVisibility(View.VISIBLE); - new TaskExecutor() - .executeAsync( - new FileTreeCallable(getContext(), mRoot, projectDir), - (result) -> { - getScrollView().setVisibility(View.VISIBLE); - getLoadingProgress().setVisibility(View.GONE); - AndroidTreeView tree = createTreeView(mRoot); - if (tree != null) { - tree.setUseAutoToggle(false); - tree.setDefaultNodeClickListener(FileTreeFragment.this); - tree.setDefaultNodeLongClickListener(FileTreeFragment.this); - getScrollView().removeAllViews(); - - final var view = tree.getView(); - getScrollView().addView(view); - - view.post(this::tryRestoreState); - } - }); - } - - public AndroidTreeView createTreeView(TreeNode node) { - Context ctx = null; - if (getActivity() != null) { - ctx = getActivity(); - } else if (getContext() != null) { - ctx = getContext(); - } - - if (ctx == null) { - return null; - } - - return mFileTreeView = new AndroidTreeView(ctx, node, R.drawable.bg_ripple); - } - - public HorizontalScrollView getScrollView() { - return binding.filetreeHorizontalScrollView; - } - - public ProgressBar getLoadingProgress() { - return binding.fileTreeLoadingProgress; - } - - private void tryRestoreState() { - tryRestoreState(mTreeState); - } - - private void tryRestoreState(String state) { - if (!TextUtils.isEmpty(state) && mFileTreeView != null) { - - LOG.debug("Restoring tree view state:", "'" + state + "'"); - - mFileTreeView.collapseAll(); - final var openNodesArray = state.split(NODES_PATH_SEPARATOR); - final var openNodes = new HashSet<>(Arrays.asList(openNodesArray)); - restoreNodeState(mRoot, openNodes); - } else { - LOG.error( - "Unable to restore tree state", - "treeState=" + state, - "treeView=" + mFileTreeView); - } - } - - private void restoreNodeState(@NonNull TreeNode root, Set openNodes) { - final var children = root.getChildren(); - for (int i = 0, childrenSize = children.size(); i < childrenSize; i++) { - final var node = children.get(i); - if (openNodes.contains(node.getPath())) { - listNode( - node, - () -> { - updateChevron(node); - expandNode(node); - restoreNodeState(node, openNodes); - }); - } - } - } - - public interface FileActionListener { - CodeEditorView openFile(File file); - - void showFileOptions(File file, TreeNode node); - } + void showFileOptions(File file, TreeNode node); + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/IDELogFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/IDELogFragment.java index 8d4aba8fa..12215fad8 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/IDELogFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/IDELogFragment.java @@ -24,30 +24,30 @@ import com.itsaky.androidide.utils.ILogger; public class IDELogFragment extends LogViewFragment { - private final ILogger.LogListener listener = this::log; + private final ILogger.LogListener listener = this::log; - public IDELogFragment() { - ILogger.addLogListener(listener); - } + public IDELogFragment() { + ILogger.addLogListener(listener); + } - @Override - protected String onCreateLogString(@NonNull LogLine line) { - return line.toSimpleString(); - } + @Override + protected String onCreateLogString(@NonNull LogLine line) { + return line.toSimpleString(); + } - private void log(int priority, String tag, @NonNull String message) { - if (message.contains("\n")) { - final var split = message.split("\n"); - for (var line : split) { - logLine(priority, tag, line); - } - } else { - logLine(priority, tag, message); - } + private void log(int priority, String tag, @NonNull String message) { + if (message.contains("\n")) { + final var split = message.split("\n"); + for (var line : split) { + logLine(priority, tag, line); + } + } else { + logLine(priority, tag, message); } + } - private void logLine(int priority, String tag, String message) { - final var line = new LogLine(ILogger.priorityChar(priority), tag, message); - appendLog(line); - } + private void logLine(int priority, String tag, String message) { + final var line = new LogLine(ILogger.priorityChar(priority), tag, message); + appendLog(line); + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/LogViewFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/LogViewFragment.java index 6828d07bf..e7be4b12c 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/LogViewFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/LogViewFragment.java @@ -35,47 +35,47 @@ import io.github.rosemoe.sora.lang.Language; public class LogViewFragment extends NonEditableEditorFragment { - private final Language language = getLanguage(); - private final List unsavedLines = new ArrayList<>(); + private final Language language = getLanguage(); + private final List unsavedLines = new ArrayList<>(); - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - Objects.requireNonNull(getEditor()).setEditorLanguage(language); - - if (!unsavedLines.isEmpty()) { - for (var line : unsavedLines) { - applyToLanguage(line); - getEditor().append(line.toString().trim() + "\n"); - } - unsavedLines.clear(); - } - } - - private void applyToLanguage(LogLine line) { - if (language instanceof LogLanguageImpl) { - ((LogLanguageImpl) this.language).addLine(line); - } - } - - public void appendLog(LogLine line) { - if (getEditor() == null) { - unsavedLines.add(line); - return; - } + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + Objects.requireNonNull(getEditor()).setEditorLanguage(language); + if (!unsavedLines.isEmpty()) { + for (var line : unsavedLines) { applyToLanguage(line); + getEditor().append(line.toString().trim() + "\n"); + } + unsavedLines.clear(); + } + } - final var lineString = onCreateLogString(line); - final var msg = lineString.endsWith("\n") ? lineString : lineString + "\n"; - ThreadUtils.runOnUiThread(() -> getEditor().append(msg)); + private void applyToLanguage(LogLine line) { + if (language instanceof LogLanguageImpl) { + ((LogLanguageImpl) this.language).addLine(line); + } + } + + public void appendLog(LogLine line) { + if (getEditor() == null) { + unsavedLines.add(line); + return; } - protected String onCreateLogString(@NonNull LogLine line) { - return line.toString(); - } + applyToLanguage(line); - protected Language getLanguage() { - return new LogLanguageImpl(); - } + final var lineString = onCreateLogString(line); + final var msg = lineString.endsWith("\n") ? lineString : lineString + "\n"; + ThreadUtils.runOnUiThread(() -> getEditor().append(msg)); + } + + protected String onCreateLogString(@NonNull LogLine line) { + return line.toString(); + } + + protected Language getLanguage() { + return new LogLanguageImpl(); + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/NonEditableEditorFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/NonEditableEditorFragment.java index e040870d2..0b400b498 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/NonEditableEditorFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/NonEditableEditorFragment.java @@ -36,39 +36,39 @@ import io.github.rosemoe.sora.lang.EmptyLanguage; public abstract class NonEditableEditorFragment extends Fragment { - private static final ILogger LOG = ILogger.newInstance("NonEditableEditorFragment"); - private FragmentNonEditableEditorBinding binding; + private static final ILogger LOG = ILogger.newInstance("NonEditableEditorFragment"); + private FragmentNonEditableEditorBinding binding; - @Nullable - @Override - public View onCreateView( - @NonNull LayoutInflater inflater, - @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - this.binding = FragmentNonEditableEditorBinding.inflate(inflater, container, false); - return binding.getRoot(); - } + @Nullable + @Override + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + this.binding = FragmentNonEditableEditorBinding.inflate(inflater, container, false); + return binding.getRoot(); + } - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - final var editor = binding.getRoot(); - editor.setEditable(false); - editor.setDividerWidth(0); - editor.setEditorLanguage(new EmptyLanguage()); - editor.setWordwrap(false); - editor.setUndoEnabled(false); - editor.setTypefaceLineNumber(TypefaceUtils.jetbrainsMono()); - editor.setTypefaceText(TypefaceUtils.jetbrainsMono()); - editor.setTextSize(12); - editor.setColorScheme(new SchemeAndroidIDE()); - } + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + final var editor = binding.getRoot(); + editor.setEditable(false); + editor.setDividerWidth(0); + editor.setEditorLanguage(new EmptyLanguage()); + editor.setWordwrap(false); + editor.setUndoEnabled(false); + editor.setTypefaceLineNumber(TypefaceUtils.jetbrainsMono()); + editor.setTypefaceText(TypefaceUtils.jetbrainsMono()); + editor.setTextSize(12); + editor.setColorScheme(new SchemeAndroidIDE()); + } - @Nullable - public IDEEditor getEditor() { - if (binding == null) { - return null; - } - return binding.editor; + @Nullable + public IDEEditor getEditor() { + if (binding == null) { + return null; } + return binding.editor; + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/SearchResultFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/SearchResultFragment.java index 551587a49..65cfd14ea 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/SearchResultFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/SearchResultFragment.java @@ -34,52 +34,52 @@ import com.itsaky.androidide.views.EmptyView; public class SearchResultFragment extends Fragment { - private RecyclerView list; - private EmptyView emptyView; + private RecyclerView list; + private EmptyView emptyView; - private SearchListAdapter unsavedAdapter; + private SearchListAdapter unsavedAdapter; - @Nullable - @Override - public View onCreateView( - @NonNull LayoutInflater inflater, - @Nullable ViewGroup parent, - @Nullable Bundle savedInstanceState) { - final var container = new LinearLayout(inflater.getContext()); - container.setOrientation(LinearLayout.VERTICAL); - container.setLayoutParams(new ViewGroup.LayoutParams(-1, -1)); + @Nullable + @Override + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup parent, + @Nullable Bundle savedInstanceState) { + final var container = new LinearLayout(inflater.getContext()); + container.setOrientation(LinearLayout.VERTICAL); + container.setLayoutParams(new ViewGroup.LayoutParams(-1, -1)); - this.list = new RecyclerView(inflater.getContext()); - this.emptyView = new EmptyView(inflater.getContext()); + this.list = new RecyclerView(inflater.getContext()); + this.emptyView = new EmptyView(inflater.getContext()); - container.addView(list, new LinearLayout.LayoutParams(-1, -1)); - container.addView(emptyView, new LinearLayout.LayoutParams(-1, -1)); + container.addView(list, new LinearLayout.LayoutParams(-1, -1)); + container.addView(emptyView, new LinearLayout.LayoutParams(-1, -1)); - return container; + return container; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + list.setLayoutManager(new LinearLayoutManager(list.getContext())); + list.setAdapter( + unsavedAdapter != null ? unsavedAdapter : new SearchListAdapter(null, null, null)); + unsavedAdapter = null; + } + + public void setAdapter(@NonNull final SearchListAdapter adapter) { + if (list == null) { + unsavedAdapter = adapter; + return; } + this.list.setAdapter(adapter); + } - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - list.setLayoutManager(new LinearLayoutManager(list.getContext())); - list.setAdapter( - unsavedAdapter != null ? unsavedAdapter : new SearchListAdapter(null, null, null)); - unsavedAdapter = null; - } - - public void setAdapter(@NonNull final SearchListAdapter adapter) { - if (list == null) { - unsavedAdapter = adapter; - return; - } - this.list.setAdapter(adapter); - } - - public void handleResultVisibility(boolean error) { - if (emptyView != null && list != null) { - emptyView.setVisibility(error ? View.VISIBLE : View.GONE); - list.setVisibility(error ? View.GONE : View.VISIBLE); - } + public void handleResultVisibility(boolean error) { + if (emptyView != null && list != null) { + emptyView.setVisibility(error ? View.VISIBLE : View.GONE); + list.setVisibility(error ? View.GONE : View.VISIBLE); } + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/SimpleOutputFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/SimpleOutputFragment.java index 19c848477..cb32e9fd7 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/SimpleOutputFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/SimpleOutputFragment.java @@ -31,30 +31,29 @@ import java.util.Objects; public class SimpleOutputFragment extends NonEditableEditorFragment { - private final List unsavedLines = new ArrayList<>(); + private final List unsavedLines = new ArrayList<>(); - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); - if (!unsavedLines.isEmpty()) { - for (String line : unsavedLines) { - Objects.requireNonNull(getEditor()).append(line.trim() + "\n"); - } - unsavedLines.clear(); - } + if (!unsavedLines.isEmpty()) { + for (String line : unsavedLines) { + Objects.requireNonNull(getEditor()).append(line.trim() + "\n"); + } + unsavedLines.clear(); } + } - public void appendOutput(String output) { - if (getEditor() == null) { - unsavedLines.add(output); - return; - } - ThreadUtils.runOnUiThread( - () -> { - final var message = - output == null || output.endsWith("\n") ? output : output + "\n"; - getEditor().append(message); - }); + public void appendOutput(String output) { + if (getEditor() == null) { + unsavedLines.add(output); + return; } + ThreadUtils.runOnUiThread( + () -> { + final var message = output == null || output.endsWith("\n") ? output : output + "\n"; + getEditor().append(message); + }); + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/attr/AbstractReferenceEditor.java b/app/src/main/java/com/itsaky/androidide/fragments/attr/AbstractReferenceEditor.java index 9f843dc5b..0e35367ad 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/attr/AbstractReferenceEditor.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/attr/AbstractReferenceEditor.java @@ -38,67 +38,66 @@ import java.util.concurrent.CompletableFuture; */ public abstract class AbstractReferenceEditor extends BaseValueEditorFragment { - private static final ILogger LOG = ILogger.newInstance("ReferenceEditor"); - public TextWatcherAdapter resInputWatcher; + private static final ILogger LOG = ILogger.newInstance("ReferenceEditor"); + public TextWatcherAdapter resInputWatcher; - @Override - protected void notifyValueChanged(@NonNull String newValue) { - try { - super.notifyValueChanged(newValue); - } catch (Throwable e) { - LOG.error("Unable to update resource value to '" + newValue + "'", e); - } + @Override + protected void notifyValueChanged(@NonNull String newValue) { + try { + super.notifyValueChanged(newValue); + } catch (Throwable e) { + LOG.error("Unable to update resource value to '" + newValue + "'", e); + } + } + + protected void setupReferenceInput(MaterialAutoCompleteTextView referenceInput) { + Objects.requireNonNull(referenceInput); + + referenceInput.setText(attribute.getValue()); + + if (resInputWatcher != null) { + referenceInput.removeTextChangedListener(resInputWatcher); } - protected void setupReferenceInput(MaterialAutoCompleteTextView referenceInput) { - Objects.requireNonNull(referenceInput); + resInputWatcher = + new TextWatcherAdapter() { + @Override + public void afterTextChanged(@NonNull Editable s) { + final var text = s.toString().trim(); + if (TextUtils.isEmpty(text)) { + return; + } - referenceInput.setText(attribute.getValue()); + notifyValueChanged(text); + } + }; - if (resInputWatcher != null) { - referenceInput.removeTextChangedListener(resInputWatcher); - } + referenceInput.addTextChangedListener(resInputWatcher); - resInputWatcher = - new TextWatcherAdapter() { - @Override - public void afterTextChanged(@NonNull Editable s) { - final var text = s.toString().trim(); - if (TextUtils.isEmpty(text)) { - return; - } + CompletableFuture.supplyAsync(this::computeReferenceItems) + .whenComplete((list, error) -> handleAutoCompleteResult(list, error, referenceInput)); + } - notifyValueChanged(text); - } - }; - - referenceInput.addTextChangedListener(resInputWatcher); - - CompletableFuture.supplyAsync(this::computeReferenceItems) - .whenComplete( - (list, error) -> handleAutoCompleteResult(list, error, referenceInput)); + protected void handleAutoCompleteResult( + List list, Throwable error, AutoCompleteTextView referenceInput) { + if (list == null || list.isEmpty()) { + LOG.error("No completion items found"); + LOG.error("Error was:", error); + return; } - protected void handleAutoCompleteResult( - List list, Throwable error, AutoCompleteTextView referenceInput) { - if (list == null || list.isEmpty()) { - LOG.error("No completion items found"); - LOG.error("Error was:", error); - return; - } + LOG.debug("Found", list.size(), "resource items for completion"); - LOG.debug("Found", list.size(), "resource items for completion"); + ThreadUtils.runOnUiThread( + () -> { + final var adapter = new AttrValueCompletionAdapter(requireContext(), list); + referenceInput.setThreshold(1); + referenceInput.setAdapter(adapter); + adapter.notifyDataSetChanged(); + }); + } - ThreadUtils.runOnUiThread( - () -> { - final var adapter = new AttrValueCompletionAdapter(requireContext(), list); - referenceInput.setThreshold(1); - referenceInput.setAdapter(adapter); - adapter.notifyDataSetChanged(); - }); - } - - protected List computeReferenceItems() { - return new ArrayList<>(); - } + protected List computeReferenceItems() { + return new ArrayList<>(); + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/attr/BaseValueEditorFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/attr/BaseValueEditorFragment.java index ed38c0153..b29a576d0 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/attr/BaseValueEditorFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/attr/BaseValueEditorFragment.java @@ -37,42 +37,42 @@ import java.util.Objects; */ public class BaseValueEditorFragment extends Fragment { - public static final String KEY_ATTR = "editor_attr"; - public static final String KEY_NAME = "editor_name"; - protected OnValueChangeListener mValueChangeListener; - protected XMLAttribute attribute; - protected String name; + public static final String KEY_ATTR = "editor_attr"; + public static final String KEY_NAME = "editor_name"; + protected OnValueChangeListener mValueChangeListener; + protected XMLAttribute attribute; + protected String name; - @Override - public void onAttach(@NonNull Context context) { - super.onAttach(context); - mValueChangeListener = (OnValueChangeListener) getParentFragment(); + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + mValueChangeListener = (OnValueChangeListener) getParentFragment(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + final var args = requireArguments(); + this.attribute = args.getParcelable(KEY_ATTR); + this.name = args.getString(KEY_NAME); + } + + protected void notifyValueChanged(@NonNull String newValue) { + Objects.requireNonNull(newValue); + + if (mValueChangeListener != null) { + mValueChangeListener.onValueChanged(this.attribute, newValue); } + } - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - final var args = requireArguments(); - this.attribute = args.getParcelable(KEY_ATTR); - this.name = args.getString(KEY_NAME); - } - - protected void notifyValueChanged(@NonNull String newValue) { - Objects.requireNonNull(newValue); - - if (mValueChangeListener != null) { - mValueChangeListener.onValueChanged(this.attribute, newValue); - } - } - - /** Listener for listening for value change event. */ - public interface OnValueChangeListener { - /** - * Called when the value was changed. - * - * @param attribute The attribute whose value was changed. - * @param newValue The new value for the editor. - */ - void onValueChanged(IAttribute attribute, String newValue); - } + /** Listener for listening for value change event. */ + public interface OnValueChangeListener { + /** + * Called when the value was changed. + * + * @param attribute The attribute whose value was changed. + * @param newValue The new value for the editor. + */ + void onValueChanged(IAttribute attribute, String newValue); + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/attr/BooleanEditor.java b/app/src/main/java/com/itsaky/androidide/fragments/attr/BooleanEditor.java index fe613b12a..de0c94234 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/attr/BooleanEditor.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/attr/BooleanEditor.java @@ -41,58 +41,56 @@ import java.util.stream.Collectors; */ public class BooleanEditor extends AbstractReferenceEditor { - private LayoutBooleanAttrEditorBinding binding; + private LayoutBooleanAttrEditorBinding binding; - @Nullable - @Override - public View onCreateView( - @NonNull LayoutInflater inflater, - @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - this.binding = LayoutBooleanAttrEditorBinding.inflate(inflater, container, false); - return this.binding.getRoot(); + @Nullable + @Override + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + this.binding = LayoutBooleanAttrEditorBinding.inflate(inflater, container, false); + return this.binding.getRoot(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + this.binding.choiceGroup.setOnCheckedChangeListener( + (group, checkedId) -> { + final var checked = (RadioButton) group.findViewById(checkedId); + notifyValueChanged(checked.getText().toString().trim()); + }); + + if (!TextUtils.isEmpty(attribute.getValue())) { + switch (attribute.getValue()) { + case "true": + this.binding.boolTrue.setChecked(true); + break; + case "false": + this.binding.boolFalse.setChecked(true); + } } - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); + setupReferenceInput((MaterialAutoCompleteTextView) this.binding.booleanResInput.getEditText()); + } - this.binding.choiceGroup.setOnCheckedChangeListener( - (group, checkedId) -> { - final var checked = (RadioButton) group.findViewById(checkedId); - notifyValueChanged(checked.getText().toString().trim()); - }); - - if (!TextUtils.isEmpty(attribute.getValue())) { - switch (attribute.getValue()) { - case "true": - this.binding.boolTrue.setChecked(true); - break; - case "false": - this.binding.boolFalse.setChecked(true); - } - } - - setupReferenceInput( - (MaterialAutoCompleteTextView) this.binding.booleanResInput.getEditText()); + @Override + protected List computeReferenceItems() { + final var list = new ArrayList(); + final var tables = ValuesTableFactory.getAllTables(); + for (var entry : tables.entrySet()) { + final var dimens = entry.getValue().getTable("bool"); + if (dimens != null) { + list.addAll(dimens.keySet().stream().map("@bool/"::concat).collect(Collectors.toSet())); + } } - @Override - protected List computeReferenceItems() { - final var list = new ArrayList(); - final var tables = ValuesTableFactory.getAllTables(); - for (var entry : tables.entrySet()) { - final var dimens = entry.getValue().getTable("bool"); - if (dimens != null) { - list.addAll( - dimens.keySet().stream().map("@bool/"::concat).collect(Collectors.toSet())); - } - } - - list.addAll( - FrameworkValues.listBools().stream() - .map("@android:bool/"::concat) - .collect(Collectors.toList())); - return list; - } + list.addAll( + FrameworkValues.listBools().stream() + .map("@android:bool/"::concat) + .collect(Collectors.toList())); + return list; + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/attr/ColorEditor.java b/app/src/main/java/com/itsaky/androidide/fragments/attr/ColorEditor.java index 9a08704c0..78d706fa4 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/attr/ColorEditor.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/attr/ColorEditor.java @@ -44,81 +44,77 @@ import java.util.stream.Collectors; */ public class ColorEditor extends AbstractReferenceEditor { - private LayoutColorAttrEditorBinding binding; - private Dialog colorPickerDialog; + private LayoutColorAttrEditorBinding binding; + private Dialog colorPickerDialog; - @Nullable - @Override - public View onCreateView( - @NonNull LayoutInflater inflater, - @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - this.binding = LayoutColorAttrEditorBinding.inflate(inflater, container, false); - return this.binding.getRoot(); + @Nullable + @Override + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + this.binding = LayoutColorAttrEditorBinding.inflate(inflater, container, false); + return this.binding.getRoot(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + this.binding.pickColor.setOnClickListener(v -> getColorPickerDialog().show()); + setupReferenceInput((MaterialAutoCompleteTextView) this.binding.colorResInput.getEditText()); + + try { + final var col = Color.parseColor(attribute.getValue()); + this.binding.colorPreview.setCardBackgroundColor(col); + } catch (Throwable th) { + // ignored + } + } + + @Override + protected List computeReferenceItems() { + final var list = new ArrayList(); + final var tables = ValuesTableFactory.getAllTables(); + for (var entry : tables.entrySet()) { + final var dimens = entry.getValue().getTable("color"); + if (dimens != null) { + list.addAll(dimens.keySet().stream().map("@color/"::concat).collect(Collectors.toSet())); + } } - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); + list.addAll( + FrameworkValues.listColors().stream() + .map("@android:color/"::concat) + .collect(Collectors.toList())); - this.binding.pickColor.setOnClickListener(v -> getColorPickerDialog().show()); - setupReferenceInput( - (MaterialAutoCompleteTextView) this.binding.colorResInput.getEditText()); + final var resTable = StudioApp.getInstance().getResourceTable(); + list.addAll(resTable.listResourceNames("color")); - try { - final var col = Color.parseColor(attribute.getValue()); - this.binding.colorPreview.setCardBackgroundColor(col); - } catch (Throwable th) { - // ignored - } - } + return list; + } - @Override - protected List computeReferenceItems() { - final var list = new ArrayList(); - final var tables = ValuesTableFactory.getAllTables(); - for (var entry : tables.entrySet()) { - final var dimens = entry.getValue().getTable("color"); - if (dimens != null) { - list.addAll( - dimens.keySet().stream() - .map("@color/"::concat) - .collect(Collectors.toSet())); + private Dialog getColorPickerDialog() { + + if (colorPickerDialog == null) { + final var builder = DialogUtils.newMaterialDialogBuilder(requireContext()); + ColorPickerView colorPickerView = new ColorPickerView(requireContext()); + + builder.setView(colorPickerView); + + colorPickerView.setOnPickListener( + (color, hexCode) -> { + if (colorPickerDialog != null) { + colorPickerDialog.dismiss(); } - } - list.addAll( - FrameworkValues.listColors().stream() - .map("@android:color/"::concat) - .collect(Collectors.toList())); + this.binding.colorPreview.setCardBackgroundColor(color); + notifyValueChanged(hexCode); + }); - final var resTable = StudioApp.getInstance().getResourceTable(); - list.addAll(resTable.listResourceNames("color")); - - return list; + colorPickerDialog = builder.create(); } - private Dialog getColorPickerDialog() { - - if (colorPickerDialog == null) { - final var builder = DialogUtils.newMaterialDialogBuilder(requireContext()); - ColorPickerView colorPickerView = new ColorPickerView(requireContext()); - - builder.setView(colorPickerView); - - colorPickerView.setOnPickListener( - (color, hexCode) -> { - if (colorPickerDialog != null) { - colorPickerDialog.dismiss(); - } - - this.binding.colorPreview.setCardBackgroundColor(color); - notifyValueChanged(hexCode); - }); - - colorPickerDialog = builder.create(); - } - - return colorPickerDialog; - } + return colorPickerDialog; + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/attr/DimensionEditor.java b/app/src/main/java/com/itsaky/androidide/fragments/attr/DimensionEditor.java index e06adac84..771c91671 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/attr/DimensionEditor.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/attr/DimensionEditor.java @@ -47,99 +47,93 @@ import java.util.stream.Collectors; */ public class DimensionEditor extends AbstractReferenceEditor { - private static final ILogger LOG = ILogger.newInstance("DimensionEditor"); - public TextWatcherAdapter dimensionInputWatcher; + private static final ILogger LOG = ILogger.newInstance("DimensionEditor"); + public TextWatcherAdapter dimensionInputWatcher; - private LayoutDimensionAttrEditorBinding binding; - private String[] dimensionUnits; + private LayoutDimensionAttrEditorBinding binding; + private String[] dimensionUnits; - @Nullable - @Override - public View onCreateView( - @NonNull LayoutInflater inflater, - @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - this.binding = LayoutDimensionAttrEditorBinding.inflate(inflater, container, false); - return this.binding.getRoot(); + @Nullable + @Override + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + this.binding = LayoutDimensionAttrEditorBinding.inflate(inflater, container, false); + return this.binding.getRoot(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + final var dimensionInput = Objects.requireNonNull(this.binding.dimensionInput.getEditText()); + setDimensionValue(dimensionInput); + if (dimensionInputWatcher != null) { + dimensionInput.removeTextChangedListener(dimensionInputWatcher); } - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); + dimensionInputWatcher = + new TextWatcherAdapter() { + @Override + public void afterTextChanged(@NonNull Editable s) { + final var text = s.toString().trim(); + final var unit = getDimensionUnits()[binding.unitSelector.getSelectedItemPosition()]; - final var dimensionInput = - Objects.requireNonNull(this.binding.dimensionInput.getEditText()); - setDimensionValue(dimensionInput); - if (dimensionInputWatcher != null) { - dimensionInput.removeTextChangedListener(dimensionInputWatcher); - } + // This will call CommonParseUtils#parseDimension + notifyValueChanged(text.concat(unit)); + } + }; - dimensionInputWatcher = - new TextWatcherAdapter() { - @Override - public void afterTextChanged(@NonNull Editable s) { - final var text = s.toString().trim(); - final var unit = - getDimensionUnits()[binding.unitSelector.getSelectedItemPosition()]; + dimensionInput.addTextChangedListener(dimensionInputWatcher); - // This will call CommonParseUtils#parseDimension - notifyValueChanged(text.concat(unit)); - } - }; + final var dimensionResInput = + (MaterialAutoCompleteTextView) + Objects.requireNonNull(binding.dimensionResInput.getEditText()); + setupReferenceInput(dimensionResInput); + } - dimensionInput.addTextChangedListener(dimensionInputWatcher); - - final var dimensionResInput = - (MaterialAutoCompleteTextView) - Objects.requireNonNull(binding.dimensionResInput.getEditText()); - setupReferenceInput(dimensionResInput); + private void setDimensionValue(EditText dimensionInput) { + final var val = attribute.getValue(); + if (TextUtils.isEmpty(val)) { + return; } - private void setDimensionValue(EditText dimensionInput) { - final var val = attribute.getValue(); - if (TextUtils.isEmpty(val)) { - return; - } + var dimension = val.substring(0, val.length() - 2); + if (TextUtils.isDigitsOnly(dimension)) { + dimensionInput.setText(dimension); + final var arr = requireContext().getResources().getStringArray(R.array.dimension_units); + final var index = Arrays.asList(arr).indexOf(val.substring(dimension.length())); + if (index >= 0) { + this.binding.unitSelector.setSelection(index); + } + } + } - var dimension = val.substring(0, val.length() - 2); - if (TextUtils.isDigitsOnly(dimension)) { - dimensionInput.setText(dimension); - final var arr = requireContext().getResources().getStringArray(R.array.dimension_units); - final var index = Arrays.asList(arr).indexOf(val.substring(dimension.length())); - if (index >= 0) { - this.binding.unitSelector.setSelection(index); - } - } + @NonNull + private String[] getDimensionUnits() { + if (dimensionUnits == null) { + dimensionUnits = requireContext().getResources().getStringArray(R.array.dimension_units); } - @NonNull - private String[] getDimensionUnits() { - if (dimensionUnits == null) { - dimensionUnits = - requireContext().getResources().getStringArray(R.array.dimension_units); - } + return dimensionUnits; + } - return dimensionUnits; + @Override + protected List computeReferenceItems() { + final var list = new ArrayList(); + final var tables = ValuesTableFactory.getAllTables(); + for (var entry : tables.entrySet()) { + final var dimens = entry.getValue().getTable("dimen"); + if (dimens != null) { + list.addAll(dimens.keySet().stream().map("@dimen/"::concat).collect(Collectors.toSet())); + } } - @Override - protected List computeReferenceItems() { - final var list = new ArrayList(); - final var tables = ValuesTableFactory.getAllTables(); - for (var entry : tables.entrySet()) { - final var dimens = entry.getValue().getTable("dimen"); - if (dimens != null) { - list.addAll( - dimens.keySet().stream() - .map("@dimen/"::concat) - .collect(Collectors.toSet())); - } - } - - list.addAll( - FrameworkValues.listDimens().stream() - .map("@android:dimen/"::concat) - .collect(Collectors.toList())); - return list; - } + list.addAll( + FrameworkValues.listDimens().stream() + .map("@android:dimen/"::concat) + .collect(Collectors.toList())); + return list; + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/attr/EnumEditor.java b/app/src/main/java/com/itsaky/androidide/fragments/attr/EnumEditor.java index 7d62920f9..46259bfe9 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/attr/EnumEditor.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/attr/EnumEditor.java @@ -32,36 +32,36 @@ import com.itsaky.androidide.utils.ILogger; */ public class EnumEditor extends FixedValueEditor { - private static final ILogger LOG = ILogger.newInstance("EnumEditor"); + private static final ILogger LOG = ILogger.newInstance("EnumEditor"); - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); - this.chipGroup.setSingleSelection(true); - this.chipGroup.setSelectionRequired(true); + this.chipGroup.setSingleSelection(true); + this.chipGroup.setSelectionRequired(true); - final var val = this.attribute.getValue(); - if (!TextUtils.isEmpty(val)) { - for (int i = 0; i < this.chipGroup.getChildCount(); i++) { - final var chip = (Chip) this.chipGroup.getChildAt(i); - if (val.equals(chip.getText().toString().trim())) { - chip.setChecked(true); - break; - } - } + final var val = this.attribute.getValue(); + if (!TextUtils.isEmpty(val)) { + for (int i = 0; i < this.chipGroup.getChildCount(); i++) { + final var chip = (Chip) this.chipGroup.getChildAt(i); + if (val.equals(chip.getText().toString().trim())) { + chip.setChecked(true); + break; } + } + } + } + + @Override + protected void onCheckChanged(@NonNull ChipGroup group, int checkedId) { + final var chip = (Chip) group.findViewById(group.getCheckedChipId()); + if (chip == null) { + LOG.error("Unable to update enum value. Checked Chip view is null."); + return; } - @Override - protected void onCheckChanged(@NonNull ChipGroup group, int checkedId) { - final var chip = (Chip) group.findViewById(group.getCheckedChipId()); - if (chip == null) { - LOG.error("Unable to update enum value. Checked Chip view is null."); - return; - } - - final var val = chip.getText().toString().trim(); - notifyValueChanged(val); - } + final var val = chip.getText().toString().trim(); + notifyValueChanged(val); + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/attr/FixedValueEditor.java b/app/src/main/java/com/itsaky/androidide/fragments/attr/FixedValueEditor.java index 780ee7498..504be5e6d 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/attr/FixedValueEditor.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/attr/FixedValueEditor.java @@ -38,65 +38,64 @@ import java.util.Objects; */ public abstract class FixedValueEditor extends BaseValueEditorFragment { - protected ChipGroup chipGroup; + protected ChipGroup chipGroup; - @Nullable - @Override - public View onCreateView( - @NonNull LayoutInflater inflater, - @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - this.chipGroup = new ChipGroup(inflater.getContext()); - this.chipGroup.setLayoutParams( - new ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - this.chipGroup.setSelectionRequired(true); - this.chipGroup.setSingleSelection(false); + @Nullable + @Override + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + this.chipGroup = new ChipGroup(inflater.getContext()); + this.chipGroup.setLayoutParams( + new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + this.chipGroup.setSelectionRequired(true); + this.chipGroup.setSingleSelection(false); - return this.chipGroup; + return this.chipGroup; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + Objects.requireNonNull(this.attribute); + Objects.requireNonNull(this.chipGroup); + + this.chipGroup.setSingleSelection(false); + this.chipGroup.setSelectionRequired(true); + + final var attr = this.attribute.getAttr(); + for (var value : attr.possibleValues) { + if (TextUtils.isEmpty(value)) { + continue; + } + + this.chipGroup.addView(newChip(value, value.equals(this.attribute.getValue()))); } - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); + this.chipGroup.setOnCheckedChangeListener(this::onCheckChanged); + } - Objects.requireNonNull(this.attribute); - Objects.requireNonNull(this.chipGroup); + protected abstract void onCheckChanged(@NonNull ChipGroup group, int checkedId); - this.chipGroup.setSingleSelection(false); - this.chipGroup.setSelectionRequired(true); + protected Chip newChip(String title, boolean checked) { + final var chip = new Chip(requireContext()); + chip.setId(View.generateViewId()); + chip.setText(title); + chip.setCheckedIcon(ContextCompat.getDrawable(requireContext(), R.drawable.ic_ok)); + chip.setChecked(checked); + chip.setChipBackgroundColor( + ContextCompat.getColorStateList(requireContext(), R.color.bg_enum_chips)); + chip.setTextColor(ContextCompat.getColor(requireContext(), R.color.primaryTextColor)); + chip.setLayoutParams( + new ChipGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + chip.setChipStrokeWidth(1f); + chip.setChipStrokeColor( + ColorStateList.valueOf(ContextCompat.getColor(requireContext(), R.color.primaryDarkColor))); - final var attr = this.attribute.getAttr(); - for (var value : attr.possibleValues) { - if (TextUtils.isEmpty(value)) { - continue; - } - - this.chipGroup.addView(newChip(value, value.equals(this.attribute.getValue()))); - } - - this.chipGroup.setOnCheckedChangeListener(this::onCheckChanged); - } - - protected abstract void onCheckChanged(@NonNull ChipGroup group, int checkedId); - - protected Chip newChip(String title, boolean checked) { - final var chip = new Chip(requireContext()); - chip.setId(View.generateViewId()); - chip.setText(title); - chip.setCheckedIcon(ContextCompat.getDrawable(requireContext(), R.drawable.ic_ok)); - chip.setChecked(checked); - chip.setChipBackgroundColor( - ContextCompat.getColorStateList(requireContext(), R.color.bg_enum_chips)); - chip.setTextColor(ContextCompat.getColor(requireContext(), R.color.primaryTextColor)); - chip.setLayoutParams( - new ChipGroup.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - chip.setChipStrokeWidth(1f); - chip.setChipStrokeColor( - ColorStateList.valueOf( - ContextCompat.getColor(requireContext(), R.color.primaryDarkColor))); - - return chip; - } + return chip; + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/attr/FlagEditor.java b/app/src/main/java/com/itsaky/androidide/fragments/attr/FlagEditor.java index ec7e5b978..e20a42c46 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/attr/FlagEditor.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/attr/FlagEditor.java @@ -37,52 +37,52 @@ import java.util.Arrays; */ public class FlagEditor extends FixedValueEditor { - private static final ILogger LOG = ILogger.newInstance("FlagEditor"); + private static final ILogger LOG = ILogger.newInstance("FlagEditor"); - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); - this.chipGroup.setSingleSelection(false); - this.chipGroup.setSelectionRequired(true); - this.chipGroup.setOnCheckedChangeListener(null); // does not work for multiple selections + this.chipGroup.setSingleSelection(false); + this.chipGroup.setSelectionRequired(true); + this.chipGroup.setOnCheckedChangeListener(null); // does not work for multiple selections - if (!TextUtils.isEmpty(attribute.getValue())) { - final var values = Arrays.asList(this.attribute.getValue().split("\\|")); - for (int i = 0; i < this.chipGroup.getChildCount(); i++) { - final var chip = (Chip) this.chipGroup.getChildAt(i); - if (values.contains(chip.getText().toString().trim())) { - chip.setChecked(true); - } - } + if (!TextUtils.isEmpty(attribute.getValue())) { + final var values = Arrays.asList(this.attribute.getValue().split("\\|")); + for (int i = 0; i < this.chipGroup.getChildCount(); i++) { + final var chip = (Chip) this.chipGroup.getChildAt(i); + if (values.contains(chip.getText().toString().trim())) { + chip.setChecked(true); } + } + } + } + + @Override + protected void onCheckChanged(@NonNull ChipGroup group, int checkedId) { + // Does noting... + } + + protected void onCheckChangedMultiple(CompoundButton compoundButton, boolean b) { + final var items = new ArrayList(); + for (var id : this.chipGroup.getCheckedChipIds()) { + final var chip = (Chip) this.chipGroup.findViewById(id); + if (chip == null) { + LOG.error("Unable to find chip with checked id:", id); + continue; + } + + final var val = chip.getText().toString().trim(); + items.add(val); } - @Override - protected void onCheckChanged(@NonNull ChipGroup group, int checkedId) { - // Does noting... - } + notifyValueChanged(TextUtils.join("|", items)); + } - protected void onCheckChangedMultiple(CompoundButton compoundButton, boolean b) { - final var items = new ArrayList(); - for (var id : this.chipGroup.getCheckedChipIds()) { - final var chip = (Chip) this.chipGroup.findViewById(id); - if (chip == null) { - LOG.error("Unable to find chip with checked id:", id); - continue; - } - - final var val = chip.getText().toString().trim(); - items.add(val); - } - - notifyValueChanged(TextUtils.join("|", items)); - } - - @Override - protected Chip newChip(String title, boolean checked) { - final var chip = super.newChip(title, checked); - chip.setOnCheckedChangeListener(this::onCheckChangedMultiple); - return chip; - } + @Override + protected Chip newChip(String title, boolean checked) { + final var chip = super.newChip(title, checked); + chip.setOnCheckedChangeListener(this::onCheckChangedMultiple); + return chip; + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/attr/ReferenceEditor.java b/app/src/main/java/com/itsaky/androidide/fragments/attr/ReferenceEditor.java index 20ce81937..54f2e85c7 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/attr/ReferenceEditor.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/attr/ReferenceEditor.java @@ -40,44 +40,44 @@ import java.util.stream.Collectors; */ public class ReferenceEditor extends AbstractReferenceEditor { - private LayoutReferenceAttrEditorBinding binding; + private LayoutReferenceAttrEditorBinding binding; - @Nullable - @Override - public View onCreateView( - @NonNull LayoutInflater inflater, - @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - this.binding = LayoutReferenceAttrEditorBinding.inflate(inflater, container, false); - return this.binding.getRoot(); + @Nullable + @Override + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + this.binding = LayoutReferenceAttrEditorBinding.inflate(inflater, container, false); + return this.binding.getRoot(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + setupReferenceInput((MaterialAutoCompleteTextView) this.binding.resInput.getEditText()); + } + + @Override + protected List computeReferenceItems() { + final var resTable = StudioApp.getInstance().getResourceTable(); + final var list = new ArrayList<>(resTable.listResourceNames(null)); + + final var tables = ValuesTableFactory.getAllTables(); + for (var entry : tables.entrySet()) { + final var resourceMap = entry.getValue().getResourceMap(); + for (var resourceEntries : resourceMap.entrySet()) { + final var name = resourceEntries.getKey(); + list.addAll( + resourceEntries.getValue().keySet().stream() + .map(("@android:" + name + "/")::concat) + .collect(Collectors.toList())); + } } - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); + list.addAll(FrameworkValues.listAllResources()); - setupReferenceInput((MaterialAutoCompleteTextView) this.binding.resInput.getEditText()); - } - - @Override - protected List computeReferenceItems() { - final var resTable = StudioApp.getInstance().getResourceTable(); - final var list = new ArrayList<>(resTable.listResourceNames(null)); - - final var tables = ValuesTableFactory.getAllTables(); - for (var entry : tables.entrySet()) { - final var resourceMap = entry.getValue().getResourceMap(); - for (var resourceEntries : resourceMap.entrySet()) { - final var name = resourceEntries.getKey(); - list.addAll( - resourceEntries.getValue().keySet().stream() - .map(("@android:" + name + "/")::concat) - .collect(Collectors.toList())); - } - } - - list.addAll(FrameworkValues.listAllResources()); - - return list; - } + return list; + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/attr/StringEditor.java b/app/src/main/java/com/itsaky/androidide/fragments/attr/StringEditor.java index 6bb5a922a..84a9a4153 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/attr/StringEditor.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/attr/StringEditor.java @@ -41,54 +41,50 @@ import java.util.stream.Collectors; */ public class StringEditor extends AbstractReferenceEditor { - private LayoutStringAttrEditorBinding binding; + private LayoutStringAttrEditorBinding binding; - @Nullable - @Override - public View onCreateView( - @NonNull LayoutInflater inflater, - @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - this.binding = LayoutStringAttrEditorBinding.inflate(inflater, container, false); - return this.binding.getRoot(); + @Nullable + @Override + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + this.binding = LayoutStringAttrEditorBinding.inflate(inflater, container, false); + return this.binding.getRoot(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + final var stringInput = Objects.requireNonNull(this.binding.stringInput.getEditText()); + stringInput.addTextChangedListener( + new TextWatcherAdapter() { + @Override + public void afterTextChanged(@NonNull Editable s) { + notifyValueChanged(s.toString()); + } + }); + + stringInput.setText(this.attribute.getValue()); + setupReferenceInput((MaterialAutoCompleteTextView) this.binding.stringResInput.getEditText()); + } + + @Override + protected List computeReferenceItems() { + final var list = new ArrayList(); + final var tables = ValuesTableFactory.getAllTables(); + for (var entry : tables.entrySet()) { + final var dimens = entry.getValue().getTable("string"); + if (dimens != null) { + list.addAll(dimens.keySet().stream().map("@string/"::concat).collect(Collectors.toSet())); + } } - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - final var stringInput = Objects.requireNonNull(this.binding.stringInput.getEditText()); - stringInput.addTextChangedListener( - new TextWatcherAdapter() { - @Override - public void afterTextChanged(@NonNull Editable s) { - notifyValueChanged(s.toString()); - } - }); - - stringInput.setText(this.attribute.getValue()); - setupReferenceInput( - (MaterialAutoCompleteTextView) this.binding.stringResInput.getEditText()); - } - - @Override - protected List computeReferenceItems() { - final var list = new ArrayList(); - final var tables = ValuesTableFactory.getAllTables(); - for (var entry : tables.entrySet()) { - final var dimens = entry.getValue().getTable("string"); - if (dimens != null) { - list.addAll( - dimens.keySet().stream() - .map("@string/"::concat) - .collect(Collectors.toSet())); - } - } - - list.addAll( - FrameworkValues.listStrings().stream() - .map("@android:string/"::concat) - .collect(Collectors.toList())); - return list; - } + list.addAll( + FrameworkValues.listStrings().stream() + .map("@android:string/"::concat) + .collect(Collectors.toList())); + return list; + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/preferences/BasePreferenceFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/preferences/BasePreferenceFragment.java index 3a0b1da3f..1a54bff9a 100755 --- a/app/src/main/java/com/itsaky/androidide/fragments/preferences/BasePreferenceFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/preferences/BasePreferenceFragment.java @@ -31,25 +31,24 @@ import com.itsaky.androidide.managers.PreferenceManager; public abstract class BasePreferenceFragment extends PreferenceFragmentCompat { - @Override - public View onCreateView( - @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View v = super.onCreateView(inflater, container, savedInstanceState); - if (v != null) { - v.setBackgroundColor( - ContextCompat.getColor( - StudioApp.getInstance(), - com.itsaky.androidide.R.color.content_background)); - } - return v; + @Override + public View onCreateView( + @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View v = super.onCreateView(inflater, container, savedInstanceState); + if (v != null) { + v.setBackgroundColor( + ContextCompat.getColor( + StudioApp.getInstance(), com.itsaky.androidide.R.color.content_background)); } + return v; + } - @Override - public void onCreatePreferences(Bundle p1, String p2) { - setPreferencesFromResource(com.itsaky.androidide.R.xml.ide_prefs, p2); - } + @Override + public void onCreatePreferences(Bundle p1, String p2) { + setPreferencesFromResource(com.itsaky.androidide.R.xml.ide_prefs, p2); + } - protected PreferenceManager getPrefManager() { - return StudioApp.getInstance().getPrefManager(); - } + protected PreferenceManager getPrefManager() { + return StudioApp.getInstance().getPrefManager(); + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/preferences/BuildPreferences.java b/app/src/main/java/com/itsaky/androidide/fragments/preferences/BuildPreferences.java index 5b8c1d2c4..2c612ee20 100755 --- a/app/src/main/java/com/itsaky/androidide/fragments/preferences/BuildPreferences.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/preferences/BuildPreferences.java @@ -37,127 +37,128 @@ import com.itsaky.androidide.utils.Environment; import java.io.File; public class BuildPreferences extends BasePreferenceFragment - implements Preference.OnPreferenceClickListener { + implements Preference.OnPreferenceClickListener { - public static final String KEY_GRADLE_COMMANDS = "idepref_build_gradleCommands"; - public static final String KEY_GRADLE_CLEAR_CACHE = "idepref_build_gradleClearCache"; + public static final String KEY_GRADLE_COMMANDS = "idepref_build_gradleCommands"; + public static final String KEY_GRADLE_CLEAR_CACHE = "idepref_build_gradleClearCache"; - private ProgressSheet progressSheet; + private ProgressSheet progressSheet; - @Override - public void onCreatePreferences(Bundle p1, String p2) { - super.onCreatePreferences(p1, p2); - if (getContext() == null) return; - final PreferenceScreen screen = getPreferenceScreen(); - final PreferenceCategory categoryGradle = new PreferenceCategory(getContext()); - final Preference customCommands = new Preference(getContext()); - final Preference clearCache = new Preference(getContext()); + @Override + public void onCreatePreferences(Bundle p1, String p2) { + super.onCreatePreferences(p1, p2); + if (getContext() == null) return; + final PreferenceScreen screen = getPreferenceScreen(); + final PreferenceCategory categoryGradle = new PreferenceCategory(getContext()); + final Preference customCommands = new Preference(getContext()); + final Preference clearCache = new Preference(getContext()); - screen.addPreference(categoryGradle); + screen.addPreference(categoryGradle); - customCommands.setKey(KEY_GRADLE_COMMANDS); - customCommands.setIcon(R.drawable.ic_bash_commands); - customCommands.setTitle(R.string.idepref_build_customgradlecommands_title); - customCommands.setSummary(R.string.idepref_build_customgradlecommands_summary); + customCommands.setKey(KEY_GRADLE_COMMANDS); + customCommands.setIcon(R.drawable.ic_bash_commands); + customCommands.setTitle(R.string.idepref_build_customgradlecommands_title); + customCommands.setSummary(R.string.idepref_build_customgradlecommands_summary); - clearCache.setKey(KEY_GRADLE_CLEAR_CACHE); - clearCache.setIcon(R.drawable.ic_delete); - clearCache.setTitle(R.string.idepref_build_clearCache_title); - clearCache.setSummary(R.string.idepref_build_clearCache_summary); + clearCache.setKey(KEY_GRADLE_CLEAR_CACHE); + clearCache.setIcon(R.drawable.ic_delete); + clearCache.setTitle(R.string.idepref_build_clearCache_title); + clearCache.setSummary(R.string.idepref_build_clearCache_summary); - categoryGradle.setTitle(R.string.gradle); - categoryGradle.addPreference(customCommands); - categoryGradle.addPreference(clearCache); + categoryGradle.setTitle(R.string.gradle); + categoryGradle.addPreference(customCommands); + categoryGradle.addPreference(clearCache); - setPreferenceScreen(screen); + setPreferenceScreen(screen); - customCommands.setOnPreferenceClickListener(this); - clearCache.setOnPreferenceClickListener(this); + customCommands.setOnPreferenceClickListener(this); + clearCache.setOnPreferenceClickListener(this); + } + + @Override + public boolean onPreferenceClick(Preference p1) { + final String key = p1.getKey(); + if (key.equals(KEY_GRADLE_COMMANDS)) { + showGradleCommandsDialog(); + } else if (key.equals(KEY_GRADLE_CLEAR_CACHE)) { + showClearCacheDialog(); } + return true; + } - @Override - public boolean onPreferenceClick(Preference p1) { - final String key = p1.getKey(); - if (key.equals(KEY_GRADLE_COMMANDS)) { - showGradleCommandsDialog(); - } else if (key.equals(KEY_GRADLE_CLEAR_CACHE)) { - showClearCacheDialog(); - } - return true; - } + private void showGradleCommandsDialog() { + final String[] labels = { + "--stacktrace", + "--info", + "--debug", + "--scan", + "--warning-mode all", + "--build-cache", + "--offline" + }; + final boolean[] checked = { + getPrefManager().isStackTraceEnabled(), + getPrefManager().isGradleInfoEnabled(), + getPrefManager().isGradleDebugEnabled(), + getPrefManager().isGradleScanEnabled(), + getPrefManager().isGradleWarningEnabled(), + getPrefManager().isGradleBuildCacheEnabled(), + getPrefManager().isGradleOfflineModeEnabled() + }; + final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(getContext()); + builder.setTitle(R.string.idepref_build_customgradlecommands_title); + builder.setMultiChoiceItems( + labels, + checked, + (p1, p2, p3) -> { + if (p2 == 0) { + getPrefManager().setGradleStacktraceEnabled(p3); + } else if (p2 == 1) { + getPrefManager().setGradleInfoEnabled(p3); + } else if (p2 == 2) { + getPrefManager().setGradleDebugEnabled(p3); + } else if (p2 == 3) { + getPrefManager().setGradleScanEnabled(p3); + } else if (p2 == 4) { + getPrefManager().setGradleWarningEnabled(p3); + } else if (p2 == 5) { + getPrefManager().setGradleBuildCacheEnabled(p3); + } else if (p2 == 6) { + getPrefManager().setGradleOfflineModeEnabled(p3); + } + }); + builder.setPositiveButton(android.R.string.ok, null); + builder.setCancelable(false); + builder.create().show(); + } - private void showGradleCommandsDialog() { - final String[] labels = { - "--stacktrace", "--info", "--debug", "--scan", "--warning-mode all", "--build-cache", "--offline" - }; - final boolean[] checked = { - getPrefManager().isStackTraceEnabled(), - getPrefManager().isGradleInfoEnabled(), - getPrefManager().isGradleDebugEnabled(), - getPrefManager().isGradleScanEnabled(), - getPrefManager().isGradleWarningEnabled(), - getPrefManager().isGradleBuildCacheEnabled(), - getPrefManager().isGradleOfflineModeEnabled() - }; - final MaterialAlertDialogBuilder builder = - DialogUtils.newMaterialDialogBuilder(getContext()); - builder.setTitle(R.string.idepref_build_customgradlecommands_title); - builder.setMultiChoiceItems( - labels, - checked, - (p1, p2, p3) -> { - if (p2 == 0) { - getPrefManager().setGradleStacktraceEnabled(p3); - } else if (p2 == 1) { - getPrefManager().setGradleInfoEnabled(p3); - } else if (p2 == 2) { - getPrefManager().setGradleDebugEnabled(p3); - } else if (p2 == 3) { - getPrefManager().setGradleScanEnabled(p3); - } else if (p2 == 4) { - getPrefManager().setGradleWarningEnabled(p3); - } else if (p2 == 5) { - getPrefManager().setGradleBuildCacheEnabled(p3); - } - else if (p2 == 6) { - getPrefManager().setGradleOfflineModeEnabled(p3); - } - }); - builder.setPositiveButton(android.R.string.ok, null); - builder.setCancelable(false); - builder.create().show(); - } + private void showClearCacheDialog() { + final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(getContext()); + builder.setTitle(R.string.idepref_build_clearCache_title); + builder.setMessage(R.string.msg_clear_cache); + builder.setCancelable(false); + builder.setPositiveButton( + R.string.yes, + (p1, p2) -> { + p1.dismiss(); + getProgressSheet().show(getChildFragmentManager(), "progress_sheet"); + new TaskExecutor().executeAsync(this::deleteCaches, __ -> getProgressSheet().dismiss()); + }); + builder.setNegativeButton(R.string.no, null); + builder.create().show(); + } - private void showClearCacheDialog() { - final MaterialAlertDialogBuilder builder = - DialogUtils.newMaterialDialogBuilder(getContext()); - builder.setTitle(R.string.idepref_build_clearCache_title); - builder.setMessage(R.string.msg_clear_cache); - builder.setCancelable(false); - builder.setPositiveButton( - R.string.yes, - (p1, p2) -> { - p1.dismiss(); - getProgressSheet().show(getChildFragmentManager(), "progress_sheet"); - new TaskExecutor() - .executeAsync(this::deleteCaches, __ -> getProgressSheet().dismiss()); - }); - builder.setNegativeButton(R.string.no, null); - builder.create().show(); + private Object deleteCaches() { + File file = new File(Environment.GRADLE_USER_HOME, "caches"); + if (file.exists()) { + FileUtils.delete(file); } + return null; + } - private Object deleteCaches() { - File file = new File(Environment.GRADLE_USER_HOME, "caches"); - if (file.exists()) { - FileUtils.delete(file); - } - return null; - } - - private ProgressSheet getProgressSheet() { - return progressSheet == null - ? progressSheet = new ProgressSheet().setMessage(getString(R.string.please_wait)) - : progressSheet; - } + private ProgressSheet getProgressSheet() { + return progressSheet == null + ? progressSheet = new ProgressSheet().setMessage(getString(R.string.please_wait)) + : progressSheet; + } } - diff --git a/app/src/main/java/com/itsaky/androidide/fragments/preferences/EditorPreferences.java b/app/src/main/java/com/itsaky/androidide/fragments/preferences/EditorPreferences.java index 18478a92c..839d567b2 100755 --- a/app/src/main/java/com/itsaky/androidide/fragments/preferences/EditorPreferences.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/preferences/EditorPreferences.java @@ -48,279 +48,272 @@ import com.itsaky.androidide.models.ConstantsBridge; import com.itsaky.androidide.utils.DialogUtils; public class EditorPreferences extends BasePreferenceFragment - implements Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener { + implements Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener { - @Override - public void onCreatePreferences(Bundle p1, String p2) { - super.onCreatePreferences(p1, p2); + @Override + public void onCreatePreferences(Bundle p1, String p2) { + super.onCreatePreferences(p1, p2); - if (getContext() == null) { - return; - } - - final var screen = getPreferenceScreen(); - final var commonCategory = new PreferenceCategory(getContext()); - final var javaCategory = new PreferenceCategory(getContext()); - - final var fontSize = new Preference(getContext()); - final var nonPrintable = new Preference(getContext()); - final var tabSize = new Preference(getContext()); - final var drawHex = new SwitchPreference(getContext()); - final var wordWrap = new SwitchPreference(getContext()); - final var magnifier = new SwitchPreference(getContext()); - final var fontLigatures = new SwitchPreference(getContext()); - final var autoSave = new SwitchPreference(getContext()); - final var useGoogleCodeStyle = new SwitchPreference(getContext()); - final var visiblePasswordFlag = new SwitchPreference(getContext()); - - final var completionsMatchLower = new SwitchPreference(getContext()); - - screen.addPreference(commonCategory); - screen.addPreference(javaCategory); - - fontSize.setIcon(R.drawable.ic_text_size); - fontSize.setKey(KEY_EDITOR_FONT_SIZE); - fontSize.setTitle(R.string.idepref_editor_fontsize_title); - fontSize.setSummary(R.string.idepref_editor_fontsize_summary); - - wordWrap.setIcon(R.drawable.ic_wrap_text); - wordWrap.setKey(PreferenceManager.KEY_EDITOR_WORD_WRAP); - wordWrap.setTitle(R.string.idepref_editor_word_wrap_title); - wordWrap.setSummary(R.string.idepref_editor_word_wrap_summary); - - magnifier.setIcon(R.drawable.ic_loupe); - magnifier.setKey(PreferenceManager.KEY_EDITOR_USE_MAGNIFER); - magnifier.setTitle(R.string.idepref_editor_use_magnifier_title); - magnifier.setSummary(R.string.idepref_editor_use_magnifier_summary); - - nonPrintable.setIcon(R.drawable.ic_drawing); - nonPrintable.setKey(KEY_EDITOR_PRINTABLE_CHARS); - nonPrintable.setTitle(R.string.idepref_editor_paintingflags_title); - nonPrintable.setSummary(R.string.idepref_editor_paintingflags_summary); - - tabSize.setIcon(R.drawable.ic_tab); - tabSize.setKey(KEY_EDITOR_TAB_SIZE); - tabSize.setTitle(R.string.title_tab_size); - tabSize.setSummary(R.string.msg_tab_size); - - drawHex.setIcon(R.drawable.ic_hexadecimal); - drawHex.setKey(KEY_EDITOR_DRAW_HEX); - drawHex.setTitle(R.string.idepref_editor_drawhexcolors_title); - drawHex.setSummary(R.string.idepref_editor_drawhexcolors_summary); - - fontLigatures.setIcon(R.drawable.ic_font_ligatures); - fontLigatures.setKey(KEY_EDITOR_FONT_LIGATURES); - fontLigatures.setTitle(getString(R.string.idepref_editor_ligatures_title)); - fontLigatures.setSummary(getString(R.string.idepref_editor_ligatures_summary)); - - autoSave.setIcon(R.drawable.ic_save); - autoSave.setKey(KEY_EDITOR_AUTO_SAVE); - autoSave.setTitle(getString(R.string.idepref_editor_autoSave_title)); - autoSave.setSummary(getString(R.string.idepref_editor_autoSave_summary)); - - completionsMatchLower.setKey(KEY_COMPLETIONS_MATCH_LOWER); - completionsMatchLower.setIcon(R.drawable.ic_text_lower); - completionsMatchLower.setTitle(getString(R.string.idepref_java_matchLower_title)); - completionsMatchLower.setSummary(getString(R.string.idepref_java_matchLower_summary)); - - useGoogleCodeStyle.setIcon(R.drawable.ic_format_code); - useGoogleCodeStyle.setKey(KEY_JAVA_PREF_GOOGLE_CODE_STYLE); - useGoogleCodeStyle.setTitle(getString(R.string.idepref_java_useGoogleStyle_title)); - useGoogleCodeStyle.setSummary(getString(R.string.idepref_java_useGoogleStyle_summary)); - - visiblePasswordFlag.setIcon(R.drawable.ic_password_input); - visiblePasswordFlag.setKey(KEY_EDITOR_FLAG_PASSWORD); - visiblePasswordFlag.setTitle(getString(R.string.idepref_visiblePassword_title)); - visiblePasswordFlag.setSummary(getString(R.string.idepref_visiblePassword_summary)); - - commonCategory.setTitle(getString(R.string.idepref_editor_category_common)); - commonCategory.addPreference(fontSize); - commonCategory.addPreference(fontLigatures); - commonCategory.addPreference(tabSize); - commonCategory.addPreference(wordWrap); - commonCategory.addPreference(magnifier); - commonCategory.addPreference(completionsMatchLower); - commonCategory.addPreference(visiblePasswordFlag); - commonCategory.addPreference(nonPrintable); - commonCategory.addPreference(drawHex); - commonCategory.addPreference(autoSave); - - javaCategory.setTitle(getString(R.string.idepref_editor_category_java)); - javaCategory.addPreference(useGoogleCodeStyle); - - setPreferenceScreen(screen); - - fontSize.setOnPreferenceClickListener(this); - fontLigatures.setOnPreferenceChangeListener(this); - nonPrintable.setOnPreferenceClickListener(this); - tabSize.setOnPreferenceClickListener(this); - drawHex.setOnPreferenceChangeListener(this); - autoSave.setOnPreferenceChangeListener(this); - completionsMatchLower.setOnPreferenceChangeListener(this); - useGoogleCodeStyle.setOnPreferenceChangeListener(this); - visiblePasswordFlag.setOnPreferenceChangeListener(this); - wordWrap.setOnPreferenceChangeListener(this); - magnifier.setOnPreferenceChangeListener(this); - - fontLigatures.setChecked(getPrefManager().getBoolean(KEY_EDITOR_FONT_LIGATURES, true)); - drawHex.setChecked(getPrefManager().getBoolean(KEY_EDITOR_DRAW_HEX, true)); - autoSave.setChecked(getPrefManager().getBoolean(KEY_EDITOR_AUTO_SAVE, false)); - completionsMatchLower.setChecked( - getPrefManager().getBoolean(KEY_COMPLETIONS_MATCH_LOWER, false)); - useGoogleCodeStyle.setChecked( - getPrefManager().getBoolean(KEY_JAVA_PREF_GOOGLE_CODE_STYLE, false)); - visiblePasswordFlag.setChecked(getPrefManager().getBoolean(KEY_EDITOR_FLAG_PASSWORD, true)); - wordWrap.setChecked(getPrefManager().getBoolean(PreferenceManager.KEY_EDITOR_WORD_WRAP, false)); - magnifier.setChecked(getPrefManager().getBoolean(PreferenceManager.KEY_EDITOR_USE_MAGNIFER, true)); - + if (getContext() == null) { + return; } - @Override - public boolean onPreferenceChange(@NonNull Preference preference, Object newValue) { - final boolean value = (boolean) newValue; + final var screen = getPreferenceScreen(); + final var commonCategory = new PreferenceCategory(getContext()); + final var javaCategory = new PreferenceCategory(getContext()); - switch (preference.getKey()) { - case KEY_EDITOR_FLAG_PASSWORD: - ConstantsBridge.EDITOR_PREF_VISIBLE_PASSWORD_CHANGED = true; - break; - case KEY_EDITOR_DRAW_HEX: - ConstantsBridge.EDITOR_PREF_DRAW_HEX_CHANGED = true; - break; - case KEY_EDITOR_FONT_LIGATURES: - ConstantsBridge.EDITOR_PREF_LIGATURES_CHANGED = true; - break; - case PreferenceManager.KEY_EDITOR_WORD_WRAP: - ConstantsBridge.EDITOR_PREF_WORD_WRAP_CHANGED = true; - break; - case PreferenceManager.KEY_EDITOR_USE_MAGNIFER: - ConstantsBridge.EDITOR_PREF_USE_MAGNIFIER_CHANGED = true; - break; - } + final var fontSize = new Preference(getContext()); + final var nonPrintable = new Preference(getContext()); + final var tabSize = new Preference(getContext()); + final var drawHex = new SwitchPreference(getContext()); + final var wordWrap = new SwitchPreference(getContext()); + final var magnifier = new SwitchPreference(getContext()); + final var fontLigatures = new SwitchPreference(getContext()); + final var autoSave = new SwitchPreference(getContext()); + final var useGoogleCodeStyle = new SwitchPreference(getContext()); + final var visiblePasswordFlag = new SwitchPreference(getContext()); - getPrefManager().putBoolean(preference.getKey(), value); + final var completionsMatchLower = new SwitchPreference(getContext()); - return true; + screen.addPreference(commonCategory); + screen.addPreference(javaCategory); + + fontSize.setIcon(R.drawable.ic_text_size); + fontSize.setKey(KEY_EDITOR_FONT_SIZE); + fontSize.setTitle(R.string.idepref_editor_fontsize_title); + fontSize.setSummary(R.string.idepref_editor_fontsize_summary); + + wordWrap.setIcon(R.drawable.ic_wrap_text); + wordWrap.setKey(PreferenceManager.KEY_EDITOR_WORD_WRAP); + wordWrap.setTitle(R.string.idepref_editor_word_wrap_title); + wordWrap.setSummary(R.string.idepref_editor_word_wrap_summary); + + magnifier.setIcon(R.drawable.ic_loupe); + magnifier.setKey(PreferenceManager.KEY_EDITOR_USE_MAGNIFER); + magnifier.setTitle(R.string.idepref_editor_use_magnifier_title); + magnifier.setSummary(R.string.idepref_editor_use_magnifier_summary); + + nonPrintable.setIcon(R.drawable.ic_drawing); + nonPrintable.setKey(KEY_EDITOR_PRINTABLE_CHARS); + nonPrintable.setTitle(R.string.idepref_editor_paintingflags_title); + nonPrintable.setSummary(R.string.idepref_editor_paintingflags_summary); + + tabSize.setIcon(R.drawable.ic_tab); + tabSize.setKey(KEY_EDITOR_TAB_SIZE); + tabSize.setTitle(R.string.title_tab_size); + tabSize.setSummary(R.string.msg_tab_size); + + drawHex.setIcon(R.drawable.ic_hexadecimal); + drawHex.setKey(KEY_EDITOR_DRAW_HEX); + drawHex.setTitle(R.string.idepref_editor_drawhexcolors_title); + drawHex.setSummary(R.string.idepref_editor_drawhexcolors_summary); + + fontLigatures.setIcon(R.drawable.ic_font_ligatures); + fontLigatures.setKey(KEY_EDITOR_FONT_LIGATURES); + fontLigatures.setTitle(getString(R.string.idepref_editor_ligatures_title)); + fontLigatures.setSummary(getString(R.string.idepref_editor_ligatures_summary)); + + autoSave.setIcon(R.drawable.ic_save); + autoSave.setKey(KEY_EDITOR_AUTO_SAVE); + autoSave.setTitle(getString(R.string.idepref_editor_autoSave_title)); + autoSave.setSummary(getString(R.string.idepref_editor_autoSave_summary)); + + completionsMatchLower.setKey(KEY_COMPLETIONS_MATCH_LOWER); + completionsMatchLower.setIcon(R.drawable.ic_text_lower); + completionsMatchLower.setTitle(getString(R.string.idepref_java_matchLower_title)); + completionsMatchLower.setSummary(getString(R.string.idepref_java_matchLower_summary)); + + useGoogleCodeStyle.setIcon(R.drawable.ic_format_code); + useGoogleCodeStyle.setKey(KEY_JAVA_PREF_GOOGLE_CODE_STYLE); + useGoogleCodeStyle.setTitle(getString(R.string.idepref_java_useGoogleStyle_title)); + useGoogleCodeStyle.setSummary(getString(R.string.idepref_java_useGoogleStyle_summary)); + + visiblePasswordFlag.setIcon(R.drawable.ic_password_input); + visiblePasswordFlag.setKey(KEY_EDITOR_FLAG_PASSWORD); + visiblePasswordFlag.setTitle(getString(R.string.idepref_visiblePassword_title)); + visiblePasswordFlag.setSummary(getString(R.string.idepref_visiblePassword_summary)); + + commonCategory.setTitle(getString(R.string.idepref_editor_category_common)); + commonCategory.addPreference(fontSize); + commonCategory.addPreference(fontLigatures); + commonCategory.addPreference(tabSize); + commonCategory.addPreference(wordWrap); + commonCategory.addPreference(magnifier); + commonCategory.addPreference(completionsMatchLower); + commonCategory.addPreference(visiblePasswordFlag); + commonCategory.addPreference(nonPrintable); + commonCategory.addPreference(drawHex); + commonCategory.addPreference(autoSave); + + javaCategory.setTitle(getString(R.string.idepref_editor_category_java)); + javaCategory.addPreference(useGoogleCodeStyle); + + setPreferenceScreen(screen); + + fontSize.setOnPreferenceClickListener(this); + fontLigatures.setOnPreferenceChangeListener(this); + nonPrintable.setOnPreferenceClickListener(this); + tabSize.setOnPreferenceClickListener(this); + drawHex.setOnPreferenceChangeListener(this); + autoSave.setOnPreferenceChangeListener(this); + completionsMatchLower.setOnPreferenceChangeListener(this); + useGoogleCodeStyle.setOnPreferenceChangeListener(this); + visiblePasswordFlag.setOnPreferenceChangeListener(this); + wordWrap.setOnPreferenceChangeListener(this); + magnifier.setOnPreferenceChangeListener(this); + + fontLigatures.setChecked(getPrefManager().getBoolean(KEY_EDITOR_FONT_LIGATURES, true)); + drawHex.setChecked(getPrefManager().getBoolean(KEY_EDITOR_DRAW_HEX, true)); + autoSave.setChecked(getPrefManager().getBoolean(KEY_EDITOR_AUTO_SAVE, false)); + completionsMatchLower.setChecked( + getPrefManager().getBoolean(KEY_COMPLETIONS_MATCH_LOWER, false)); + useGoogleCodeStyle.setChecked( + getPrefManager().getBoolean(KEY_JAVA_PREF_GOOGLE_CODE_STYLE, false)); + visiblePasswordFlag.setChecked(getPrefManager().getBoolean(KEY_EDITOR_FLAG_PASSWORD, true)); + wordWrap.setChecked(getPrefManager().getBoolean(PreferenceManager.KEY_EDITOR_WORD_WRAP, false)); + magnifier.setChecked( + getPrefManager().getBoolean(PreferenceManager.KEY_EDITOR_USE_MAGNIFER, true)); + } + + @Override + public boolean onPreferenceChange(@NonNull Preference preference, Object newValue) { + final boolean value = (boolean) newValue; + + switch (preference.getKey()) { + case KEY_EDITOR_FLAG_PASSWORD: + ConstantsBridge.EDITOR_PREF_VISIBLE_PASSWORD_CHANGED = true; + break; + case KEY_EDITOR_DRAW_HEX: + ConstantsBridge.EDITOR_PREF_DRAW_HEX_CHANGED = true; + break; + case KEY_EDITOR_FONT_LIGATURES: + ConstantsBridge.EDITOR_PREF_LIGATURES_CHANGED = true; + break; + case PreferenceManager.KEY_EDITOR_WORD_WRAP: + ConstantsBridge.EDITOR_PREF_WORD_WRAP_CHANGED = true; + break; + case PreferenceManager.KEY_EDITOR_USE_MAGNIFER: + ConstantsBridge.EDITOR_PREF_USE_MAGNIFIER_CHANGED = true; + break; } - @Override - public boolean onPreferenceClick(@NonNull Preference preference) { - final String key = preference.getKey(); - switch (key) { - case KEY_EDITOR_FONT_SIZE: - showTextSizeDialog(); - break; - case KEY_EDITOR_PRINTABLE_CHARS: - showPrintableCharsDialog(); - break; - case KEY_EDITOR_TAB_SIZE: - showTabSizeDialog(); - break; - } - return true; + getPrefManager().putBoolean(preference.getKey(), value); + + return true; + } + + @Override + public boolean onPreferenceClick(@NonNull Preference preference) { + final String key = preference.getKey(); + switch (key) { + case KEY_EDITOR_FONT_SIZE: + showTextSizeDialog(); + break; + case KEY_EDITOR_PRINTABLE_CHARS: + showPrintableCharsDialog(); + break; + case KEY_EDITOR_TAB_SIZE: + showTabSizeDialog(); + break; + } + return true; + } + + private void showTabSizeDialog() { + final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(getContext()); + final String[] sizes = new String[] {"2", "4", "6", "8"}; + + int current = (getPrefManager().getEditorTabSize() / 2) - 1; + if (current < 0 || current >= sizes.length) { + current = 1; } - private void showTabSizeDialog() { - final MaterialAlertDialogBuilder builder = - DialogUtils.newMaterialDialogBuilder(getContext()); - final String[] sizes = new String[] {"2", "4", "6", "8"}; + builder.setTitle(R.string.title_tab_size); + builder.setSingleChoiceItems( + sizes, + current, + (d, i) -> { + d.dismiss(); - int current = (getPrefManager().getEditorTabSize() / 2) - 1; - if (current < 0 || current >= sizes.length) { - current = 1; - } + int tabSize = (i + 1) * 2; + if (tabSize < 2 || tabSize > 8) { + tabSize = 4; + } + getPrefManager().putInt(KEY_EDITOR_TAB_SIZE, tabSize); + }); + builder.setCancelable(true); + builder.show(); + } - builder.setTitle(R.string.title_tab_size); - builder.setSingleChoiceItems( - sizes, - current, - (d, i) -> { - d.dismiss(); - - int tabSize = (i + 1) * 2; - if (tabSize < 2 || tabSize > 8) { - tabSize = 4; - } - getPrefManager().putInt(KEY_EDITOR_TAB_SIZE, tabSize); - }); - builder.setCancelable(true); - builder.show(); + private void showTextSizeDialog() { + final LayoutTextSizeSliderBinding binding = + LayoutTextSizeSliderBinding.inflate(LayoutInflater.from(getContext())); + final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(getContext()); + float size = getPrefManager().getFloat(KEY_EDITOR_FONT_SIZE, 14); + if (size < 6 || size > 32) { + size = 14; } + changeTextSize(binding, size); - private void showTextSizeDialog() { - final LayoutTextSizeSliderBinding binding = - LayoutTextSizeSliderBinding.inflate(LayoutInflater.from(getContext())); - final MaterialAlertDialogBuilder builder = - DialogUtils.newMaterialDialogBuilder(getContext()); - float size = getPrefManager().getFloat(KEY_EDITOR_FONT_SIZE, 14); - if (size < 6 || size > 32) { - size = 14; - } - changeTextSize(binding, size); + binding.slider.setLabelFormatter(p1 -> String.valueOf((int) p1)); + builder.setTitle(R.string.title_change_text_size); + builder.setMessage(R.string.msg_editor_font_size); + builder.setView(binding.getRoot()); + builder.setPositiveButton( + android.R.string.ok, + (p1, p2) -> { + p1.dismiss(); + float s = binding.slider.getValue(); + changeTextSize(binding, s); + }); + builder.setNeutralButton(R.string.reset, (p1, p2) -> changeTextSize(binding, 14)); + builder.setCancelable(false); + builder.show(); + } - binding.slider.setLabelFormatter(p1 -> String.valueOf((int) p1)); - builder.setTitle(R.string.title_change_text_size); - builder.setMessage(R.string.msg_editor_font_size); - builder.setView(binding.getRoot()); - builder.setPositiveButton( - android.R.string.ok, - (p1, p2) -> { - p1.dismiss(); - float s = binding.slider.getValue(); - changeTextSize(binding, s); - }); - builder.setNeutralButton(R.string.reset, (p1, p2) -> changeTextSize(binding, 14)); - builder.setCancelable(false); - builder.show(); - } + private void changeTextSize(LayoutTextSizeSliderBinding binding, float size) { + getPrefManager().putFloat(KEY_EDITOR_FONT_SIZE, size); + binding.slider.setValue(size); + ConstantsBridge.EDITOR_PREF_SIZE_CHANGED = true; + } - private void changeTextSize(LayoutTextSizeSliderBinding binding, float size) { - getPrefManager().putFloat(KEY_EDITOR_FONT_SIZE, size); - binding.slider.setValue(size); - ConstantsBridge.EDITOR_PREF_SIZE_CHANGED = true; - } + private void showPrintableCharsDialog() { + String[] labels = { + "Leading whitespaces", + "Trailing whitespaces", + "Inner whitespaces", + "Whitespaces in empty lines", + "Line breaks" + }; + final boolean[] checked = { + getPrefManager().getBoolean(KEY_EDITOR_FLAG_WS_LEADING, true), + getPrefManager().getBoolean(KEY_EDITOR_FLAG_WS_TRAILING, false), + getPrefManager().getBoolean(KEY_EDITOR_FLAG_WS_INNER, true), + getPrefManager().getBoolean(KEY_EDITOR_FLAG_WS_EMPTY_LINE, true), + getPrefManager().getBoolean(KEY_EDITOR_FLAG_LINE_BREAK, true) + }; + final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(getContext()); + builder.setTitle(R.string.idepref_editor_paintingflags_title); + builder.setMultiChoiceItems( + labels, + checked, + (dialog, which, isChecked) -> { + if (which == 0) { + getPrefManager().putBoolean(KEY_EDITOR_FLAG_WS_LEADING, isChecked); + } else if (which == 1) { + getPrefManager().putBoolean(KEY_EDITOR_FLAG_WS_TRAILING, isChecked); + } else if (which == 2) { + getPrefManager().putBoolean(KEY_EDITOR_FLAG_WS_INNER, isChecked); + } else if (which == 3) { + getPrefManager().putBoolean(KEY_EDITOR_FLAG_WS_EMPTY_LINE, isChecked); + } else if (which == 4) { + getPrefManager().putBoolean(KEY_EDITOR_FLAG_LINE_BREAK, isChecked); + } - private void showPrintableCharsDialog() { - String[] labels = { - "Leading whitespaces", - "Trailing whitespaces", - "Inner whitespaces", - "Whitespaces in empty lines", - "Line breaks" - }; - final boolean[] checked = { - getPrefManager().getBoolean(KEY_EDITOR_FLAG_WS_LEADING, true), - getPrefManager().getBoolean(KEY_EDITOR_FLAG_WS_TRAILING, false), - getPrefManager().getBoolean(KEY_EDITOR_FLAG_WS_INNER, true), - getPrefManager().getBoolean(KEY_EDITOR_FLAG_WS_EMPTY_LINE, true), - getPrefManager().getBoolean(KEY_EDITOR_FLAG_LINE_BREAK, true) - }; - final MaterialAlertDialogBuilder builder = - DialogUtils.newMaterialDialogBuilder(getContext()); - builder.setTitle(R.string.idepref_editor_paintingflags_title); - builder.setMultiChoiceItems( - labels, - checked, - (dialog, which, isChecked) -> { - if (which == 0) { - getPrefManager().putBoolean(KEY_EDITOR_FLAG_WS_LEADING, isChecked); - } else if (which == 1) { - getPrefManager().putBoolean(KEY_EDITOR_FLAG_WS_TRAILING, isChecked); - } else if (which == 2) { - getPrefManager().putBoolean(KEY_EDITOR_FLAG_WS_INNER, isChecked); - } else if (which == 3) { - getPrefManager().putBoolean(KEY_EDITOR_FLAG_WS_EMPTY_LINE, isChecked); - } else if (which == 4) { - getPrefManager().putBoolean(KEY_EDITOR_FLAG_LINE_BREAK, isChecked); - } - - ConstantsBridge.EDITOR_PREF_FLAGS_CHANGED = true; - }); - builder.setPositiveButton(android.R.string.ok, null); - builder.setCancelable(false); - builder.show(); - } + ConstantsBridge.EDITOR_PREF_FLAGS_CHANGED = true; + }); + builder.setPositiveButton(android.R.string.ok, null); + builder.setCancelable(false); + builder.show(); + } } - - - - diff --git a/app/src/main/java/com/itsaky/androidide/fragments/preferences/GeneralPreferences.java b/app/src/main/java/com/itsaky/androidide/fragments/preferences/GeneralPreferences.java index a1d272fe1..3d46ab4ee 100755 --- a/app/src/main/java/com/itsaky/androidide/fragments/preferences/GeneralPreferences.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/preferences/GeneralPreferences.java @@ -31,63 +31,63 @@ import androidx.preference.SwitchPreference; import com.itsaky.androidide.R; public class GeneralPreferences extends BasePreferenceFragment - implements Preference.OnPreferenceChangeListener { + implements Preference.OnPreferenceChangeListener { - @Override - public void onCreatePreferences(Bundle p1, String p2) { - super.onCreatePreferences(p1, p2); - if (getContext() == null) return; + @Override + public void onCreatePreferences(Bundle p1, String p2) { + super.onCreatePreferences(p1, p2); + if (getContext() == null) return; - final PreferenceScreen screen = getPreferenceScreen(); - final SwitchPreference openProjects = new SwitchPreference(getContext()); - final SwitchPreference confirmProjectOpen = new SwitchPreference(getContext()); - final SwitchPreference shell = new SwitchPreference(getContext()); + final PreferenceScreen screen = getPreferenceScreen(); + final SwitchPreference openProjects = new SwitchPreference(getContext()); + final SwitchPreference confirmProjectOpen = new SwitchPreference(getContext()); + final SwitchPreference shell = new SwitchPreference(getContext()); - openProjects.setKey(KEY_OPEN_PROJECTS); - openProjects.setTitle(R.string.title_open_projects); - openProjects.setIcon(R.drawable.ic_open_project); - openProjects.setSummary(R.string.msg_open_projects); + openProjects.setKey(KEY_OPEN_PROJECTS); + openProjects.setTitle(R.string.title_open_projects); + openProjects.setIcon(R.drawable.ic_open_project); + openProjects.setSummary(R.string.msg_open_projects); - confirmProjectOpen.setKey(KEY_CONFIRM_PROJECT_OPEN); - confirmProjectOpen.setTitle(R.string.title_confirm_project_open); - confirmProjectOpen.setSummary(R.string.msg_confirm_project_open); - confirmProjectOpen.setIcon(R.drawable.ic_open_project); + confirmProjectOpen.setKey(KEY_CONFIRM_PROJECT_OPEN); + confirmProjectOpen.setTitle(R.string.title_confirm_project_open); + confirmProjectOpen.setSummary(R.string.msg_confirm_project_open); + confirmProjectOpen.setIcon(R.drawable.ic_open_project); - shell.setKey(KEY_TERMINAL_USE_SYSTEM_SHELL); - shell.setTitle(getString(R.string.title_default_shell)); - shell.setSummary(getString(R.string.msg_default_shell)); - shell.setIcon(R.drawable.ic_bash_commands); + shell.setKey(KEY_TERMINAL_USE_SYSTEM_SHELL); + shell.setTitle(getString(R.string.title_default_shell)); + shell.setSummary(getString(R.string.msg_default_shell)); + shell.setIcon(R.drawable.ic_bash_commands); - openProjects.setChecked(getPrefManager().autoOpenProject()); - confirmProjectOpen.setChecked(getPrefManager().confirmProjectOpen()); - shell.setChecked(getPrefManager().getBoolean(KEY_TERMINAL_USE_SYSTEM_SHELL)); + openProjects.setChecked(getPrefManager().autoOpenProject()); + confirmProjectOpen.setChecked(getPrefManager().confirmProjectOpen()); + shell.setChecked(getPrefManager().getBoolean(KEY_TERMINAL_USE_SYSTEM_SHELL)); - screen.addPreference(openProjects); - screen.addPreference(confirmProjectOpen); - screen.addPreference(shell); + screen.addPreference(openProjects); + screen.addPreference(confirmProjectOpen); + screen.addPreference(shell); - setPreferenceScreen(screen); + setPreferenceScreen(screen); - openProjects.setOnPreferenceChangeListener(this); - confirmProjectOpen.setOnPreferenceChangeListener(this); - shell.setOnPreferenceChangeListener(this); - } - - @Override - public boolean onPreferenceChange(@NonNull Preference p1, Object p2) { - boolean checked = (Boolean) p2; - final var key = p1.getKey(); - switch (key) { - case KEY_OPEN_PROJECTS: - getPrefManager().putBoolean(KEY_OPEN_PROJECTS, checked); - break; - case KEY_CONFIRM_PROJECT_OPEN: - getPrefManager().putBoolean(KEY_CONFIRM_PROJECT_OPEN, checked); - break; - case KEY_TERMINAL_USE_SYSTEM_SHELL: - getPrefManager().putBoolean(KEY_TERMINAL_USE_SYSTEM_SHELL, checked); - break; - } - return true; + openProjects.setOnPreferenceChangeListener(this); + confirmProjectOpen.setOnPreferenceChangeListener(this); + shell.setOnPreferenceChangeListener(this); + } + + @Override + public boolean onPreferenceChange(@NonNull Preference p1, Object p2) { + boolean checked = (Boolean) p2; + final var key = p1.getKey(); + switch (key) { + case KEY_OPEN_PROJECTS: + getPrefManager().putBoolean(KEY_OPEN_PROJECTS, checked); + break; + case KEY_CONFIRM_PROJECT_OPEN: + getPrefManager().putBoolean(KEY_CONFIRM_PROJECT_OPEN, checked); + break; + case KEY_TERMINAL_USE_SYSTEM_SHELL: + getPrefManager().putBoolean(KEY_TERMINAL_USE_SYSTEM_SHELL, checked); + break; } + return true; + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/preferences/IDEPreferences.java b/app/src/main/java/com/itsaky/androidide/fragments/preferences/IDEPreferences.java index f56c20cae..15dcba6d9 100755 --- a/app/src/main/java/com/itsaky/androidide/fragments/preferences/IDEPreferences.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/preferences/IDEPreferences.java @@ -34,128 +34,126 @@ import com.itsaky.androidide.utils.ILogger; import com.itsaky.toaster.Toaster; public class IDEPreferences extends BasePreferenceFragment - implements Preference.OnPreferenceClickListener { + implements Preference.OnPreferenceClickListener { - public static final String KEY_GENERAL = "idepref_general"; - public static final String KEY_EDITOR = "idepref_editor"; - public static final String KEY_BUILD = "idepref_build"; - public static final String KEY_TELEGRAM = "idepref_telegram"; - public static final String KEY_ISSUES = "idepref_issues"; - public static final String KEY_CHANGELOG = "idepref_changelog"; - public static final String KEY_ABOUT = "idepref_about"; - private static final ILogger LOG = ILogger.newInstance("IDEPreferences"); - private GeneralPreferences mGeneralPrefs; - private BuildPreferences mBuildPreferences; - private EditorPreferences mEditorPreferences; + public static final String KEY_GENERAL = "idepref_general"; + public static final String KEY_EDITOR = "idepref_editor"; + public static final String KEY_BUILD = "idepref_build"; + public static final String KEY_TELEGRAM = "idepref_telegram"; + public static final String KEY_ISSUES = "idepref_issues"; + public static final String KEY_CHANGELOG = "idepref_changelog"; + public static final String KEY_ABOUT = "idepref_about"; + private static final ILogger LOG = ILogger.newInstance("IDEPreferences"); + private GeneralPreferences mGeneralPrefs; + private BuildPreferences mBuildPreferences; + private EditorPreferences mEditorPreferences; - @Override - public void onCreatePreferences(Bundle savedState, String rootKey) { - super.onCreatePreferences(savedState, rootKey); - if (getContext() == null) return; + @Override + public void onCreatePreferences(Bundle savedState, String rootKey) { + super.onCreatePreferences(savedState, rootKey); + if (getContext() == null) return; - final PreferenceScreen screen = getPreferenceScreen(); - final Preference general = new Preference(getContext()); - final Preference editor = new Preference(getContext()); - final Preference build = new Preference(getContext()); - final Preference telegram = new Preference(getContext()); - final Preference github = new Preference(getContext()); - final Preference changelog = new Preference(getContext()); - final Preference about = new Preference(getContext()); + final PreferenceScreen screen = getPreferenceScreen(); + final Preference general = new Preference(getContext()); + final Preference editor = new Preference(getContext()); + final Preference build = new Preference(getContext()); + final Preference telegram = new Preference(getContext()); + final Preference github = new Preference(getContext()); + final Preference changelog = new Preference(getContext()); + final Preference about = new Preference(getContext()); - general.setKey(KEY_GENERAL); - general.setIconSpaceReserved(false); - general.setFragment(getGeneralFrag().getClass().getName()); - general.setTitle(R.string.title_general); + general.setKey(KEY_GENERAL); + general.setIconSpaceReserved(false); + general.setFragment(getGeneralFrag().getClass().getName()); + general.setTitle(R.string.title_general); - editor.setKey(KEY_EDITOR); - editor.setIconSpaceReserved(false); - editor.setFragment(getEditorFrag().getClass().getName()); - editor.setTitle(R.string.idepref_editor_title); + editor.setKey(KEY_EDITOR); + editor.setIconSpaceReserved(false); + editor.setFragment(getEditorFrag().getClass().getName()); + editor.setTitle(R.string.idepref_editor_title); - build.setKey(KEY_BUILD); - build.setIconSpaceReserved(false); - build.setFragment(getBuildFrag().getClass().getName()); - build.setTitle(R.string.idepref_build_title); + build.setKey(KEY_BUILD); + build.setIconSpaceReserved(false); + build.setFragment(getBuildFrag().getClass().getName()); + build.setTitle(R.string.idepref_build_title); - github.setKey(KEY_ISSUES); - github.setIconSpaceReserved(false); - github.setTitle(R.string.title_github); + github.setKey(KEY_ISSUES); + github.setIconSpaceReserved(false); + github.setTitle(R.string.title_github); - telegram.setKey(KEY_TELEGRAM); - telegram.setIconSpaceReserved(false); - telegram.setTitle(R.string.discussions_on_telegram); + telegram.setKey(KEY_TELEGRAM); + telegram.setIconSpaceReserved(false); + telegram.setTitle(R.string.discussions_on_telegram); - changelog.setKey(KEY_CHANGELOG); - changelog.setIconSpaceReserved(false); - changelog.setTitle(R.string.pref_changelog); + changelog.setKey(KEY_CHANGELOG); + changelog.setIconSpaceReserved(false); + changelog.setTitle(R.string.pref_changelog); - about.setKey(KEY_ABOUT); - about.setIconSpaceReserved(false); - about.setTitle(R.string.idepref_about_title); + about.setKey(KEY_ABOUT); + about.setIconSpaceReserved(false); + about.setTitle(R.string.idepref_about_title); - screen.addPreference(general); - screen.addPreference(editor); - screen.addPreference(build); - screen.addPreference(github); - screen.addPreference(telegram); - screen.addPreference(changelog); - screen.addPreference(about); + screen.addPreference(general); + screen.addPreference(editor); + screen.addPreference(build); + screen.addPreference(github); + screen.addPreference(telegram); + screen.addPreference(changelog); + screen.addPreference(about); - github.setOnPreferenceClickListener(this); - telegram.setOnPreferenceClickListener(this); - changelog.setOnPreferenceClickListener(this); - about.setOnPreferenceClickListener(this); + github.setOnPreferenceClickListener(this); + telegram.setOnPreferenceClickListener(this); + changelog.setOnPreferenceClickListener(this); + about.setOnPreferenceClickListener(this); + } + + private GeneralPreferences getGeneralFrag() { + return mGeneralPrefs == null ? mGeneralPrefs = new GeneralPreferences() : mGeneralPrefs; + } + + private EditorPreferences getEditorFrag() { + return mEditorPreferences == null + ? mEditorPreferences = new EditorPreferences() + : mEditorPreferences; + } + + private BuildPreferences getBuildFrag() { + return mBuildPreferences == null + ? mBuildPreferences = new BuildPreferences() + : mBuildPreferences; + } + + private void showChangelog() { + final var intent = new Intent(Intent.ACTION_VIEW); + intent.setData( + Uri.parse( + BaseApplication.GITHUB_URL.concat("/releases/tag/v").concat(BuildConfig.VERSION_NAME))); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + try { + startActivity(intent); + } catch (Throwable th) { + LOG.error("Unable to start activity to show changelog", th); + StudioApp.getInstance().toast("Unable to start activity", Toaster.Type.ERROR); } + } - private GeneralPreferences getGeneralFrag() { - return mGeneralPrefs == null ? mGeneralPrefs = new GeneralPreferences() : mGeneralPrefs; - } - - private EditorPreferences getEditorFrag() { - return mEditorPreferences == null - ? mEditorPreferences = new EditorPreferences() - : mEditorPreferences; - } - - private BuildPreferences getBuildFrag() { - return mBuildPreferences == null - ? mBuildPreferences = new BuildPreferences() - : mBuildPreferences; - } - - private void showChangelog() { - final var intent = new Intent(Intent.ACTION_VIEW); - intent.setData( - Uri.parse( - BaseApplication.GITHUB_URL - .concat("/releases/tag/v") - .concat(BuildConfig.VERSION_NAME))); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - try { - startActivity(intent); - } catch (Throwable th) { - LOG.error("Unable to start activity to show changelog", th); - StudioApp.getInstance().toast("Unable to start activity", Toaster.Type.ERROR); - } - } - - @Override - public boolean onPreferenceClick(@NonNull Preference preference) { - final String key = preference.getKey(); - switch (key) { - case KEY_CHANGELOG: - showChangelog(); - break; - case KEY_TELEGRAM: - StudioApp.getInstance().openTelegramGroup(); - break; - case KEY_ISSUES: - StudioApp.getInstance().openGitHub(); - break; - case KEY_ABOUT: - startActivity(new Intent(getContext(), AboutActivity.class)); - break; - } - return true; + @Override + public boolean onPreferenceClick(@NonNull Preference preference) { + final String key = preference.getKey(); + switch (key) { + case KEY_CHANGELOG: + showChangelog(); + break; + case KEY_TELEGRAM: + StudioApp.getInstance().openTelegramGroup(); + break; + case KEY_ISSUES: + StudioApp.getInstance().openGitHub(); + break; + case KEY_ABOUT: + startActivity(new Intent(getContext(), AboutActivity.class)); + break; } + return true; + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/sheets/AttrEditorSheet.java b/app/src/main/java/com/itsaky/androidide/fragments/sheets/AttrEditorSheet.java index 5ede2c18b..0a01e99d1 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/sheets/AttrEditorSheet.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/sheets/AttrEditorSheet.java @@ -58,312 +58,300 @@ import java.util.function.Consumer; import java.util.stream.Collectors; public class AttrEditorSheet extends BottomSheetDialogFragment - implements SimpleIconTextAdapter.OnBindListener, - Consumer, - BaseValueEditorFragment.OnValueChangeListener { + implements SimpleIconTextAdapter.OnBindListener, + Consumer, + BaseValueEditorFragment.OnValueChangeListener { - private static final List VIEW_ACTIONS = new ArrayList<>(); - private static final ILogger LOG = ILogger.newInstance("AttrBottomSheet"); - private AttributeListSheet mAttrListSheet; - private AttrValueEditorSheet mValueEditorSheet; - private IView selectedView; + private static final List VIEW_ACTIONS = new ArrayList<>(); + private static final ILogger LOG = ILogger.newInstance("AttrBottomSheet"); + private AttributeListSheet mAttrListSheet; + private AttrValueEditorSheet mValueEditorSheet; + private IView selectedView; - @SuppressWarnings({"FieldCanBeLocal", "unused"}) - private File layout; + @SuppressWarnings({"FieldCanBeLocal", "unused"}) + private File layout; - private LayoutAttrEditorSheetBinding binding; - private OnViewDeletionFailedListener mDeletionFailedListener; + private LayoutAttrEditorSheetBinding binding; + private OnViewDeletionFailedListener mDeletionFailedListener; - @Nullable - @Override - public View onCreateView( - @NonNull LayoutInflater inflater, - @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - this.binding = - LayoutAttrEditorSheetBinding.inflate( - LayoutInflater.from(getContext()), container, false); - return binding.getRoot(); + @Nullable + @Override + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + this.binding = + LayoutAttrEditorSheetBinding.inflate(LayoutInflater.from(getContext()), container, false); + return binding.getRoot(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + if (VIEW_ACTIONS.isEmpty()) { + Collections.addAll( + VIEW_ACTIONS, + IconTextListItem.create(getString(R.string.msg_viewaction_add_attr), R.drawable.ic_add), + IconTextListItem.create( + getString(R.string.title_viewaction_delete), R.drawable.ic_delete), + IconTextListItem.create( + getString(R.string.msg_viewaction_select_parent), R.drawable.ic_view_select_parent)); } - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); + setupViewData(); + } - if (VIEW_ACTIONS.isEmpty()) { - Collections.addAll( - VIEW_ACTIONS, - IconTextListItem.create( - getString(R.string.msg_viewaction_add_attr), R.drawable.ic_add), - IconTextListItem.create( - getString(R.string.title_viewaction_delete), R.drawable.ic_delete), - IconTextListItem.create( - getString(R.string.msg_viewaction_select_parent), - R.drawable.ic_view_select_parent)); + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + mDeletionFailedListener = (OnViewDeletionFailedListener) context; + } + + public AttrEditorSheet setLayout(File layout) { + Objects.requireNonNull(layout); + + this.layout = layout; + return this; + } + + private void setupViewData() { + binding.actionsList.setAdapter(new SimpleIconTextAdapter(VIEW_ACTIONS).setOnBindListener(this)); + + if (dismissIfSelectedNull()) { + LOG.error("Cannot edit attributes of a null view."); + return; + } + + binding.widgetName.setText(this.selectedView.getXmlTag()); + binding.attrList.setAdapter( + new XMLAttributeListAdapter( + this.selectedView.getAttributes().stream() + .map(XMLAttribute::new) + .collect(Collectors.toList()), + this::onAttrClick)); + } + + public void setSelectedView(IView view) { + this.selectedView = view; + } + + private void onAttrClick( + LayoutAttrEditorSheetItemBinding binding, @NonNull XMLAttribute attribute) { + + if (dismissIfSelectedNull()) { + LOG.error("Cannot edit attributes of a null view."); + return; + } + + final var format = attribute.findFormat(); + if (format == -1) { + StudioApp.getInstance().toast(getString(R.string.msg_no_attr_format), Toaster.Type.ERROR); + LOG.error(getString(R.string.msg_no_attr_format), attribute); + return; + } + + showValueEditorSheet(attribute); + } + + private void showValueEditorSheet(@NonNull XMLAttribute attribute) { + getValueEditorSheet(attribute).show(getChildFragmentManager(), "attr_value_editor_sheet"); + } + + @Override + public void postBind( + IconTextListItem item, @NonNull SimpleIconTextAdapter.VH holder, int position) { + final var binding = holder.binding; + binding.getRoot().setOnClickListener(v -> onViewActionClick(position)); + } + + private void onViewActionClick(int position) { + if (dismissIfSelectedNull()) { + return; + } + + if (position == 0) { // Add attribute + var tag = this.selectedView.getXmlTag(); + if ("include".equals(tag) || "merge".equals(tag)) { + tag = "View"; + } + + final var attrs = StudioApp.getInstance().attrInfo(); + final var style = attrs.getStyle(tag); + if (style == null) { + LOG.error("Unable to retrieve attributes for tag:", tag); + return; + } + + final var attributes = new TreeSet(Comparator.comparing(attr -> attr.name)); + attributes.addAll(style.attributes); + attributes.addAll(attrs.NO_PARENT.attributes); + + final var widgetInfo = StudioApp.getInstance().widgetInfo(); + final var widget = widgetInfo.getWidgetBySimpleName(tag); + if (widget != null) { + for (var superclass : widget.superclasses) { + if ("java.lang.Object".equals(superclass)) { + break; + } + + final var simpleName = superclass.substring(superclass.lastIndexOf(".") + 1); + final var superStyle = attrs.getStyle(simpleName); + if (superStyle != null) { + attributes.addAll(superStyle.attributes); + } } + } - setupViewData(); - } + final var parent = this.selectedView.getParent(); + if (parent != null) { + final var parentTag = parent.getXmlTag(); + final var parentWidget = widgetInfo.getWidgetBySimpleName(parentTag); + if (parentWidget != null) { + final var parentLayoutParams = attrs.getStyle(parentTag + "_Layout"); + if (parentLayoutParams != null) { + attributes.addAll(parentLayoutParams.attributes); + } - @Override - public void onAttach(@NonNull Context context) { - super.onAttach(context); - mDeletionFailedListener = (OnViewDeletionFailedListener) context; - } + final var paramSuperclasses = widgetInfo.getLayoutParamSuperClasses(parentWidget.name); + if (paramSuperclasses != null) { + for (var superclass : paramSuperclasses) { + if ("java.lang.Object".equals(superclass)) { + continue; + } - public AttrEditorSheet setLayout(File layout) { - Objects.requireNonNull(layout); - - this.layout = layout; - return this; - } - - private void setupViewData() { - binding.actionsList.setAdapter( - new SimpleIconTextAdapter(VIEW_ACTIONS).setOnBindListener(this)); - - if (dismissIfSelectedNull()) { - LOG.error("Cannot edit attributes of a null view."); - return; - } - - binding.widgetName.setText(this.selectedView.getXmlTag()); - binding.attrList.setAdapter( - new XMLAttributeListAdapter( - this.selectedView.getAttributes().stream() - .map(XMLAttribute::new) - .collect(Collectors.toList()), - this::onAttrClick)); - } - - public void setSelectedView(IView view) { - this.selectedView = view; - } - - private void onAttrClick( - LayoutAttrEditorSheetItemBinding binding, @NonNull XMLAttribute attribute) { - - if (dismissIfSelectedNull()) { - LOG.error("Cannot edit attributes of a null view."); - return; - } - - final var format = attribute.findFormat(); - if (format == -1) { - StudioApp.getInstance() - .toast(getString(R.string.msg_no_attr_format), Toaster.Type.ERROR); - LOG.error(getString(R.string.msg_no_attr_format), attribute); - return; - } - - showValueEditorSheet(attribute); - } - - private void showValueEditorSheet(@NonNull XMLAttribute attribute) { - getValueEditorSheet(attribute).show(getChildFragmentManager(), "attr_value_editor_sheet"); - } - - @Override - public void postBind( - IconTextListItem item, @NonNull SimpleIconTextAdapter.VH holder, int position) { - final var binding = holder.binding; - binding.getRoot().setOnClickListener(v -> onViewActionClick(position)); - } - - private void onViewActionClick(int position) { - if (dismissIfSelectedNull()) { - return; - } - - if (position == 0) { // Add attribute - var tag = this.selectedView.getXmlTag(); - if ("include".equals(tag) || "merge".equals(tag)) { - tag = "View"; + final var split = superclass.split("\\."); + final var simpleClassName = split[split.length - 2]; + var paramName = split[split.length - 1]; + paramName = paramName.substring(0, paramName.length() - "Params".length()); + final var superParamEntry = attrs.getStyle(simpleClassName + "_" + paramName); + if (superParamEntry != null) { + attributes.addAll(superParamEntry.attributes); + } } + } + } + } - final var attrs = StudioApp.getInstance().attrInfo(); - final var style = attrs.getStyle(tag); - if (style == null) { - LOG.error("Unable to retrieve attributes for tag:", tag); - return; - } + final var sheet = getAttrListSheet(); + sheet.setItems(filterAppliedAttributes(attributes)); + sheet.show(getChildFragmentManager(), "attr_list_sheet"); - final var attributes = new TreeSet(Comparator.comparing(attr -> attr.name)); - attributes.addAll(style.attributes); - attributes.addAll(attrs.NO_PARENT.attributes); - - final var widgetInfo = StudioApp.getInstance().widgetInfo(); - final var widget = widgetInfo.getWidgetBySimpleName(tag); - if (widget != null) { - for (var superclass : widget.superclasses) { - if ("java.lang.Object".equals(superclass)) { - break; - } - - final var simpleName = superclass.substring(superclass.lastIndexOf(".") + 1); - final var superStyle = attrs.getStyle(simpleName); - if (superStyle != null) { - attributes.addAll(superStyle.attributes); - } + } else if (position == 1) { // Delete + DialogUtils.newYesNoDialog( + getContext(), + (dialog, which) -> { + var handled = selectedView.removeFromParent(); + if (!handled) { + handled = + mDeletionFailedListener != null + && mDeletionFailedListener.onDeletionFailed(this.selectedView); } - } - - final var parent = this.selectedView.getParent(); - if (parent != null) { - final var parentTag = parent.getXmlTag(); - final var parentWidget = widgetInfo.getWidgetBySimpleName(parentTag); - if (parentWidget != null) { - final var parentLayoutParams = attrs.getStyle(parentTag + "_Layout"); - if (parentLayoutParams != null) { - attributes.addAll(parentLayoutParams.attributes); - } - - final var paramSuperclasses = - widgetInfo.getLayoutParamSuperClasses(parentWidget.name); - if (paramSuperclasses != null) { - for (var superclass : paramSuperclasses) { - if ("java.lang.Object".equals(superclass)) { - continue; - } - - final var split = superclass.split("\\."); - final var simpleClassName = split[split.length - 2]; - var paramName = split[split.length - 1]; - paramName = - paramName.substring(0, paramName.length() - "Params".length()); - final var superParamEntry = - attrs.getStyle(simpleClassName + "_" + paramName); - if (superParamEntry != null) { - attributes.addAll(superParamEntry.attributes); - } - } - } + if (!handled) { + StudioApp.getInstance() + .toast(getString(R.string.msg_view_deletion_failed), Toaster.Type.ERROR); + } else { + dismiss(); } - } + }, + (dialog, which) -> dialog.dismiss()) + .show(); + } else if (position == 2) { // Select parent + if (this.selectedView.getParent() == null) { + StudioApp.getInstance().toast(getString(R.string.msg_no_view_parent), Toaster.Type.ERROR); + return; + } - final var sheet = getAttrListSheet(); - sheet.setItems(filterAppliedAttributes(attributes)); - sheet.show(getChildFragmentManager(), "attr_list_sheet"); + this.selectedView = this.selectedView.getParent(); - } else if (position == 1) { // Delete - DialogUtils.newYesNoDialog( - getContext(), - (dialog, which) -> { - var handled = selectedView.removeFromParent(); - if (!handled) { - handled = - mDeletionFailedListener != null - && mDeletionFailedListener.onDeletionFailed( - this.selectedView); - } - if (!handled) { - StudioApp.getInstance() - .toast( - getString(R.string.msg_view_deletion_failed), - Toaster.Type.ERROR); - } else { - dismiss(); - } - }, - (dialog, which) -> dialog.dismiss()) - .show(); - } else if (position == 2) { // Select parent - if (this.selectedView.getParent() == null) { - StudioApp.getInstance() - .toast(getString(R.string.msg_no_view_parent), Toaster.Type.ERROR); - return; - } + TransitionManager.beginDelayedTransition(binding.getRoot()); + setupViewData(); + } + } - this.selectedView = this.selectedView.getParent(); + @NonNull + @Contract("_ -> new") + private List filterAppliedAttributes(@NonNull TreeSet attributes) { + attributes.removeIf(attr -> this.selectedView.hasAttribute(attr.namespace, attr.name)); + return new ArrayList<>(attributes); + } - TransitionManager.beginDelayedTransition(binding.getRoot()); - setupViewData(); - } + @Override + public void accept(Attr attr) { + addNewAttribute(attr); + } + + @NonNull + private AttributeListSheet getAttrListSheet() { + if (mAttrListSheet == null) { + mAttrListSheet = new AttributeListSheet(); + mAttrListSheet.setCancelable(true); } - @NonNull - @Contract("_ -> new") - private List filterAppliedAttributes(@NonNull TreeSet attributes) { - attributes.removeIf(attr -> this.selectedView.hasAttribute(attr.namespace, attr.name)); - return new ArrayList<>(attributes); + return mAttrListSheet; + } + + @NonNull + public AttrValueEditorSheet getValueEditorSheet(@NonNull XMLAttribute attribute) { + + if (mValueEditorSheet == null) { + mValueEditorSheet = AttrValueEditorSheet.newInstance(attribute); } - @Override - public void accept(Attr attr) { - addNewAttribute(attr); + mValueEditorSheet.setAttribute(attribute); + return mValueEditorSheet; + } + + @Override + public void onValueChanged(@NonNull IAttribute attribute, String newValue) { + if (dismissIfSelectedNull()) { + LOG.warn("Cannot update attribute with new value. Selected view is null."); + return; } - @NonNull - private AttributeListSheet getAttrListSheet() { - if (mAttrListSheet == null) { - mAttrListSheet = new AttributeListSheet(); - mAttrListSheet.setCancelable(true); - } + final var attributeName = attribute.getAttributeName(); + final var namespace = attribute.getNamespace(); - return mAttrListSheet; + if (this.selectedView.hasAttribute(namespace, attributeName)) { + this.selectedView.updateAttribute(namespace, attributeName, newValue); + } else { + final var attr = new UiAttribute(namespace, attributeName, newValue); + this.selectedView.addAttribute(attr); } + } - @NonNull - public AttrValueEditorSheet getValueEditorSheet(@NonNull XMLAttribute attribute) { + private void addNewAttribute(@NonNull Attr attr) { + final XMLAttribute attribute = new XMLAttribute(attr.namespace, attr.name, "", false); + attribute.setAttr(attr); + showValueEditorSheet(attribute); + } - if (mValueEditorSheet == null) { - mValueEditorSheet = AttrValueEditorSheet.newInstance(attribute); - } - - mValueEditorSheet.setAttribute(attribute); - return mValueEditorSheet; + private boolean dismissIfSelectedNull() { + if (selectedView == null) { + dismiss(); + return true; } + return false; + } - @Override - public void onValueChanged(@NonNull IAttribute attribute, String newValue) { - if (dismissIfSelectedNull()) { - LOG.warn("Cannot update attribute with new value. Selected view is null."); - return; - } - - final var attributeName = attribute.getAttributeName(); - final var namespace = attribute.getNamespace(); - - if (this.selectedView.hasAttribute(namespace, attributeName)) { - this.selectedView.updateAttribute(namespace, attributeName, newValue); - } else { - final var attr = new UiAttribute(namespace, attributeName, newValue); - this.selectedView.addAttribute(attr); - } - } - - private void addNewAttribute(@NonNull Attr attr) { - final XMLAttribute attribute = new XMLAttribute(attr.namespace, attr.name, "", false); - attribute.setAttr(attr); - showValueEditorSheet(attribute); - } - - private boolean dismissIfSelectedNull() { - if (selectedView == null) { - dismiss(); - return true; - } - return false; - } + /** + * A listener can be used to get notified when we fail to remove a view from its parent. This is + * used in {@link com.itsaky.androidide.DesignerActivity DesignerActivity}. + * + *

When the user tries to remove the outermost view of the inflated layout, AttrEditorSheet + * fails to remove the view from its parent ({@link IView#getParent()} is null for root XML + * layout). In this case, DesignerActivity check if the view that we were trying to delete is the + * root layout or not. If it is the root layout, then it deletes the layout from the layout + * container. + * + * @author Akash Yadav + */ + public interface OnViewDeletionFailedListener { /** - * A listener can be used to get notified when we fail to remove a view from its parent. This is - * used in {@link com.itsaky.androidide.DesignerActivity DesignerActivity}. - * - *

When the user tries to remove the outermost view of the inflated layout, AttrEditorSheet - * fails to remove the view from its parent ({@link IView#getParent()} is null for root XML - * layout). In this case, DesignerActivity check if the view that we were trying to delete is - * the root layout or not. If it is the root layout, then it deletes the layout from the layout - * container. - * - * @author Akash Yadav + * @param view The view that was not removed from its parent. + * @return {@code true} if the listener handled the error. {@code false} otherwise. */ - public interface OnViewDeletionFailedListener { - - /** - * @param view The view that was not removed from its parent. - * @return {@code true} if the listener handled the error. {@code false} otherwise. - */ - boolean onDeletionFailed(IView view); - } + boolean onDeletionFailed(IView view); + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/sheets/AttrValueEditorSheet.java b/app/src/main/java/com/itsaky/androidide/fragments/sheets/AttrValueEditorSheet.java index 5a7a3990a..b77211c08 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/sheets/AttrValueEditorSheet.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/sheets/AttrValueEditorSheet.java @@ -66,128 +66,127 @@ import java.util.Objects; * @author Akash Yadav */ public class AttrValueEditorSheet extends BottomSheetDialogFragment - implements BaseValueEditorFragment.OnValueChangeListener { + implements BaseValueEditorFragment.OnValueChangeListener { - private static final String KEY_ATTRIBUTE = "attrEditorSheet_attribute"; - private BaseValueEditorFragment.OnValueChangeListener valueChangeListener; - private LayoutAttrValueEditorBinding binding; - private AttrValueFormatTabAdapter adapter; - private XMLAttribute attribute; + private static final String KEY_ATTRIBUTE = "attrEditorSheet_attribute"; + private BaseValueEditorFragment.OnValueChangeListener valueChangeListener; + private LayoutAttrValueEditorBinding binding; + private AttrValueFormatTabAdapter adapter; + private XMLAttribute attribute; - @NonNull - public static AttrValueEditorSheet newInstance(final XMLAttribute attribute) { - Objects.requireNonNull(attribute); + @NonNull + public static AttrValueEditorSheet newInstance(final XMLAttribute attribute) { + Objects.requireNonNull(attribute); - final var args = new Bundle(); - args.putParcelable(KEY_ATTRIBUTE, attribute); + final var args = new Bundle(); + args.putParcelable(KEY_ATTRIBUTE, attribute); - final var fragment = new AttrValueEditorSheet(); - fragment.setArguments(args); - return fragment; + final var fragment = new AttrValueEditorSheet(); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + this.valueChangeListener = (BaseValueEditorFragment.OnValueChangeListener) getParentFragment(); + } + + @Nullable + @Override + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + this.binding = LayoutAttrValueEditorBinding.inflate(inflater, container, false); + return binding.getRoot(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + if (attribute == null) { + this.attribute = requireArguments().getParcelable(KEY_ATTRIBUTE); } - @Override - public void onAttach(@NonNull Context context) { - super.onAttach(context); - this.valueChangeListener = - (BaseValueEditorFragment.OnValueChangeListener) getParentFragment(); + Objects.requireNonNull(this.attribute); + + final var namespace = this.attribute.getNamespace(); + final var formatText = Attr.createFormatText(attribute.getFormat()); + this.binding.attributeName.setText( + String.format("%s:%s", namespace.getPrefix(), attribute.getAttributeName())); + this.binding.attributeFormat.setText(formatText); + + setupTabs(formatText); + } + + @SuppressLint("NotifyDataSetChanged") + private void setupTabs(@NonNull String formatText) { + this.binding.tabs.removeAllTabs(); + this.binding.pager.setSaveEnabled(false); + + if (this.adapter != null) { + this.adapter.removeAll(); + this.adapter.notifyDataSetChanged(); } - @Nullable - @Override - public View onCreateView( - @NonNull LayoutInflater inflater, - @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - this.binding = LayoutAttrValueEditorBinding.inflate(inflater, container, false); - return binding.getRoot(); + this.adapter = new AttrValueFormatTabAdapter(getChildFragmentManager(), attribute); + + final var formats = formatText.split("\\|"); + for (var formatName : formats) { + if (TextUtils.isEmpty(formatName) || formatName.length() < 2) { + continue; + } + + final var frag = getFragmentClass(formatName); + if (frag != null) { + this.adapter.addFragment(frag, formatName); + } } - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); + binding.pager.setAdapter(this.adapter); + binding.tabs.setupWithViewPager(binding.pager); + } - if (attribute == null) { - this.attribute = requireArguments().getParcelable(KEY_ATTRIBUTE); - } - - Objects.requireNonNull(this.attribute); - - final var namespace = this.attribute.getNamespace(); - final var formatText = Attr.createFormatText(attribute.getFormat()); - this.binding.attributeName.setText( - String.format("%s:%s", namespace.getPrefix (), attribute.getAttributeName())); - this.binding.attributeFormat.setText(formatText); - - setupTabs(formatText); + @Nullable + @Contract(pure = true) + private Class getFragmentClass(@NonNull String name) { + switch (name) { + case FORMAT_REFERENCE: + return ReferenceEditor.class; + case FORMAT_COLOR: + return ColorEditor.class; + case FORMAT_BOOLEAN: + return BooleanEditor.class; + case FORMAT_DIMENSION: + return DimensionEditor.class; + case FORMAT_FLOAT: + return FloatEditor.class; + case FORMAT_INTEGER: + return IntegerEditor.class; + case FORMAT_FRACTION: + return FractionEditor.class; + case FORMAT_STRING: + return StringEditor.class; + case FORMAT_ENUM: + return EnumEditor.class; + case FORMAT_FLAG: + return FlagEditor.class; } - @SuppressLint("NotifyDataSetChanged") - private void setupTabs(@NonNull String formatText) { - this.binding.tabs.removeAllTabs(); - this.binding.pager.setSaveEnabled(false); + return null; + } - if (this.adapter != null) { - this.adapter.removeAll(); - this.adapter.notifyDataSetChanged(); - } + public void setAttribute(XMLAttribute attribute) { + Objects.requireNonNull(attribute); + this.attribute = attribute; + } - this.adapter = new AttrValueFormatTabAdapter(getChildFragmentManager(), attribute); - - final var formats = formatText.split("\\|"); - for (var formatName : formats) { - if (TextUtils.isEmpty(formatName) || formatName.length() < 2) { - continue; - } - - final var frag = getFragmentClass(formatName); - if (frag != null) { - this.adapter.addFragment(frag, formatName); - } - } - - binding.pager.setAdapter(this.adapter); - binding.tabs.setupWithViewPager(binding.pager); - } - - @Nullable - @Contract(pure = true) - private Class getFragmentClass(@NonNull String name) { - switch (name) { - case FORMAT_REFERENCE: - return ReferenceEditor.class; - case FORMAT_COLOR: - return ColorEditor.class; - case FORMAT_BOOLEAN: - return BooleanEditor.class; - case FORMAT_DIMENSION: - return DimensionEditor.class; - case FORMAT_FLOAT: - return FloatEditor.class; - case FORMAT_INTEGER: - return IntegerEditor.class; - case FORMAT_FRACTION: - return FractionEditor.class; - case FORMAT_STRING: - return StringEditor.class; - case FORMAT_ENUM: - return EnumEditor.class; - case FORMAT_FLAG: - return FlagEditor.class; - } - - return null; - } - - public void setAttribute(XMLAttribute attribute) { - Objects.requireNonNull(attribute); - this.attribute = attribute; - } - - @Override - public void onValueChanged(IAttribute attribute, String newValue) { - if (valueChangeListener != null) { - valueChangeListener.onValueChanged(attribute, newValue); - } + @Override + public void onValueChanged(IAttribute attribute, String newValue) { + if (valueChangeListener != null) { + valueChangeListener.onValueChanged(attribute, newValue); } + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/sheets/AttributeListSheet.java b/app/src/main/java/com/itsaky/androidide/fragments/sheets/AttributeListSheet.java index ecf5c440e..0a01a1981 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/sheets/AttributeListSheet.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/sheets/AttributeListSheet.java @@ -49,87 +49,84 @@ import java.util.stream.Collectors; */ public class AttributeListSheet extends BottomSheetDialogFragment { - private LayoutAddAttrSheetBinding binding; - private Consumer clickConsumer; - private List mItems; + private LayoutAddAttrSheetBinding binding; + private Consumer clickConsumer; + private List mItems; - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setStyle(DialogFragment.STYLE_NORMAL, R.style.AppTheme_SheetAboveKeyboard); + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setStyle(DialogFragment.STYLE_NORMAL, R.style.AppTheme_SheetAboveKeyboard); + } + + @Nullable + @Override + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + this.binding = LayoutAddAttrSheetBinding.inflate(inflater, container, false); + return binding.getRoot(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + binding.searchBar.setOnQueryTextListener( + new SearchView.OnQueryTextListener() { + + @Override + public boolean onQueryTextSubmit(String query) { + return filterAttributes(query.trim()); + } + + @Override + public boolean onQueryTextChange(String newText) { + return filterAttributes(newText.trim()); + } + }); + binding.attrList.setAdapter(new AttrListAdapter(clickConsumer, mItems)); + } + + private boolean filterAttributes(@NonNull String query) { + if (mItems == null || TextUtils.isEmpty(query)) { + update(); + return true; } - @Nullable - @Override - public View onCreateView( - @NonNull LayoutInflater inflater, - @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - this.binding = LayoutAddAttrSheetBinding.inflate(inflater, container, false); - return binding.getRoot(); + final var filtered = + this.mItems.stream() + .filter( + attr -> attr.name.toLowerCase(Locale.ROOT).contains(query.toLowerCase(Locale.ROOT))) + .collect(Collectors.toList()); + + update(filtered); + return true; + } + + public void setItems(@Nullable List items) { + if (items == null) { + items = new ArrayList<>(); } - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - binding.searchBar.setOnQueryTextListener( - new SearchView.OnQueryTextListener() { + mItems = items; + update(); + } - @Override - public boolean onQueryTextSubmit(String query) { - return filterAttributes(query.trim()); - } + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + //noinspection unchecked + this.clickConsumer = (Consumer) getParentFragment(); + } - @Override - public boolean onQueryTextChange(String newText) { - return filterAttributes(newText.trim()); - } - }); - binding.attrList.setAdapter(new AttrListAdapter(clickConsumer, mItems)); - } - - private boolean filterAttributes(@NonNull String query) { - if (mItems == null || TextUtils.isEmpty(query)) { - update(); - return true; - } - - final var filtered = - this.mItems.stream() - .filter( - attr -> - attr.name - .toLowerCase(Locale.ROOT) - .contains(query.toLowerCase(Locale.ROOT))) - .collect(Collectors.toList()); - - update(filtered); - return true; - } - - public void setItems(@Nullable List items) { - if (items == null) { - items = new ArrayList<>(); - } - - mItems = items; - update(); - } - - @Override - public void onAttach(@NonNull Context context) { - super.onAttach(context); - //noinspection unchecked - this.clickConsumer = (Consumer) getParentFragment(); - } - - public void update() { - update(mItems); - } - - public void update(List items) { - if (binding != null) { - binding.attrList.setAdapter(new AttrListAdapter(clickConsumer, items)); - } + public void update() { + update(mItems); + } + + public void update(List items) { + if (binding != null) { + binding.attrList.setAdapter(new AttrListAdapter(clickConsumer, items)); } + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/sheets/BaseBottomSheetFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/sheets/BaseBottomSheetFragment.java index b663ad0e8..31b5be043 100755 --- a/app/src/main/java/com/itsaky/androidide/fragments/sheets/BaseBottomSheetFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/sheets/BaseBottomSheetFragment.java @@ -31,118 +31,118 @@ import com.itsaky.androidide.databinding.LayoutSheetBinding; public abstract class BaseBottomSheetFragment extends BottomSheetDialogFragment { - private static final String KEY_SHADOW_ENABLED = "shadowEnabled"; - private static final String KEY_TITLE_ENABLED = "titleEnabled"; - private static final String KEY_TITLE = "fragTitle"; - protected Dialog mDialog; - protected boolean shadowEnabled = true; - protected boolean titleEnabled = true; - private LayoutSheetBinding binding; + private static final String KEY_SHADOW_ENABLED = "shadowEnabled"; + private static final String KEY_TITLE_ENABLED = "titleEnabled"; + private static final String KEY_TITLE = "fragTitle"; + protected Dialog mDialog; + protected boolean shadowEnabled = true; + protected boolean titleEnabled = true; + private LayoutSheetBinding binding; - @NonNull - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - mDialog = super.onCreateDialog(savedInstanceState); - mDialog.setOnShowListener(p1 -> onShow()); - return mDialog; + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + mDialog = super.onCreateDialog(savedInstanceState); + mDialog.setOnShowListener(p1 -> onShow()); + return mDialog; + } + + @Override + public View onCreateView( + @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + binding = LayoutSheetBinding.inflate(inflater, container, false); + return binding.getRoot(); + } + + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + boolean titleSet = false; + if (savedInstanceState != null) { + if (savedInstanceState.containsKey(KEY_TITLE_ENABLED)) { + this.titleEnabled = savedInstanceState.getBoolean(KEY_TITLE_ENABLED); + } + + if (savedInstanceState.containsKey(KEY_SHADOW_ENABLED)) { + this.shadowEnabled = savedInstanceState.getBoolean(KEY_SHADOW_ENABLED); + } + + if (savedInstanceState.containsKey(KEY_TITLE)) { + setTitle(savedInstanceState.getString(KEY_TITLE)); + titleSet = true; + } } - @Override - public View onCreateView( - @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - binding = LayoutSheetBinding.inflate(inflater, container, false); - return binding.getRoot(); + bind(binding.container); + + if (!titleSet) { + binding.title.setText(getTitle()); } - @Override - public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); + binding.title.setOnClickListener(v -> handleTitleClick()); - boolean titleSet = false; - if (savedInstanceState != null) { - if (savedInstanceState.containsKey(KEY_TITLE_ENABLED)) { - this.titleEnabled = savedInstanceState.getBoolean(KEY_TITLE_ENABLED); - } - - if (savedInstanceState.containsKey(KEY_SHADOW_ENABLED)) { - this.shadowEnabled = savedInstanceState.getBoolean(KEY_SHADOW_ENABLED); - } - - if (savedInstanceState.containsKey(KEY_TITLE)) { - setTitle(savedInstanceState.getString(KEY_TITLE)); - titleSet = true; - } - } - - bind(binding.container); - - if (!titleSet) { - binding.title.setText(getTitle()); - } - - binding.title.setOnClickListener(v -> handleTitleClick()); - - if (shouldHideTitle()) { - binding.getRoot().removeView(binding.title); - } - - if (!shadowEnabled) { - binding.shadow.setVisibility(View.GONE); - } + if (shouldHideTitle()) { + binding.getRoot().removeView(binding.title); } - private void handleTitleClick() { - if (isCancelable()) { - dismiss(); - } + if (!shadowEnabled) { + binding.shadow.setVisibility(View.GONE); } + } - public boolean isShowing() { - return mDialog != null && mDialog.isShowing(); + private void handleTitleClick() { + if (isCancelable()) { + dismiss(); } + } - protected boolean shouldHideTitle() { - return !titleEnabled; + public boolean isShowing() { + return mDialog != null && mDialog.isShowing(); + } + + protected boolean shouldHideTitle() { + return !titleEnabled; + } + + protected String getTitle() { + return ""; + } + + public BaseBottomSheetFragment setTitle(int title) { + if (binding != null) { + binding.title.setText(title); } + return this; + } - protected String getTitle() { - return ""; + public BaseBottomSheetFragment setTitle(String title) { + if (binding != null) { + binding.title.setText(title); } + return this; + } - public BaseBottomSheetFragment setTitle(int title) { - if (binding != null) { - binding.title.setText(title); - } - return this; - } - - public BaseBottomSheetFragment setTitle(String title) { - if (binding != null) { - binding.title.setText(title); - } - return this; - } - - public void setShowShadow(boolean enabled) { - this.shadowEnabled = enabled; - } - - public void setShowTitle(boolean enabled) { - this.titleEnabled = enabled; - } - - protected void onShow() {} - - protected abstract void bind(LinearLayout container); - - @Override - public void onSaveInstanceState(@NonNull Bundle outState) { - super.onSaveInstanceState(outState); - outState.putBoolean(KEY_SHADOW_ENABLED, shadowEnabled); - outState.putBoolean(KEY_TITLE_ENABLED, titleEnabled); - - if (binding != null) { - outState.putString(KEY_TITLE, binding.title.getText().toString()); - } + public void setShowShadow(boolean enabled) { + this.shadowEnabled = enabled; + } + + public void setShowTitle(boolean enabled) { + this.titleEnabled = enabled; + } + + protected void onShow() {} + + protected abstract void bind(LinearLayout container); + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + outState.putBoolean(KEY_SHADOW_ENABLED, shadowEnabled); + outState.putBoolean(KEY_TITLE_ENABLED, titleEnabled); + + if (binding != null) { + outState.putString(KEY_TITLE, binding.title.getText().toString()); } + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/sheets/OptionsListFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/sheets/OptionsListFragment.java index 8d284cd89..0de6aa74c 100755 --- a/app/src/main/java/com/itsaky/androidide/fragments/sheets/OptionsListFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/sheets/OptionsListFragment.java @@ -37,65 +37,65 @@ import java.util.List; public class OptionsListFragment extends BaseBottomSheetFragment { - private final List mOptions = new ArrayList<>(); - protected boolean dismissOnItemClick = true; - private RecyclerView mList; - private OnOptionsClickListener listener; + private final List mOptions = new ArrayList<>(); + protected boolean dismissOnItemClick = true; + private RecyclerView mList; + private OnOptionsClickListener listener; - @Override - protected void bind(@NonNull LinearLayout container) { - mList = new RecyclerView(requireContext()); - container.removeAllViews(); - container.addView(mList, new LinearLayout.LayoutParams(-1, -1)); - } + @Override + protected void bind(@NonNull LinearLayout container) { + mList = new RecyclerView(requireContext()); + container.removeAllViews(); + container.addView(mList, new LinearLayout.LayoutParams(-1, -1)); + } - @Override - public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - mList.setLayoutManager(new LinearLayoutManager(getContext())); - mList.setAdapter( - new OptionsSheetAdapter( - mOptions, - __ -> { - if (dismissOnItemClick) dismiss(); - if (listener != null) { - listener.onOptionsClick(__); - } - })); - } + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + mList.setLayoutManager(new LinearLayoutManager(getContext())); + mList.setAdapter( + new OptionsSheetAdapter( + mOptions, + __ -> { + if (dismissOnItemClick) dismiss(); + if (listener != null) { + listener.onOptionsClick(__); + } + })); + } - @Override - public void onAttach(@NonNull Context context) { - super.onAttach(context); - this.listener = (OnOptionsClickListener) context; - } + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + this.listener = (OnOptionsClickListener) context; + } - public void addOption(SheetOption option) { - if (!mOptions.contains(option)) { - mOptions.add(option); - } + public void addOption(SheetOption option) { + if (!mOptions.contains(option)) { + mOptions.add(option); } + } - public OptionsListFragment removeOption(int optionIndex) { - return removeOption(mOptions.get(optionIndex)); - } + public OptionsListFragment removeOption(int optionIndex) { + return removeOption(mOptions.get(optionIndex)); + } - public OptionsListFragment removeOption(SheetOption option) { - mOptions.remove(option); - return this; - } + public OptionsListFragment removeOption(SheetOption option) { + mOptions.remove(option); + return this; + } - public OptionsListFragment setDismissOnItemClick(boolean dissmiss) { - this.dismissOnItemClick = dissmiss; - return this; - } + public OptionsListFragment setDismissOnItemClick(boolean dissmiss) { + this.dismissOnItemClick = dissmiss; + return this; + } - @Override - protected String getTitle() { - return getString(com.itsaky.androidide.R.string.file_options); - } + @Override + protected String getTitle() { + return getString(com.itsaky.androidide.R.string.file_options); + } - public static interface OnOptionsClickListener { - void onOptionsClick(SheetOption option); - } + public static interface OnOptionsClickListener { + void onOptionsClick(SheetOption option); + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/sheets/ProgressSheet.java b/app/src/main/java/com/itsaky/androidide/fragments/sheets/ProgressSheet.java index 6a61b4fdd..c0deb4d9a 100755 --- a/app/src/main/java/com/itsaky/androidide/fragments/sheets/ProgressSheet.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/sheets/ProgressSheet.java @@ -34,94 +34,94 @@ import com.itsaky.androidide.utils.ILogger; public class ProgressSheet extends BaseBottomSheetFragment { - private final ILogger LOG = ILogger.newInstance("ProgressSheet"); - private LayoutProgressSheetBinding binding; - private String message = ""; - private String subMessage = ""; - private boolean subMessageEnabled = false; - private boolean welcomeTextEnabled = false; + private final ILogger LOG = ILogger.newInstance("ProgressSheet"); + private LayoutProgressSheetBinding binding; + private String message = ""; + private String subMessage = ""; + private boolean subMessageEnabled = false; + private boolean welcomeTextEnabled = false; - @Override - protected void bind(LinearLayout container) { - binding = LayoutProgressSheetBinding.inflate(LayoutInflater.from(getContext())); - container.addView(binding.getRoot()); + @Override + protected void bind(LinearLayout container) { + binding = LayoutProgressSheetBinding.inflate(LayoutInflater.from(getContext())); + container.addView(binding.getRoot()); + } + + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + binding.message.setText(message); + if (subMessageEnabled) { + binding.subMessage.setText(subMessage); + binding.subMessage.setVisibility(View.VISIBLE); + RelativeLayout.LayoutParams p = + (RelativeLayout.LayoutParams) binding.message.getLayoutParams(); + try { + p.removeRule(RelativeLayout.CENTER_VERTICAL); + } catch (Throwable th) { + LOG.error("Unable to remove center_vertical rule.", th); + } + binding.message.setLayoutParams(p); + } else { + binding.subMessage.setVisibility(View.GONE); + RelativeLayout.LayoutParams p = + (RelativeLayout.LayoutParams) binding.message.getLayoutParams(); + try { + p.addRule(RelativeLayout.CENTER_VERTICAL); + } catch (Throwable th) { + LOG.error("Unable to remove center_vertical rule.", th); + } + binding.message.setLayoutParams(p); } - @Override - public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); + if (!welcomeTextEnabled) { + binding.welcomeText.setVisibility(View.GONE); + } + } - binding.message.setText(message); - if (subMessageEnabled) { - binding.subMessage.setText(subMessage); - binding.subMessage.setVisibility(View.VISIBLE); - RelativeLayout.LayoutParams p = - (RelativeLayout.LayoutParams) binding.message.getLayoutParams(); - try { - p.removeRule(RelativeLayout.CENTER_VERTICAL); - } catch (Throwable th) { - LOG.error("Unable to remove center_vertical rule.", th); - } - binding.message.setLayoutParams(p); - } else { - binding.subMessage.setVisibility(View.GONE); - RelativeLayout.LayoutParams p = - (RelativeLayout.LayoutParams) binding.message.getLayoutParams(); - try { - p.addRule(RelativeLayout.CENTER_VERTICAL); - } catch (Throwable th) { - LOG.error("Unable to remove center_vertical rule.", th); - } - binding.message.setLayoutParams(p); - } + @Override + protected boolean shouldHideTitle() { + return true; + } - if (!welcomeTextEnabled) { - binding.welcomeText.setVisibility(View.GONE); - } + public ProgressSheet setWelcomeTextEnabled(boolean enabled) { + this.welcomeTextEnabled = enabled; + return this; + } + + public ProgressSheet setSubMessageEnabled(boolean enabled) { + this.subMessageEnabled = enabled; + return this; + } + + public ProgressSheet setSubMessage(String msg) { + this.subMessage = msg; + if (isShowing()) { + binding.subMessage.setText(msg); + } + return this; + } + + public ProgressSheet setMessage(String message) { + this.message = message; + if (isShowing()) { + binding.message.setText(message); } - @Override - protected boolean shouldHideTitle() { - return true; + return this; + } + + public ProgressSheet setProgressDrawable(Drawable drawable) { + if (isShowing()) { + binding.progress.setIndeterminateDrawable(drawable); } - public ProgressSheet setWelcomeTextEnabled(boolean enabled) { - this.welcomeTextEnabled = enabled; - return this; - } + return this; + } - public ProgressSheet setSubMessageEnabled(boolean enabled) { - this.subMessageEnabled = enabled; - return this; - } - - public ProgressSheet setSubMessage(String msg) { - this.subMessage = msg; - if (isShowing()) { - binding.subMessage.setText(msg); - } - return this; - } - - public ProgressSheet setMessage(String message) { - this.message = message; - if (isShowing()) { - binding.message.setText(message); - } - - return this; - } - - public ProgressSheet setProgressDrawable(Drawable drawable) { - if (isShowing()) { - binding.progress.setIndeterminateDrawable(drawable); - } - - return this; - } - - @Override - public void dismiss() { - if (isShowing()) super.dismiss(); - } + @Override + public void dismiss() { + if (isShowing()) super.dismiss(); + } } diff --git a/app/src/main/java/com/itsaky/androidide/fragments/sheets/TextSheetFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/sheets/TextSheetFragment.java index ad49bbe6d..dffd7ccd8 100755 --- a/app/src/main/java/com/itsaky/androidide/fragments/sheets/TextSheetFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/sheets/TextSheetFragment.java @@ -33,103 +33,102 @@ import io.github.rosemoe.sora.lang.EmptyLanguage; public class TextSheetFragment extends BaseBottomSheetFragment { - private SpannableStringBuilder outputBuilder; - private IDEEditor editor; + private SpannableStringBuilder outputBuilder; + private IDEEditor editor; - public TextSheetFragment setTextSelectable(boolean textSelectable) { - return this; - } + public TextSheetFragment setTextSelectable(boolean textSelectable) { + return this; + } - @Override - protected void bind(@NonNull LinearLayout container) { - final IDEEditor e = getEditor(); - container.setPadding(0, 0, 0, 0); - container.setPaddingRelative(0, 0, 0, 0); - LinearLayout.LayoutParams p = new LinearLayout.LayoutParams(-1, 0); - p.weight = 1.0f; - if (e.getParent() != null && e.getParent() instanceof ViewGroup) { - ((ViewGroup) e.getParent()).removeView(e); - } - container.addView(getEditor(), p); + @Override + protected void bind(@NonNull LinearLayout container) { + final IDEEditor e = getEditor(); + container.setPadding(0, 0, 0, 0); + container.setPaddingRelative(0, 0, 0, 0); + LinearLayout.LayoutParams p = new LinearLayout.LayoutParams(-1, 0); + p.weight = 1.0f; + if (e.getParent() != null && e.getParent() instanceof ViewGroup) { + ((ViewGroup) e.getParent()).removeView(e); } + container.addView(getEditor(), p); + } - @Override - protected String getTitle() { - return getString(R.string.build_output); - } + @Override + protected String getTitle() { + return getString(R.string.build_output); + } - @Override - protected void onShow() { - super.onShow(); - editor.setText(getOutputBuilder()); - } + @Override + protected void onShow() { + super.onShow(); + editor.setText(getOutputBuilder()); + } - private void scrollToBottom() { - if (getActivity() != null) getActivity().runOnUiThread(() -> editor.goToEnd()); - } + private void scrollToBottom() { + if (getActivity() != null) getActivity().runOnUiThread(() -> editor.goToEnd()); + } - public TextSheetFragment setTitleText(int res) { - super.setTitle(res); - return this; - } + public TextSheetFragment setTitleText(int res) { + super.setTitle(res); + return this; + } - public TextSheetFragment setTitleText(String res) { - super.setTitle(res); - return this; - } + public TextSheetFragment setTitleText(String res) { + super.setTitle(res); + return this; + } - public void append(String text) { - append(text, -1, true); - } + public void append(String text) { + append(text, -1, true); + } - public void append(String text, int spanColor, boolean appendLine) { - if (text != null && text.trim().length() > 0) { - appendInternal( - appendLine && !text.endsWith("\n") ? text.concat("\n") : text, spanColor); - } + public void append(String text, int spanColor, boolean appendLine) { + if (text != null && text.trim().length() > 0) { + appendInternal(appendLine && !text.endsWith("\n") ? text.concat("\n") : text, spanColor); } + } - private void appendInternal(String text, int spanColor) { - SpannableString str = new SpannableString(text); - str.setSpan( - new ForegroundColorSpan(spanColor == -1 ? Color.WHITE : spanColor), - 0, - text.length(), - SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE); - getOutputBuilder().append(str); - if (mDialog != null && mDialog.isShowing()) { - setToEditor(str); - } + private void appendInternal(String text, int spanColor) { + SpannableString str = new SpannableString(text); + str.setSpan( + new ForegroundColorSpan(spanColor == -1 ? Color.WHITE : spanColor), + 0, + text.length(), + SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE); + getOutputBuilder().append(str); + if (mDialog != null && mDialog.isShowing()) { + setToEditor(str); } + } - private void setToEditor(SpannableString line) { - if (getActivity() != null) - getActivity() - .runOnUiThread( - () -> { - editor.append(line); - scrollToBottom(); - }); - } + private void setToEditor(SpannableString line) { + if (getActivity() != null) + getActivity() + .runOnUiThread( + () -> { + editor.append(line); + scrollToBottom(); + }); + } - private IDEEditor getEditor() { - return editor == null ? createEditor() : editor; - } + private IDEEditor getEditor() { + return editor == null ? createEditor() : editor; + } - private IDEEditor createEditor() { - editor = new IDEEditor(getContext()); - editor.setEditable(false); - editor.setDividerWidth(0); - editor.setEditorLanguage(new EmptyLanguage()); - editor.setWordwrap(false); - editor.setUndoEnabled(false); - editor.setTypefaceLineNumber(TypefaceUtils.jetbrainsMono()); - editor.setTypefaceText(TypefaceUtils.jetbrainsMono()); - editor.setTextSize(12); - return editor; - } + private IDEEditor createEditor() { + editor = new IDEEditor(getContext()); + editor.setEditable(false); + editor.setDividerWidth(0); + editor.setEditorLanguage(new EmptyLanguage()); + editor.setWordwrap(false); + editor.setUndoEnabled(false); + editor.setTypefaceLineNumber(TypefaceUtils.jetbrainsMono()); + editor.setTypefaceText(TypefaceUtils.jetbrainsMono()); + editor.setTextSize(12); + return editor; + } - private SpannableStringBuilder getOutputBuilder() { - return outputBuilder == null ? outputBuilder = new SpannableStringBuilder() : outputBuilder; - } + private SpannableStringBuilder getOutputBuilder() { + return outputBuilder == null ? outputBuilder = new SpannableStringBuilder() : outputBuilder; + } } diff --git a/app/src/main/java/com/itsaky/androidide/handlers/FileOptionsHandler.java b/app/src/main/java/com/itsaky/androidide/handlers/FileOptionsHandler.java index a238bb2a1..70b00a322 100755 --- a/app/src/main/java/com/itsaky/androidide/handlers/FileOptionsHandler.java +++ b/app/src/main/java/com/itsaky/androidide/handlers/FileOptionsHandler.java @@ -41,396 +41,359 @@ import java.util.Objects; import java.util.regex.Pattern; public class FileOptionsHandler extends IDEHandler - implements OptionsListFragment.OnOptionsClickListener { + implements OptionsListFragment.OnOptionsClickListener { - public static final String RES_PATH_REGEX = "/.*/src/.*/res"; - public static final String LAYOUT_RES_PATH_REGEX = "/.*/src/.*/res/layout"; - public static final String MENU_RES_PATH_REGEX = "/.*/src/.*/res/menu"; - public static final String DRAWABLE_RES_PATH_REGEX = "/.*/src/.*/res/drawable"; - public static final String JAVA_PATH_REGEX = "/.*/src/.*/java"; + public static final String RES_PATH_REGEX = "/.*/src/.*/res"; + public static final String LAYOUT_RES_PATH_REGEX = "/.*/src/.*/res/layout"; + public static final String MENU_RES_PATH_REGEX = "/.*/src/.*/res/menu"; + public static final String DRAWABLE_RES_PATH_REGEX = "/.*/src/.*/res/drawable"; + public static final String JAVA_PATH_REGEX = "/.*/src/.*/java"; - public FileOptionsHandler(Provider provider) { - super(provider); + public FileOptionsHandler(Provider provider) { + super(provider); + } + + @Override + public void start() { + Objects.requireNonNull(this.activity()); + } + + @Override + public void stop() {} + + @Override + public void onOptionsClick(SheetOption option) { + if (option.extra instanceof File) { + final File f = (File) option.extra; + switch (option.id) { + case 0: + ClipboardUtils.copyText("[AndroidIDE] Copied File Path", f.getAbsolutePath()); + activity().getApp().toast(R.string.copied, Toaster.Type.SUCCESS); + break; + case 1: + renameFile(f); + break; + case 2: + delete(f); + break; + case 3: + createNewFile(f); + break; + case 4: + createNewFolder(f); + break; + } } + } - @Override - public void start() { - Objects.requireNonNull(this.activity()); + private void createNewFile(File f) { + createNewFile(f, false); + } + + private void createNewFile(final File f, boolean forceUnknownType) { + if (forceUnknownType) { + createNewEmptyFile(f); + } else { + final var projectDir = ProjectManager.INSTANCE.getProjectDirPath(); + Objects.requireNonNull(projectDir); + + final boolean isJava = + Pattern.compile(Pattern.quote(projectDir) + JAVA_PATH_REGEX) + .matcher(f.getAbsolutePath()) + .find(); + final boolean isRes = + Pattern.compile(Pattern.quote(projectDir) + RES_PATH_REGEX) + .matcher(f.getAbsolutePath()) + .find(); + final boolean isLayoutRes = + Pattern.compile(Pattern.quote(projectDir) + LAYOUT_RES_PATH_REGEX) + .matcher(f.getAbsolutePath()) + .find(); + final boolean isMenuRes = + Pattern.compile(Pattern.quote(projectDir) + MENU_RES_PATH_REGEX) + .matcher(f.getAbsolutePath()) + .find(); + final boolean isDrawableRes = + Pattern.compile(Pattern.quote(projectDir) + DRAWABLE_RES_PATH_REGEX) + .matcher(f.getAbsolutePath()) + .find(); + if (isJava) { + createJavaClass(f); + } else if (isLayoutRes && f.getName().equals("layout")) { + createLayoutRes(f); + } else if (isMenuRes && f.getName().equals("menu")) { + createMenuRes(f); + } else if (isDrawableRes && f.getName().equals("drawable")) { + createDrawableRes(f); + } else if (isRes && f.getName().equals("res")) { + createNewResource(f); + } else { + createNewEmptyFile(f); + } } + } - @Override - public void stop() {} - - @Override - public void onOptionsClick(SheetOption option) { - if (option.extra instanceof File) { - final File f = (File) option.extra; - switch (option.id) { - case 0: - ClipboardUtils.copyText("[AndroidIDE] Copied File Path", f.getAbsolutePath()); - activity().getApp().toast(R.string.copied, Toaster.Type.SUCCESS); - break; - case 1: - renameFile(f); - break; - case 2: - delete(f); - break; - case 3: - createNewFile(f); - break; - case 4: - createNewFolder(f); - break; - } - } - } - - private void createNewFile(File f) { - createNewFile(f, false); - } - - private void createNewFile(final File f, boolean forceUnknownType) { - if (forceUnknownType) { - createNewEmptyFile(f); - } else { - final var projectDir = ProjectManager.INSTANCE.getProjectDirPath (); - Objects.requireNonNull(projectDir); - - final boolean isJava = - Pattern.compile(Pattern.quote(projectDir) + JAVA_PATH_REGEX) - .matcher(f.getAbsolutePath()) - .find(); - final boolean isRes = - Pattern.compile(Pattern.quote(projectDir) + RES_PATH_REGEX) - .matcher(f.getAbsolutePath()) - .find(); - final boolean isLayoutRes = - Pattern.compile(Pattern.quote(projectDir) + LAYOUT_RES_PATH_REGEX) - .matcher(f.getAbsolutePath()) - .find(); - final boolean isMenuRes = - Pattern.compile(Pattern.quote(projectDir) + MENU_RES_PATH_REGEX) - .matcher(f.getAbsolutePath()) - .find(); - final boolean isDrawableRes = - Pattern.compile(Pattern.quote(projectDir) + DRAWABLE_RES_PATH_REGEX) - .matcher(f.getAbsolutePath()) - .find(); - if (isJava) { - createJavaClass(f); - } else if (isLayoutRes && f.getName().equals("layout")) { - createLayoutRes(f); - } else if (isMenuRes && f.getName().equals("menu")) { - createMenuRes(f); - } else if (isDrawableRes && f.getName().equals("drawable")) { - createDrawableRes(f); - } else if (isRes && f.getName().equals("res")) { - createNewResource(f); + private void createJavaClass(final File f) { + final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity()); + final LayoutCreateFileJavaBinding binding = + LayoutCreateFileJavaBinding.inflate(activity().getLayoutInflater()); + builder.setView(binding.getRoot()); + builder.setTitle(R.string.new_java_class); + builder.setPositiveButton( + R.string.text_create, + (p1, p2) -> { + p1.dismiss(); + final String name = + Objects.requireNonNull(binding.name.getEditText()).getText().toString().trim(); + final String pkgName = ProjectWriter.getPackageName(f); + if (pkgName == null || pkgName.trim().length() <= 0) { + activity().getApp().toast(R.string.msg_get_package_failed, Toaster.Type.ERROR); + } else { + final int id = binding.typeGroup.getCheckedButtonId(); + final var javaName = name.endsWith(".java") ? name : name.concat(".java"); + final var className = + !name.contains(".") ? name : name.substring(0, name.lastIndexOf(".")); + if (id == binding.typeClass.getId()) { + createFile(f, javaName, ProjectWriter.createJavaClass(pkgName, className)); + } else if (id == binding.typeInterface.getId()) { + createFile(f, javaName, ProjectWriter.createJavaInterface(pkgName, className)); + } else if (id == binding.typeEnum.getId()) { + createFile(f, javaName, ProjectWriter.createJavaEnum(pkgName, className)); + } else if (id == binding.typeActivity.getId()) { + createFile(f, javaName, ProjectWriter.createActivity(pkgName, className)); } else { - createNewEmptyFile(f); + createFile(f, name, ""); } + } + }); + builder.setNegativeButton(android.R.string.cancel, null); + builder.setCancelable(false); + builder.create().show(); + } + + private void createLayoutRes(File f) { + createNewFileWithContent(Environment.mkdirIfNotExits(f), ProjectWriter.createLayout(), ".xml"); + } + + private void createMenuRes(File f) { + createNewFileWithContent(Environment.mkdirIfNotExits(f), ProjectWriter.createMenu(), ".xml"); + } + + private void createDrawableRes(File f) { + createNewFileWithContent( + Environment.mkdirIfNotExits(f), ProjectWriter.createDrawable(), ".xml"); + } + + private void createNewResource(File f) { + final String[] labels = { + activity().getString(R.string.restype_drawable), + activity().getString(R.string.restype_layout), + activity().getString(R.string.restype_menu), + activity().getString(R.string.restype_other) + }; + final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity()); + builder.setTitle(R.string.new_xml_resource); + builder.setItems( + labels, + (dialogInterface, position) -> { + if (position == 0) { + createDrawableRes(new File(f, "drawable")); + } else if (position == 1) { + createLayoutRes(new File(f, "layout")); + } else if (position == 2) { + createMenuRes(new File(f, "menu")); + } else if (position == 3) { + createNewFile(f, true); + } + }); + builder.create().show(); + } + + private void createNewEmptyFile(File f) { + createNewFileWithContent(f, ""); + } + + private void createNewFileWithContent(File f, String content) { + createNewFileWithContent(f, content, null); + } + + private void createNewFileWithContent(File folder, String content, String extension) { + final LayoutDialogTextInputBinding binding = + LayoutDialogTextInputBinding.inflate(activity().getLayoutInflater()); + final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity()); + + Objects.requireNonNull(binding.name.getEditText()).setHint(R.string.file_name); + + builder.setTitle(R.string.new_file); + builder.setMessage( + activity() + .getString(R.string.msg_can_contain_slashes) + .concat("\n\n") + .concat(activity().getString(R.string.msg_newfile_dest, folder.getAbsolutePath()))); + builder.setView(binding.getRoot()); + builder.setCancelable(false); + builder.setPositiveButton( + R.string.text_create, + (p1, p2) -> { + p1.dismiss(); + String name = binding.name.getEditText().getText().toString().trim(); + if (extension != null && extension.trim().length() > 0) { + name = name.endsWith(extension) ? name : name.concat(extension); + } + createFile(folder, name, content); + }); + builder.setNegativeButton(android.R.string.cancel, null); + builder.create().show(); + } + + private void createFile(File f, String name, String content) { + if (name.length() > 0 && name.length() <= 40 && !name.startsWith("/")) { + final File file = new File(f, name); + if (file.exists()) { + activity().getApp().toast(R.string.msg_file_exists, Toaster.Type.ERROR); + } else { + if (FileIOUtils.writeFileFromString(file, content)) { + // TODO Notify language servers about file created event + activity().getApp().toast(R.string.msg_file_created, Toaster.Type.SUCCESS); + if (activity().getLastHoldTreeNode() != null) { + TreeNode node = new TreeNode(file); + node.setViewHolder(new FileTreeViewHolder(activity())); + activity().getLastHoldTreeNode().addChild(node); + activity().getFileTreeFragment().expandNode(activity().getLastHoldTreeNode()); + } else { + activity().getFileTreeFragment().listProjectFiles(); + } + } else { + activity().getApp().toast(R.string.msg_file_creation_failed, Toaster.Type.ERROR); } + } + } else { + activity().getApp().toast(R.string.msg_invalid_name, Toaster.Type.ERROR); } + } - private void createJavaClass(final File f) { - final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity()); - final LayoutCreateFileJavaBinding binding = - LayoutCreateFileJavaBinding.inflate(activity().getLayoutInflater()); - builder.setView(binding.getRoot()); - builder.setTitle(R.string.new_java_class); - builder.setPositiveButton( - R.string.text_create, - (p1, p2) -> { - p1.dismiss(); - final String name = - Objects.requireNonNull(binding.name.getEditText()) - .getText() - .toString() - .trim(); - final String pkgName = ProjectWriter.getPackageName(f); - if (pkgName == null || pkgName.trim().length() <= 0) { - activity() - .getApp() - .toast(R.string.msg_get_package_failed, Toaster.Type.ERROR); - } else { - final int id = binding.typeGroup.getCheckedButtonId(); - final var javaName = name.endsWith(".java") ? name : name.concat(".java"); - final var className = - !name.contains(".") - ? name - : name.substring(0, name.lastIndexOf(".")); - if (id == binding.typeClass.getId()) { - createFile( - f, javaName, ProjectWriter.createJavaClass(pkgName, className)); - } else if (id == binding.typeInterface.getId()) { - createFile( - f, - javaName, - ProjectWriter.createJavaInterface(pkgName, className)); - } else if (id == binding.typeEnum.getId()) { - createFile( - f, javaName, ProjectWriter.createJavaEnum(pkgName, className)); - } else if (id == binding.typeActivity.getId()) { - createFile( - f, javaName, ProjectWriter.createActivity(pkgName, className)); - } else { - createFile(f, name, ""); - } - } - }); - builder.setNegativeButton(android.R.string.cancel, null); - builder.setCancelable(false); - builder.create().show(); - } + private void createNewFolder(File f) { + final LayoutDialogTextInputBinding binding = + LayoutDialogTextInputBinding.inflate(activity().getLayoutInflater()); + final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity()); - private void createLayoutRes(File f) { - createNewFileWithContent( - Environment.mkdirIfNotExits(f), ProjectWriter.createLayout(), ".xml"); - } + Objects.requireNonNull(binding.name.getEditText()).setHint(R.string.folder_name); - private void createMenuRes(File f) { - createNewFileWithContent( - Environment.mkdirIfNotExits(f), ProjectWriter.createMenu(), ".xml"); - } - - private void createDrawableRes(File f) { - createNewFileWithContent( - Environment.mkdirIfNotExits(f), ProjectWriter.createDrawable(), ".xml"); - } - - private void createNewResource(File f) { - final String[] labels = { - activity().getString(R.string.restype_drawable), - activity().getString(R.string.restype_layout), - activity().getString(R.string.restype_menu), - activity().getString(R.string.restype_other) - }; - final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity()); - builder.setTitle(R.string.new_xml_resource); - builder.setItems( - labels, - (dialogInterface, position) -> { - if (position == 0) { - createDrawableRes(new File(f, "drawable")); - } else if (position == 1) { - createLayoutRes(new File(f, "layout")); - } else if (position == 2) { - createMenuRes(new File(f, "menu")); - } else if (position == 3) { - createNewFile(f, true); - } - }); - builder.create().show(); - } - - private void createNewEmptyFile(File f) { - createNewFileWithContent(f, ""); - } - - private void createNewFileWithContent(File f, String content) { - createNewFileWithContent(f, content, null); - } - - private void createNewFileWithContent(File folder, String content, String extension) { - final LayoutDialogTextInputBinding binding = - LayoutDialogTextInputBinding.inflate(activity().getLayoutInflater()); - final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity()); - - Objects.requireNonNull(binding.name.getEditText()).setHint(R.string.file_name); - - builder.setTitle(R.string.new_file); - builder.setMessage( - activity() - .getString(R.string.msg_can_contain_slashes) - .concat("\n\n") - .concat( - activity() - .getString( - R.string.msg_newfile_dest, - folder.getAbsolutePath()))); - builder.setView(binding.getRoot()); - builder.setCancelable(false); - builder.setPositiveButton( - R.string.text_create, - (p1, p2) -> { - p1.dismiss(); - String name = binding.name.getEditText().getText().toString().trim(); - if (extension != null && extension.trim().length() > 0) { - name = name.endsWith(extension) ? name : name.concat(extension); - } - createFile(folder, name, content); - }); - builder.setNegativeButton(android.R.string.cancel, null); - builder.create().show(); - } - - private void createFile(File f, String name, String content) { - if (name.length() > 0 && name.length() <= 40 && !name.startsWith("/")) { + builder.setTitle(R.string.new_folder); + builder.setMessage(R.string.msg_can_contain_slashes); + builder.setView(binding.getRoot()); + builder.setCancelable(false); + builder.setPositiveButton( + R.string.text_create, + (p1, p2) -> { + p1.dismiss(); + final String name = binding.name.getEditText().getText().toString().trim(); + if (name.length() > 0 && name.length() <= 40 && !name.startsWith("/")) { final File file = new File(f, name); if (file.exists()) { - activity().getApp().toast(R.string.msg_file_exists, Toaster.Type.ERROR); + activity().getApp().toast(R.string.msg_folder_exists, Toaster.Type.ERROR); } else { - if (FileIOUtils.writeFileFromString(file, content)) { - // TODO Notify language servers about file created event - activity().getApp().toast(R.string.msg_file_created, Toaster.Type.SUCCESS); - if (activity().getLastHoldTreeNode() != null) { - TreeNode node = new TreeNode(file); - node.setViewHolder(new FileTreeViewHolder(activity())); - activity().getLastHoldTreeNode().addChild(node); - activity() - .getFileTreeFragment() - .expandNode(activity().getLastHoldTreeNode()); - } else { - activity().getFileTreeFragment().listProjectFiles(); - } + if (file.mkdirs()) { + activity().getApp().toast(R.string.msg_folder_created, Toaster.Type.SUCCESS); + if (activity().getLastHoldTreeNode() != null) { + TreeNode node = new TreeNode(file); + node.setViewHolder(new FileTreeViewHolder(activity())); + activity().getLastHoldTreeNode().addChild(node); + activity().getFileTreeFragment().expandNode(activity().getLastHoldTreeNode()); } else { - activity() - .getApp() - .toast(R.string.msg_file_creation_failed, Toaster.Type.ERROR); + activity().getFileTreeFragment().listProjectFiles(); } + } else { + activity().getApp().toast(R.string.msg_folder_creation_failed, Toaster.Type.ERROR); + } } - } else { + } else { activity().getApp().toast(R.string.msg_invalid_name, Toaster.Type.ERROR); - } - } + } + }); + builder.setNegativeButton(android.R.string.cancel, null); + builder.create().show(); + } - private void createNewFolder(File f) { - final LayoutDialogTextInputBinding binding = - LayoutDialogTextInputBinding.inflate(activity().getLayoutInflater()); - final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity()); + private void delete(final File f) { + MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity()); + builder + .setNegativeButton(R.string.no, null) + .setPositiveButton( + R.string.yes, + (p1, p2) -> { + p1.dismiss(); + final boolean deleted = FileUtils.delete(f); + activity() + .getApp() + .toast( + deleted ? R.string.deleted : R.string.delete_failed, + deleted ? Toaster.Type.SUCCESS : Toaster.Type.ERROR); + if (deleted) { + // TODO Notify language servers about file delete event + if (activity().getLastHoldTreeNode() != null) { + TreeNode parent = activity().getLastHoldTreeNode().getParent(); + parent.deleteChild(activity().getLastHoldTreeNode()); + activity().getFileTreeFragment().expandNode(parent); + } else { + activity().getFileTreeFragment().listProjectFiles(); + } - Objects.requireNonNull(binding.name.getEditText()).setHint(R.string.folder_name); + final var frag = activity().getEditorForFile(f); + if (frag != null) { + activity().closeFile(activity().findIndexOfEditorByFile(frag.getFile())); + } + } + }) + .setTitle(R.string.title_confirm_delete) + .setMessage( + activity() + .getString( + R.string.msg_confirm_delete, + String.format("%s [%s]", f.getName(), f.getAbsolutePath()))) + .setCancelable(false) + .create() + .show(); + } - builder.setTitle(R.string.new_folder); - builder.setMessage(R.string.msg_can_contain_slashes); - builder.setView(binding.getRoot()); - builder.setCancelable(false); - builder.setPositiveButton( - R.string.text_create, - (p1, p2) -> { - p1.dismiss(); - final String name = binding.name.getEditText().getText().toString().trim(); - if (name.length() > 0 && name.length() <= 40 && !name.startsWith("/")) { - final File file = new File(f, name); - if (file.exists()) { - activity() - .getApp() - .toast(R.string.msg_folder_exists, Toaster.Type.ERROR); - } else { - if (file.mkdirs()) { - activity() - .getApp() - .toast(R.string.msg_folder_created, Toaster.Type.SUCCESS); - if (activity().getLastHoldTreeNode() != null) { - TreeNode node = new TreeNode(file); - node.setViewHolder(new FileTreeViewHolder(activity())); - activity().getLastHoldTreeNode().addChild(node); - activity() - .getFileTreeFragment() - .expandNode(activity().getLastHoldTreeNode()); - } else { - activity().getFileTreeFragment().listProjectFiles(); - } - } else { - activity() - .getApp() - .toast( - R.string.msg_folder_creation_failed, - Toaster.Type.ERROR); - } - } - } else { - activity().getApp().toast(R.string.msg_invalid_name, Toaster.Type.ERROR); - } - }); - builder.setNegativeButton(android.R.string.cancel, null); - builder.create().show(); - } + private void renameFile(File f) { + final LayoutDialogTextInputBinding binding = + LayoutDialogTextInputBinding.inflate(LayoutInflater.from(activity())); + MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity()); - private void delete(final File f) { - MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity()); - builder.setNegativeButton(R.string.no, null) - .setPositiveButton( - R.string.yes, - (p1, p2) -> { - p1.dismiss(); - final boolean deleted = FileUtils.delete(f); - activity() - .getApp() - .toast( - deleted ? R.string.deleted : R.string.delete_failed, - deleted ? Toaster.Type.SUCCESS : Toaster.Type.ERROR); - if (deleted) { - // TODO Notify language servers about file delete event - if (activity().getLastHoldTreeNode() != null) { - TreeNode parent = activity().getLastHoldTreeNode().getParent(); - parent.deleteChild(activity().getLastHoldTreeNode()); - activity().getFileTreeFragment().expandNode(parent); - } else { - activity().getFileTreeFragment().listProjectFiles(); - } + Objects.requireNonNull(binding.name.getEditText()) + .setHint(activity().getString(R.string.new_name)); + binding.name.getEditText().setText(f.getName()); - final var frag = activity().getEditorForFile(f); - if (frag != null) { - activity() - .closeFile( - activity() - .findIndexOfEditorByFile( - frag.getFile())); - } - } - }) - .setTitle(R.string.title_confirm_delete) - .setMessage( - activity() - .getString( - R.string.msg_confirm_delete, - String.format("%s [%s]", f.getName(), f.getAbsolutePath()))) - .setCancelable(false) - .create() - .show(); - } - - private void renameFile(File f) { - final LayoutDialogTextInputBinding binding = - LayoutDialogTextInputBinding.inflate(LayoutInflater.from(activity())); - MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity()); - - Objects.requireNonNull(binding.name.getEditText()) - .setHint(activity().getString(R.string.new_name)); - binding.name.getEditText().setText(f.getName()); - - builder.setTitle(R.string.rename_file); - builder.setMessage(R.string.msg_rename_file); - builder.setView(binding.getRoot()); - builder.setNegativeButton(android.R.string.cancel, null); - builder.setPositiveButton( - R.string.rename_file, - (p1, p2) -> { - p1.dismiss(); - String name = binding.name.getEditText().getText().toString().trim(); - boolean renamed = - name.length() > 0 && name.length() <= 40 && FileUtils.rename(f, name); - activity() - .getApp() - .toast( - renamed ? R.string.renamed : R.string.rename_failed, - renamed ? Toaster.Type.SUCCESS : Toaster.Type.ERROR); - if (renamed) { - // TODO Notify language servers about file rename event - if (activity().getLastHoldTreeNode() != null) { - TreeNode parent = activity().getLastHoldTreeNode().getParent(); - parent.deleteChild(activity().getLastHoldTreeNode()); - TreeNode node = new TreeNode(new File(f.getParentFile(), name)); - node.setViewHolder(new FileTreeViewHolder(activity())); - parent.addChild(node); - activity().getFileTreeFragment().expandNode(parent); - } else { - activity().getFileTreeFragment().listProjectFiles(); - } - } - }); - builder.create().show(); - } + builder.setTitle(R.string.rename_file); + builder.setMessage(R.string.msg_rename_file); + builder.setView(binding.getRoot()); + builder.setNegativeButton(android.R.string.cancel, null); + builder.setPositiveButton( + R.string.rename_file, + (p1, p2) -> { + p1.dismiss(); + String name = binding.name.getEditText().getText().toString().trim(); + boolean renamed = name.length() > 0 && name.length() <= 40 && FileUtils.rename(f, name); + activity() + .getApp() + .toast( + renamed ? R.string.renamed : R.string.rename_failed, + renamed ? Toaster.Type.SUCCESS : Toaster.Type.ERROR); + if (renamed) { + // TODO Notify language servers about file rename event + if (activity().getLastHoldTreeNode() != null) { + TreeNode parent = activity().getLastHoldTreeNode().getParent(); + parent.deleteChild(activity().getLastHoldTreeNode()); + TreeNode node = new TreeNode(new File(f.getParentFile(), name)); + node.setViewHolder(new FileTreeViewHolder(activity())); + parent.addChild(node); + activity().getFileTreeFragment().expandNode(parent); + } else { + activity().getFileTreeFragment().listProjectFiles(); + } + } + }); + builder.create().show(); + } } diff --git a/app/src/main/java/com/itsaky/androidide/handlers/IDEHandler.java b/app/src/main/java/com/itsaky/androidide/handlers/IDEHandler.java index 1772e80c8..4e4ae2c4c 100755 --- a/app/src/main/java/com/itsaky/androidide/handlers/IDEHandler.java +++ b/app/src/main/java/com/itsaky/androidide/handlers/IDEHandler.java @@ -26,30 +26,30 @@ import com.itsaky.androidide.utils.ILogger; */ public abstract class IDEHandler { - protected static final ILogger LOG = ILogger.instance(); + protected static final ILogger LOG = ILogger.instance(); - protected Provider provider; + protected Provider provider; - public IDEHandler(Provider provider) { - this.provider = provider; - } + public IDEHandler(Provider provider) { + this.provider = provider; + } - public abstract void start(); + public abstract void start(); - public abstract void stop(); + public abstract void stop(); - protected EditorActivity activity() { - return provider.provideEditorActivity(); - } + protected EditorActivity activity() { + return provider.provideEditorActivity(); + } - /** An interface to communicate between a handler and its client */ - public interface Provider { + /** An interface to communicate between a handler and its client */ + public interface Provider { - /** - * Called by handler to get a reference to {@link EditorActivity} - * - * @throws NullPointerException is this is required - */ - EditorActivity provideEditorActivity(); - } + /** + * Called by handler to get a reference to {@link EditorActivity} + * + * @throws NullPointerException is this is required + */ + EditorActivity provideEditorActivity(); + } } diff --git a/app/src/main/java/com/itsaky/androidide/interfaces/DiagnosticClickListener.java b/app/src/main/java/com/itsaky/androidide/interfaces/DiagnosticClickListener.java index 6d347055d..b3fa654af 100755 --- a/app/src/main/java/com/itsaky/androidide/interfaces/DiagnosticClickListener.java +++ b/app/src/main/java/com/itsaky/androidide/interfaces/DiagnosticClickListener.java @@ -23,7 +23,7 @@ import com.itsaky.lsp.models.DiagnosticItem; import java.io.File; public interface DiagnosticClickListener { - void onGroupClick(DiagnosticGroup group); + void onGroupClick(DiagnosticGroup group); - void onDiagnosticClick(File file, DiagnosticItem diagnostic); + void onDiagnosticClick(File file, DiagnosticItem diagnostic); } diff --git a/app/src/main/java/com/itsaky/androidide/interfaces/EditorActivityProvider.java b/app/src/main/java/com/itsaky/androidide/interfaces/EditorActivityProvider.java index e39ffa78d..fa4ef4a48 100755 --- a/app/src/main/java/com/itsaky/androidide/interfaces/EditorActivityProvider.java +++ b/app/src/main/java/com/itsaky/androidide/interfaces/EditorActivityProvider.java @@ -21,10 +21,9 @@ import com.itsaky.androidide.EditorActivity; public interface EditorActivityProvider { - /** - * Provides a reference to {@link com.itsaky.androidide.EditorActivity EditorActivity}. This is - * used by LanguageClients to get reference to EditorActivity without keeping a strong - * reference. - */ - EditorActivity provide(); + /** + * Provides a reference to {@link com.itsaky.androidide.EditorActivity EditorActivity}. This is + * used by LanguageClients to get reference to EditorActivity without keeping a strong reference. + */ + EditorActivity provide(); } diff --git a/app/src/main/java/com/itsaky/androidide/language/CommonCompletionProvider.java b/app/src/main/java/com/itsaky/androidide/language/CommonCompletionProvider.java index 7c08715d0..3ed1e342b 100644 --- a/app/src/main/java/com/itsaky/androidide/language/CommonCompletionProvider.java +++ b/app/src/main/java/com/itsaky/androidide/language/CommonCompletionProvider.java @@ -54,85 +54,83 @@ import io.github.rosemoe.sora.util.MyCharacter; * @author Akash Yadav */ public class CommonCompletionProvider { - - private static final ILogger LOG = ILogger.newInstance("CommonCompletionProvider"); - private final ILanguageServer server; - private CompletableFuture future; - public CommonCompletionProvider(ILanguageServer server) { - this.server = server; + private static final ILogger LOG = ILogger.newInstance("CommonCompletionProvider"); + private final ILanguageServer server; + private CompletableFuture future; + + public CommonCompletionProvider(ILanguageServer server) { + this.server = server; + } + + public static boolean checkJavaCompletionChar(char c) { + return MyCharacter.isJavaIdentifierPart(c) || c == '.'; + } + + public static boolean checkXMLCompletionChar(char c) { + return MyCharacter.isJavaIdentifierPart(c) || c == '<' || c == '/'; + } + + /** + * Computes completion items using the provided language server instance. + * + * @param content The reference to the content of the editor. + * @param file The file to compute completions for. + * @param position The position of the cursor in the content. + * @return The computed completion items. May return an empty list if the there was an error + * computing the completion items. + */ + public List complete( + ContentReference content, + Path file, + CharPosition position, + Predicate prefixMatcher) { + if (this.future != null && !this.future.isDone()) { + try { + this.future.cancel(true); + } catch (CancellationException e) { + return new ArrayList<>(); + } } - public static boolean checkJavaCompletionChar(char c) { - return MyCharacter.isJavaIdentifierPart(c) || c == '.'; - } - - public static boolean checkXMLCompletionChar(char c) { - return MyCharacter.isJavaIdentifierPart(c) || c == '<' || c == '/'; - } - - /** - * Computes completion items using the provided language server instance. - * - * @param content The reference to the content of the editor. - * @param file The file to compute completions for. - * @param position The position of the cursor in the content. - * @return The computed completion items. May return an empty list if the there was an error - * computing the completion items. - */ - public List complete( - ContentReference content, - Path file, - CharPosition position, - Predicate prefixMatcher) { - if (this.future != null && !this.future.isDone()) { - try { - this.future.cancel(true); - } catch (CancellationException e) { - return new ArrayList<>(); - } - } - - this.future = - CompletableFuture.supplyAsync( - () -> { - final var prefix = - CompletionHelper.computePrefix( - content, position, prefixMatcher::test); - final var completer = server.getCompletionProvider(); - - if (!completer.canComplete(file)) { - return CompletionResult.EMPTY; - } - - IdeGradleProject fileModule = null; - try { - fileModule = ProjectManager.INSTANCE.findModuleForFile (file.toFile ()).get (); } catch (Throwable e) { - LOG.error ("Unable to find module for current file", e); - } - - final var params = - new CompletionParams( - new Position( - position.line, position.column, position.index), - file); - params.setContent(content); - params.setPrefix(prefix); - params.setModule(fileModule); - final var result = completer.complete(params); - Collections.sort(result.getItems()); - return result; - }); - - try { - return future.get().getItems(); - } catch (Throwable e) { - // Do not log if completion was interrupted or cancelled - if (!(e instanceof InterruptedException || e instanceof CompletionCancelledException)) { - LOG.error("Unable to compute completions", e); - } - - return Collections.emptyList(); - } + this.future = + CompletableFuture.supplyAsync( + () -> { + final var prefix = + CompletionHelper.computePrefix(content, position, prefixMatcher::test); + final var completer = server.getCompletionProvider(); + + if (!completer.canComplete(file)) { + return CompletionResult.EMPTY; + } + + IdeGradleProject fileModule = null; + try { + fileModule = ProjectManager.INSTANCE.findModuleForFile(file.toFile()).get(); + } catch (Throwable e) { + LOG.error("Unable to find module for current file", e); + } + + final var params = + new CompletionParams( + new Position(position.line, position.column, position.index), file); + params.setContent(content); + params.setPrefix(prefix); + params.setModule(fileModule); + final var result = completer.complete(params); + Collections.sort(result.getItems()); + return result; + }); + + try { + return future.get().getItems(); + } catch (Throwable e) { + // Do not log if completion was interrupted or cancelled + if (!(e instanceof InterruptedException || e instanceof CompletionCancelledException)) { + LOG.error("Unable to compute completions", e); + } + + return Collections.emptyList(); } + } } diff --git a/app/src/main/java/com/itsaky/androidide/language/IAnalyzeManager.java b/app/src/main/java/com/itsaky/androidide/language/IAnalyzeManager.java index e6186d481..669f211eb 100644 --- a/app/src/main/java/com/itsaky/androidide/language/IAnalyzeManager.java +++ b/app/src/main/java/com/itsaky/androidide/language/IAnalyzeManager.java @@ -30,18 +30,18 @@ import java.util.List; */ public interface IAnalyzeManager { - /** - * Update the diagnostic items. - * - * @param diagnostics The new list of diagnostics. - */ - default void updateDiagnostics(@NonNull List diagnostics) {} + /** + * Update the diagnostic items. + * + * @param diagnostics The new list of diagnostics. + */ + default void updateDiagnostics(@NonNull List diagnostics) {} - /** - * Get the diagnostic items. - * - * @return The diagnostic items. Must not be null. - */ - @NonNull - List getDiagnostics(); + /** + * Get the diagnostic items. + * + * @return The diagnostic items. Must not be null. + */ + @NonNull + List getDiagnostics(); } diff --git a/app/src/main/java/com/itsaky/androidide/language/IDELanguage.java b/app/src/main/java/com/itsaky/androidide/language/IDELanguage.java index 721612aa2..a93da8c95 100644 --- a/app/src/main/java/com/itsaky/androidide/language/IDELanguage.java +++ b/app/src/main/java/com/itsaky/androidide/language/IDELanguage.java @@ -35,52 +35,52 @@ import io.github.rosemoe.sora.lang.Language; */ public abstract class IDELanguage implements Language { - @Override - public boolean useTab() { - return false; + @Override + public boolean useTab() { + return false; + } + + public int getTabSize() { + return StudioApp.getInstance().getPrefManager().getEditorTabSize(); + } + + /** + * Get the diagnostics from the last analyze. + * + * @return The diagnostics. Must not be null. + */ + @NonNull + public List getDiagnostics() { + return Collections.emptyList(); + } + + public void setDiagnostics(List diagnostics) { + if (diagnostics == null) { + diagnostics = new ArrayList<>(0); } - public int getTabSize() { - return StudioApp.getInstance().getPrefManager().getEditorTabSize(); + final var analyzer = getAnalyzeManager(); + if (analyzer instanceof IAnalyzeManager) { + ((IAnalyzeManager) analyzer).updateDiagnostics(diagnostics); + analyzer.rerun(); + } + } + + @Override + public CharSequence format(CharSequence text) { + final var server = getLanguageServer(); + if (server != null) { + return server.formatCode(text); } - /** - * Get the diagnostics from the last analyze. - * - * @return The diagnostics. Must not be null. - */ - @NonNull - public List getDiagnostics() { - return Collections.emptyList(); - } + return text; + } - public void setDiagnostics(List diagnostics) { - if (diagnostics == null) { - diagnostics = new ArrayList<>(0); - } + public int getIndentAdvance(@NonNull String line) { + return 0; + } - final var analyzer = getAnalyzeManager(); - if (analyzer instanceof IAnalyzeManager) { - ((IAnalyzeManager) analyzer).updateDiagnostics(diagnostics); - analyzer.rerun(); - } - } - - @Override - public CharSequence format(CharSequence text) { - final var server = getLanguageServer(); - if (server != null) { - return server.formatCode(text); - } - - return text; - } - - public int getIndentAdvance(@NonNull String line) { - return 0; - } - - protected ILanguageServer getLanguageServer() { - return null; - } + protected ILanguageServer getLanguageServer() { + return null; + } } diff --git a/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyAnalyzer.java b/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyAnalyzer.java index e5dad19c1..054189269 100755 --- a/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyAnalyzer.java +++ b/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyAnalyzer.java @@ -46,219 +46,219 @@ import io.github.rosemoe.sora.lang.styling.Styles; public class GroovyAnalyzer extends SimpleAnalyzeManager { - private static final ILogger LOG = ILogger.newInstance("GroovyAnalyzer"); + private static final ILogger LOG = ILogger.newInstance("GroovyAnalyzer"); - @Override - protected Styles analyze(StringBuilder text, Delegate delegate) { - final var styles = new Styles(); - final var colors = new MappedSpans.Builder(); - final CodePointCharStream stream; - try { - stream = CharStreams.fromReader(new CharSequenceReader(text)); - } catch (IOException e) { - LOG.error("Unable to read text to analyze", e); - return styles; - } - - final var lexer = new GroovyLexer(stream); - final var stack = new Stack(); - - Token token; - int line, column, lastLine = 1, currSwitch = 0, maxSwitch = 0, type, previous = 0; - boolean isFirst = true; - - while (!delegate.isCancelled()) { - token = lexer.nextToken(); - - if (token == null) { - break; - } - - line = token.getLine() - 1; - column = token.getCharPositionInLine(); - type = token.getType(); - - if (type == GroovyLexer.EOF) { - lastLine = line; - break; - } - - switch (type) { - case GroovyLexer.WS: - if (isFirst) { - colors.addNormalIfNull(); - } - break; - case GroovyLexer.ABSTRACT: - case GroovyLexer.ASSERT: - case GroovyLexer.BREAK: - case GroovyLexer.CASE: - case GroovyLexer.CATCH: - case GroovyLexer.CLASS: - case GroovyLexer.CONST: - case GroovyLexer.CONTINUE: - case GroovyLexer.DEFAULT: - case GroovyLexer.DO: - case GroovyLexer.ELSE: - case GroovyLexer.EXTENDS: - case GroovyLexer.FINAL: - case GroovyLexer.FINALLY: - case GroovyLexer.FOR: - case GroovyLexer.IF: - case GroovyLexer.GOTO: - case GroovyLexer.IMPLEMENTS: - case GroovyLexer.IMPORT: - case GroovyLexer.INSTANCEOF: - case GroovyLexer.INTERFACE: - case GroovyLexer.NATIVE: - case GroovyLexer.NEW: - case GroovyLexer.PACKAGE: - case GroovyLexer.PRIVATE: - case GroovyLexer.PROTECTED: - case GroovyLexer.PUBLIC: - case GroovyLexer.RETURN: - case GroovyLexer.STATIC: - case GroovyLexer.STRICTFP: - case GroovyLexer.SUPER: - case GroovyLexer.SWITCH: - case GroovyLexer.SYNCHRONIZED: - case GroovyLexer.THIS: - case GroovyLexer.THROW: - case GroovyLexer.THROWS: - case GroovyLexer.TRANSIENT: - case GroovyLexer.TRY: - case GroovyLexer.VOID: - case GroovyLexer.VOLATILE: - case GroovyLexer.WHILE: - colors.addIfNeeded(line, column, forKeyword()); - break; - case GroovyLexer.DECIMAL_LITERAL: - case GroovyLexer.HEX_LITERAL: - case GroovyLexer.OCT_LITERAL: - case GroovyLexer.BINARY_LITERAL: - case GroovyLexer.FLOAT_LITERAL: - case GroovyLexer.HEX_FLOAT_LITERAL: - case GroovyLexer.BOOL_LITERAL: - case GroovyLexer.CHAR_LITERAL: - case GroovyLexer.NULL_LITERAL: - colors.addIfNeeded(line, column, get(LITERAL)); - break; - case GroovyLexer.STRING_LITERAL: - case GroovyLexer.SINGLE_QUOTE_STRING: - colors.addIfNeeded(line, column, forString()); - break; - case GroovyLexer.LPAREN: - case GroovyLexer.RPAREN: - case GroovyLexer.LBRACK: - case GroovyLexer.RBRACK: - case GroovyLexer.SEMI: - case GroovyLexer.COMMA: - case GroovyLexer.ASSIGN: - case GroovyLexer.GT: - case GroovyLexer.LT: - case GroovyLexer.BANG: - case GroovyLexer.TILDE: - case GroovyLexer.QUESTION: - case GroovyLexer.COLON: - case GroovyLexer.EQUAL: - case GroovyLexer.GE: - case GroovyLexer.LE: - case GroovyLexer.NOTEQUAL: - case GroovyLexer.AND: - case GroovyLexer.OR: - case GroovyLexer.INC: - case GroovyLexer.DEC: - case GroovyLexer.ADD: - case GroovyLexer.SUB: - case GroovyLexer.MUL: - case GroovyLexer.DIV: - case GroovyLexer.BITAND: - case GroovyLexer.BITOR: - case GroovyLexer.CARET: - case GroovyLexer.MOD: - case GroovyLexer.ADD_ASSIGN: - case GroovyLexer.SUB_ASSIGN: - case GroovyLexer.MUL_ASSIGN: - case GroovyLexer.DIV_ASSIGN: - case GroovyLexer.AND_ASSIGN: - case GroovyLexer.OR_ASSIGN: - case GroovyLexer.XOR_ASSIGN: - case GroovyLexer.MOD_ASSIGN: - case GroovyLexer.LSHIFT_ASSIGN: - case GroovyLexer.RSHIFT_ASSIGN: - case GroovyLexer.URSHIFT_ASSIGN: - case GroovyLexer.ARROW: - case GroovyLexer.COLONCOLON: - case GroovyLexer.ELLIPSIS: - case GroovyLexer.DOT: - colors.addIfNeeded(line, column, get(OPERATOR)); - break; - case GroovyLexer.BOOLEAN: - case GroovyLexer.BYTE: - case GroovyLexer.CHAR: - case GroovyLexer.DOUBLE: - case GroovyLexer.ENUM: - case GroovyLexer.FLOAT: - case GroovyLexer.INT: - case GroovyLexer.LONG: - case GroovyLexer.SHORT: - colors.addIfNeeded(line, column, get(TYPE_NAME)); - break; - case GroovyLexer.COMMENT: - case GroovyLexer.LINE_COMMENT: - colors.addIfNeeded(line, column, forComment()); - break; - case GroovyLexer.AT: - colors.addIfNeeded(line, column, get(ANNOTATION)); - break; - case GroovyLexer.IDENTIFIER: - if (previous == GroovyLexer.AT) { - colors.addIfNeeded(line, column, get(ANNOTATION)); - break; - } - - colors.addIfNeeded(line, column, get(TEXT_NORMAL)); - break; - case GroovyLexer.LBRACE: - colors.addIfNeeded(line, column, get(OPERATOR)); - if (stack.isEmpty()) { - if (currSwitch > maxSwitch) maxSwitch = currSwitch; - currSwitch = 0; - } - currSwitch++; - var block = styles.obtainNewBlock(); - block.startLine = line; - block.startColumn = column; - stack.push(block); - break; - case GroovyLexer.RBRACE: - colors.addIfNeeded(line, column, get(OPERATOR)); - if (!stack.isEmpty()) { - var block2 = stack.pop(); - block2.endLine = line; - block2.endColumn = column; - if (block2.startLine != block2.endLine) styles.addCodeBlock(block2); - } - break; - default: - colors.addIfNeeded(line, column, get(TEXT_NORMAL)); - break; - } - - isFirst = false; - if (type != GroovyLexer.WS) { - previous = type; - } - } - - if (stack.isEmpty() && currSwitch > maxSwitch) { - maxSwitch = currSwitch; - } - - colors.determine(lastLine); - styles.setSuppressSwitch(maxSwitch + 10); - styles.spans = colors.build(); - - return styles; + @Override + protected Styles analyze(StringBuilder text, Delegate delegate) { + final var styles = new Styles(); + final var colors = new MappedSpans.Builder(); + final CodePointCharStream stream; + try { + stream = CharStreams.fromReader(new CharSequenceReader(text)); + } catch (IOException e) { + LOG.error("Unable to read text to analyze", e); + return styles; } + + final var lexer = new GroovyLexer(stream); + final var stack = new Stack(); + + Token token; + int line, column, lastLine = 1, currSwitch = 0, maxSwitch = 0, type, previous = 0; + boolean isFirst = true; + + while (!delegate.isCancelled()) { + token = lexer.nextToken(); + + if (token == null) { + break; + } + + line = token.getLine() - 1; + column = token.getCharPositionInLine(); + type = token.getType(); + + if (type == GroovyLexer.EOF) { + lastLine = line; + break; + } + + switch (type) { + case GroovyLexer.WS: + if (isFirst) { + colors.addNormalIfNull(); + } + break; + case GroovyLexer.ABSTRACT: + case GroovyLexer.ASSERT: + case GroovyLexer.BREAK: + case GroovyLexer.CASE: + case GroovyLexer.CATCH: + case GroovyLexer.CLASS: + case GroovyLexer.CONST: + case GroovyLexer.CONTINUE: + case GroovyLexer.DEFAULT: + case GroovyLexer.DO: + case GroovyLexer.ELSE: + case GroovyLexer.EXTENDS: + case GroovyLexer.FINAL: + case GroovyLexer.FINALLY: + case GroovyLexer.FOR: + case GroovyLexer.IF: + case GroovyLexer.GOTO: + case GroovyLexer.IMPLEMENTS: + case GroovyLexer.IMPORT: + case GroovyLexer.INSTANCEOF: + case GroovyLexer.INTERFACE: + case GroovyLexer.NATIVE: + case GroovyLexer.NEW: + case GroovyLexer.PACKAGE: + case GroovyLexer.PRIVATE: + case GroovyLexer.PROTECTED: + case GroovyLexer.PUBLIC: + case GroovyLexer.RETURN: + case GroovyLexer.STATIC: + case GroovyLexer.STRICTFP: + case GroovyLexer.SUPER: + case GroovyLexer.SWITCH: + case GroovyLexer.SYNCHRONIZED: + case GroovyLexer.THIS: + case GroovyLexer.THROW: + case GroovyLexer.THROWS: + case GroovyLexer.TRANSIENT: + case GroovyLexer.TRY: + case GroovyLexer.VOID: + case GroovyLexer.VOLATILE: + case GroovyLexer.WHILE: + colors.addIfNeeded(line, column, forKeyword()); + break; + case GroovyLexer.DECIMAL_LITERAL: + case GroovyLexer.HEX_LITERAL: + case GroovyLexer.OCT_LITERAL: + case GroovyLexer.BINARY_LITERAL: + case GroovyLexer.FLOAT_LITERAL: + case GroovyLexer.HEX_FLOAT_LITERAL: + case GroovyLexer.BOOL_LITERAL: + case GroovyLexer.CHAR_LITERAL: + case GroovyLexer.NULL_LITERAL: + colors.addIfNeeded(line, column, get(LITERAL)); + break; + case GroovyLexer.STRING_LITERAL: + case GroovyLexer.SINGLE_QUOTE_STRING: + colors.addIfNeeded(line, column, forString()); + break; + case GroovyLexer.LPAREN: + case GroovyLexer.RPAREN: + case GroovyLexer.LBRACK: + case GroovyLexer.RBRACK: + case GroovyLexer.SEMI: + case GroovyLexer.COMMA: + case GroovyLexer.ASSIGN: + case GroovyLexer.GT: + case GroovyLexer.LT: + case GroovyLexer.BANG: + case GroovyLexer.TILDE: + case GroovyLexer.QUESTION: + case GroovyLexer.COLON: + case GroovyLexer.EQUAL: + case GroovyLexer.GE: + case GroovyLexer.LE: + case GroovyLexer.NOTEQUAL: + case GroovyLexer.AND: + case GroovyLexer.OR: + case GroovyLexer.INC: + case GroovyLexer.DEC: + case GroovyLexer.ADD: + case GroovyLexer.SUB: + case GroovyLexer.MUL: + case GroovyLexer.DIV: + case GroovyLexer.BITAND: + case GroovyLexer.BITOR: + case GroovyLexer.CARET: + case GroovyLexer.MOD: + case GroovyLexer.ADD_ASSIGN: + case GroovyLexer.SUB_ASSIGN: + case GroovyLexer.MUL_ASSIGN: + case GroovyLexer.DIV_ASSIGN: + case GroovyLexer.AND_ASSIGN: + case GroovyLexer.OR_ASSIGN: + case GroovyLexer.XOR_ASSIGN: + case GroovyLexer.MOD_ASSIGN: + case GroovyLexer.LSHIFT_ASSIGN: + case GroovyLexer.RSHIFT_ASSIGN: + case GroovyLexer.URSHIFT_ASSIGN: + case GroovyLexer.ARROW: + case GroovyLexer.COLONCOLON: + case GroovyLexer.ELLIPSIS: + case GroovyLexer.DOT: + colors.addIfNeeded(line, column, get(OPERATOR)); + break; + case GroovyLexer.BOOLEAN: + case GroovyLexer.BYTE: + case GroovyLexer.CHAR: + case GroovyLexer.DOUBLE: + case GroovyLexer.ENUM: + case GroovyLexer.FLOAT: + case GroovyLexer.INT: + case GroovyLexer.LONG: + case GroovyLexer.SHORT: + colors.addIfNeeded(line, column, get(TYPE_NAME)); + break; + case GroovyLexer.COMMENT: + case GroovyLexer.LINE_COMMENT: + colors.addIfNeeded(line, column, forComment()); + break; + case GroovyLexer.AT: + colors.addIfNeeded(line, column, get(ANNOTATION)); + break; + case GroovyLexer.IDENTIFIER: + if (previous == GroovyLexer.AT) { + colors.addIfNeeded(line, column, get(ANNOTATION)); + break; + } + + colors.addIfNeeded(line, column, get(TEXT_NORMAL)); + break; + case GroovyLexer.LBRACE: + colors.addIfNeeded(line, column, get(OPERATOR)); + if (stack.isEmpty()) { + if (currSwitch > maxSwitch) maxSwitch = currSwitch; + currSwitch = 0; + } + currSwitch++; + var block = styles.obtainNewBlock(); + block.startLine = line; + block.startColumn = column; + stack.push(block); + break; + case GroovyLexer.RBRACE: + colors.addIfNeeded(line, column, get(OPERATOR)); + if (!stack.isEmpty()) { + var block2 = stack.pop(); + block2.endLine = line; + block2.endColumn = column; + if (block2.startLine != block2.endLine) styles.addCodeBlock(block2); + } + break; + default: + colors.addIfNeeded(line, column, get(TEXT_NORMAL)); + break; + } + + isFirst = false; + if (type != GroovyLexer.WS) { + previous = type; + } + } + + if (stack.isEmpty() && currSwitch > maxSwitch) { + maxSwitch = currSwitch; + } + + colors.determine(lastLine); + styles.setSuppressSwitch(maxSwitch + 10); + styles.spans = colors.build(); + + return styles; + } } diff --git a/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyAutoComplete.java b/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyAutoComplete.java index 69a9dfeca..9c2be7daf 100755 --- a/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyAutoComplete.java +++ b/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyAutoComplete.java @@ -37,351 +37,347 @@ import io.github.rosemoe.sora.util.MyCharacter; public class GroovyAutoComplete { - private static final List ANDROIDX_ARTIFACTS = createArtifacts(); - private static final List CONFIGURATIONS = createConfigs(); - private static final List OTHERS = createOtherCompletions(); + private static final List ANDROIDX_ARTIFACTS = createArtifacts(); + private static final List CONFIGURATIONS = createConfigs(); + private static final List OTHERS = createOtherCompletions(); - public void complete( - ContentReference content, - CharPosition position, - CompletionPublisher publisher, - Bundle extraArguments) { - publisher.setUpdateThreshold(0); - final var prefix = - CompletionHelper.computePrefix( - content, position, MyCharacter::isJavaIdentifierPart); - for (String artifact : ANDROIDX_ARTIFACTS) { - if (!artifact.toLowerCase(Locale.ROOT).startsWith(prefix.toLowerCase(Locale.ROOT))) - continue; - publisher.addItem(createCompletionItem(artifact)); - } - - for (String config : CONFIGURATIONS) { - if (!config.toLowerCase(Locale.ROOT).startsWith(prefix.toLowerCase(Locale.ROOT))) - continue; - publisher.addItem(createCompletionItem(config)); - } - - for (String other : OTHERS) { - if (!other.toLowerCase(Locale.ROOT).startsWith(prefix.toLowerCase(Locale.ROOT))) - continue; - publisher.addItem(createCompletionItem(other)); - } + public void complete( + ContentReference content, + CharPosition position, + CompletionPublisher publisher, + Bundle extraArguments) { + publisher.setUpdateThreshold(0); + final var prefix = + CompletionHelper.computePrefix(content, position, MyCharacter::isJavaIdentifierPart); + for (String artifact : ANDROIDX_ARTIFACTS) { + if (!artifact.toLowerCase(Locale.ROOT).startsWith(prefix.toLowerCase(Locale.ROOT))) continue; + publisher.addItem(createCompletionItem(artifact)); } - private CompletionItem createCompletionItem(String itemLabel) { - CompletionItem item = new CompletionItem(); - item.setLabel(itemLabel); - item.setDetail(""); - item.setInsertText(itemLabel); - item.setSortText("0" + itemLabel); - item.setInsertTextFormat(InsertTextFormat.PLAIN_TEXT); - item.setKind(CompletionItemKind.SNIPPET); - return item; + for (String config : CONFIGURATIONS) { + if (!config.toLowerCase(Locale.ROOT).startsWith(prefix.toLowerCase(Locale.ROOT))) continue; + publisher.addItem(createCompletionItem(config)); } - private static List createArtifacts() { - var artifacts = new ArrayList(); - artifacts.add("androidx.arch.core:core-common"); - artifacts.add("androidx.arch.core:core"); - artifacts.add("androidx.arch.core:core-testing"); - artifacts.add("androidx.arch.core:core-runtime"); - artifacts.add("androidx.lifecycle:lifecycle-common"); - artifacts.add("androidx.lifecycle:lifecycle-common-java8"); - artifacts.add("androidx.lifecycle:lifecycle-compiler"); - artifacts.add("androidx.lifecycle:lifecycle-extensions"); - artifacts.add("androidx.lifecycle:lifecycle-livedata"); - artifacts.add("androidx.lifecycle:lifecycle-livedata-core"); - artifacts.add("androidx.lifecycle:lifecycle-reactivestreams"); - artifacts.add("androidx.lifecycle:lifecycle-runtime"); - artifacts.add("androidx.lifecycle:lifecycle-viewmodel"); - artifacts.add("androidx.paging:paging-common"); - artifacts.add("androidx.paging:paging-runtime"); - artifacts.add("androidx.paging:paging-rxjava2"); - artifacts.add("androidx.room:room-common"); - artifacts.add("androidx.room:room-compiler"); - artifacts.add("androidx.room:room-guava"); - artifacts.add("androidx.room:room-migration"); - artifacts.add("androidx.room:room-runtime"); - artifacts.add("androidx.room:room-rxjava2"); - artifacts.add("androidx.room:room-testing"); - artifacts.add("androidx.sqlite:sqlite"); - artifacts.add("androidx.sqlite:sqlite-framework"); - artifacts.add("androidx.constraintlayout:constraintlayout"); - artifacts.add("androidx.constraintlayout:constraintlayout-solver"); - artifacts.add("androidx.test.espresso.idling:idling-concurrent"); - artifacts.add("androidx.test.espresso.idling:idling-net"); - artifacts.add("androidx.test.espresso:espresso-accessibility"); - artifacts.add("androidx.test.espresso:espresso-contrib"); - artifacts.add("androidx.test.espresso:espresso-core"); - artifacts.add("androidx.test.espresso:espresso-idling-resource"); - artifacts.add("androidx.test.espresso:espresso-intents"); - artifacts.add("androidx.test.espresso:espresso-remote"); - artifacts.add("androidx.test.espresso:espresso-web"); - artifacts.add("androidx.test.jank:janktesthelper"); - artifacts.add("androidx.test:test-services"); - artifacts.add("androidx.test.uiautomator:uiautomator"); - artifacts.add("androidx.test:monitor"); - artifacts.add("androidx.test:orchestrator"); - artifacts.add("androidx.test:rules"); - artifacts.add("androidx.test:runner"); - artifacts.add("androidx.vectordrawable:vectordrawable-animated"); - artifacts.add("androidx.appcompat:appcompat"); - artifacts.add("androidx.asynclayoutinflater:asynclayoutinflater"); - artifacts.add("androidx.car:car"); - artifacts.add("androidx.cardview:cardview"); - artifacts.add("androidx.collection:collection"); - artifacts.add("androidx.coordinatorlayout:coordinatorlayout"); - artifacts.add("androidx.cursoradapter:cursoradapter"); - artifacts.add("androidx.browser:browser"); - artifacts.add("androidx.customview:customview"); - artifacts.add("com.google.android.material:material"); - artifacts.add("androidx.documentfile:documentfile"); - artifacts.add("androidx.drawerlayout:drawerlayout"); - artifacts.add("androidx.exifinterface:exifinterface"); - artifacts.add("androidx.gridlayout:gridlayout"); - artifacts.add("androidx.heifwriter:heifwriter"); - artifacts.add("androidx.interpolator:interpolator"); - artifacts.add("androidx.leanback:leanback"); - artifacts.add("androidx.loader:loader"); - artifacts.add("androidx.localbroadcastmanager:localbroadcastmanager"); - artifacts.add("androidx.media2:media2"); - artifacts.add("androidx.media2:media2-exoplayer"); - artifacts.add("androidx.mediarouter:mediarouter"); - artifacts.add("androidx.multidex:multidex"); - artifacts.add("androidx.multidex:multidex-instrumentation"); - artifacts.add("androidx.palette:palette"); - artifacts.add("androidx.percentlayout:percentlayout"); - artifacts.add("androidx.leanback:leanback-preference"); - artifacts.add("androidx.legacy:legacy-preference-v14"); - artifacts.add("androidx.preference:preference"); - artifacts.add("androidx.print:print"); - artifacts.add("androidx.recommendation:recommendation"); - artifacts.add("androidx.recyclerview:recyclerview-selection"); - artifacts.add("androidx.recyclerview:recyclerview"); - artifacts.add("androidx.slice:slice-builders"); - artifacts.add("androidx.slice:slice-core"); - artifacts.add("androidx.slice:slice-view"); - artifacts.add("androidx.slidingpanelayout:slidingpanelayout"); - artifacts.add("androidx.annotation:annotation"); - artifacts.add("androidx.core:core"); - artifacts.add("androidx.contentpager:contentpager"); - artifacts.add("androidx.legacy:legacy-support-core-ui"); - artifacts.add("androidx.legacy:legacy-support-core-utils"); - artifacts.add("androidx.dynamicanimation:dynamicanimation"); - artifacts.add("androidx.emoji:emoji"); - artifacts.add("androidx.emoji:emoji-appcompat"); - artifacts.add("androidx.emoji:emoji-bundled"); - artifacts.add("androidx.fragment:fragment"); - artifacts.add("androidx.media:media"); - artifacts.add("androidx.tvprovider:tvprovider"); - artifacts.add("androidx.legacy:legacy-support-v13"); - artifacts.add("androidx.legacy:legacy-support-v4"); - artifacts.add("androidx.vectordrawable:vectordrawable"); - artifacts.add("androidx.swiperefreshlayout:swiperefreshlayout"); - artifacts.add("androidx.textclassifier:textclassifier"); - artifacts.add("androidx.transition:transition"); - artifacts.add("androidx.versionedparcelable:versionedparcelable"); - artifacts.add("androidx.viewpager:viewpager"); - artifacts.add("androidx.wear:wear"); - artifacts.add("androidx.webkit:webkit"); - - artifacts.add("fileTree(dir: 'libs', include: ['*.jar'])"); - return artifacts; + for (String other : OTHERS) { + if (!other.toLowerCase(Locale.ROOT).startsWith(prefix.toLowerCase(Locale.ROOT))) continue; + publisher.addItem(createCompletionItem(other)); } + } - private static List createConfigs() { - var configs = new ArrayList(); - configs.add("androidApis"); - configs.add("androidTestAnnotationProcessor"); - configs.add("androidTestApi"); - configs.add("androidTestApk"); - configs.add("androidTestCompile"); - configs.add("androidTestCompileOnly"); - configs.add("androidTestDebugAnnotationProcessor"); - configs.add("androidTestDebugApi"); - configs.add("androidTestDebugApk"); - configs.add("androidTestDebugCompile"); - configs.add("androidTestDebugCompileOnly"); - configs.add("androidTestDebugImplementation"); - configs.add("androidTestDebugProvided"); - configs.add("androidTestDebugRuntimeOnly"); - configs.add("androidTestDebugWearApp"); - configs.add("androidTestImplementation"); - configs.add("androidTestProvided"); - configs.add("androidTestRuntimeOnly"); - configs.add("androidTestUtil"); - configs.add("androidTestWearApp"); - configs.add("annotationProcessor"); - configs.add("api"); - configs.add("apk"); - configs.add("archives"); - configs.add("compile"); - configs.add("compileOnly"); - configs.add("coreLibraryDesugaring"); - configs.add("debugAnnotationProcessor"); - configs.add("debugApi"); - configs.add("debugApk"); - configs.add("debugCompile"); - configs.add("debugCompileOnly"); - configs.add("debugImplementation"); - configs.add("debugProvided"); - configs.add("debugRuntimeOnly"); - configs.add("debugWearApp"); - configs.add("default"); - configs.add("implementation"); - configs.add("implementation platform()"); - configs.add("lintChecks"); - configs.add("lintClassPath"); - configs.add("lintPublish"); - configs.add("provided"); - configs.add("releaseAnnotationProcessor"); - configs.add("releaseApi"); - configs.add("releaseApk"); - configs.add("releaseCompile"); - configs.add("releaseCompileOnly"); - configs.add("releaseImplementation"); - configs.add("releaseProvided"); - configs.add("releaseRuntimeOnly"); - configs.add("releaseWearApp"); - configs.add("runtimeOnly"); - configs.add("testAnnotationProcessor"); - configs.add("testApi"); - configs.add("testApk"); - configs.add("testCompile"); - configs.add("testCompileOnly"); - configs.add("testDebugAnnotationProcessor"); - configs.add("testDebugApi"); - configs.add("testDebugApk"); - configs.add("testDebugCompile"); - configs.add("testDebugCompileOnly"); - configs.add("testDebugImplementation"); - configs.add("testDebugProvided"); - configs.add("testDebugRuntimeOnly"); - configs.add("testDebugWearApp"); - configs.add("testImplementation"); - configs.add("testProvided"); - configs.add("testReleaseAnnotationProcessor"); - configs.add("testReleaseApi"); - configs.add("testReleaseApk"); - configs.add("testReleaseCompile"); - configs.add("testReleaseCompileOnly"); - configs.add("testReleaseImplementation"); - configs.add("testReleaseProvided"); - configs.add("testReleaseRuntimeOnly"); - configs.add("testReleaseWearApp"); - configs.add("testRuntimeOnly"); - configs.add("testWearApp"); - configs.add("wearApp"); - return configs; - } + private CompletionItem createCompletionItem(String itemLabel) { + CompletionItem item = new CompletionItem(); + item.setLabel(itemLabel); + item.setDetail(""); + item.setInsertText(itemLabel); + item.setSortText("0" + itemLabel); + item.setInsertTextFormat(InsertTextFormat.PLAIN_TEXT); + item.setKind(CompletionItemKind.SNIPPET); + return item; + } - private static List createOtherCompletions() { - var others = new ArrayList(); + private static List createArtifacts() { + var artifacts = new ArrayList(); + artifacts.add("androidx.arch.core:core-common"); + artifacts.add("androidx.arch.core:core"); + artifacts.add("androidx.arch.core:core-testing"); + artifacts.add("androidx.arch.core:core-runtime"); + artifacts.add("androidx.lifecycle:lifecycle-common"); + artifacts.add("androidx.lifecycle:lifecycle-common-java8"); + artifacts.add("androidx.lifecycle:lifecycle-compiler"); + artifacts.add("androidx.lifecycle:lifecycle-extensions"); + artifacts.add("androidx.lifecycle:lifecycle-livedata"); + artifacts.add("androidx.lifecycle:lifecycle-livedata-core"); + artifacts.add("androidx.lifecycle:lifecycle-reactivestreams"); + artifacts.add("androidx.lifecycle:lifecycle-runtime"); + artifacts.add("androidx.lifecycle:lifecycle-viewmodel"); + artifacts.add("androidx.paging:paging-common"); + artifacts.add("androidx.paging:paging-runtime"); + artifacts.add("androidx.paging:paging-rxjava2"); + artifacts.add("androidx.room:room-common"); + artifacts.add("androidx.room:room-compiler"); + artifacts.add("androidx.room:room-guava"); + artifacts.add("androidx.room:room-migration"); + artifacts.add("androidx.room:room-runtime"); + artifacts.add("androidx.room:room-rxjava2"); + artifacts.add("androidx.room:room-testing"); + artifacts.add("androidx.sqlite:sqlite"); + artifacts.add("androidx.sqlite:sqlite-framework"); + artifacts.add("androidx.constraintlayout:constraintlayout"); + artifacts.add("androidx.constraintlayout:constraintlayout-solver"); + artifacts.add("androidx.test.espresso.idling:idling-concurrent"); + artifacts.add("androidx.test.espresso.idling:idling-net"); + artifacts.add("androidx.test.espresso:espresso-accessibility"); + artifacts.add("androidx.test.espresso:espresso-contrib"); + artifacts.add("androidx.test.espresso:espresso-core"); + artifacts.add("androidx.test.espresso:espresso-idling-resource"); + artifacts.add("androidx.test.espresso:espresso-intents"); + artifacts.add("androidx.test.espresso:espresso-remote"); + artifacts.add("androidx.test.espresso:espresso-web"); + artifacts.add("androidx.test.jank:janktesthelper"); + artifacts.add("androidx.test:test-services"); + artifacts.add("androidx.test.uiautomator:uiautomator"); + artifacts.add("androidx.test:monitor"); + artifacts.add("androidx.test:orchestrator"); + artifacts.add("androidx.test:rules"); + artifacts.add("androidx.test:runner"); + artifacts.add("androidx.vectordrawable:vectordrawable-animated"); + artifacts.add("androidx.appcompat:appcompat"); + artifacts.add("androidx.asynclayoutinflater:asynclayoutinflater"); + artifacts.add("androidx.car:car"); + artifacts.add("androidx.cardview:cardview"); + artifacts.add("androidx.collection:collection"); + artifacts.add("androidx.coordinatorlayout:coordinatorlayout"); + artifacts.add("androidx.cursoradapter:cursoradapter"); + artifacts.add("androidx.browser:browser"); + artifacts.add("androidx.customview:customview"); + artifacts.add("com.google.android.material:material"); + artifacts.add("androidx.documentfile:documentfile"); + artifacts.add("androidx.drawerlayout:drawerlayout"); + artifacts.add("androidx.exifinterface:exifinterface"); + artifacts.add("androidx.gridlayout:gridlayout"); + artifacts.add("androidx.heifwriter:heifwriter"); + artifacts.add("androidx.interpolator:interpolator"); + artifacts.add("androidx.leanback:leanback"); + artifacts.add("androidx.loader:loader"); + artifacts.add("androidx.localbroadcastmanager:localbroadcastmanager"); + artifacts.add("androidx.media2:media2"); + artifacts.add("androidx.media2:media2-exoplayer"); + artifacts.add("androidx.mediarouter:mediarouter"); + artifacts.add("androidx.multidex:multidex"); + artifacts.add("androidx.multidex:multidex-instrumentation"); + artifacts.add("androidx.palette:palette"); + artifacts.add("androidx.percentlayout:percentlayout"); + artifacts.add("androidx.leanback:leanback-preference"); + artifacts.add("androidx.legacy:legacy-preference-v14"); + artifacts.add("androidx.preference:preference"); + artifacts.add("androidx.print:print"); + artifacts.add("androidx.recommendation:recommendation"); + artifacts.add("androidx.recyclerview:recyclerview-selection"); + artifacts.add("androidx.recyclerview:recyclerview"); + artifacts.add("androidx.slice:slice-builders"); + artifacts.add("androidx.slice:slice-core"); + artifacts.add("androidx.slice:slice-view"); + artifacts.add("androidx.slidingpanelayout:slidingpanelayout"); + artifacts.add("androidx.annotation:annotation"); + artifacts.add("androidx.core:core"); + artifacts.add("androidx.contentpager:contentpager"); + artifacts.add("androidx.legacy:legacy-support-core-ui"); + artifacts.add("androidx.legacy:legacy-support-core-utils"); + artifacts.add("androidx.dynamicanimation:dynamicanimation"); + artifacts.add("androidx.emoji:emoji"); + artifacts.add("androidx.emoji:emoji-appcompat"); + artifacts.add("androidx.emoji:emoji-bundled"); + artifacts.add("androidx.fragment:fragment"); + artifacts.add("androidx.media:media"); + artifacts.add("androidx.tvprovider:tvprovider"); + artifacts.add("androidx.legacy:legacy-support-v13"); + artifacts.add("androidx.legacy:legacy-support-v4"); + artifacts.add("androidx.vectordrawable:vectordrawable"); + artifacts.add("androidx.swiperefreshlayout:swiperefreshlayout"); + artifacts.add("androidx.textclassifier:textclassifier"); + artifacts.add("androidx.transition:transition"); + artifacts.add("androidx.versionedparcelable:versionedparcelable"); + artifacts.add("androidx.viewpager:viewpager"); + artifacts.add("androidx.wear:wear"); + artifacts.add("androidx.webkit:webkit"); - // Common words that user may use - others.add("android"); - others.add("applicationVariants"); - others.add("compileSdkVersion"); - others.add("buildToolsVersion"); - others.add("defaultConfig"); - others.add("applicationId"); - others.add("minSdkVersion"); - others.add("targetSdkVersion"); - others.add("versionCode"); - others.add("versionName"); - others.add("multiDexEnabled"); - others.add("vectorDrawables.useSupportLibrary"); - others.add("compileOptions"); - others.add("coreLibraryDesugaringEnabled"); - others.add("sourceCompatibility"); - others.add("targetCompatibility"); - others.add("JavaVersion.VERSION_1_8"); - others.add("JavaVersion.VERSION_1_7"); + artifacts.add("fileTree(dir: 'libs', include: ['*.jar'])"); + return artifacts; + } - // Lint options - others.add("lintOptions"); - others.add("quiet"); - others.add("abortOnError"); - others.add("checkReleaseBuilds"); - others.add("ignoreWarnings"); - others.add("checkAllWarnings"); - others.add("warningsAsErrors"); - others.add("disable"); - others.add("enable"); - others.add("check"); - others.add("noLines"); - others.add("showAll"); - others.add("explainIssues"); - others.add("lintConfig"); - others.add("textReport"); - others.add("textOutput"); - others.add("xmlReport"); - others.add("xmlOutput"); - others.add("htmlReport"); - others.add("htmlOutput"); - others.add("fatal"); - others.add("error"); - others.add("warning"); - others.add("ignore"); - others.add("informational"); - others.add("baseline"); - others.add("checkTestSources"); - others.add("ignoreTestSources"); - others.add("checkGeneratedSources"); - others.add("checkDependencies"); + private static List createConfigs() { + var configs = new ArrayList(); + configs.add("androidApis"); + configs.add("androidTestAnnotationProcessor"); + configs.add("androidTestApi"); + configs.add("androidTestApk"); + configs.add("androidTestCompile"); + configs.add("androidTestCompileOnly"); + configs.add("androidTestDebugAnnotationProcessor"); + configs.add("androidTestDebugApi"); + configs.add("androidTestDebugApk"); + configs.add("androidTestDebugCompile"); + configs.add("androidTestDebugCompileOnly"); + configs.add("androidTestDebugImplementation"); + configs.add("androidTestDebugProvided"); + configs.add("androidTestDebugRuntimeOnly"); + configs.add("androidTestDebugWearApp"); + configs.add("androidTestImplementation"); + configs.add("androidTestProvided"); + configs.add("androidTestRuntimeOnly"); + configs.add("androidTestUtil"); + configs.add("androidTestWearApp"); + configs.add("annotationProcessor"); + configs.add("api"); + configs.add("apk"); + configs.add("archives"); + configs.add("compile"); + configs.add("compileOnly"); + configs.add("coreLibraryDesugaring"); + configs.add("debugAnnotationProcessor"); + configs.add("debugApi"); + configs.add("debugApk"); + configs.add("debugCompile"); + configs.add("debugCompileOnly"); + configs.add("debugImplementation"); + configs.add("debugProvided"); + configs.add("debugRuntimeOnly"); + configs.add("debugWearApp"); + configs.add("default"); + configs.add("implementation"); + configs.add("implementation platform()"); + configs.add("lintChecks"); + configs.add("lintClassPath"); + configs.add("lintPublish"); + configs.add("provided"); + configs.add("releaseAnnotationProcessor"); + configs.add("releaseApi"); + configs.add("releaseApk"); + configs.add("releaseCompile"); + configs.add("releaseCompileOnly"); + configs.add("releaseImplementation"); + configs.add("releaseProvided"); + configs.add("releaseRuntimeOnly"); + configs.add("releaseWearApp"); + configs.add("runtimeOnly"); + configs.add("testAnnotationProcessor"); + configs.add("testApi"); + configs.add("testApk"); + configs.add("testCompile"); + configs.add("testCompileOnly"); + configs.add("testDebugAnnotationProcessor"); + configs.add("testDebugApi"); + configs.add("testDebugApk"); + configs.add("testDebugCompile"); + configs.add("testDebugCompileOnly"); + configs.add("testDebugImplementation"); + configs.add("testDebugProvided"); + configs.add("testDebugRuntimeOnly"); + configs.add("testDebugWearApp"); + configs.add("testImplementation"); + configs.add("testProvided"); + configs.add("testReleaseAnnotationProcessor"); + configs.add("testReleaseApi"); + configs.add("testReleaseApk"); + configs.add("testReleaseCompile"); + configs.add("testReleaseCompileOnly"); + configs.add("testReleaseImplementation"); + configs.add("testReleaseProvided"); + configs.add("testReleaseRuntimeOnly"); + configs.add("testReleaseWearApp"); + configs.add("testRuntimeOnly"); + configs.add("testWearApp"); + configs.add("wearApp"); + return configs; + } - // buildFeatures options - others.add("buildFeatures"); - others.add("aidl"); - others.add("buildConfig"); - others.add("compose"); - others.add("prefab"); - others.add("renderScript"); - others.add("resValues"); - others.add("shaders"); - others.add("viewBinding"); + private static List createOtherCompletions() { + var others = new ArrayList(); - // Signing configs and all - others.add("signingConfigs"); - others.add("debug"); - others.add("storeFile"); - others.add("storePassword"); - others.add("keyAlias"); - others.add("keyPassword"); - others.add("release"); - others.add("storeFile"); - others.add("storePassword"); - others.add("keyAlias"); - others.add("keyPassword"); - others.add("buildTypes"); - others.add("debug"); - others.add("minifyEnabled"); - others.add("proguardFiles"); - others.add("signingConfig"); - others.add("stringfog.enable"); - others.add("release"); - others.add("minifyEnabled"); - others.add("proguardFiles"); - others.add("signingConfig"); - others.add("stringfog.enable"); - others.add("packagingOptions"); - others.add("exclude"); + // Common words that user may use + others.add("android"); + others.add("applicationVariants"); + others.add("compileSdkVersion"); + others.add("buildToolsVersion"); + others.add("defaultConfig"); + others.add("applicationId"); + others.add("minSdkVersion"); + others.add("targetSdkVersion"); + others.add("versionCode"); + others.add("versionName"); + others.add("multiDexEnabled"); + others.add("vectorDrawables.useSupportLibrary"); + others.add("compileOptions"); + others.add("coreLibraryDesugaringEnabled"); + others.add("sourceCompatibility"); + others.add("targetCompatibility"); + others.add("JavaVersion.VERSION_1_8"); + others.add("JavaVersion.VERSION_1_7"); - // Maybe used in root project's build.gradle - others.add("buildscript"); - others.add("ext"); - others.add("project.ext"); - others.add("repositories"); - others.add("maven"); - others.add("url"); - others.add("google()"); - others.add("mavenLocal()"); - others.add("mavenCentral()"); - others.add("jcenter()"); - others.add("classpath"); - others.add("allprojects"); - others.add("subprojects"); - return others; - } + // Lint options + others.add("lintOptions"); + others.add("quiet"); + others.add("abortOnError"); + others.add("checkReleaseBuilds"); + others.add("ignoreWarnings"); + others.add("checkAllWarnings"); + others.add("warningsAsErrors"); + others.add("disable"); + others.add("enable"); + others.add("check"); + others.add("noLines"); + others.add("showAll"); + others.add("explainIssues"); + others.add("lintConfig"); + others.add("textReport"); + others.add("textOutput"); + others.add("xmlReport"); + others.add("xmlOutput"); + others.add("htmlReport"); + others.add("htmlOutput"); + others.add("fatal"); + others.add("error"); + others.add("warning"); + others.add("ignore"); + others.add("informational"); + others.add("baseline"); + others.add("checkTestSources"); + others.add("ignoreTestSources"); + others.add("checkGeneratedSources"); + others.add("checkDependencies"); + + // buildFeatures options + others.add("buildFeatures"); + others.add("aidl"); + others.add("buildConfig"); + others.add("compose"); + others.add("prefab"); + others.add("renderScript"); + others.add("resValues"); + others.add("shaders"); + others.add("viewBinding"); + + // Signing configs and all + others.add("signingConfigs"); + others.add("debug"); + others.add("storeFile"); + others.add("storePassword"); + others.add("keyAlias"); + others.add("keyPassword"); + others.add("release"); + others.add("storeFile"); + others.add("storePassword"); + others.add("keyAlias"); + others.add("keyPassword"); + others.add("buildTypes"); + others.add("debug"); + others.add("minifyEnabled"); + others.add("proguardFiles"); + others.add("signingConfig"); + others.add("stringfog.enable"); + others.add("release"); + others.add("minifyEnabled"); + others.add("proguardFiles"); + others.add("signingConfig"); + others.add("stringfog.enable"); + others.add("packagingOptions"); + others.add("exclude"); + + // Maybe used in root project's build.gradle + others.add("buildscript"); + others.add("ext"); + others.add("project.ext"); + others.add("repositories"); + others.add("maven"); + others.add("url"); + others.add("google()"); + others.add("mavenLocal()"); + others.add("mavenCentral()"); + others.add("jcenter()"); + others.add("classpath"); + others.add("allprojects"); + others.add("subprojects"); + return others; + } } diff --git a/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyLanguage.java b/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyLanguage.java index 1cc7e3685..1f92ae3f4 100755 --- a/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyLanguage.java +++ b/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyLanguage.java @@ -43,132 +43,126 @@ import io.github.rosemoe.sora.widget.SymbolPairMatch; public class GroovyLanguage extends IDELanguage { - private static final ILogger LOG = ILogger.newInstance("GroovyLanguage"); - private final GroovyAnalyzer analyzer; - private final GroovyAutoComplete completer; - private final NewlineHandler[] newlineHandlers = new NewlineHandler[] {new BraceHandler()}; + private static final ILogger LOG = ILogger.newInstance("GroovyLanguage"); + private final GroovyAnalyzer analyzer; + private final GroovyAutoComplete completer; + private final NewlineHandler[] newlineHandlers = new NewlineHandler[] {new BraceHandler()}; - public GroovyLanguage() { - analyzer = new GroovyAnalyzer(); - completer = new GroovyAutoComplete(); - } + public GroovyLanguage() { + analyzer = new GroovyAnalyzer(); + completer = new GroovyAutoComplete(); + } - @Override - public int getIndentAdvance(@NonNull String p1) { - try { - GroovyLexer lexer = new GroovyLexer(CharStreams.fromReader(new StringReader(p1))); - Token token = null; - int advance = 0; - while (((token = lexer.nextToken()) != null && token.getType() != token.EOF)) { - switch (token.getType()) { - case GroovyLexer.LBRACE: - advance++; - break; - case GroovyLexer.RBRACE: - advance--; - break; - } - } - advance = Math.max(0, advance); - return advance * getTabSize(); - } catch (Throwable e) { - LOG.error("Failed to calculate indent advance", e); + @Override + public int getIndentAdvance(@NonNull String p1) { + try { + GroovyLexer lexer = new GroovyLexer(CharStreams.fromReader(new StringReader(p1))); + Token token = null; + int advance = 0; + while (((token = lexer.nextToken()) != null && token.getType() != token.EOF)) { + switch (token.getType()) { + case GroovyLexer.LBRACE: + advance++; + break; + case GroovyLexer.RBRACE: + advance--; + break; } - return 0; + } + advance = Math.max(0, advance); + return advance * getTabSize(); + } catch (Throwable e) { + LOG.error("Failed to calculate indent advance", e); } + return 0; + } - @Override - public SymbolPairMatch getSymbolPairs() { - return new SymbolPairMatch.DefaultSymbolPairs(); - } + @Override + public SymbolPairMatch getSymbolPairs() { + return new SymbolPairMatch.DefaultSymbolPairs(); + } - @NonNull - @Override - public AnalyzeManager getAnalyzeManager() { - return analyzer; - } + @NonNull + @Override + public AnalyzeManager getAnalyzeManager() { + return analyzer; + } - @Override - public int getInterruptionLevel() { - return INTERRUPTION_LEVEL_STRONG; - } + @Override + public int getInterruptionLevel() { + return INTERRUPTION_LEVEL_STRONG; + } - @Override - public void requireAutoComplete( - @NonNull ContentReference content, - @NonNull CharPosition position, - @NonNull CompletionPublisher publisher, - @NonNull Bundle extraArguments) - throws CompletionCancelledException { + @Override + public void requireAutoComplete( + @NonNull ContentReference content, + @NonNull CharPosition position, + @NonNull CompletionPublisher publisher, + @NonNull Bundle extraArguments) + throws CompletionCancelledException { - completer.complete(content, position, publisher, extraArguments); - } + completer.complete(content, position, publisher, extraArguments); + } - @Override - public int getIndentAdvance(@NonNull ContentReference content, int line, int column) { - try { - GroovyLexer lexer = - new GroovyLexer(CharStreams.fromReader(new CharSequenceReader(content))); - Token token; - int advance = 0; - while (((token = lexer.nextToken()) != null && token.getType() != token.EOF)) { - switch (token.getType()) { - case GroovyLexer.LBRACE: - advance++; - break; - case GroovyLexer.RBRACE: - advance--; - break; - } - } - advance = Math.max(0, advance); - return advance * getTabSize(); - } catch (Throwable e) { - LOG.error("Failed to calculate indent advance", e); + @Override + public int getIndentAdvance(@NonNull ContentReference content, int line, int column) { + try { + GroovyLexer lexer = new GroovyLexer(CharStreams.fromReader(new CharSequenceReader(content))); + Token token; + int advance = 0; + while (((token = lexer.nextToken()) != null && token.getType() != token.EOF)) { + switch (token.getType()) { + case GroovyLexer.LBRACE: + advance++; + break; + case GroovyLexer.RBRACE: + advance--; + break; } - return 0; + } + advance = Math.max(0, advance); + return advance * getTabSize(); + } catch (Throwable e) { + LOG.error("Failed to calculate indent advance", e); + } + return 0; + } + + @Override + public CharSequence format(CharSequence content) { + return content; + } + + @Override + public NewlineHandler[] getNewlineHandlers() { + return newlineHandlers; + } + + @Override + public void destroy() {} + + class BraceHandler implements NewlineHandler { + + @Override + public boolean matchesRequirement(String beforeText, String afterText) { + beforeText = beforeText.trim(); + afterText = afterText.trim(); + return beforeText.endsWith("{") && afterText.startsWith("}"); } @Override - public CharSequence format(CharSequence content) { - return content; - } - - @Override - public NewlineHandler[] getNewlineHandlers() { - return newlineHandlers; - } - - @Override - public void destroy() {} - - class BraceHandler implements NewlineHandler { - - @Override - public boolean matchesRequirement(String beforeText, String afterText) { - beforeText = beforeText.trim(); - afterText = afterText.trim(); - return beforeText.endsWith("{") && afterText.startsWith("}"); - } - - @Override - public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { - int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); - int advanceBefore = getIndentAdvance(beforeText); - int advanceAfter = getIndentAdvance(afterText); - String text; - StringBuilder sb = - new StringBuilder("\n") - .append( - TextUtils.createIndent( - count + advanceBefore, tabSize, useTab())) - .append('\n') - .append( - text = - TextUtils.createIndent( - count + advanceAfter, tabSize, useTab())); - int shiftLeft = text.length() + 1; - return new NewlineHandleResult(sb, shiftLeft); - } + public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { + int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); + int advanceBefore = getIndentAdvance(beforeText); + int advanceAfter = getIndentAdvance(afterText); + String text; + StringBuilder sb = + new StringBuilder("\n") + .append(TextUtils.createIndent(count + advanceBefore, tabSize, useTab())) + .append('\n') + .append(text = TextUtils.createIndent(count + advanceAfter, tabSize, useTab())); + int shiftLeft = text.length() + 1; + return new NewlineHandleResult(sb, shiftLeft); } + } } diff --git a/app/src/main/java/com/itsaky/androidide/language/java/JavaAnalyzer.java b/app/src/main/java/com/itsaky/androidide/language/java/JavaAnalyzer.java index 43f8834b5..88453aa62 100644 --- a/app/src/main/java/com/itsaky/androidide/language/java/JavaAnalyzer.java +++ b/app/src/main/java/com/itsaky/androidide/language/java/JavaAnalyzer.java @@ -62,326 +62,324 @@ import io.github.rosemoe.sora.lang.styling.Styles; */ public class JavaAnalyzer extends SimpleAnalyzeManager implements IAnalyzeManager { - private static final ILogger LOG = ILogger.newInstance("JavaAnalyzer"); - private final List ideDiagnostics = new ArrayList<>(); - private List diagnostics = new ArrayList<>(); + private static final ILogger LOG = ILogger.newInstance("JavaAnalyzer"); + private final List ideDiagnostics = new ArrayList<>(); + private List diagnostics = new ArrayList<>(); - @Override - public void updateDiagnostics(@NonNull List diagnostics) { - this.diagnostics = diagnostics; + @Override + public void updateDiagnostics(@NonNull List diagnostics) { + this.diagnostics = diagnostics; + } + + @NonNull + @Override + public List getDiagnostics() { + final var result = new ArrayList<>(ideDiagnostics); + if (diagnostics != null && !diagnostics.isEmpty()) { + result.addAll(diagnostics); + } + result.sort(DiagnosticItem.START_COMPARATOR); + return result; + } + + @Override + protected Styles analyze(StringBuilder text, Delegate delegate) { + final CodePointCharStream stream; + final var styles = new Styles(); + final var colors = new MappedSpans.Builder(); + try { + stream = CharStreams.fromReader(new CharSequenceReader(text)); + } catch (IOException e) { + LOG.error("Unable to create char stream for text", e); + return styles; + } + final var lexer = new JavaLexer(stream); + final var stack = new Stack(); + + Token token; + int line, column, lastLine = 1, type, currSwitch = 0, maxSwitch = 0, previous = -1; + boolean isFirst = true; + + ideDiagnostics.clear(); + + while (!delegate.isCancelled()) { + token = lexer.nextToken(); + + if (token == null) { + break; + } + + type = token.getType(); + line = token.getLine() - 1; + column = token.getCharPositionInLine(); + + if (type == JavaLexer.EOF) { + lastLine = line; + break; + } + + final var tokenLength = token.getText().length(); + switch (type) { + case JavaLexer.WS: + if (isFirst) { + colors.addNormalIfNull(); + } + break; + case JavaLexer.ABSTRACT: + case JavaLexer.ASSERT: + case JavaLexer.BREAK: + case JavaLexer.CASE: + case JavaLexer.CATCH: + case JavaLexer.CLASS: + case JavaLexer.CONST: + case JavaLexer.CONTINUE: + case JavaLexer.DEFAULT: + case JavaLexer.DO: + case JavaLexer.ELSE: + case JavaLexer.EXTENDS: + case JavaLexer.FINAL: + case JavaLexer.FINALLY: + case JavaLexer.FOR: + case JavaLexer.IF: + case JavaLexer.GOTO: + case JavaLexer.IMPLEMENTS: + case JavaLexer.IMPORT: + case JavaLexer.INSTANCEOF: + case JavaLexer.INTERFACE: + case JavaLexer.NATIVE: + case JavaLexer.NEW: + case JavaLexer.PACKAGE: + case JavaLexer.PRIVATE: + case JavaLexer.PROTECTED: + case JavaLexer.PUBLIC: + case JavaLexer.RETURN: + case JavaLexer.STATIC: + case JavaLexer.STRICTFP: + case JavaLexer.SUPER: + case JavaLexer.SWITCH: + case JavaLexer.SYNCHRONIZED: + case JavaLexer.THIS: + case JavaLexer.THROW: + case JavaLexer.THROWS: + case JavaLexer.TRANSIENT: + case JavaLexer.TRY: + case JavaLexer.VOID: + case JavaLexer.VOLATILE: + case JavaLexer.WHILE: + case JavaLexer.VAR: + colors.addIfNeeded(line, column, forKeyword()); + break; + case JavaLexer.DECIMAL_LITERAL: + case JavaLexer.HEX_LITERAL: + case JavaLexer.OCT_LITERAL: + case JavaLexer.BINARY_LITERAL: + case JavaLexer.FLOAT_LITERAL: + case JavaLexer.HEX_FLOAT_LITERAL: + case JavaLexer.BOOL_LITERAL: + case JavaLexer.CHAR_LITERAL: + case JavaLexer.NULL_LITERAL: + colors.addIfNeeded(line, column, get(LITERAL)); + break; + case JavaLexer.STRING_LITERAL: + colors.addIfNeeded(line, column, forString()); + break; + case JavaLexer.LPAREN: + case JavaLexer.RPAREN: + case JavaLexer.LBRACK: + case JavaLexer.RBRACK: + case JavaLexer.SEMI: + case JavaLexer.COMMA: + case JavaLexer.ASSIGN: + case JavaLexer.GT: + case JavaLexer.LT: + case JavaLexer.BANG: + case JavaLexer.TILDE: + case JavaLexer.QUESTION: + case JavaLexer.COLON: + case JavaLexer.EQUAL: + case JavaLexer.GE: + case JavaLexer.LE: + case JavaLexer.NOTEQUAL: + case JavaLexer.AND: + case JavaLexer.OR: + case JavaLexer.INC: + case JavaLexer.DEC: + case JavaLexer.ADD: + case JavaLexer.SUB: + case JavaLexer.MUL: + case JavaLexer.DIV: + case JavaLexer.BITAND: + case JavaLexer.BITOR: + case JavaLexer.CARET: + case JavaLexer.MOD: + case JavaLexer.ADD_ASSIGN: + case JavaLexer.SUB_ASSIGN: + case JavaLexer.MUL_ASSIGN: + case JavaLexer.DIV_ASSIGN: + case JavaLexer.AND_ASSIGN: + case JavaLexer.OR_ASSIGN: + case JavaLexer.XOR_ASSIGN: + case JavaLexer.MOD_ASSIGN: + case JavaLexer.LSHIFT_ASSIGN: + case JavaLexer.RSHIFT_ASSIGN: + case JavaLexer.URSHIFT_ASSIGN: + case JavaLexer.ARROW: + case JavaLexer.COLONCOLON: + case JavaLexer.ELLIPSIS: + case JavaLexer.DOT: + colors.addIfNeeded(line, column, get(OPERATOR)); + break; + case JavaLexer.BOOLEAN: + case JavaLexer.BYTE: + case JavaLexer.CHAR: + case JavaLexer.DOUBLE: + case JavaLexer.ENUM: + case JavaLexer.FLOAT: + case JavaLexer.INT: + case JavaLexer.LONG: + case JavaLexer.SHORT: + colors.addIfNeeded(line, column, get(TYPE_NAME)); + break; + case JavaLexer.BLOCK_COMMENT: + colors.addIfNeeded(line, column, forComment()); + break; + case JavaLexer.LINE_COMMENT: + var commentType = SchemeAndroidIDE.COMMENT; + + // highlight special line comments + var commentText = token.getText(); + if (commentText.length() > 2) { + commentText = commentText.substring(2); + commentText = commentText.trim(); + + var mark = true; + if ("todo".equalsIgnoreCase(commentText.substring(0, 4))) { + commentType = SchemeAndroidIDE.TODO_COMMENT; + } else if ("fixme".equalsIgnoreCase(commentText.substring(0, 5))) { + commentType = SchemeAndroidIDE.FIXME_COMMENT; + } else { + mark = false; + } + + if (mark) { + if (diagnostics == null) { + diagnostics = new ArrayList<>(); + } + final var diagnostic = new DiagnosticItem(); + diagnostic.setSeverity(DiagnosticSeverity.WARNING); + diagnostic.setMessage(commentText); + diagnostic.setCode("special.comment"); + diagnostic.setRange( + new Range(new Position(line, column), new Position(line, column + tokenLength))); + diagnostic.setSource(commentText); + ideDiagnostics.add(diagnostic); + } + } + + colors.addIfNeeded(line, column, withoutCompletion(commentType)); + + break; + case JavaLexer.AT: + colors.addIfNeeded(line, column, get(ANNOTATION)); + break; + case JavaLexer.IDENTIFIER: + int colorId = SchemeAndroidIDE.TEXT_NORMAL; + if (previous == JavaLexer.AT) { + colorId = SchemeAndroidIDE.ANNOTATION; + } + colors.addIfNeeded(line, column, get(colorId)); + break; + case JavaLexer.LBRACE: + colors.addIfNeeded(line, column, get(OPERATOR)); + if (stack.isEmpty()) { + if (currSwitch > maxSwitch) { + maxSwitch = currSwitch; + } + currSwitch = 0; + } + currSwitch++; + var block = styles.obtainNewBlock(); + block.startLine = line; + block.startColumn = column; + stack.push(block); + break; + case JavaLexer.RBRACE: + colors.addIfNeeded(line, column, get(OPERATOR)); + if (!stack.isEmpty()) { + var block2 = stack.pop(); + block2.endLine = line; + block2.endColumn = column; + if (block2.startLine != block2.endLine) { + styles.addCodeBlock(block2); + } + } + break; + default: + colors.addIfNeeded(line, column, get(TEXT_NORMAL)); + break; + } + + isFirst = false; + + if (type != JavaLexer.WS) { + previous = type; + } } - @NonNull - @Override - public List getDiagnostics() { - final var result = new ArrayList<>(ideDiagnostics); - if (diagnostics != null && !diagnostics.isEmpty()) { - result.addAll(diagnostics); - } - result.sort(DiagnosticItem.START_COMPARATOR); - return result; + if (stack.isEmpty() && currSwitch > maxSwitch) { + maxSwitch = currSwitch; } - @Override - protected Styles analyze(StringBuilder text, Delegate delegate) { - final CodePointCharStream stream; - final var styles = new Styles(); - final var colors = new MappedSpans.Builder(); - try { - stream = CharStreams.fromReader(new CharSequenceReader(text)); - } catch (IOException e) { - LOG.error("Unable to create char stream for text", e); - return styles; - } - final var lexer = new JavaLexer(stream); - final var stack = new Stack(); + colors.determine(lastLine); + styles.setSuppressSwitch(maxSwitch + 10); - Token token; - int line, column, lastLine = 1, type, currSwitch = 0, maxSwitch = 0, previous = -1; - boolean isFirst = true; - - ideDiagnostics.clear(); - - while (!delegate.isCancelled()) { - token = lexer.nextToken(); - - if (token == null) { - break; - } - - type = token.getType(); - line = token.getLine() - 1; - column = token.getCharPositionInLine(); - - if (type == JavaLexer.EOF) { - lastLine = line; - break; - } - - final var tokenLength = token.getText().length(); - switch (type) { - case JavaLexer.WS: - if (isFirst) { - colors.addNormalIfNull(); - } - break; - case JavaLexer.ABSTRACT: - case JavaLexer.ASSERT: - case JavaLexer.BREAK: - case JavaLexer.CASE: - case JavaLexer.CATCH: - case JavaLexer.CLASS: - case JavaLexer.CONST: - case JavaLexer.CONTINUE: - case JavaLexer.DEFAULT: - case JavaLexer.DO: - case JavaLexer.ELSE: - case JavaLexer.EXTENDS: - case JavaLexer.FINAL: - case JavaLexer.FINALLY: - case JavaLexer.FOR: - case JavaLexer.IF: - case JavaLexer.GOTO: - case JavaLexer.IMPLEMENTS: - case JavaLexer.IMPORT: - case JavaLexer.INSTANCEOF: - case JavaLexer.INTERFACE: - case JavaLexer.NATIVE: - case JavaLexer.NEW: - case JavaLexer.PACKAGE: - case JavaLexer.PRIVATE: - case JavaLexer.PROTECTED: - case JavaLexer.PUBLIC: - case JavaLexer.RETURN: - case JavaLexer.STATIC: - case JavaLexer.STRICTFP: - case JavaLexer.SUPER: - case JavaLexer.SWITCH: - case JavaLexer.SYNCHRONIZED: - case JavaLexer.THIS: - case JavaLexer.THROW: - case JavaLexer.THROWS: - case JavaLexer.TRANSIENT: - case JavaLexer.TRY: - case JavaLexer.VOID: - case JavaLexer.VOLATILE: - case JavaLexer.WHILE: - case JavaLexer.VAR: - colors.addIfNeeded(line, column, forKeyword()); - break; - case JavaLexer.DECIMAL_LITERAL: - case JavaLexer.HEX_LITERAL: - case JavaLexer.OCT_LITERAL: - case JavaLexer.BINARY_LITERAL: - case JavaLexer.FLOAT_LITERAL: - case JavaLexer.HEX_FLOAT_LITERAL: - case JavaLexer.BOOL_LITERAL: - case JavaLexer.CHAR_LITERAL: - case JavaLexer.NULL_LITERAL: - colors.addIfNeeded(line, column, get(LITERAL)); - break; - case JavaLexer.STRING_LITERAL: - colors.addIfNeeded(line, column, forString()); - break; - case JavaLexer.LPAREN: - case JavaLexer.RPAREN: - case JavaLexer.LBRACK: - case JavaLexer.RBRACK: - case JavaLexer.SEMI: - case JavaLexer.COMMA: - case JavaLexer.ASSIGN: - case JavaLexer.GT: - case JavaLexer.LT: - case JavaLexer.BANG: - case JavaLexer.TILDE: - case JavaLexer.QUESTION: - case JavaLexer.COLON: - case JavaLexer.EQUAL: - case JavaLexer.GE: - case JavaLexer.LE: - case JavaLexer.NOTEQUAL: - case JavaLexer.AND: - case JavaLexer.OR: - case JavaLexer.INC: - case JavaLexer.DEC: - case JavaLexer.ADD: - case JavaLexer.SUB: - case JavaLexer.MUL: - case JavaLexer.DIV: - case JavaLexer.BITAND: - case JavaLexer.BITOR: - case JavaLexer.CARET: - case JavaLexer.MOD: - case JavaLexer.ADD_ASSIGN: - case JavaLexer.SUB_ASSIGN: - case JavaLexer.MUL_ASSIGN: - case JavaLexer.DIV_ASSIGN: - case JavaLexer.AND_ASSIGN: - case JavaLexer.OR_ASSIGN: - case JavaLexer.XOR_ASSIGN: - case JavaLexer.MOD_ASSIGN: - case JavaLexer.LSHIFT_ASSIGN: - case JavaLexer.RSHIFT_ASSIGN: - case JavaLexer.URSHIFT_ASSIGN: - case JavaLexer.ARROW: - case JavaLexer.COLONCOLON: - case JavaLexer.ELLIPSIS: - case JavaLexer.DOT: - colors.addIfNeeded(line, column, get(OPERATOR)); - break; - case JavaLexer.BOOLEAN: - case JavaLexer.BYTE: - case JavaLexer.CHAR: - case JavaLexer.DOUBLE: - case JavaLexer.ENUM: - case JavaLexer.FLOAT: - case JavaLexer.INT: - case JavaLexer.LONG: - case JavaLexer.SHORT: - colors.addIfNeeded(line, column, get(TYPE_NAME)); - break; - case JavaLexer.BLOCK_COMMENT: - colors.addIfNeeded(line, column, forComment()); - break; - case JavaLexer.LINE_COMMENT: - var commentType = SchemeAndroidIDE.COMMENT; - - // highlight special line comments - var commentText = token.getText(); - if (commentText.length() > 2) { - commentText = commentText.substring(2); - commentText = commentText.trim(); - - var mark = true; - if ("todo".equalsIgnoreCase(commentText.substring(0, 4))) { - commentType = SchemeAndroidIDE.TODO_COMMENT; - } else if ("fixme".equalsIgnoreCase(commentText.substring(0, 5))) { - commentType = SchemeAndroidIDE.FIXME_COMMENT; - } else { - mark = false; - } - - if (mark) { - if (diagnostics == null) { - diagnostics = new ArrayList<>(); - } - final var diagnostic = new DiagnosticItem(); - diagnostic.setSeverity(DiagnosticSeverity.WARNING); - diagnostic.setMessage(commentText); - diagnostic.setCode("special.comment"); - diagnostic.setRange( - new Range( - new Position(line, column), - new Position(line, column + tokenLength))); - diagnostic.setSource(commentText); - ideDiagnostics.add(diagnostic); - } - } - - colors.addIfNeeded(line, column, withoutCompletion(commentType)); - - break; - case JavaLexer.AT: - colors.addIfNeeded(line, column, get(ANNOTATION)); - break; - case JavaLexer.IDENTIFIER: - int colorId = SchemeAndroidIDE.TEXT_NORMAL; - if (previous == JavaLexer.AT) { - colorId = SchemeAndroidIDE.ANNOTATION; - } - colors.addIfNeeded(line, column, get(colorId)); - break; - case JavaLexer.LBRACE: - colors.addIfNeeded(line, column, get(OPERATOR)); - if (stack.isEmpty()) { - if (currSwitch > maxSwitch) { - maxSwitch = currSwitch; - } - currSwitch = 0; - } - currSwitch++; - var block = styles.obtainNewBlock(); - block.startLine = line; - block.startColumn = column; - stack.push(block); - break; - case JavaLexer.RBRACE: - colors.addIfNeeded(line, column, get(OPERATOR)); - if (!stack.isEmpty()) { - var block2 = stack.pop(); - block2.endLine = line; - block2.endColumn = column; - if (block2.startLine != block2.endLine) { - styles.addCodeBlock(block2); - } - } - break; - default: - colors.addIfNeeded(line, column, get(TEXT_NORMAL)); - break; - } - - isFirst = false; - - if (type != JavaLexer.WS) { - previous = type; - } - } - - if (stack.isEmpty() && currSwitch > maxSwitch) { - maxSwitch = currSwitch; - } - - colors.determine(lastLine); - styles.setSuppressSwitch(maxSwitch + 10); - - if (diagnostics != null && !diagnostics.isEmpty()) { - markDiagnostics(colors, diagnostics); - } - - if (!ideDiagnostics.isEmpty()) { - markDiagnostics(colors, ideDiagnostics); - } - - styles.spans = colors.build(); - - return styles; + if (diagnostics != null && !diagnostics.isEmpty()) { + markDiagnostics(colors, diagnostics); } - private void markDiagnostics(MappedSpans.Builder colors, List diagnostics) { - diagnostics.sort(DiagnosticItem.START_COMPARATOR); - for (var d : diagnostics) { - if (d == null) { - continue; - } - - var start = d.getRange().getStart(); - var end = d.getRange().getEnd(); - - try { - colors.markProblemRegion( - convertToSpanFlag(d.getSeverity()), - start.getLine(), - start.getColumn(), - end.getLine(), - end.getColumn()); - } catch (Throwable e) { - // Might happen frequently if user types faster - } - } + if (!ideDiagnostics.isEmpty()) { + markDiagnostics(colors, ideDiagnostics); } - @Contract(pure = true) - private int convertToSpanFlag(@NonNull DiagnosticSeverity severity) { - switch (severity) { - case WARNING: - return Span.FLAG_WARNING; - case ERROR: - return Span.FLAG_ERROR; - case HINT: - case INFO: - default: - return Span.FLAG_TYPO; - } + styles.spans = colors.build(); + + return styles; + } + + private void markDiagnostics(MappedSpans.Builder colors, List diagnostics) { + diagnostics.sort(DiagnosticItem.START_COMPARATOR); + for (var d : diagnostics) { + if (d == null) { + continue; + } + + var start = d.getRange().getStart(); + var end = d.getRange().getEnd(); + + try { + colors.markProblemRegion( + convertToSpanFlag(d.getSeverity()), + start.getLine(), + start.getColumn(), + end.getLine(), + end.getColumn()); + } catch (Throwable e) { + // Might happen frequently if user types faster + } } + } + + @Contract(pure = true) + private int convertToSpanFlag(@NonNull DiagnosticSeverity severity) { + switch (severity) { + case WARNING: + return Span.FLAG_WARNING; + case ERROR: + return Span.FLAG_ERROR; + case HINT: + case INFO: + default: + return Span.FLAG_TYPO; + } + } } diff --git a/app/src/main/java/com/itsaky/androidide/language/java/JavaLanguage.java b/app/src/main/java/com/itsaky/androidide/language/java/JavaLanguage.java index 3069d8aed..65639b54d 100755 --- a/app/src/main/java/com/itsaky/androidide/language/java/JavaLanguage.java +++ b/app/src/main/java/com/itsaky/androidide/language/java/JavaLanguage.java @@ -52,145 +52,137 @@ import io.github.rosemoe.sora.widget.SymbolPairMatch; public class JavaLanguage extends IDELanguage { - private static final ILogger LOG = ILogger.newInstance("JavaLanguage"); - private final NewlineHandler[] newlineHandlers; - private JavaAnalyzer analyzer; - private CommonCompletionProvider completer; + private static final ILogger LOG = ILogger.newInstance("JavaLanguage"); + private final NewlineHandler[] newlineHandlers; + private JavaAnalyzer analyzer; + private CommonCompletionProvider completer; - public JavaLanguage() { - final var server = getLanguageServer(); - this.analyzer = new JavaAnalyzer(); - this.completer = new CommonCompletionProvider(server); + public JavaLanguage() { + final var server = getLanguageServer(); + this.analyzer = new JavaAnalyzer(); + this.completer = new CommonCompletionProvider(server); - this.newlineHandlers = new NewlineHandler[1]; - this.newlineHandlers[0] = new BraceHandler(); - } + this.newlineHandlers = new NewlineHandler[1]; + this.newlineHandlers[0] = new BraceHandler(); + } - @NonNull - @Override - public List getDiagnostics() { - return analyzer == null ? Collections.emptyList() : analyzer.getDiagnostics(); - } + @NonNull + @Override + public List getDiagnostics() { + return analyzer == null ? Collections.emptyList() : analyzer.getDiagnostics(); + } - @Override - public int getIndentAdvance(@NonNull String line) { - try { - JavaLexer lexer = new JavaLexer(CharStreams.fromReader(new StringReader(line))); - Token token; - int advance = 0; - while (((token = lexer.nextToken()) != null && token.getType() != token.EOF)) { - switch (token.getType()) { - case JavaLexer.LBRACE: - advance++; - break; - case JavaParser.RBRACE: - advance--; - break; - } - } - advance = Math.max(0, advance); - return advance * getTabSize(); - } catch (Throwable e) { - LOG.error("Error calculating indent advance", e); + @Override + public int getIndentAdvance(@NonNull String line) { + try { + JavaLexer lexer = new JavaLexer(CharStreams.fromReader(new StringReader(line))); + Token token; + int advance = 0; + while (((token = lexer.nextToken()) != null && token.getType() != token.EOF)) { + switch (token.getType()) { + case JavaLexer.LBRACE: + advance++; + break; + case JavaParser.RBRACE: + advance--; + break; } + } + advance = Math.max(0, advance); + return advance * getTabSize(); + } catch (Throwable e) { + LOG.error("Error calculating indent advance", e); + } - return 0; + return 0; + } + + @Override + public SymbolPairMatch getSymbolPairs() { + return new JavaSymbolPairs(); + } + + @NonNull + @Override + public AnalyzeManager getAnalyzeManager() { + return analyzer; + } + + @Override + public int getInterruptionLevel() { + return 0; + } + + @Override + public void requireAutoComplete( + @NonNull ContentReference content, + @NonNull CharPosition position, + @NonNull CompletionPublisher publisher, + @NonNull Bundle extraArguments) + throws CompletionCancelledException { + if (!extraArguments.containsKey(IDEEditor.KEY_FILE)) { + return; + } + final var file = Paths.get(extraArguments.getString(IDEEditor.KEY_FILE)); + publisher.setUpdateThreshold(0); + publisher.addItems( + new ArrayList<>( + completer.complete( + content, file, position, CommonCompletionProvider::checkJavaCompletionChar))); + } + + @Override + public int getIndentAdvance(@NonNull ContentReference content, int line, int column) { + return getIndentAdvance(content.getLine(line).substring(0, column)); + } + + @Override + protected ILanguageServer getLanguageServer() { + return StudioApp.getInstance().getJavaLanguageServer(); + } + + @Override + public NewlineHandler[] getNewlineHandlers() { + return newlineHandlers; + } + + @Override + public void destroy() { + analyzer = null; + completer = null; + } + + private static class JavaSymbolPairs extends SymbolPairMatch { + public JavaSymbolPairs() { + super.putPair('{', new Replacement("{}", 1)); + super.putPair('(', new Replacement("()", 1)); + super.putPair('[', new Replacement("[]", 1)); + super.putPair('"', new Replacement("\"\"", 1)); + super.putPair('\'', new Replacement("''", 1)); + super.putPair('<', new Replacement("<>", 1)); + } + } + + class BraceHandler implements NewlineHandler { + + @Override + public boolean matchesRequirement(String beforeText, String afterText) { + return beforeText.endsWith("{") && afterText.startsWith("}"); } @Override - public SymbolPairMatch getSymbolPairs() { - return new JavaSymbolPairs(); - } - - @NonNull - @Override - public AnalyzeManager getAnalyzeManager() { - return analyzer; - } - - @Override - public int getInterruptionLevel() { - return 0; - } - - @Override - public void requireAutoComplete( - @NonNull ContentReference content, - @NonNull CharPosition position, - @NonNull CompletionPublisher publisher, - @NonNull Bundle extraArguments) - throws CompletionCancelledException { - if (!extraArguments.containsKey(IDEEditor.KEY_FILE)) { - return; - } - final var file = Paths.get(extraArguments.getString(IDEEditor.KEY_FILE)); - publisher.setUpdateThreshold(0); - publisher.addItems( - new ArrayList<>( - completer.complete( - content, - file, - position, - CommonCompletionProvider::checkJavaCompletionChar))); - } - - @Override - public int getIndentAdvance(@NonNull ContentReference content, int line, int column) { - return getIndentAdvance(content.getLine(line).substring(0, column)); - } - - @Override - protected ILanguageServer getLanguageServer() { - return StudioApp.getInstance().getJavaLanguageServer(); - } - - @Override - public NewlineHandler[] getNewlineHandlers() { - return newlineHandlers; - } - - @Override - public void destroy() { - analyzer = null; - completer = null; - } - - private static class JavaSymbolPairs extends SymbolPairMatch { - public JavaSymbolPairs() { - super.putPair('{', new Replacement("{}", 1)); - super.putPair('(', new Replacement("()", 1)); - super.putPair('[', new Replacement("[]", 1)); - super.putPair('"', new Replacement("\"\"", 1)); - super.putPair('\'', new Replacement("''", 1)); - super.putPair('<', new Replacement("<>", 1)); - } - } - - class BraceHandler implements NewlineHandler { - - @Override - public boolean matchesRequirement(String beforeText, String afterText) { - return beforeText.endsWith("{") && afterText.startsWith("}"); - } - - @Override - public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { - int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); - int advanceBefore = getIndentAdvance(beforeText); - int advanceAfter = getIndentAdvance(afterText); - String text; - StringBuilder sb = - new StringBuilder("\n") - .append( - TextUtils.createIndent( - count + advanceBefore, tabSize, useTab())) - .append('\n') - .append( - text = - TextUtils.createIndent( - count + advanceAfter, tabSize, useTab())); - int shiftLeft = text.length() + 1; - return new NewlineHandleResult(sb, shiftLeft); - } + public NewlineHandleResult handleNewline(String beforeText, String afterText, int tabSize) { + int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize); + int advanceBefore = getIndentAdvance(beforeText); + int advanceAfter = getIndentAdvance(afterText); + String text; + StringBuilder sb = + new StringBuilder("\n") + .append(TextUtils.createIndent(count + advanceBefore, tabSize, useTab())) + .append('\n') + .append(text = TextUtils.createIndent(count + advanceAfter, tabSize, useTab())); + int shiftLeft = text.length() + 1; + return new NewlineHandleResult(sb, shiftLeft); } + } } diff --git a/app/src/main/java/com/itsaky/androidide/language/logs/LogLanguageImpl.java b/app/src/main/java/com/itsaky/androidide/language/logs/LogLanguageImpl.java index aed1f7286..5b7a3adec 100755 --- a/app/src/main/java/com/itsaky/androidide/language/logs/LogLanguageImpl.java +++ b/app/src/main/java/com/itsaky/androidide/language/logs/LogLanguageImpl.java @@ -41,80 +41,80 @@ import io.github.rosemoe.sora.widget.SymbolPairMatch; public class LogLanguageImpl extends IDELanguage { - private static final LogAnalyzer analyzer = new LogAnalyzer(); + private static final LogAnalyzer analyzer = new LogAnalyzer(); - public void addLine(LogLine line) { - analyzer.addLine(line); - } + public void addLine(LogLine line) { + analyzer.addLine(line); + } - @NonNull - @Override - public AnalyzeManager getAnalyzeManager() { - return analyzer; + @NonNull + @Override + public AnalyzeManager getAnalyzeManager() { + return analyzer; + } + + @Override + public int getInterruptionLevel() { + return 0; + } + + @Override + public void requireAutoComplete( + @NonNull ContentReference content, + @NonNull CharPosition position, + @NonNull CompletionPublisher publisher, + @NonNull Bundle extraArguments) + throws CompletionCancelledException {} + + @Override + public int getIndentAdvance(@NonNull ContentReference content, int line, int column) { + return 0; + } + + @Override + public CharSequence format(CharSequence text) { + return text; + } + + @Override + public SymbolPairMatch getSymbolPairs() { + return new SymbolPairMatch.DefaultSymbolPairs(); + } + + @Override + public NewlineHandler[] getNewlineHandlers() { + return new NewlineHandler[0]; + } + + @Override + public void destroy() {} + + private static class LogAnalyzer extends SimpleAnalyzeManager { + + private static final List lines = new ArrayList<>(); + + public LogAnalyzer addLine(LogLine line) { + if (line != null) lines.add(line); + + return this; } @Override - public int getInterruptionLevel() { - return 0; - } - - @Override - public void requireAutoComplete( - @NonNull ContentReference content, - @NonNull CharPosition position, - @NonNull CompletionPublisher publisher, - @NonNull Bundle extraArguments) - throws CompletionCancelledException {} - - @Override - public int getIndentAdvance(@NonNull ContentReference content, int line, int column) { - return 0; - } - - @Override - public CharSequence format(CharSequence text) { - return text; - } - - @Override - public SymbolPairMatch getSymbolPairs() { - return new SymbolPairMatch.DefaultSymbolPairs(); - } - - @Override - public NewlineHandler[] getNewlineHandlers() { - return new NewlineHandler[0]; - } - - @Override - public void destroy() {} - - private static class LogAnalyzer extends SimpleAnalyzeManager { - - private static final List lines = new ArrayList<>(); - - public LogAnalyzer addLine(LogLine line) { - if (line != null) lines.add(line); - - return this; - } - - @Override - protected Styles analyze(StringBuilder text, Delegate delegate) { - final var styles = new Styles(); - final var colors = new MappedSpans.Builder(); - int lastLine = 0; - for (int i = 0; i < lines.size() && !delegate.isCancelled(); i++) { - if (i == 0) colors.addNormalIfNull(); - LogLine line = lines.get(i); - colors.addIfNeeded(i, 0, SchemeAndroidIDE.forLogPriority(line.priority)); - lastLine = i; - } - colors.determine(lastLine); - - styles.spans = colors.build(); - - return styles; - } + protected Styles analyze(StringBuilder text, Delegate delegate) { + final var styles = new Styles(); + final var colors = new MappedSpans.Builder(); + int lastLine = 0; + for (int i = 0; i < lines.size() && !delegate.isCancelled(); i++) { + if (i == 0) colors.addNormalIfNull(); + LogLine line = lines.get(i); + colors.addIfNeeded(i, 0, SchemeAndroidIDE.forLogPriority(line.priority)); + lastLine = i; + } + colors.determine(lastLine); + + styles.spans = colors.build(); + + return styles; } + } } diff --git a/app/src/main/java/com/itsaky/androidide/language/xml/XMLAnalyzer.java b/app/src/main/java/com/itsaky/androidide/language/xml/XMLAnalyzer.java index 52f740468..13b4b75c3 100755 --- a/app/src/main/java/com/itsaky/androidide/language/xml/XMLAnalyzer.java +++ b/app/src/main/java/com/itsaky/androidide/language/xml/XMLAnalyzer.java @@ -46,110 +46,109 @@ import io.github.rosemoe.sora.lang.styling.Styles; */ public class XMLAnalyzer extends SimpleAnalyzeManager { - private static final ILogger LOG = ILogger.newInstance("XMLAnalyzer"); + private static final ILogger LOG = ILogger.newInstance("XMLAnalyzer"); - @Override - protected Styles analyze(StringBuilder text, Delegate delegate) { - final var styles = new Styles(); - final var colors = new MappedSpans.Builder(); + @Override + protected Styles analyze(StringBuilder text, Delegate delegate) { + final var styles = new Styles(); + final var colors = new MappedSpans.Builder(); - CodePointCharStream stream; - try { - stream = CharStreams.fromReader(new CharSequenceReader(text)); - } catch (IOException e) { - LOG.error("Unable to create stream for analyze", e); - return styles; - } - - XMLLexer lexer = new XMLLexer(stream); - Token token; - int previous = 0; - int line, column, lastLine = 1; - var first = true; - - final var stack = new Stack(); - - while (!delegate.isCancelled()) { - token = lexer.nextToken(); - if (token == null) { - break; - } - - if (token.getType() == XMLLexer.EOF) { - lastLine = token.getLine() - 1; - break; - } - - line = token.getLine() - 1; - column = token.getCharPositionInLine(); - lastLine = line; - - switch (token.getType()) { - case XMLLexer.S: - case XMLLexer.SEA_WS: - if (first) { - colors.addNormalIfNull(); - } - - break; - case XMLLexer.COMMENT: - colors.addIfNeeded(line, column, forComment()); - break; - case XMLLexer.OPEN: - case XMLLexer.OPEN_SLASH: - case XMLLexer.CLOSE: - case XMLLexer.SLASH: - case XMLLexer.SPECIAL_CLOSE: - case XMLLexer.EQUALS: - case XMLLexer.COLON: - case XMLLexer.XMLDeclOpen: - colors.addIfNeeded(line, column, get(OPERATOR)); - break; - case XMLLexer.SLASH_CLOSE: - colors.addIfNeeded(line, column, get(OPERATOR)); - final var closeBlock = stack.pop(); - closeBlock.endLine = line; - closeBlock.endColumn = column; - styles.addCodeBlock(closeBlock); - break; - case XMLLexer.STRING: - colors.addIfNeeded(line, column, LITERAL); - break; - case XMLLexer.Name: - var type = SchemeAndroidIDE.TEXT_NORMAL; - if (previous == XMLLexer.OPEN) { - type = SchemeAndroidIDE.XML_TAG; - final var block = styles.obtainNewBlock(); - block.startLine = line; - block.startColumn = column; - stack.push(block); - } - - if (previous == XMLLexer.OPEN_SLASH) { - type = SchemeAndroidIDE.XML_TAG; - final var block = stack.pop(); - block.endLine = line; - block.endColumn = column; - styles.addCodeBlock(block); - } - - colors.addIfNeeded(line, column, get(type)); - break; - case XMLLexer.TEXT: - default: - colors.addIfNeeded(line, column, get(SchemeAndroidIDE.TEXT_NORMAL)); - break; - } - first = false; - if (token.getType() != XMLLexer.SEA_WS) { - previous = token.getType(); - } - } - colors.determine(lastLine); - - styles.spans = colors.build(); - - return styles; + CodePointCharStream stream; + try { + stream = CharStreams.fromReader(new CharSequenceReader(text)); + } catch (IOException e) { + LOG.error("Unable to create stream for analyze", e); + return styles; } -} + XMLLexer lexer = new XMLLexer(stream); + Token token; + int previous = 0; + int line, column, lastLine = 1; + var first = true; + + final var stack = new Stack(); + + while (!delegate.isCancelled()) { + token = lexer.nextToken(); + if (token == null) { + break; + } + + if (token.getType() == XMLLexer.EOF) { + lastLine = token.getLine() - 1; + break; + } + + line = token.getLine() - 1; + column = token.getCharPositionInLine(); + lastLine = line; + + switch (token.getType()) { + case XMLLexer.S: + case XMLLexer.SEA_WS: + if (first) { + colors.addNormalIfNull(); + } + + break; + case XMLLexer.COMMENT: + colors.addIfNeeded(line, column, forComment()); + break; + case XMLLexer.OPEN: + case XMLLexer.OPEN_SLASH: + case XMLLexer.CLOSE: + case XMLLexer.SLASH: + case XMLLexer.SPECIAL_CLOSE: + case XMLLexer.EQUALS: + case XMLLexer.COLON: + case XMLLexer.XMLDeclOpen: + colors.addIfNeeded(line, column, get(OPERATOR)); + break; + case XMLLexer.SLASH_CLOSE: + colors.addIfNeeded(line, column, get(OPERATOR)); + final var closeBlock = stack.pop(); + closeBlock.endLine = line; + closeBlock.endColumn = column; + styles.addCodeBlock(closeBlock); + break; + case XMLLexer.STRING: + colors.addIfNeeded(line, column, LITERAL); + break; + case XMLLexer.Name: + var type = SchemeAndroidIDE.TEXT_NORMAL; + if (previous == XMLLexer.OPEN) { + type = SchemeAndroidIDE.XML_TAG; + final var block = styles.obtainNewBlock(); + block.startLine = line; + block.startColumn = column; + stack.push(block); + } + + if (previous == XMLLexer.OPEN_SLASH) { + type = SchemeAndroidIDE.XML_TAG; + final var block = stack.pop(); + block.endLine = line; + block.endColumn = column; + styles.addCodeBlock(block); + } + + colors.addIfNeeded(line, column, get(type)); + break; + case XMLLexer.TEXT: + default: + colors.addIfNeeded(line, column, get(SchemeAndroidIDE.TEXT_NORMAL)); + break; + } + first = false; + if (token.getType() != XMLLexer.SEA_WS) { + previous = token.getType(); + } + } + colors.determine(lastLine); + + styles.spans = colors.build(); + + return styles; + } +} diff --git a/app/src/main/java/com/itsaky/androidide/language/xml/XMLLanguage.java b/app/src/main/java/com/itsaky/androidide/language/xml/XMLLanguage.java index 6454c974b..7f87a11ec 100755 --- a/app/src/main/java/com/itsaky/androidide/language/xml/XMLLanguage.java +++ b/app/src/main/java/com/itsaky/androidide/language/xml/XMLLanguage.java @@ -47,104 +47,101 @@ import io.github.rosemoe.sora.widget.SymbolPairMatch; public class XMLLanguage extends IDELanguage { - private static final ILogger LOG = ILogger.newInstance("XMLLanguage"); - private final CommonCompletionProvider completer; - private final NewlineHandler[] newlineHandlers; - private XMLAnalyzer analyzer; + private static final ILogger LOG = ILogger.newInstance("XMLLanguage"); + private final CommonCompletionProvider completer; + private final NewlineHandler[] newlineHandlers; + private XMLAnalyzer analyzer; - public XMLLanguage() { - this.completer = new CommonCompletionProvider(getLanguageServer()); - this.analyzer = new XMLAnalyzer(); - this.newlineHandlers = new NewlineHandler[0]; - } + public XMLLanguage() { + this.completer = new CommonCompletionProvider(getLanguageServer()); + this.analyzer = new XMLAnalyzer(); + this.newlineHandlers = new NewlineHandler[0]; + } - public boolean isAutoCompleteChar(char ch) { - return JavaCharacter.isJavaIdentifierPart(ch) || ch == '<' || ch == '/'; - } + public boolean isAutoCompleteChar(char ch) { + return JavaCharacter.isJavaIdentifierPart(ch) || ch == '<' || ch == '/'; + } - @Override - public int getIndentAdvance(@NonNull String content) { - try { - XMLLexer lexer = new XMLLexer(CharStreams.fromReader(new StringReader(content))); - Token token; - int advance = 0; - while (((token = lexer.nextToken()) != null && token.getType() != token.EOF)) { - switch (token.getType()) { - case XMLLexer.OPEN: - advance++; - break; - case XMLLexer.CLOSE: - case XMLLexer.SLASH_CLOSE: - advance--; - break; - default: - break; - } - } - advance = Math.max(0, advance); - return advance * getTabSize(); - } catch (Throwable e) { - LOG.error("Failed to compute indent advance", e); + @Override + public int getIndentAdvance(@NonNull String content) { + try { + XMLLexer lexer = new XMLLexer(CharStreams.fromReader(new StringReader(content))); + Token token; + int advance = 0; + while (((token = lexer.nextToken()) != null && token.getType() != token.EOF)) { + switch (token.getType()) { + case XMLLexer.OPEN: + advance++; + break; + case XMLLexer.CLOSE: + case XMLLexer.SLASH_CLOSE: + advance--; + break; + default: + break; } - return 0; + } + advance = Math.max(0, advance); + return advance * getTabSize(); + } catch (Throwable e) { + LOG.error("Failed to compute indent advance", e); + } + return 0; + } + + @Override + public SymbolPairMatch getSymbolPairs() { + return new SymbolPairMatch.DefaultSymbolPairs(); + } + + @NonNull + @Override + public AnalyzeManager getAnalyzeManager() { + return analyzer; + } + + @Override + public int getInterruptionLevel() { + return INTERRUPTION_LEVEL_STRONG; + } + + @Override + public void requireAutoComplete( + @NonNull ContentReference content, + @NonNull CharPosition position, + @NonNull CompletionPublisher publisher, + @NonNull Bundle extraArguments) + throws CompletionCancelledException { + if (!extraArguments.containsKey(IDEEditor.KEY_FILE)) { + return; } - @Override - public SymbolPairMatch getSymbolPairs() { - return new SymbolPairMatch.DefaultSymbolPairs(); - } + final var file = Paths.get(extraArguments.getString(IDEEditor.KEY_FILE)); + publisher.setUpdateThreshold(0); + publisher.addItems( + new ArrayList<>( + completer.complete( + content, file, position, CommonCompletionProvider::checkXMLCompletionChar))); + } - @NonNull - @Override - public AnalyzeManager getAnalyzeManager() { - return analyzer; - } + @Override + public int getIndentAdvance(@NonNull ContentReference content, int line, int column) { + final var text = content.getLine(line).substring(0, column); + return getIndentAdvance(text.trim()); + } - @Override - public int getInterruptionLevel() { - return INTERRUPTION_LEVEL_STRONG; - } + @Override + protected ILanguageServer getLanguageServer() { + return StudioApp.getInstance().getXMLLanguageServer(); + } - @Override - public void requireAutoComplete( - @NonNull ContentReference content, - @NonNull CharPosition position, - @NonNull CompletionPublisher publisher, - @NonNull Bundle extraArguments) - throws CompletionCancelledException { - if (!extraArguments.containsKey(IDEEditor.KEY_FILE)) { - return; - } + @Override + public NewlineHandler[] getNewlineHandlers() { + return newlineHandlers; + } - final var file = Paths.get(extraArguments.getString(IDEEditor.KEY_FILE)); - publisher.setUpdateThreshold(0); - publisher.addItems( - new ArrayList<>( - completer.complete( - content, - file, - position, - CommonCompletionProvider::checkXMLCompletionChar))); - } - - @Override - public int getIndentAdvance(@NonNull ContentReference content, int line, int column) { - final var text = content.getLine(line).substring(0, column); - return getIndentAdvance(text.trim()); - } - - @Override - protected ILanguageServer getLanguageServer() { - return StudioApp.getInstance().getXMLLanguageServer(); - } - - @Override - public NewlineHandler[] getNewlineHandlers() { - return newlineHandlers; - } - - @Override - public void destroy() { - analyzer = null; - } + @Override + public void destroy() { + analyzer = null; + } } diff --git a/app/src/main/java/com/itsaky/androidide/lsp/IDELanguageClientImpl.java b/app/src/main/java/com/itsaky/androidide/lsp/IDELanguageClientImpl.java index 7fe89c917..9289c8f39 100755 --- a/app/src/main/java/com/itsaky/androidide/lsp/IDELanguageClientImpl.java +++ b/app/src/main/java/com/itsaky/androidide/lsp/IDELanguageClientImpl.java @@ -64,357 +64,344 @@ import io.github.rosemoe.sora.text.Content; /** AndroidIDE specific implementation of the LanguageClient */ public class IDELanguageClientImpl implements ILanguageClient { - protected static final Gson gson = new Gson(); - protected static final ILogger LOG = ILogger.newInstance("AbstractLanguageClient"); - private static IDELanguageClientImpl mInstance; - private final Map> diagnostics = new HashMap<>(); - protected EditorActivityProvider activityProvider; + protected static final Gson gson = new Gson(); + protected static final ILogger LOG = ILogger.newInstance("AbstractLanguageClient"); + private static IDELanguageClientImpl mInstance; + private final Map> diagnostics = new HashMap<>(); + protected EditorActivityProvider activityProvider; - private IDELanguageClientImpl(EditorActivityProvider provider) { - setActivityProvider(provider); + private IDELanguageClientImpl(EditorActivityProvider provider) { + setActivityProvider(provider); + } + + public static IDELanguageClientImpl getInstance() { + if (mInstance == null) { + throw new IllegalStateException("Client not initialized"); } - public static IDELanguageClientImpl getInstance() { - if (mInstance == null) { - throw new IllegalStateException("Client not initialized"); + return mInstance; + } + + public static IDELanguageClientImpl initialize(EditorActivityProvider provider) { + if (mInstance != null) { + throw new IllegalStateException("Client is already initialized"); + } + + mInstance = new IDELanguageClientImpl(provider); + + return getInstance(); + } + + public static void shutdown() { + mInstance = null; + } + + public static boolean isInitialized() { + return mInstance != null; + } + + public void setActivityProvider(EditorActivityProvider provider) { + this.activityProvider = provider; + } + + protected EditorActivity activity() { + if (activityProvider == null) return null; + return activityProvider.provide(); + } + + private CodeEditorView findEditorByFile(File file) { + return activity().getEditorForFile(file); + } + + public void hideDiagnostics() { + if (activity() == null || activity().getDiagnosticBinding() == null) { + return; + } + + activity().getDiagnosticBinding().getRoot().setVisibility(View.GONE); + } + + private void hideBottomDiagnosticView(final File file) { + if (activity() == null || file == null) { + return; + } + + final var frag = findEditorByFile(file); + if (frag == null || frag.getBinding() == null) { + return; + } + + frag.getBinding().diagnosticTextContainer.setVisibility(View.GONE); + frag.getBinding().diagnosticText.setVisibility(View.GONE); + frag.getBinding().diagnosticText.setClickable(false); + } + + @Override + public void publishDiagnostics(DiagnosticResult result) { + boolean error = result == null; + activity().handleDiagnosticsResultVisibility(error || result.getDiagnostics().isEmpty()); + + if (error) { + return; + } + + File file = result.getFile().toFile(); + if (!file.exists() || !file.isFile()) return; + + final var editorView = activity().getEditorForFile(file); + if (editorView != null) { + final var editor = editorView.getEditor(); + if (editor != null) { + final var editorLanguage = editor.getEditorLanguage(); + if (editorLanguage instanceof IDELanguage) { + ((IDELanguage) editorLanguage).setDiagnostics(result.getDiagnostics()); + } + } + } + + diagnostics.put(file, result.getDiagnostics()); + activity().setDiagnosticsAdapter(newDiagnosticsAdapter()); + } + + /** Called by {@link IDEEditor IDEEditor} to show locations in EditorActivity */ + public void showLocations(List locations) { + + // Cannot show anything if the activity() is null + if (activity() == null) { + return; + } + + boolean error = locations == null || locations.isEmpty(); + activity().handleSearchResultVisibility(error); + + if (error) { + activity().setSearchResultAdapter(new SearchListAdapter(null, null, null)); + return; + } + + final Map> results = new HashMap<>(); + for (int i = 0; i < locations.size(); i++) { + try { + final Location loc = locations.get(i); + if (loc == null) { + continue; } - return mInstance; + final File file = loc.getFile().toFile(); + if (!file.exists() || !file.isFile()) continue; + var frag = findEditorByFile(file); + Content content; + if (frag != null && frag.getEditor() != null) content = frag.getEditor().getText(); + else content = new Content(FileIOUtils.readFile2String(file)); + final List matches = + results.containsKey(file) ? results.get(file) : new ArrayList<>(); + Objects.requireNonNull(matches) + .add( + new SearchResult( + loc.getRange(), + file, + content.getLineString(loc.getRange().getStart().getLine()), + content + .subContent( + loc.getRange().getStart().getLine(), + loc.getRange().getStart().getColumn(), + loc.getRange().getEnd().getLine(), + loc.getRange().getEnd().getColumn()) + .toString())); + results.put(file, matches); + } catch (Throwable th) { + LOG.error("Failed to show file location", th); + } } - public static IDELanguageClientImpl initialize(EditorActivityProvider provider) { - if (mInstance != null) { - throw new IllegalStateException("Client is already initialized"); - } + activity().handleSearchResults(results); + } - mInstance = new IDELanguageClientImpl(provider); + @Override + public void performCodeAction(File file, CodeActionItem actionItem) { + final var editor = activity().getEditorForFile(file); + if (editor != null) { + performCodeAction(editor.getEditor(), actionItem); + } + } - return getInstance(); + /** + * Perform the given {@link CodeActionItem} + * + * @param editor The {@link IDEEditor} that invoked the code action request. This is required to + * reduce the time finding the code action from the edits. + * @param action The action to perform + */ + public void performCodeAction(IDEEditor editor, CodeActionItem action) { + if (activity() == null || editor == null || action == null) { + LOG.error( + "Unable to perform code action", + "activity=" + activity(), + "editor=" + editor, + "action=" + action); + StudioApp.getInstance().toast(R.string.msg_cannot_perform_fix, Toaster.Type.ERROR); + return; } - public static void shutdown() { - mInstance = null; + final ProgressSheet progress = new ProgressSheet(); + progress.setSubMessageEnabled(false); + progress.setWelcomeTextEnabled(false); + progress.setCancelable(false); + progress.setMessage(activity().getString(R.string.msg_performing_actions)); + progress.show(activity().getSupportFragmentManager(), "quick_fix_progress"); + + new TaskExecutor() + .executeAsyncProvideError( + () -> performCodeActionAsync(editor, action), + (result, throwable) -> { + ThreadUtils.runOnUiThread(progress::dismiss); + if (result == null || throwable != null || !result) { + LOG.error( + "Unable to perform code action", "result=" + result, "throwable=" + throwable); + StudioApp.getInstance().toast(R.string.msg_cannot_perform_fix, Toaster.Type.ERROR); + } else { + editor.executeCommand(action.getCommand()); + } + }); + } + + private List mapAsGroup(Map> diags) { + List groups = new ArrayList<>(); + if (diags == null || diags.size() <= 0) return groups; + for (File file : diags.keySet()) { + var fileDiags = diags.get(file); + if (fileDiags == null || fileDiags.size() <= 0) continue; + DiagnosticGroup group = new DiagnosticGroup(R.drawable.ic_language_java, file, fileDiags); + groups.add(group); + } + return groups; + } + + private void showAvailableQuickfixes(IDEEditor editor, List actions) { + final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity()); + builder.setTitle(R.string.msg_code_actions); + builder.setItems( + asArray(actions), + (d, w) -> { + d.dismiss(); + hideDiagnostics(); + hideBottomDiagnosticView(editor.getFile()); + performCodeAction(editor, actions.get(w)); + }); + builder.show(); + } + + private CharSequence[] asArray(List actions) { + final String[] arr = new String[actions.size()]; + for (int i = 0; i < actions.size(); i++) { + arr[i] = actions.get(i).getTitle(); + } + return arr; + } + + private Boolean performCodeActionAsync(final IDEEditor editor, final CodeActionItem action) { + LOG.debug("Performing code action:", action); + final var changes = action.getChanges(); + if (changes.isEmpty()) { + return Boolean.FALSE; } - public static boolean isInitialized() { - return mInstance != null; - } + for (var change : changes) { + final var path = change.getFile(); + if (path == null) { + continue; + } - public void setActivityProvider(EditorActivityProvider provider) { - this.activityProvider = provider; - } + final File file = path.toFile(); + if (!file.exists()) { + continue; + } - protected EditorActivity activity() { - if (activityProvider == null) return null; - return activityProvider.provide(); - } + for (TextEdit edit : change.getEdits()) { + final String editorFilepath = + editor.getFile() == null ? "" : editor.getFile().getAbsolutePath(); + if (file.getAbsolutePath().equals(editorFilepath)) { + // Edit is in the same editor which requested the code action + editInEditor(editor, edit); + } else { + var openedFrag = findEditorByFile(file); - private CodeEditorView findEditorByFile(File file) { - return activity().getEditorForFile(file); - } - - public void hideDiagnostics() { - if (activity() == null || activity().getDiagnosticBinding() == null) { - return; - } - - activity().getDiagnosticBinding().getRoot().setVisibility(View.GONE); - } - - private void hideBottomDiagnosticView(final File file) { - if (activity() == null || file == null) { - return; - } - - final var frag = findEditorByFile(file); - if (frag == null || frag.getBinding() == null) { - return; - } - - frag.getBinding().diagnosticTextContainer.setVisibility(View.GONE); - frag.getBinding().diagnosticText.setVisibility(View.GONE); - frag.getBinding().diagnosticText.setClickable(false); - } - - @Override - public void publishDiagnostics(DiagnosticResult result) { - boolean error = result == null; - activity().handleDiagnosticsResultVisibility(error || result.getDiagnostics().isEmpty()); - - if (error) { - return; - } - - File file = result.getFile().toFile(); - if (!file.exists() || !file.isFile()) return; - - final var editorView = activity().getEditorForFile(file); - if (editorView != null) { - final var editor = editorView.getEditor(); - if (editor != null) { - final var editorLanguage = editor.getEditorLanguage(); - if (editorLanguage instanceof IDELanguage) { - ((IDELanguage) editorLanguage).setDiagnostics(result.getDiagnostics()); - } + if (openedFrag != null && openedFrag.getEditor() != null) { + // Edit is in another 'opened' file + editInEditor(openedFrag.getEditor(), edit); + } else { + // Edit is in some other file which is not opened + // We should open that file and perform the edit + openedFrag = activity().openFile(file); + if (openedFrag != null && openedFrag.getEditor() != null) { + editInEditor(openedFrag.getEditor(), edit); } + } } - - diagnostics.put(file, result.getDiagnostics()); - activity().setDiagnosticsAdapter(newDiagnosticsAdapter()); + } } - /** Called by {@link IDEEditor IDEEditor} to show locations in EditorActivity */ - public void showLocations(List locations) { + return Boolean.TRUE; + } - // Cannot show anything if the activity() is null - if (activity() == null) { - return; - } + private void execCommand(IDEEditor editor, Command command) { + editor.executeCommand(command); + } - boolean error = locations == null || locations.isEmpty(); - activity().handleSearchResultVisibility(error); + private void editInEditor(final IDEEditor editor, final TextEdit edit) { + final Range range = edit.getRange(); + final int startLine = range.getStart().getLine(); + final int startCol = range.getStart().getColumn(); + final int endLine = range.getEnd().getLine(); + final int endCol = range.getEnd().getColumn(); - if (error) { - activity().setSearchResultAdapter(new SearchListAdapter(null, null, null)); - return; - } + activity() + .runOnUiThread( + () -> { + if (startLine == endLine && startCol == endCol) { + editor.getText().insert(startLine, startCol, edit.getNewText()); + } else { + editor.getText().replace(startLine, startCol, endLine, endCol, edit.getNewText()); + } + }); + } - final Map> results = new HashMap<>(); - for (int i = 0; i < locations.size(); i++) { - try { - final Location loc = locations.get(i); - if (loc == null) { - continue; - } + public DiagnosticsAdapter newDiagnosticsAdapter() { + return new DiagnosticsAdapter(mapAsGroup(this.diagnostics), activity()); + } - final File file = loc.getFile().toFile(); - if (!file.exists() || !file.isFile()) continue; - var frag = findEditorByFile(file); - Content content; - if (frag != null && frag.getEditor() != null) content = frag.getEditor().getText(); - else content = new Content(FileIOUtils.readFile2String(file)); - final List matches = - results.containsKey(file) ? results.get(file) : new ArrayList<>(); - Objects.requireNonNull(matches) - .add( - new SearchResult( - loc.getRange(), - file, - content.getLineString(loc.getRange().getStart().getLine()), - content.subContent( - loc.getRange().getStart().getLine(), - loc.getRange().getStart().getColumn(), - loc.getRange().getEnd().getLine(), - loc.getRange().getEnd().getColumn()) - .toString())); - results.put(file, matches); - } catch (Throwable th) { - LOG.error("Failed to show file location", th); - } - } - - activity().handleSearchResults(results); + @Override + public com.itsaky.lsp.models.ShowDocumentResult showDocument( + com.itsaky.lsp.models.ShowDocumentParams params) { + boolean success = false; + final var result = new com.itsaky.lsp.models.ShowDocumentResult(false); + if (activity() == null) { + return result; } - @Override - public void performCodeAction(File file, CodeActionItem actionItem) { - final var editor = activity().getEditorForFile(file); - if (editor != null) { - performCodeAction(editor.getEditor(), actionItem); + if (params != null) { + File file = params.getFile().toFile(); + if (file.exists() && file.isFile() && FileUtils.isUtf8(file)) { + final var range = params.getSelection(); + var frag = + activity().getEditorAtIndex(activity().getBinding().tabs.getSelectedTabPosition()); + if (frag != null + && frag.getFile() != null + && frag.getEditor() != null + && frag.getFile().getAbsolutePath().equals(file.getAbsolutePath())) { + if (LSPUtils.isEqual(range.getStart(), range.getEnd())) { + frag.getEditor().setSelection(range.getStart().getLine(), range.getStart().getColumn()); + } else { + frag.getEditor().setSelection(range); + } + } else { + activity().openFileAndSelect(file, range); } + success = true; + } } - /** - * Perform the given {@link CodeActionItem} - * - * @param editor The {@link IDEEditor} that invoked the code action request. This is required to - * reduce the time finding the code action from the edits. - * @param action The action to perform - */ - public void performCodeAction(IDEEditor editor, CodeActionItem action) { - if (activity() == null || editor == null || action == null) { - LOG.error( - "Unable to perform code action", - "activity=" + activity(), - "editor=" + editor, - "action=" + action); - StudioApp.getInstance().toast(R.string.msg_cannot_perform_fix, Toaster.Type.ERROR); - return; - } - - final ProgressSheet progress = new ProgressSheet(); - progress.setSubMessageEnabled(false); - progress.setWelcomeTextEnabled(false); - progress.setCancelable(false); - progress.setMessage(activity().getString(R.string.msg_performing_actions)); - progress.show(activity().getSupportFragmentManager(), "quick_fix_progress"); - - new TaskExecutor() - .executeAsyncProvideError( - () -> performCodeActionAsync(editor, action), - (result, throwable) -> { - ThreadUtils.runOnUiThread(progress::dismiss); - if (result == null || throwable != null || !result) { - LOG.error( - "Unable to perform code action", - "result=" + result, - "throwable=" + throwable); - StudioApp.getInstance() - .toast(R.string.msg_cannot_perform_fix, Toaster.Type.ERROR); - } else { - editor.executeCommand(action.getCommand()); - } - }); - } - - private List mapAsGroup(Map> diags) { - List groups = new ArrayList<>(); - if (diags == null || diags.size() <= 0) return groups; - for (File file : diags.keySet()) { - var fileDiags = diags.get(file); - if (fileDiags == null || fileDiags.size() <= 0) continue; - DiagnosticGroup group = - new DiagnosticGroup(R.drawable.ic_language_java, file, fileDiags); - groups.add(group); - } - return groups; - } - - private void showAvailableQuickfixes(IDEEditor editor, List actions) { - final MaterialAlertDialogBuilder builder = DialogUtils.newMaterialDialogBuilder(activity()); - builder.setTitle(R.string.msg_code_actions); - builder.setItems( - asArray(actions), - (d, w) -> { - d.dismiss(); - hideDiagnostics(); - hideBottomDiagnosticView(editor.getFile()); - performCodeAction(editor, actions.get(w)); - }); - builder.show(); - } - - private CharSequence[] asArray(List actions) { - final String[] arr = new String[actions.size()]; - for (int i = 0; i < actions.size(); i++) { - arr[i] = actions.get(i).getTitle(); - } - return arr; - } - - private Boolean performCodeActionAsync(final IDEEditor editor, final CodeActionItem action) { - LOG.debug("Performing code action:", action); - final var changes = action.getChanges(); - if (changes.isEmpty()) { - return Boolean.FALSE; - } - - for (var change : changes) { - final var path = change.getFile(); - if (path == null) { - continue; - } - - final File file = path.toFile(); - if (!file.exists()) { - continue; - } - - for (TextEdit edit : change.getEdits()) { - final String editorFilepath = - editor.getFile() == null ? "" : editor.getFile().getAbsolutePath(); - if (file.getAbsolutePath().equals(editorFilepath)) { - // Edit is in the same editor which requested the code action - editInEditor(editor, edit); - } else { - var openedFrag = findEditorByFile(file); - - if (openedFrag != null && openedFrag.getEditor() != null) { - // Edit is in another 'opened' file - editInEditor(openedFrag.getEditor(), edit); - } else { - // Edit is in some other file which is not opened - // We should open that file and perform the edit - openedFrag = activity().openFile(file); - if (openedFrag != null && openedFrag.getEditor() != null) { - editInEditor(openedFrag.getEditor(), edit); - } - } - } - } - } - - return Boolean.TRUE; - } - - private void execCommand(IDEEditor editor, Command command) { - editor.executeCommand(command); - } - - private void editInEditor(final IDEEditor editor, final TextEdit edit) { - final Range range = edit.getRange(); - final int startLine = range.getStart().getLine(); - final int startCol = range.getStart().getColumn(); - final int endLine = range.getEnd().getLine(); - final int endCol = range.getEnd().getColumn(); - - activity() - .runOnUiThread( - () -> { - if (startLine == endLine && startCol == endCol) { - editor.getText().insert(startLine, startCol, edit.getNewText()); - } else { - editor.getText() - .replace( - startLine, - startCol, - endLine, - endCol, - edit.getNewText()); - } - }); - } - - public DiagnosticsAdapter newDiagnosticsAdapter() { - return new DiagnosticsAdapter(mapAsGroup(this.diagnostics), activity()); - } - - @Override - public com.itsaky.lsp.models.ShowDocumentResult showDocument( - com.itsaky.lsp.models.ShowDocumentParams params) { - boolean success = false; - final var result = new com.itsaky.lsp.models.ShowDocumentResult(false); - if (activity() == null) { - return result; - } - - if (params != null) { - File file = params.getFile().toFile(); - if (file.exists() && file.isFile() && FileUtils.isUtf8(file)) { - final var range = params.getSelection(); - var frag = - activity() - .getEditorAtIndex( - activity().getBinding().tabs.getSelectedTabPosition()); - if (frag != null - && frag.getFile() != null - && frag.getEditor() != null - && frag.getFile().getAbsolutePath().equals(file.getAbsolutePath())) { - if (LSPUtils.isEqual(range.getStart(), range.getEnd())) { - frag.getEditor() - .setSelection( - range.getStart().getLine(), range.getStart().getColumn()); - } else { - frag.getEditor().setSelection(range); - } - } else { - activity().openFileAndSelect(file, range); - } - success = true; - } - } - - result.setSuccess(success); - return result; - } + result.setSuccess(success); + return result; + } } diff --git a/app/src/main/java/com/itsaky/androidide/models/ApkMetadata.java b/app/src/main/java/com/itsaky/androidide/models/ApkMetadata.java index d289a8df3..31982ebc4 100755 --- a/app/src/main/java/com/itsaky/androidide/models/ApkMetadata.java +++ b/app/src/main/java/com/itsaky/androidide/models/ApkMetadata.java @@ -35,301 +35,301 @@ import java.util.List; @SuppressWarnings("unused") public class ApkMetadata { - private static final ILogger LOG = newInstance("ApkMetadata"); + private static final ILogger LOG = newInstance("ApkMetadata"); - @SerializedName("version") - private int version; + @SerializedName("version") + private int version; - @SerializedName("artifactType") - private ArtifactType artifactType; + @SerializedName("artifactType") + private ArtifactType artifactType; - @SerializedName("applicationId") - private String applicationId; + @SerializedName("applicationId") + private String applicationId; - @SerializedName("variantName") - private String variantName; + @SerializedName("variantName") + private String variantName; - @SerializedName("elements") - private List elements; + @SerializedName("elements") + private List elements; - @SerializedName("elementType") - private String elementType; + @SerializedName("elementType") + private String elementType; - @Nullable - public static File findApkFile(@NonNull File listingFIle) { - try { + @Nullable + public static File findApkFile(@NonNull File listingFIle) { + try { - // This sometimes might be a redirect to the listing file - // So we have to handle this case too. - final var redirectedFile = ListingFileRedirect.maybeExtractRedirectedFile(listingFIle); - if (redirectedFile != null) { - listingFIle = redirectedFile; - } + // This sometimes might be a redirect to the listing file + // So we have to handle this case too. + final var redirectedFile = ListingFileRedirect.maybeExtractRedirectedFile(listingFIle); + if (redirectedFile != null) { + listingFIle = redirectedFile; + } - final var dir = listingFIle.getParentFile(); - final var metadata = - JSONUtility.gson.fromJson(new FileReader(listingFIle), ApkMetadata.class); - if (!isValid(metadata)) { - LOG.warn("Invalid APK metadata:", metadata); - return null; - } + final var dir = listingFIle.getParentFile(); + final var metadata = + JSONUtility.gson.fromJson(new FileReader(listingFIle), ApkMetadata.class); + if (!isValid(metadata)) { + LOG.warn("Invalid APK metadata:", metadata); + return null; + } - for (Element element : metadata.getElements()) { - if (element == null || element.getOutputFile() == null) { - LOG.warn("No output file specified in APK metadata element:", element); - continue; - } - - if (element.getOutputFile().endsWith(".apk")) { - final File apk = new File(dir, element.getOutputFile()); - if (apk.exists() && apk.isFile()) { - LOG.info("Found apk in metadata:", apk); - return apk; - } - } - } - - return null; - } catch (FileNotFoundException e) { - LOG.error("Metadata file not found...", e); - return null; - } - } - - private static boolean isValid(ApkMetadata metadata) { - - // Null checks - if (metadata == null - || metadata.getArtifactType() == null - || metadata.getArtifactType().getType() == null - || metadata.getElements() == null) { - LOG.warn("APK metadata null check failed. Metadata:", metadata); - return false; + for (Element element : metadata.getElements()) { + if (element == null || element.getOutputFile() == null) { + LOG.warn("No output file specified in APK metadata element:", element); + continue; } - final ArtifactType type = metadata.getArtifactType(); - final List elements = metadata.getElements(); - - if (!type.getType().equals(ArtifactType.TYPE_APK)) { - LOG.warn("Artifact is not of type APK. Metadata:", metadata); - return false; + if (element.getOutputFile().endsWith(".apk")) { + final File apk = new File(dir, element.getOutputFile()); + if (apk.exists() && apk.isFile()) { + LOG.info("Found apk in metadata:", apk); + return apk; + } } + } - if (elements.isEmpty()) { - LOG.warn("No output elements found for metadata:", metadata); - return false; - } + return null; + } catch (FileNotFoundException e) { + LOG.error("Metadata file not found...", e); + return null; + } + } - boolean atLeastOneApk = false; - for (Element element : elements) { - if (element == null - || element.getOutputFile() == null - || !element.getOutputFile().endsWith(".apk")) { - LOG.warn("Skipping output element because file is not APK:", element); - continue; - } + private static boolean isValid(ApkMetadata metadata) { - if (element.getOutputFile().endsWith(".apk")) { - atLeastOneApk = true; - break; - } - } - - LOG.debug("Output metadata validation succeeded"); - return atLeastOneApk; + // Null checks + if (metadata == null + || metadata.getArtifactType() == null + || metadata.getArtifactType().getType() == null + || metadata.getElements() == null) { + LOG.warn("APK metadata null check failed. Metadata:", metadata); + return false; } - public List getElements() { - return elements; + final ArtifactType type = metadata.getArtifactType(); + final List elements = metadata.getElements(); + + if (!type.getType().equals(ArtifactType.TYPE_APK)) { + LOG.warn("Artifact is not of type APK. Metadata:", metadata); + return false; } - public ArtifactType getArtifactType() { - return artifactType; + if (elements.isEmpty()) { + LOG.warn("No output elements found for metadata:", metadata); + return false; } - public void setArtifactType(ArtifactType artifactType) { - this.artifactType = artifactType; + boolean atLeastOneApk = false; + for (Element element : elements) { + if (element == null + || element.getOutputFile() == null + || !element.getOutputFile().endsWith(".apk")) { + LOG.warn("Skipping output element because file is not APK:", element); + continue; + } + + if (element.getOutputFile().endsWith(".apk")) { + atLeastOneApk = true; + break; + } } - public void setElements(List elements) { - this.elements = elements; + LOG.debug("Output metadata validation succeeded"); + return atLeastOneApk; + } + + public List getElements() { + return elements; + } + + public ArtifactType getArtifactType() { + return artifactType; + } + + public void setArtifactType(ArtifactType artifactType) { + this.artifactType = artifactType; + } + + public void setElements(List elements) { + this.elements = elements; + } + + public int getVersion() { + return version; + } + + public void setVersion(int version) { + this.version = version; + } + + public String getApplicationId() { + return applicationId; + } + + public void setApplicationId(String applicationId) { + this.applicationId = applicationId; + } + + public String getVariantName() { + return variantName; + } + + public void setVariantName(String variantName) { + this.variantName = variantName; + } + + public String getElementType() { + return elementType; + } + + public void setElementType(String elementType) { + this.elementType = elementType; + } + + @NonNull + @Override + public String toString() { + return "ApkMetadata{" + + "version=" + + version + + ", artifactType=" + + artifactType + + ", applicationId='" + + applicationId + + '\'' + + ", variantName='" + + variantName + + '\'' + + ", elements=" + + elements + + ", elementType='" + + elementType + + '\'' + + '}'; + } + + public static class ArtifactType { + + public static String TYPE_APK = "APK"; + + @SerializedName("type") + private String type; + + @SerializedName("kind") + private String kind; + + public String getType() { + return type; } - public int getVersion() { - return version; + public void setType(String type) { + this.type = type; } - public void setVersion(int version) { - this.version = version; + public String getKind() { + return kind; } - public String getApplicationId() { - return applicationId; - } - - public void setApplicationId(String applicationId) { - this.applicationId = applicationId; - } - - public String getVariantName() { - return variantName; - } - - public void setVariantName(String variantName) { - this.variantName = variantName; - } - - public String getElementType() { - return elementType; - } - - public void setElementType(String elementType) { - this.elementType = elementType; + public void setKind(String kind) { + this.kind = kind; } @NonNull @Override public String toString() { - return "ApkMetadata{" - + "version=" - + version - + ", artifactType=" - + artifactType - + ", applicationId='" - + applicationId - + '\'' - + ", variantName='" - + variantName - + '\'' - + ", elements=" - + elements - + ", elementType='" - + elementType - + '\'' - + '}'; + return "ArtifactType{" + "type='" + type + '\'' + ", kind='" + kind + '\'' + '}'; + } + } + + public static class Element { + + @SerializedName("type") + private String type; + + @SerializedName("filters") + private List filters; + + @SerializedName("attributes") + private List attributes; + + @SerializedName("versionCode") + private int versionCode; + + @SerializedName("versionName") + private String versionName; + + @SerializedName("outputFile") + private String outputFile; + + public String getType() { + return type; } - public static class ArtifactType { - - public static String TYPE_APK = "APK"; - - @SerializedName("type") - private String type; - - @SerializedName("kind") - private String kind; - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public String getKind() { - return kind; - } - - public void setKind(String kind) { - this.kind = kind; - } - - @NonNull - @Override - public String toString() { - return "ArtifactType{" + "type='" + type + '\'' + ", kind='" + kind + '\'' + '}'; - } + public void setType(String type) { + this.type = type; } - public static class Element { - - @SerializedName("type") - private String type; - - @SerializedName("filters") - private List filters; - - @SerializedName("attributes") - private List attributes; - - @SerializedName("versionCode") - private int versionCode; - - @SerializedName("versionName") - private String versionName; - - @SerializedName("outputFile") - private String outputFile; - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public List getFilters() { - return filters; - } - - public void setFilters(List filters) { - this.filters = filters; - } - - public List getAttributes() { - return attributes; - } - - public void setAttributes(List attributes) { - this.attributes = attributes; - } - - public int getVersionCode() { - return versionCode; - } - - public void setVersionCode(int versionCode) { - this.versionCode = versionCode; - } - - public String getVersionName() { - return versionName; - } - - public void setVersionName(String versionName) { - this.versionName = versionName; - } - - public String getOutputFile() { - return outputFile; - } - - public void setOutputFile(String outputFile) { - this.outputFile = outputFile; - } - - @NonNull - @Override - public String toString() { - return "Element{" - + "type='" - + type - + '\'' - + ", filters=" - + filters - + ", attributes=" - + attributes - + ", versionCode=" - + versionCode - + ", versionName='" - + versionName - + '\'' - + ", outputFile='" - + outputFile - + '\'' - + '}'; - } + public List getFilters() { + return filters; } + + public void setFilters(List filters) { + this.filters = filters; + } + + public List getAttributes() { + return attributes; + } + + public void setAttributes(List attributes) { + this.attributes = attributes; + } + + public int getVersionCode() { + return versionCode; + } + + public void setVersionCode(int versionCode) { + this.versionCode = versionCode; + } + + public String getVersionName() { + return versionName; + } + + public void setVersionName(String versionName) { + this.versionName = versionName; + } + + public String getOutputFile() { + return outputFile; + } + + public void setOutputFile(String outputFile) { + this.outputFile = outputFile; + } + + @NonNull + @Override + public String toString() { + return "Element{" + + "type='" + + type + + '\'' + + ", filters=" + + filters + + ", attributes=" + + attributes + + ", versionCode=" + + versionCode + + ", versionName='" + + versionName + + '\'' + + ", outputFile='" + + outputFile + + '\'' + + '}'; + } + } } diff --git a/app/src/main/java/com/itsaky/androidide/models/DiagnosticGroup.java b/app/src/main/java/com/itsaky/androidide/models/DiagnosticGroup.java index da8095cf0..44c43c46f 100755 --- a/app/src/main/java/com/itsaky/androidide/models/DiagnosticGroup.java +++ b/app/src/main/java/com/itsaky/androidide/models/DiagnosticGroup.java @@ -23,15 +23,15 @@ import java.io.File; import java.util.List; public class DiagnosticGroup { - public int icon; - public String text; - public File file; - public List diagnostics; + public int icon; + public String text; + public File file; + public List diagnostics; - public DiagnosticGroup(int icon, File file, List diagnostics) { - this.icon = icon; - this.file = file; - this.text = file.getName(); - this.diagnostics = diagnostics; - } + public DiagnosticGroup(int icon, File file, List diagnostics) { + this.icon = icon; + this.file = file; + this.text = file.getName(); + this.diagnostics = diagnostics; + } } diff --git a/app/src/main/java/com/itsaky/androidide/models/IconTextListItem.java b/app/src/main/java/com/itsaky/androidide/models/IconTextListItem.java index 16d8427de..13df30bf4 100755 --- a/app/src/main/java/com/itsaky/androidide/models/IconTextListItem.java +++ b/app/src/main/java/com/itsaky/androidide/models/IconTextListItem.java @@ -21,21 +21,21 @@ import androidx.annotation.DrawableRes; public interface IconTextListItem { - String getText(); + String getText(); - int getIconResource(); + int getIconResource(); - public static IconTextListItem create(String name, @DrawableRes int icon) { - return new IconTextListItem() { - @Override - public String getText() { - return name; - } + public static IconTextListItem create(String name, @DrawableRes int icon) { + return new IconTextListItem() { + @Override + public String getText() { + return name; + } - @Override - public int getIconResource() { - return icon; - } - }; - } + @Override + public int getIconResource() { + return icon; + } + }; + } } diff --git a/app/src/main/java/com/itsaky/androidide/models/License.java b/app/src/main/java/com/itsaky/androidide/models/License.java index 55e377a08..1c3095e6b 100755 --- a/app/src/main/java/com/itsaky/androidide/models/License.java +++ b/app/src/main/java/com/itsaky/androidide/models/License.java @@ -20,18 +20,18 @@ package com.itsaky.androidide.models; import com.google.gson.annotations.SerializedName; public class License { - @SerializedName("name") - public String name; + @SerializedName("name") + public String name; - @SerializedName("license") - public String license; + @SerializedName("license") + public String license; - @SerializedName("url") - public String url; + @SerializedName("url") + public String url; - public License(String name, String license, String url) { - this.name = name; - this.license = license; - this.url = url; - } + public License(String name, String license, String url) { + this.name = name; + this.license = license; + this.url = url; + } } diff --git a/app/src/main/java/com/itsaky/androidide/models/NewProjectDetails.java b/app/src/main/java/com/itsaky/androidide/models/NewProjectDetails.java index 2201edaff..a1360c44b 100755 --- a/app/src/main/java/com/itsaky/androidide/models/NewProjectDetails.java +++ b/app/src/main/java/com/itsaky/androidide/models/NewProjectDetails.java @@ -19,53 +19,53 @@ package com.itsaky.androidide.models; public class NewProjectDetails { - public String name; - public String packageName; - public int minSdk; - public int targetSdk; + public String name; + public String packageName; + public int minSdk; + public int targetSdk; - public NewProjectDetails() {} + public NewProjectDetails() {} - public NewProjectDetails(String name, String packageName, int minSdk, int targetSdk) { - this.name = name; - this.packageName = packageName; - this.minSdk = minSdk; - this.targetSdk = targetSdk; - } + public NewProjectDetails(String name, String packageName, int minSdk, int targetSdk) { + this.name = name; + this.packageName = packageName; + this.minSdk = minSdk; + this.targetSdk = targetSdk; + } - public NewProjectDetails setName(String name) { - this.name = name; - return this; - } + public NewProjectDetails setName(String name) { + this.name = name; + return this; + } - public String getName() { - return name; - } + public String getName() { + return name; + } - public NewProjectDetails setPackageName(String packageName) { - this.packageName = packageName; - return this; - } + public NewProjectDetails setPackageName(String packageName) { + this.packageName = packageName; + return this; + } - public String getPackageName() { - return packageName; - } + public String getPackageName() { + return packageName; + } - public NewProjectDetails setMinSdk(int minSdk) { - this.minSdk = minSdk; - return this; - } + public NewProjectDetails setMinSdk(int minSdk) { + this.minSdk = minSdk; + return this; + } - public int getMinSdk() { - return minSdk; - } + public int getMinSdk() { + return minSdk; + } - public NewProjectDetails setTargetSdk(int targetSdk) { - this.targetSdk = targetSdk; - return this; - } + public NewProjectDetails setTargetSdk(int targetSdk) { + this.targetSdk = targetSdk; + return this; + } - public int getTargetSdk() { - return targetSdk; - } + public int getTargetSdk() { + return targetSdk; + } } diff --git a/app/src/main/java/com/itsaky/androidide/models/ProjectTemplate.java b/app/src/main/java/com/itsaky/androidide/models/ProjectTemplate.java index cefeeb978..fc1d93749 100755 --- a/app/src/main/java/com/itsaky/androidide/models/ProjectTemplate.java +++ b/app/src/main/java/com/itsaky/androidide/models/ProjectTemplate.java @@ -24,62 +24,62 @@ import androidx.annotation.StringRes; public class ProjectTemplate { - private int id; - private String name; - private String description; + private int id; + private String name; + private String description; - @DrawableRes private int imageId; + @DrawableRes private int imageId; - public ProjectTemplate() {} + public ProjectTemplate() {} - public ProjectTemplate(int id, String name, String description, int imageId) { - this.id = id; - this.name = name; - this.description = description; - this.imageId = imageId; - } + public ProjectTemplate(int id, String name, String description, int imageId) { + this.id = id; + this.name = name; + this.description = description; + this.imageId = imageId; + } - public ProjectTemplate setId(int id) { - this.id = id; - return this; - } + public ProjectTemplate setId(int id) { + this.id = id; + return this; + } - public int getId() { - return id; - } + public int getId() { + return id; + } - public ProjectTemplate setName(Context ctx, @StringRes int name) { - return setName(ctx.getString(name)); - } + public ProjectTemplate setName(Context ctx, @StringRes int name) { + return setName(ctx.getString(name)); + } - public ProjectTemplate setName(String name) { - this.name = name; - return this; - } + public ProjectTemplate setName(String name) { + this.name = name; + return this; + } - public String getName() { - return name; - } + public String getName() { + return name; + } - public ProjectTemplate setDescription(Context ctx, @StringRes int desc) { - return setDescription(ctx.getString(desc)); - } + public ProjectTemplate setDescription(Context ctx, @StringRes int desc) { + return setDescription(ctx.getString(desc)); + } - public ProjectTemplate setDescription(String description) { - this.description = description; - return this; - } + public ProjectTemplate setDescription(String description) { + this.description = description; + return this; + } - public String getDescription() { - return description; - } + public String getDescription() { + return description; + } - public ProjectTemplate setImageId(@DrawableRes int imageId) { - this.imageId = imageId; - return this; - } + public ProjectTemplate setImageId(@DrawableRes int imageId) { + this.imageId = imageId; + return this; + } - public int getImageId() { - return imageId; - } + public int getImageId() { + return imageId; + } } diff --git a/app/src/main/java/com/itsaky/androidide/models/SearchResult.java b/app/src/main/java/com/itsaky/androidide/models/SearchResult.java index 4ee6e7484..f5eb3fd6e 100755 --- a/app/src/main/java/com/itsaky/androidide/models/SearchResult.java +++ b/app/src/main/java/com/itsaky/androidide/models/SearchResult.java @@ -22,14 +22,14 @@ import com.itsaky.lsp.models.Range; import java.io.File; public class SearchResult extends Range { - public File file; - public String line; - public String match; + public File file; + public String line; + public String match; - public SearchResult(Range src, File file, String line, String match) { - super(src.getStart(), src.getEnd()); - this.file = file; - this.line = line; - this.match = match; - } + public SearchResult(Range src, File file, String line, String match) { + super(src.getStart(), src.getEnd()); + this.file = file; + this.line = line; + this.match = match; + } } diff --git a/app/src/main/java/com/itsaky/androidide/models/UIWidget.java b/app/src/main/java/com/itsaky/androidide/models/UIWidget.java index 390d2de43..7a1c496e4 100755 --- a/app/src/main/java/com/itsaky/androidide/models/UIWidget.java +++ b/app/src/main/java/com/itsaky/androidide/models/UIWidget.java @@ -32,93 +32,93 @@ import java.util.Objects; */ public class UIWidget implements IconTextListItem, Parcelable { - private final String name; - private final int icon; + private final String name; + private final int icon; - private final String clazz; + private final String clazz; - private UIWidget(Parcel in) { - this.name = in.readString(); - this.icon = in.readInt(); - this.clazz = in.readString(); - } + private UIWidget(Parcel in) { + this.name = in.readString(); + this.icon = in.readInt(); + this.clazz = in.readString(); + } - public UIWidget(String name, @DrawableRes int icon, Class clazz) { - this.name = name; - this.icon = icon; - this.clazz = clazz.getName(); - } + public UIWidget(String name, @DrawableRes int icon, Class clazz) { + this.name = name; + this.icon = icon; + this.clazz = clazz.getName(); + } - public static final Creator CREATOR = - new Creator() { - @Override - public UIWidget createFromParcel(Parcel in) { - return new UIWidget(in); - } + public static final Creator CREATOR = + new Creator() { + @Override + public UIWidget createFromParcel(Parcel in) { + return new UIWidget(in); + } - @Override - public UIWidget[] newArray(int size) { - return new UIWidget[size]; - } - }; + @Override + public UIWidget[] newArray(int size) { + return new UIWidget[size]; + } + }; - public String getName() { - return name; - } + public String getName() { + return name; + } - public int getIcon() { - return icon; - } + public int getIcon() { + return icon; + } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - UIWidget widget = (UIWidget) o; - return getIcon() == widget.getIcon() - && getName().equals(widget.getName()) - && clazz.equals(widget.clazz); - } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + UIWidget widget = (UIWidget) o; + return getIcon() == widget.getIcon() + && getName().equals(widget.getName()) + && clazz.equals(widget.clazz); + } - @Override - public int hashCode() { - return Objects.hash(getName(), getIcon(), clazz); - } + @Override + public int hashCode() { + return Objects.hash(getName(), getIcon(), clazz); + } - /** - * Get the class of this widget. - * - * @return The class that this widget represents - * @throws ClassNotFoundException If there is no class with this widget's class name. However, - * this will never be thrown. - */ - public Class asClass() throws ClassNotFoundException { - return getClass().getClassLoader().loadClass(this.clazz).asSubclass(View.class); - } + /** + * Get the class of this widget. + * + * @return The class that this widget represents + * @throws ClassNotFoundException If there is no class with this widget's class name. However, + * this will never be thrown. + */ + public Class asClass() throws ClassNotFoundException { + return getClass().getClassLoader().loadClass(this.clazz).asSubclass(View.class); + } - public String getWidgetClassName() { - return this.clazz; - } + public String getWidgetClassName() { + return this.clazz; + } - @Override - public String getText() { - return getName(); - } + @Override + public String getText() { + return getName(); + } - @Override - public int getIconResource() { - return getIcon(); - } + @Override + public int getIconResource() { + return getIcon(); + } - @Override - public int describeContents() { - return 0; - } + @Override + public int describeContents() { + return 0; + } - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeString(name); - dest.writeInt(icon); - dest.writeString(clazz); - } + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(name); + dest.writeInt(icon); + dest.writeString(clazz); + } } diff --git a/app/src/main/java/com/itsaky/androidide/models/UIWidgetGroup.java b/app/src/main/java/com/itsaky/androidide/models/UIWidgetGroup.java index 622a3e3f0..168dcdebc 100755 --- a/app/src/main/java/com/itsaky/androidide/models/UIWidgetGroup.java +++ b/app/src/main/java/com/itsaky/androidide/models/UIWidgetGroup.java @@ -38,131 +38,131 @@ import java.util.Objects; */ public class UIWidgetGroup implements IconTextListItem, Parcelable { - /** Name of this group. */ - private final String name; + /** Name of this group. */ + private final String name; - /** Children of this group. */ - private final List children; + /** Children of this group. */ + private final List children; - /** Is this group currently selected in the group list? */ - private boolean selected; + /** Is this group currently selected in the group list? */ + private boolean selected; - public UIWidgetGroup(String name) { - this.name = name; - this.selected = false; - this.children = new ArrayList<>(); - } + public UIWidgetGroup(String name) { + this.name = name; + this.selected = false; + this.children = new ArrayList<>(); + } - protected UIWidgetGroup(@NonNull Parcel in) { - this.name = in.readString(); - this.children = in.createTypedArrayList(UIWidget.CREATOR); - this.selected = in.readByte() != 0; - } + protected UIWidgetGroup(@NonNull Parcel in) { + this.name = in.readString(); + this.children = in.createTypedArrayList(UIWidget.CREATOR); + this.selected = in.readByte() != 0; + } - public static final Creator CREATOR = - new Creator() { + public static final Creator CREATOR = + new Creator() { - @NonNull - @Contract("_ -> new") - @Override - public UIWidgetGroup createFromParcel(Parcel in) { - return new UIWidgetGroup(in); - } - - @NonNull - @Contract(value = "_ -> new", pure = true) - @Override - public UIWidgetGroup[] newArray(int size) { - return new UIWidgetGroup[size]; - } - }; - - /** - * Get the name of this group. - * - * @return The name of this group. - */ - public String getName() { - return name; - } - - /** - * Get the children of this group. - * - * @return The children. - */ - @NonNull - public List getChildren() { - return children; - } - - public void setSelected(boolean selected) { - this.selected = selected; - } - - public boolean isSelected() { - return selected; - } - - /** - * Add this widget to this group. Does nothing if the child is already present. - * - * @param child The child to add. - * @return This instance for chained method calls. - */ - public UIWidgetGroup addChild(UIWidget child) { - if (children.contains(child)) { - return this; + @NonNull + @Contract("_ -> new") + @Override + public UIWidgetGroup createFromParcel(Parcel in) { + return new UIWidgetGroup(in); } - this.children.add(child); - return this; - } - /** - * Get the child at the given index. - * - * @param index The index of the child. - * @return The child at the index. - * @throws IndexOutOfBoundsException If the given index is not valid. - */ - public UIWidget getChild(int index) { - return children.get(index); - } + @NonNull + @Contract(value = "_ -> new", pure = true) + @Override + public UIWidgetGroup[] newArray(int size) { + return new UIWidgetGroup[size]; + } + }; - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - UIWidgetGroup group = (UIWidgetGroup) o; - return isSelected() == group.isSelected() - && getName().equals(group.getName()) - && getChildren().equals(group.getChildren()); - } + /** + * Get the name of this group. + * + * @return The name of this group. + */ + public String getName() { + return name; + } - @Override - public int hashCode() { - return Objects.hash(getName(), getChildren(), isSelected()); - } + /** + * Get the children of this group. + * + * @return The children. + */ + @NonNull + public List getChildren() { + return children; + } - @Override - public String getText() { - return getName(); - } + public void setSelected(boolean selected) { + this.selected = selected; + } - @Override - public int getIconResource() { - return -1; - } + public boolean isSelected() { + return selected; + } - @Override - public int describeContents() { - return 0; + /** + * Add this widget to this group. Does nothing if the child is already present. + * + * @param child The child to add. + * @return This instance for chained method calls. + */ + public UIWidgetGroup addChild(UIWidget child) { + if (children.contains(child)) { + return this; } + this.children.add(child); + return this; + } - @Override - public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeString(name); - dest.writeTypedList(children); - dest.writeByte((byte) (selected ? 1 : 0)); - } + /** + * Get the child at the given index. + * + * @param index The index of the child. + * @return The child at the index. + * @throws IndexOutOfBoundsException If the given index is not valid. + */ + public UIWidget getChild(int index) { + return children.get(index); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + UIWidgetGroup group = (UIWidgetGroup) o; + return isSelected() == group.isSelected() + && getName().equals(group.getName()) + && getChildren().equals(group.getChildren()); + } + + @Override + public int hashCode() { + return Objects.hash(getName(), getChildren(), isSelected()); + } + + @Override + public String getText() { + return getName(); + } + + @Override + public int getIconResource() { + return -1; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(name); + dest.writeTypedList(children); + dest.writeByte((byte) (selected ? 1 : 0)); + } } diff --git a/app/src/main/java/com/itsaky/androidide/models/XMLAttribute.java b/app/src/main/java/com/itsaky/androidide/models/XMLAttribute.java index 89fbc773b..c2163f4a1 100644 --- a/app/src/main/java/com/itsaky/androidide/models/XMLAttribute.java +++ b/app/src/main/java/com/itsaky/androidide/models/XMLAttribute.java @@ -39,144 +39,140 @@ import java.util.Objects; */ public class XMLAttribute extends UiAttribute implements Parcelable { - public static final Creator CREATOR = - new Creator<>() { - @NonNull - @Contract("_ -> new") - @Override - public XMLAttribute createFromParcel(Parcel in) { - return new XMLAttribute(in); - } - - @NonNull - @Contract(value = "_ -> new", pure = true) - @Override - public XMLAttribute[] newArray(int size) { - return new XMLAttribute[size]; - } - }; - public Attr attr; - /** Is this attribute applied to any view? */ - private boolean isApplied; - /** - * Format of this attribute. May be stored here so we don't have to look in {@link - * com.itsaky.attrinfo.AttrInfo AttrInfo} again and again. - */ - private int format = -1; - - public XMLAttribute(IAttribute attribute) { - this(attribute, true); - } - - public XMLAttribute(@NonNull IAttribute attribute, boolean isApplied) { - this( - attribute.getNamespace(), - attribute.getAttributeName(), - attribute.getValue(), - isApplied); - } - - public XMLAttribute(INamespace namespace, String name, String value, boolean isApplied) { - super(namespace, name, value); - this.isApplied = isApplied; - } - - private XMLAttribute(@NonNull Parcel in) { - this( - in.readParcelable(INamespace.class.getClassLoader()), - in.readString(), - in.readString(), - in.readByte() != 0); - this.attr = in.readParcelable(Attr.class.getClassLoader()); - this.format = in.readInt(); - } - - /** - * Create a new attribute instance with the android namespace. {@link #isApplied()} will return - * true by default. - * - * @param name The name of the attribute. - * @param value The value of the attribute. - * @return A new instance of this class with the provided data. - */ - @NonNull - @Contract("_, _ -> new") - public static XMLAttribute newAndroidAttribute(String name, String value) { - return new XMLAttribute(INamespace.ANDROID, name, value, true); - } - - /** - * Create a new attribute instance with the android namespace. The {@link #isApplied()} will - * return {@code false} by default. - * - * @param name The name of the attribute. - * @return The new attribute instance. - */ - @NonNull - @Contract("_ -> new") - public static XMLAttribute newAndroidAttribute(String name) { - return new XMLAttribute(INamespace.ANDROID, name, "", false); - } - - public int getFormat() { - return format; - } - - public void setFormat(int format) { - this.format = format; - } - - public int findFormat() { - - if (getFormat() > 0) { - return getFormat(); + public static final Creator CREATOR = + new Creator<>() { + @NonNull + @Contract("_ -> new") + @Override + public XMLAttribute createFromParcel(Parcel in) { + return new XMLAttribute(in); } - final var attrs = StudioApp.getInstance().attrInfo(); - this.attr = attrs.getAttribute(getAttributeName()); - if (this.attr != null) { - this.format = this.attr.format; + @NonNull + @Contract(value = "_ -> new", pure = true) + @Override + public XMLAttribute[] newArray(int size) { + return new XMLAttribute[size]; } + }; + public Attr attr; + /** Is this attribute applied to any view? */ + private boolean isApplied; + /** + * Format of this attribute. May be stored here so we don't have to look in {@link + * com.itsaky.attrinfo.AttrInfo AttrInfo} again and again. + */ + private int format = -1; - return getFormat(); + public XMLAttribute(IAttribute attribute) { + this(attribute, true); + } + + public XMLAttribute(@NonNull IAttribute attribute, boolean isApplied) { + this(attribute.getNamespace(), attribute.getAttributeName(), attribute.getValue(), isApplied); + } + + public XMLAttribute(INamespace namespace, String name, String value, boolean isApplied) { + super(namespace, name, value); + this.isApplied = isApplied; + } + + private XMLAttribute(@NonNull Parcel in) { + this( + in.readParcelable(INamespace.class.getClassLoader()), + in.readString(), + in.readString(), + in.readByte() != 0); + this.attr = in.readParcelable(Attr.class.getClassLoader()); + this.format = in.readInt(); + } + + /** + * Create a new attribute instance with the android namespace. {@link #isApplied()} will return + * true by default. + * + * @param name The name of the attribute. + * @param value The value of the attribute. + * @return A new instance of this class with the provided data. + */ + @NonNull + @Contract("_, _ -> new") + public static XMLAttribute newAndroidAttribute(String name, String value) { + return new XMLAttribute(INamespace.ANDROID, name, value, true); + } + + /** + * Create a new attribute instance with the android namespace. The {@link #isApplied()} will + * return {@code false} by default. + * + * @param name The name of the attribute. + * @return The new attribute instance. + */ + @NonNull + @Contract("_ -> new") + public static XMLAttribute newAndroidAttribute(String name) { + return new XMLAttribute(INamespace.ANDROID, name, "", false); + } + + public int getFormat() { + return format; + } + + public void setFormat(int format) { + this.format = format; + } + + public int findFormat() { + + if (getFormat() > 0) { + return getFormat(); } - public boolean hasFormat(int format) { - return (this.format & format) != 0; + final var attrs = StudioApp.getInstance().attrInfo(); + this.attr = attrs.getAttribute(getAttributeName()); + if (this.attr != null) { + this.format = this.attr.format; } - public Attr getAttr() { - return attr; - } + return getFormat(); + } - public void setAttr(Attr attr) { - Objects.requireNonNull(attr); - this.attr = attr; - setFormat(attr.format); - } + public boolean hasFormat(int format) { + return (this.format & format) != 0; + } - @Override - public void apply(String value) { - super.apply(value); - isApplied = true; - } + public Attr getAttr() { + return attr; + } - public boolean isApplied() { - return isApplied; - } + public void setAttr(Attr attr) { + Objects.requireNonNull(attr); + this.attr = attr; + setFormat(attr.format); + } - @Override - public int describeContents() { - return 0; - } + @Override + public void apply(String value) { + super.apply(value); + isApplied = true; + } - @Override - public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeParcelable(getNamespace(), flags); - dest.writeString(getAttributeName()); - dest.writeString(getValue()); - dest.writeByte((byte) (isApplied ? 1 : 0)); - dest.writeParcelable(getAttr(), flags); - dest.writeInt(this.format); - } + public boolean isApplied() { + return isApplied; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeParcelable(getNamespace(), flags); + dest.writeString(getAttributeName()); + dest.writeString(getValue()); + dest.writeByte((byte) (isApplied ? 1 : 0)); + dest.writeParcelable(getAttr(), flags); + dest.writeInt(this.format); + } } diff --git a/app/src/main/java/com/itsaky/androidide/projects/ProjectResourceTable.java b/app/src/main/java/com/itsaky/androidide/projects/ProjectResourceTable.java index 7781b1bd2..de6e7e8cd 100644 --- a/app/src/main/java/com/itsaky/androidide/projects/ProjectResourceTable.java +++ b/app/src/main/java/com/itsaky/androidide/projects/ProjectResourceTable.java @@ -32,281 +32,281 @@ import java.util.Locale; public class ProjectResourceTable implements IResourceTable { - private static final ILogger LOG = ILogger.newInstance("ProjectResourceFinder"); - private File resDir; - private File color; - private File layout; - private File[] drawables; - private File[] mipmaps; + private static final ILogger LOG = ILogger.newInstance("ProjectResourceFinder"); + private File resDir; + private File color; + private File layout; + private File[] drawables; + private File[] mipmaps; - @Override - public File findDrawable(@NonNull String name) { - for (File drawable : drawables) { - final File file = findFileWithName(drawable.listFiles(), name); - if (file != null) { - return file; - } - } + @Override + public File findDrawable(@NonNull String name) { + for (File drawable : drawables) { + final File file = findFileWithName(drawable.listFiles(), name); + if (file != null) { + return file; + } + } - return null; + return null; + } + + @Override + public File findLayout(@NonNull String name) { + return findFileWithName(this.layout.listFiles(), name); + } + + @Override + public String findString(@NonNull String name) { + final var table = ValuesTableFactory.getTable(resDir); + + if (table == null) { + return name; + } + + final var resource = table.findString(name); + if (resource == null) { + return name; + } + + final var value = resource.getValue(); + if (value.startsWith("@string/")) { + var ref = value.substring("@string/".length()); + if (name.equals(ref)) { + return name; + } + } + + return value; + } + + @Override + public String findBoolean(@NonNull String name) { + final var table = ValuesTableFactory.getTable(resDir); + + if (table == null) { + return name; + } + + final var resource = table.findBoolean(name); + if (resource == null) { + return name; + } + + final var val = resource.getValue(); + if (val.startsWith("@bool/")) { + final var ref = val.substring("@bool/".length()); + if (ref.equals(name)) { + // recursive reference + return name; + } + } + + return val; + } + + @Override + public String findInteger(@NonNull String name) { + final var table = ValuesTableFactory.getTable(resDir); + + if (table == null) { + return name; + } + + final var resource = table.findInteger(name); + if (resource == null) { + return name; + } + + final var val = resource.getValue(); + if (val.startsWith("@integer/")) { + final var ref = val.substring("@integer/".length()); + if (ref.equals(name)) { + // recursive reference + return name; + } + } + + return val; + } + + @Override + public String findColor(@NonNull String name) { + final var table = ValuesTableFactory.getTable(resDir); + + if (table == null) { + return name; + } + + final var resource = table.findColor(name); + if (resource == null) { + return name; + } + + final var value = resource.getValue(); + if (value.startsWith("@color/")) { + var ref = value.substring("@color/".length()); + if (name.equals(ref)) { + return name; + } + } + + return value; + } + + @Override + public String[] findArray(@NonNull String name) { + final var table = ValuesTableFactory.getTable(resDir); + if (table == null) { + return new String[] {name}; + } + + final var resource = table.findArray(name); + if (resource == null) { + return new String[] {name}; + } + + return resource.getValues(); + } + + @Override + public String findDimension(@NonNull String name) { + final var table = ValuesTableFactory.getTable(resDir); + + if (table == null) { + return name; + } + + final var resource = table.findDimension(name); + if (resource == null) { + return name; + } + + final var value = resource.getValue(); + if (value.startsWith("@dimen/")) { + var ref = value.substring("@dimen/".length()); + if (name.equals(ref)) { + return name; + } + } + + return value; + } + + @NonNull + @Override + public Collection listResourceNames(@Nullable String type) { + if (type == null) { + return listAllResourceNames(); + } else { + final var set = new HashSet(); + switch (type) { + case "drawable": + for (var folder : this.drawables) { + listAndAddTo(set, folder, type); + } + break; + case "layout": + listAndAddTo(set, this.layout, type); + break; + case "color": + listAndAddTo(set, this.color, type); + break; + case "mipmap": + for (var folder : this.mipmaps) { + listAndAddTo(set, folder, type); + } + break; + } + + return set; + } + } + + private void listAndAddTo(HashSet set, @NonNull File dir, String type) { + final var files = dir.listFiles(); + if (files == null) { + return; + } + + for (var file : files) { + var name = file.getName(); + if (!name.contains(".")) { + continue; + } + + name = name.substring(0, name.lastIndexOf('.')); + name = String.format(Locale.ROOT, "@%s/%s", type, name); + set.add(name); + } + } + + @NonNull + private Collection listAllResourceNames() { + final var set = new HashSet<>(listResourceNames("color")); + set.addAll(listResourceNames("drawable")); + set.addAll(listResourceNames("mipmap")); + set.addAll(listResourceNames("layout")); + return set; + } + + @Override + public void setInflatingFile(@NonNull File file) { + if (file.getParentFile() == null) { + throw new IllegalArgumentException("Invalid inflating file"); + } + + setupDirectories( + file // layout file + .getParentFile() // layout dir + .getParentFile()); // res dir + } + + private void setupDirectories(File resDir) { + + if (resDir == null) { + LOG.error("Null resource directory passed to resource finder. Ignoring."); + return; + } + + this.resDir = resDir; + this.color = new File(resDir, "color"); + this.layout = new File(resDir, "layout"); + + final File[] drawables = resDir.listFiles(new NameStartsWith("drawable")); + this.drawables = drawables == null ? new File[0] : drawables; + + final File[] mipmaps = resDir.listFiles(new NameStartsWith("mipmap")); + this.mipmaps = mipmaps == null ? new File[0] : mipmaps; + } + + private File findFileWithName(File[] files, String name) { + if (files == null || name == null) { + return null; + } + + for (File file : files) { + var simpleName = file.getName(); + + if (simpleName.startsWith(name)) { + return file; + } + } + + return null; + } + + private static class NameStartsWith implements FileFilter { + + private final String name; + + public NameStartsWith(String name) { + this.name = name; } @Override - public File findLayout(@NonNull String name) { - return findFileWithName(this.layout.listFiles(), name); - } - - @Override - public String findString(@NonNull String name) { - final var table = ValuesTableFactory.getTable(resDir); - - if (table == null) { - return name; - } - - final var resource = table.findString(name); - if (resource == null) { - return name; - } - - final var value = resource.getValue(); - if (value.startsWith("@string/")) { - var ref = value.substring("@string/".length()); - if (name.equals(ref)) { - return name; - } - } - - return value; - } - - @Override - public String findBoolean(@NonNull String name) { - final var table = ValuesTableFactory.getTable(resDir); - - if (table == null) { - return name; - } - - final var resource = table.findBoolean(name); - if (resource == null) { - return name; - } - - final var val = resource.getValue(); - if (val.startsWith("@bool/")) { - final var ref = val.substring("@bool/".length()); - if (ref.equals(name)) { - // recursive reference - return name; - } - } - - return val; - } - - @Override - public String findInteger(@NonNull String name) { - final var table = ValuesTableFactory.getTable(resDir); - - if (table == null) { - return name; - } - - final var resource = table.findInteger(name); - if (resource == null) { - return name; - } - - final var val = resource.getValue(); - if (val.startsWith("@integer/")) { - final var ref = val.substring("@integer/".length()); - if (ref.equals(name)) { - // recursive reference - return name; - } - } - - return val; - } - - @Override - public String findColor(@NonNull String name) { - final var table = ValuesTableFactory.getTable(resDir); - - if (table == null) { - return name; - } - - final var resource = table.findColor(name); - if (resource == null) { - return name; - } - - final var value = resource.getValue(); - if (value.startsWith("@color/")) { - var ref = value.substring("@color/".length()); - if (name.equals(ref)) { - return name; - } - } - - return value; - } - - @Override - public String[] findArray(@NonNull String name) { - final var table = ValuesTableFactory.getTable(resDir); - if (table == null) { - return new String[] {name}; - } - - final var resource = table.findArray(name); - if (resource == null) { - return new String[] {name}; - } - - return resource.getValues(); - } - - @Override - public String findDimension(@NonNull String name) { - final var table = ValuesTableFactory.getTable(resDir); - - if (table == null) { - return name; - } - - final var resource = table.findDimension(name); - if (resource == null) { - return name; - } - - final var value = resource.getValue(); - if (value.startsWith("@dimen/")) { - var ref = value.substring("@dimen/".length()); - if (name.equals(ref)) { - return name; - } - } - - return value; - } - - @NonNull - @Override - public Collection listResourceNames(@Nullable String type) { - if (type == null) { - return listAllResourceNames(); - } else { - final var set = new HashSet(); - switch (type) { - case "drawable": - for (var folder : this.drawables) { - listAndAddTo(set, folder, type); - } - break; - case "layout": - listAndAddTo(set, this.layout, type); - break; - case "color": - listAndAddTo(set, this.color, type); - break; - case "mipmap": - for (var folder : this.mipmaps) { - listAndAddTo(set, folder, type); - } - break; - } - - return set; - } - } - - private void listAndAddTo(HashSet set, @NonNull File dir, String type) { - final var files = dir.listFiles(); - if (files == null) { - return; - } - - for (var file : files) { - var name = file.getName(); - if (!name.contains(".")) { - continue; - } - - name = name.substring(0, name.lastIndexOf('.')); - name = String.format(Locale.ROOT, "@%s/%s", type, name); - set.add(name); - } - } - - @NonNull - private Collection listAllResourceNames() { - final var set = new HashSet<>(listResourceNames("color")); - set.addAll(listResourceNames("drawable")); - set.addAll(listResourceNames("mipmap")); - set.addAll(listResourceNames("layout")); - return set; - } - - @Override - public void setInflatingFile(@NonNull File file) { - if (file.getParentFile() == null) { - throw new IllegalArgumentException("Invalid inflating file"); - } - - setupDirectories( - file // layout file - .getParentFile() // layout dir - .getParentFile()); // res dir - } - - private void setupDirectories(File resDir) { - - if (resDir == null) { - LOG.error("Null resource directory passed to resource finder. Ignoring."); - return; - } - - this.resDir = resDir; - this.color = new File(resDir, "color"); - this.layout = new File(resDir, "layout"); - - final File[] drawables = resDir.listFiles(new NameStartsWith("drawable")); - this.drawables = drawables == null ? new File[0] : drawables; - - final File[] mipmaps = resDir.listFiles(new NameStartsWith("mipmap")); - this.mipmaps = mipmaps == null ? new File[0] : mipmaps; - } - - private File findFileWithName(File[] files, String name) { - if (files == null || name == null) { - return null; - } - - for (File file : files) { - var simpleName = file.getName(); - - if (simpleName.startsWith(name)) { - return file; - } - } - - return null; - } - - private static class NameStartsWith implements FileFilter { - - private final String name; - - public NameStartsWith(String name) { - this.name = name; - } - - @Override - public boolean accept(@NonNull File file) { - return file.getName().startsWith(name); - } + public boolean accept(@NonNull File file) { + return file.getName().startsWith(name); } + } } diff --git a/app/src/main/java/com/itsaky/androidide/services/GradleBuildService.java b/app/src/main/java/com/itsaky/androidide/services/GradleBuildService.java index 9f6965e92..c0179e84c 100644 --- a/app/src/main/java/com/itsaky/androidide/services/GradleBuildService.java +++ b/app/src/main/java/com/itsaky/androidide/services/GradleBuildService.java @@ -79,491 +79,489 @@ import java.util.concurrent.CompletionException; */ public class GradleBuildService extends Service implements BuildService, IToolingApiClient { - private static final ILogger LOG = newInstance("GradleBuildService"); - private static final int NOTIFICATION_ID = R.string.app_name; - private static final ILogger SERVER_System_err = newInstance("ToolingApiErrorStream"); - private final ILogger SERVER_LOGGER = newInstance("ToolingApiServer"); - private final IBinder mBinder = new GradleServiceBinder(); - private boolean isToolingServerStarted = false; - private boolean isBuildInProgress = false; - private Thread toolingServerThread; - private NotificationManager notificationManager; - private IToolingApiServer server; - private EventListener eventListener; + private static final ILogger LOG = newInstance("GradleBuildService"); + private static final int NOTIFICATION_ID = R.string.app_name; + private static final ILogger SERVER_System_err = newInstance("ToolingApiErrorStream"); + private final ILogger SERVER_LOGGER = newInstance("ToolingApiServer"); + private final IBinder mBinder = new GradleServiceBinder(); + private boolean isToolingServerStarted = false; + private boolean isBuildInProgress = false; + private Thread toolingServerThread; + private NotificationManager notificationManager; + private IToolingApiServer server; + private EventListener eventListener; - @Override - public void onCreate() { - notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - showNotification(); + @Override + public void onCreate() { + notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + showNotification(); + } + + private void showNotification() { + final var ticker = getString(R.string.title_gradle_service_notification_ticker); + final var title = getString(R.string.title_gradle_service_notification); + final var message = getString(R.string.msg_gradle_service_notification); + + final var launch = getPackageManager().getLaunchIntentForPackage(BuildConfig.APPLICATION_ID); + + final var intent = + PendingIntent.getActivity(this, 0, launch, PendingIntent.FLAG_UPDATE_CURRENT); + final var notification = + new Notification.Builder(this, BaseApplication.NOTIFICATION_GRADLE_BUILD_SERVICE) + .setSmallIcon(R.mipmap.ic_launcher_round) + .setTicker(ticker) + .setWhen(System.currentTimeMillis()) + .setContentTitle(title) + .setContentText(message) + .setContentIntent(intent) + .build(); + + LOG.info("Showing notification to user..."); + startForeground(NOTIFICATION_ID, notification); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + // No point in restarting the service if it really gets killed. + return START_NOT_STICKY; + } + + @Override + public void onDestroy() { + LOG.info("Service is being destroyed.", "Dismissing the shown notification..."); + notificationManager.cancel(NOTIFICATION_ID); + + if (server != null) { + server.cancelCurrentBuild(); + server.shutdown(); } - private void showNotification() { - final var ticker = getString(R.string.title_gradle_service_notification_ticker); - final var title = getString(R.string.title_gradle_service_notification); - final var message = getString(R.string.msg_gradle_service_notification); - - final var launch = - getPackageManager().getLaunchIntentForPackage(BuildConfig.APPLICATION_ID); - - final var intent = - PendingIntent.getActivity(this, 0, launch, PendingIntent.FLAG_UPDATE_CURRENT); - final var notification = - new Notification.Builder(this, BaseApplication.NOTIFICATION_GRADLE_BUILD_SERVICE) - .setSmallIcon(R.mipmap.ic_launcher_round) - .setTicker(ticker) - .setWhen(System.currentTimeMillis()) - .setContentTitle(title) - .setContentText(message) - .setContentIntent(intent) - .build(); - - LOG.info("Showing notification to user..."); - startForeground(NOTIFICATION_ID, notification); + if (toolingServerThread != null) { + toolingServerThread.interrupt(); } - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - // No point in restarting the service if it really gets killed. - return START_NOT_STICKY; + isToolingServerStarted = false; + } + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + @Override + public void logMessage(@NonNull LogLine line) { + SERVER_LOGGER.log(ILogger.priority(line.priorityChar), line.formattedTagAndMessage()); + } + + @Override + public void logOutput(@NonNull String line) { + if (eventListener != null) { + eventListener.onOutput(line); + } + } + + @Override + public void prepareBuild() { + if (eventListener != null) { + eventListener.prepareBuild(); + } + } + + @Override + public void onBuildSuccessful(@NonNull BuildResult result) { + if (eventListener != null) { + eventListener.onBuildSuccessful(result.getTasks()); + } + } + + @Override + public void onBuildFailed(@NonNull BuildResult result) { + if (eventListener != null) { + eventListener.onBuildFailed(result.getTasks()); + } + } + + @Override + public void onProgressEvent(@NonNull ProgressEvent event) { + if (eventListener != null) { + eventListener.onProgressEvent(event); + } + } + + @NonNull + @Override + public CompletableFuture> getBuildArguments() { + final PreferenceManager prefs = StudioApp.getInstance().getPrefManager(); + final var extraArgs = new ArrayList(); + + extraArgs.add("--init-script"); + extraArgs.add(Environment.INIT_SCRIPT.getAbsolutePath()); + + // Override AAPT2 binary + // The one downloaded from Maven is not built for Android + extraArgs.add("-Pandroid.aapt2FromMavenOverride=" + Environment.AAPT2.getAbsolutePath()); + + if (prefs.isStackTraceEnabled()) { + extraArgs.add("--stacktrace"); + } + if (prefs.isGradleInfoEnabled()) { + extraArgs.add("--info"); + } + if (prefs.isGradleDebugEnabled()) { + extraArgs.add("--debug"); + } + if (prefs.isGradleScanEnabled()) { + extraArgs.add("--scan"); + } + if (prefs.isGradleWarningEnabled()) { + extraArgs.add("--warning-mode"); + extraArgs.add("all"); + } + if (prefs.isGradleBuildCacheEnabled()) { + extraArgs.add("--build-cache"); + } + if (prefs.isGradleOfflineModeEnabled()) { + extraArgs.add("--offline"); } - @Override - public void onDestroy() { - LOG.info("Service is being destroyed.", "Dismissing the shown notification..."); - notificationManager.cancel(NOTIFICATION_ID); + return CompletableFuture.completedFuture(extraArgs); + } - if (server != null) { - server.cancelCurrentBuild(); - server.shutdown(); - } + @NonNull + @Override + public CompletableFuture checkGradleWrapperAvailability() { + return isGradleWrapperAvailable() + ? CompletableFuture.completedFuture(new GradleWrapperCheckResult(true)) + : installWrapper(); + } - if (toolingServerThread != null) { - toolingServerThread.interrupt(); - } - - isToolingServerStarted = false; + public boolean isGradleWrapperAvailable() { + final var projectDir = ProjectManager.INSTANCE.getProjectDirPath(); + if (projectDir == null || TextUtils.isEmpty(projectDir)) { + return false; } - @Nullable - @Override - public IBinder onBind(Intent intent) { - return mBinder; + final var projectRoot = Objects.requireNonNull(ProjectManager.INSTANCE.getProjectDir()); + if (!projectRoot.exists()) { + return false; } - @Override - public void logMessage(@NonNull LogLine line) { - SERVER_LOGGER.log(ILogger.priority(line.priorityChar), line.formattedTagAndMessage()); + final File gradlew = new File(projectRoot, "gradlew"); + final File gradleWrapperJar = new File(projectRoot, "gradle/wrapper/gradle-wrapper.jar"); + final File gradleWrapperProps = + new File(projectRoot, "gradle/wrapper/gradle-wrapper.properties"); + + return gradlew.exists() && gradleWrapperJar.exists() && gradleWrapperProps.exists(); + } + + private CompletableFuture installWrapper() { + if (eventListener != null) { + eventListener.onOutput("-------------------- NOTE --------------------"); + eventListener.onOutput(getString(R.string.msg_installing_gradlew)); + eventListener.onOutput("----------------------------------------------"); } - @Override - public void logOutput(@NonNull String line) { - if (eventListener != null) { - eventListener.onOutput(line); - } - } - - @Override - public void prepareBuild() { - if (eventListener != null) { - eventListener.prepareBuild(); - } - } - - @Override - public void onBuildSuccessful(@NonNull BuildResult result) { - if (eventListener != null) { - eventListener.onBuildSuccessful(result.getTasks()); - } - } - - @Override - public void onBuildFailed(@NonNull BuildResult result) { - if (eventListener != null) { - eventListener.onBuildFailed(result.getTasks()); - } - } - - @Override - public void onProgressEvent(@NonNull ProgressEvent event) { - if (eventListener != null) { - eventListener.onProgressEvent(event); - } - } - - @NonNull - @Override - public CompletableFuture> getBuildArguments() { - final PreferenceManager prefs = StudioApp.getInstance().getPrefManager(); - final var extraArgs = new ArrayList(); - - extraArgs.add("--init-script"); - extraArgs.add(Environment.INIT_SCRIPT.getAbsolutePath()); - - // Override AAPT2 binary - // The one downloaded from Maven is not built for Android - extraArgs.add("-Pandroid.aapt2FromMavenOverride=" + Environment.AAPT2.getAbsolutePath()); - - if (prefs.isStackTraceEnabled()) { - extraArgs.add("--stacktrace"); - } - if (prefs.isGradleInfoEnabled()) { - extraArgs.add("--info"); - } - if (prefs.isGradleDebugEnabled()) { - extraArgs.add("--debug"); - } - if (prefs.isGradleScanEnabled()) { - extraArgs.add("--scan"); - } - if (prefs.isGradleWarningEnabled()) { - extraArgs.add("--warning-mode"); - extraArgs.add("all"); - } - if (prefs.isGradleBuildCacheEnabled()) { - extraArgs.add("--build-cache"); - } - if (prefs.isGradleOfflineModeEnabled()) { - extraArgs.add("--offline"); - } - - return CompletableFuture.completedFuture(extraArgs); - } - - @NonNull - @Override - public CompletableFuture checkGradleWrapperAvailability() { - return isGradleWrapperAvailable() - ? CompletableFuture.completedFuture(new GradleWrapperCheckResult(true)) - : installWrapper(); - } - - public boolean isGradleWrapperAvailable() { - final var projectDir = ProjectManager.INSTANCE.getProjectDirPath(); - if (projectDir == null || TextUtils.isEmpty(projectDir)) { - return false; - } - - final var projectRoot = Objects.requireNonNull(ProjectManager.INSTANCE.getProjectDir()); - if (!projectRoot.exists()) { - return false; - } - - final File gradlew = new File(projectRoot, "gradlew"); - final File gradleWrapperJar = new File(projectRoot, "gradle/wrapper/gradle-wrapper.jar"); - final File gradleWrapperProps = - new File(projectRoot, "gradle/wrapper/gradle-wrapper.properties"); - - return gradlew.exists() && gradleWrapperJar.exists() && gradleWrapperProps.exists(); - } - - private CompletableFuture installWrapper() { - if (eventListener != null) { - eventListener.onOutput("-------------------- NOTE --------------------"); - eventListener.onOutput(getString(R.string.msg_installing_gradlew)); - eventListener.onOutput("----------------------------------------------"); - } - - return CompletableFuture.supplyAsync( - () -> { - boolean isAvailable = false; - final File extracted = new File(Environment.TMP_DIR, "gradle-wrapper.zip"); - if (ResourceUtils.copyFileFromAssets( - getCommonAsset("gradle-wrapper.zip"), extracted.getAbsolutePath())) { - try { - final List files = - ZipUtils.unzipFile( - extracted, ProjectManager.INSTANCE.getProjectDir()); - if (files != null && !files.isEmpty()) { - isAvailable = true; - } - } catch (IOException e) { - LOG.error("An error occurred while extracting Gradle wrapper", e); - } - } else { - LOG.error("Unable to extract gradle-plugin.zip from IDE resources."); - } - - return new GradleWrapperCheckResult(isAvailable); - }); - } - - @NonNull - @Override - public CompletableFuture initializeProject(@NonNull String rootDir) { - checkServerStarted(); - ensureTmpdir(); - - if (isBuildInProgress) { - logBuildInProgress(); - return failedFutureForBuildInProgress(); - } - - isBuildInProgress = true; - return server.initialize(new InitializeProjectMessage(rootDir)) - .thenApply(this::markBuildAsFinished); - } - - @NonNull - @Override - public CompletableFuture executeTasks(@NonNull String... tasks) { - checkServerStarted(); - ensureTmpdir(); - - if (isBuildInProgress) { - logBuildInProgress(); - return failedFutureForBuildInProgress(); - } - - isBuildInProgress = true; - return server.executeTasks(new TaskExecutionMessage(":", Arrays.asList(tasks))) - .thenApply(this::markBuildAsFinished); - } - - @NonNull - @Override - public CompletableFuture executeProjectTasks( - @NonNull String projectPath, @NonNull String... tasks) { - checkServerStarted(); - ensureTmpdir(); - - if (isBuildInProgress) { - logBuildInProgress(); - return failedFutureForBuildInProgress(); - } - - isBuildInProgress = true; - return server.executeTasks(new TaskExecutionMessage(projectPath, Arrays.asList(tasks))) - .thenApply(this::markBuildAsFinished); - } - - @NonNull - @Override - public CompletableFuture cancelCurrentBuild() { - return server.cancelCurrentBuild(); - } - - private void checkServerStarted() { - if (!isToolingServerStarted) { - throw new IllegalStateException("Tooling API server has not been started"); - } - } - - private void ensureTmpdir() { - Environment.mkdirIfNotExits(Environment.TMP_DIR); - } - - private void logBuildInProgress() { - LOG.warn("A build is already in progress!"); - } - - @NonNull - private CompletableFuture failedFutureForBuildInProgress() { - final var future = new CompletableFuture(); - future.completeExceptionally( - new CompletionException(new IllegalStateException("A build is already running!"))); - return future; - } - - protected T markBuildAsFinished(T t) { - isBuildInProgress = false; - return t; - } - - public boolean isBuildInProgress() { - return isBuildInProgress; - } - - public void startToolingServer(@Nullable OnServerStartListener listener) { - if (toolingServerThread != null && toolingServerThread.isAlive()) { - throw new IllegalStateException("Tooling API server is already started!"); - } - - toolingServerThread = new Thread(new ToolingServerRunner(listener)); - toolingServerThread.start(); - } - - public GradleBuildService setEventListener(EventListener eventListener) { - this.eventListener = wrap(eventListener); - return this; - } - - private EventListener wrap(EventListener listener) { - if (listener == null) { - return null; - } - - return new EventListener() { - @Override - public void prepareBuild() { - ThreadUtils.runOnUiThread(listener::prepareBuild); - } - - @Override - public void onBuildSuccessful(@NonNull List tasks) { - ThreadUtils.runOnUiThread(() -> listener.onBuildSuccessful(tasks)); - } - - @Override - public void onProgressEvent(@NonNull ProgressEvent event) { - ThreadUtils.runOnUiThread(() -> listener.onProgressEvent(event)); - } - - @Override - public void onBuildFailed(@NonNull List tasks) { - ThreadUtils.runOnUiThread(() -> listener.onBuildFailed(tasks)); - } - - @Override - public void onOutput(String line) { - ThreadUtils.runOnUiThread(() -> listener.onOutput(line)); - } - }; - } - - protected void onServerExited(int exitCode) { - LOG.warn("Tooling API process terminated with exit code:", exitCode); - stopForeground(true); - } - - private void startServerOutputReader(InputStream err) { - new Thread(new InputStreamLineReader(err, this::onServerOutput)).start(); - } - - protected void onServerOutput(String line) { - SERVER_System_err.error(line); - } - - public class GradleServiceBinder extends Binder { - public GradleBuildService getService() { - return GradleBuildService.this; - } - } - - private class ToolingServerRunner implements Runnable { - - @Nullable private final OnServerStartListener listener; - - public ToolingServerRunner(@Nullable OnServerStartListener listener) { - this.listener = listener; - } - - @Override - public void run() { + return CompletableFuture.supplyAsync( + () -> { + boolean isAvailable = false; + final File extracted = new File(Environment.TMP_DIR, "gradle-wrapper.zip"); + if (ResourceUtils.copyFileFromAssets( + getCommonAsset("gradle-wrapper.zip"), extracted.getAbsolutePath())) { try { - LOG.info("Starting tooling API server..."); - final var serverStreams = new ProcessStreamsHolder(); - final var executor = new CommonProcessExecutor(); - executor.execAsync( - serverStreams, - GradleBuildService.this::onServerExited, - false, - - // The 'java' binary executable - Environment.JAVA.getAbsolutePath(), - - // Allow reflective access to private members of classes in the following - // packages: - // - java.lang - // - java.io - // - java.util - // - // If any of the model classes in 'tooling-api-model' module send/receive - // objects from the JDK, their package name must be declared here with - // '--add-opens' to prevent InaccessibleObjectException. - // For example, some of the model classes has members of type java.io.File. - // When sending/receiving these type of objects using LSP4J, members of - // these objects are reflectively accessed by Gson. If we do no specify - // '--add-opens' for 'java.io' (for java.io.File) package, JVM will throw an - // InaccessibleObjectException. - "--add-opens", - "java.base/java.lang=ALL-UNNAMED", - "--add-opens", - "java.base/java.util=ALL-UNNAMED", - "--add-opens", - "java.base/java.io=ALL-UNNAMED", - - // The JAR file to run - "-jar", - Environment.TOOLING_API_JAR.getAbsolutePath()); - - final var launcher = - ToolingApiLauncher.newClientLauncher( - GradleBuildService.this, serverStreams.in, serverStreams.out); - final var future = launcher.startListening(); - - GradleBuildService.this.startServerOutputReader(serverStreams.err); - GradleBuildService.this.server = (IToolingApiServer) launcher.getRemoteProxy(); - GradleBuildService.this.isToolingServerStarted = true; - - ProjectManager.INSTANCE.setRootProject((IProject) launcher.getRemoteProxy()); - - if (listener != null) { - listener.onServerStarted(); - } - - // Wait(block) until the process terminates - try { - future.get(); - } catch (Throwable err) { - if (!(err instanceof CancellationException) - && !(err instanceof InterruptedException)) { - LOG.error( - "An error occurred while waiting for tooling API server to terminate", - err); - } - } - - } catch (Throwable e) { - LOG.error("Unable to start tooling API server", e); + final List files = + ZipUtils.unzipFile(extracted, ProjectManager.INSTANCE.getProjectDir()); + if (files != null && !files.isEmpty()) { + isAvailable = true; + } + } catch (IOException e) { + LOG.error("An error occurred while extracting Gradle wrapper", e); } + } else { + LOG.error("Unable to extract gradle-plugin.zip from IDE resources."); + } + + return new GradleWrapperCheckResult(isAvailable); + }); + } + + @NonNull + @Override + public CompletableFuture initializeProject(@NonNull String rootDir) { + checkServerStarted(); + ensureTmpdir(); + + if (isBuildInProgress) { + logBuildInProgress(); + return failedFutureForBuildInProgress(); + } + + isBuildInProgress = true; + return server + .initialize(new InitializeProjectMessage(rootDir)) + .thenApply(this::markBuildAsFinished); + } + + @NonNull + @Override + public CompletableFuture executeTasks(@NonNull String... tasks) { + checkServerStarted(); + ensureTmpdir(); + + if (isBuildInProgress) { + logBuildInProgress(); + return failedFutureForBuildInProgress(); + } + + isBuildInProgress = true; + return server + .executeTasks(new TaskExecutionMessage(":", Arrays.asList(tasks))) + .thenApply(this::markBuildAsFinished); + } + + @NonNull + @Override + public CompletableFuture executeProjectTasks( + @NonNull String projectPath, @NonNull String... tasks) { + checkServerStarted(); + ensureTmpdir(); + + if (isBuildInProgress) { + logBuildInProgress(); + return failedFutureForBuildInProgress(); + } + + isBuildInProgress = true; + return server + .executeTasks(new TaskExecutionMessage(projectPath, Arrays.asList(tasks))) + .thenApply(this::markBuildAsFinished); + } + + @NonNull + @Override + public CompletableFuture cancelCurrentBuild() { + return server.cancelCurrentBuild(); + } + + private void checkServerStarted() { + if (!isToolingServerStarted) { + throw new IllegalStateException("Tooling API server has not been started"); + } + } + + private void ensureTmpdir() { + Environment.mkdirIfNotExits(Environment.TMP_DIR); + } + + private void logBuildInProgress() { + LOG.warn("A build is already in progress!"); + } + + @NonNull + private CompletableFuture failedFutureForBuildInProgress() { + final var future = new CompletableFuture(); + future.completeExceptionally( + new CompletionException(new IllegalStateException("A build is already running!"))); + return future; + } + + protected T markBuildAsFinished(T t) { + isBuildInProgress = false; + return t; + } + + public boolean isBuildInProgress() { + return isBuildInProgress; + } + + public void startToolingServer(@Nullable OnServerStartListener listener) { + if (toolingServerThread != null && toolingServerThread.isAlive()) { + throw new IllegalStateException("Tooling API server is already started!"); + } + + toolingServerThread = new Thread(new ToolingServerRunner(listener)); + toolingServerThread.start(); + } + + public GradleBuildService setEventListener(EventListener eventListener) { + this.eventListener = wrap(eventListener); + return this; + } + + private EventListener wrap(EventListener listener) { + if (listener == null) { + return null; + } + + return new EventListener() { + @Override + public void prepareBuild() { + ThreadUtils.runOnUiThread(listener::prepareBuild); + } + + @Override + public void onBuildSuccessful(@NonNull List tasks) { + ThreadUtils.runOnUiThread(() -> listener.onBuildSuccessful(tasks)); + } + + @Override + public void onProgressEvent(@NonNull ProgressEvent event) { + ThreadUtils.runOnUiThread(() -> listener.onProgressEvent(event)); + } + + @Override + public void onBuildFailed(@NonNull List tasks) { + ThreadUtils.runOnUiThread(() -> listener.onBuildFailed(tasks)); + } + + @Override + public void onOutput(String line) { + ThreadUtils.runOnUiThread(() -> listener.onOutput(line)); + } + }; + } + + protected void onServerExited(int exitCode) { + LOG.warn("Tooling API process terminated with exit code:", exitCode); + stopForeground(true); + } + + private void startServerOutputReader(InputStream err) { + new Thread(new InputStreamLineReader(err, this::onServerOutput)).start(); + } + + protected void onServerOutput(String line) { + SERVER_System_err.error(line); + } + + public class GradleServiceBinder extends Binder { + public GradleBuildService getService() { + return GradleBuildService.this; + } + } + + private class ToolingServerRunner implements Runnable { + + @Nullable private final OnServerStartListener listener; + + public ToolingServerRunner(@Nullable OnServerStartListener listener) { + this.listener = listener; + } + + @Override + public void run() { + try { + LOG.info("Starting tooling API server..."); + final var serverStreams = new ProcessStreamsHolder(); + final var executor = new CommonProcessExecutor(); + executor.execAsync( + serverStreams, + GradleBuildService.this::onServerExited, + false, + + // The 'java' binary executable + Environment.JAVA.getAbsolutePath(), + + // Allow reflective access to private members of classes in the following + // packages: + // - java.lang + // - java.io + // - java.util + // + // If any of the model classes in 'tooling-api-model' module send/receive + // objects from the JDK, their package name must be declared here with + // '--add-opens' to prevent InaccessibleObjectException. + // For example, some of the model classes has members of type java.io.File. + // When sending/receiving these type of objects using LSP4J, members of + // these objects are reflectively accessed by Gson. If we do no specify + // '--add-opens' for 'java.io' (for java.io.File) package, JVM will throw an + // InaccessibleObjectException. + "--add-opens", + "java.base/java.lang=ALL-UNNAMED", + "--add-opens", + "java.base/java.util=ALL-UNNAMED", + "--add-opens", + "java.base/java.io=ALL-UNNAMED", + + // The JAR file to run + "-jar", + Environment.TOOLING_API_JAR.getAbsolutePath()); + + final var launcher = + ToolingApiLauncher.newClientLauncher( + GradleBuildService.this, serverStreams.in, serverStreams.out); + final var future = launcher.startListening(); + + GradleBuildService.this.startServerOutputReader(serverStreams.err); + GradleBuildService.this.server = (IToolingApiServer) launcher.getRemoteProxy(); + GradleBuildService.this.isToolingServerStarted = true; + + ProjectManager.INSTANCE.setRootProject((IProject) launcher.getRemoteProxy()); + + if (listener != null) { + listener.onServerStarted(); } + + // Wait(block) until the process terminates + try { + future.get(); + } catch (Throwable err) { + if (!(err instanceof CancellationException) && !(err instanceof InterruptedException)) { + LOG.error("An error occurred while waiting for tooling API server to terminate", err); + } + } + + } catch (Throwable e) { + LOG.error("Unable to start tooling API server", e); + } } + } - /** Callback to listen for Tooling API server start event. */ - public interface OnServerStartListener { + /** Callback to listen for Tooling API server start event. */ + public interface OnServerStartListener { - /** Called when the tooling API server has been successfully started. */ - void onServerStarted(); - } + /** Called when the tooling API server has been successfully started. */ + void onServerStarted(); + } - /** Handles events received from a Gradle build. */ - public interface EventListener { + /** Handles events received from a Gradle build. */ + public interface EventListener { - /** - * Called just before a build is started. - * - * @see IToolingApiClient#prepareBuild() - */ - void prepareBuild(); + /** + * Called just before a build is started. + * + * @see IToolingApiClient#prepareBuild() + */ + void prepareBuild(); - /** - * Called when a build is successful. - * - * @param tasks The tasks that were run. - * @see IToolingApiClient#onBuildSuccessful(BuildResult) - */ - void onBuildSuccessful(@NonNull List tasks); + /** + * Called when a build is successful. + * + * @param tasks The tasks that were run. + * @see IToolingApiClient#onBuildSuccessful(BuildResult) + */ + void onBuildSuccessful(@NonNull List tasks); - /** - * Called when a progress event is received from the Tooling API server. - * - * @param event The event model describing the event. - */ - void onProgressEvent(@NonNull ProgressEvent event); + /** + * Called when a progress event is received from the Tooling API server. + * + * @param event The event model describing the event. + */ + void onProgressEvent(@NonNull ProgressEvent event); - /** - * Called when a build fails. - * - * @param tasks The tasks that were run. - * @see IToolingApiClient#onBuildFailed(BuildResult) - */ - void onBuildFailed(@NonNull List tasks); + /** + * Called when a build fails. + * + * @param tasks The tasks that were run. + * @see IToolingApiClient#onBuildFailed(BuildResult) + */ + void onBuildFailed(@NonNull List tasks); - /** - * Called when the output line is received. - * - * @param line The line of the build output. - */ - void onOutput(String line); - } + /** + * Called when the output line is received. + * + * @param line The line of the build output. + */ + void onOutput(String line); + } } diff --git a/app/src/main/java/com/itsaky/androidide/services/LogReceiver.java b/app/src/main/java/com/itsaky/androidide/services/LogReceiver.java index 4c2175a20..8d27bf5c2 100755 --- a/app/src/main/java/com/itsaky/androidide/services/LogReceiver.java +++ b/app/src/main/java/com/itsaky/androidide/services/LogReceiver.java @@ -29,39 +29,39 @@ import com.itsaky.androidide.utils.ILogger; public class LogReceiver extends BroadcastReceiver { - private LogListener listener; + private LogListener listener; - public static final String APPEND_LOG = "com.itsaky.androidide.logs.APPEND_LOG"; - public static final String EXTRA_LINE = "log_line"; + public static final String APPEND_LOG = "com.itsaky.androidide.logs.APPEND_LOG"; + public static final String EXTRA_LINE = "log_line"; - private final ILogger LOG = ILogger.newInstance("LogReceiver"); + private final ILogger LOG = ILogger.newInstance("LogReceiver"); - public LogReceiver setLogListener(LogListener listener) { - this.listener = listener; - return this; + public LogReceiver setLogListener(LogListener listener) { + this.listener = listener; + return this; + } + + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(APPEND_LOG) && intent.hasExtra(EXTRA_LINE)) { + String line = intent.getStringExtra(EXTRA_LINE); + if (line == null) return; + try { + sendLogLine(line); + } catch (Throwable th) { + LOG.error("Unable to parse log line from app.", th); + } } + } - @Override - public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals(APPEND_LOG) && intent.hasExtra(EXTRA_LINE)) { - String line = intent.getStringExtra(EXTRA_LINE); - if (line == null) return; - try { - sendLogLine(line); - } catch (Throwable th) { - LOG.error("Unable to parse log line from app.", th); - } - } + private void sendLogLine(String line) { + final var log = LogLine.forLogString(line); + if (listener != null) { + listener.appendLogLine(log); } + } - private void sendLogLine(String line) { - final var log = LogLine.forLogString(line); - if (listener != null) { - listener.appendLogLine(log); - } - } - - public interface LogListener { - void appendLogLine(LogLine line); - } + public interface LogListener { + void appendLogLine(LogLine line); + } } diff --git a/app/src/main/java/com/itsaky/androidide/syntax/colorschemes/SchemeAndroidIDE.java b/app/src/main/java/com/itsaky/androidide/syntax/colorschemes/SchemeAndroidIDE.java index 4be589b05..8a2718be8 100755 --- a/app/src/main/java/com/itsaky/androidide/syntax/colorschemes/SchemeAndroidIDE.java +++ b/app/src/main/java/com/itsaky/androidide/syntax/colorschemes/SchemeAndroidIDE.java @@ -35,186 +35,186 @@ import io.github.rosemoe.sora.widget.schemes.EditorColorScheme; */ public class SchemeAndroidIDE extends EditorColorScheme { - private static int current = EditorColorScheme.END_COLOR_ID; + private static int current = EditorColorScheme.END_COLOR_ID; - public static final int LOG_INFO = current++; - public static final int LOG_DEBUG = current++; - public static final int LOG_ERROR = current++; - public static final int LOG_WARNING = current++; - public static final int DIAGNOSTIC_ERROR = current++; - public static final int DIAGNOSTIC_WARNING = current++; - public static final int DIAGNOSTIC_HINT = current++; - public static final int DIAGNOSTIC_INFO = current++; - public static final int STDERR = current++; - public static final int STDOUT = current++; - public static final int XML_TAG = current++; - public static final int FIELD = current++; - public static final int STATIC_FIELD = current++; - public static final int PACKAGE_NAME = current++; - public static final int ENUM_TYPE = current++; - public static final int INTERFACE = current++; - public static final int ENUM = current++; - public static final int PARAMETER = current++; - public static final int CONSTRUCTOR = current++; - public static final int STATIC_INIT = current++; - public static final int INSTANCE_INIT = current++; - public static final int TYPE_PARAM = current++; - public static final int RESOURCE_VARIABLE = current++; - public static final int EXCEPTION_PARAM = current++; - public static final int METHOD_DECLARATION = current++; - public static final int METHOD_INVOCATION = current++; - public static final int TYPE_NAME = current++; - public static final int LOCAL_VARIABLE = current++; - public static final int TODO_COMMENT = current++; - public static final int FIXME_COMMENT = current++; + public static final int LOG_INFO = current++; + public static final int LOG_DEBUG = current++; + public static final int LOG_ERROR = current++; + public static final int LOG_WARNING = current++; + public static final int DIAGNOSTIC_ERROR = current++; + public static final int DIAGNOSTIC_WARNING = current++; + public static final int DIAGNOSTIC_HINT = current++; + public static final int DIAGNOSTIC_INFO = current++; + public static final int STDERR = current++; + public static final int STDOUT = current++; + public static final int XML_TAG = current++; + public static final int FIELD = current++; + public static final int STATIC_FIELD = current++; + public static final int PACKAGE_NAME = current++; + public static final int ENUM_TYPE = current++; + public static final int INTERFACE = current++; + public static final int ENUM = current++; + public static final int PARAMETER = current++; + public static final int CONSTRUCTOR = current++; + public static final int STATIC_INIT = current++; + public static final int INSTANCE_INIT = current++; + public static final int TYPE_PARAM = current++; + public static final int RESOURCE_VARIABLE = current++; + public static final int EXCEPTION_PARAM = current++; + public static final int METHOD_DECLARATION = current++; + public static final int METHOD_INVOCATION = current++; + public static final int TYPE_NAME = current++; + public static final int LOCAL_VARIABLE = current++; + public static final int TODO_COMMENT = current++; + public static final int FIXME_COMMENT = current++; - /** - * Delegates to {@link TextStyle#makeStyle(int)} - * - * @param id The color id. - * @return The style flags. - */ - public static long get(int id) { - return TextStyle.makeStyle(id); + /** + * Delegates to {@link TextStyle#makeStyle(int)} + * + * @param id The color id. + * @return The style flags. + */ + public static long get(int id) { + return TextStyle.makeStyle(id); + } + + /** + * Create style for keywords. Convenient method to avoid calling {@link TextStyle#makeStyle(int, + * int, boolean, boolean, boolean)} + * + * @return The default style for keywords. + */ + public static long forKeyword() { + return TextStyle.makeStyle(KEYWORD, 0, true, false, false); + } + + /** + * Create style for string literals. The returned style sets the {@link + * TextStyle#NO_COMPLETION_BIT}. + * + * @return The style for string literals. + */ + public static long forString() { + return TextStyle.makeStyle(LITERAL, true); + } + + /** + * Create color style for default comments. + * + * @return The style for {@link #COMMENT}. + */ + public static long forComment() { + return TextStyle.makeStyle(COMMENT, true); + } + + /** + * Create style for the given id without completions. + * + * @param id The id to create style for. + * @return The style for the id. + */ + public static long withoutCompletion(int id) { + return TextStyle.makeStyle(id, true); + } + + /** + * Make style for the given log priority. + * + * @param priority The priority. + * @return The style. + */ + public static long forLogPriority(int priority) { + return get(getLogPriorityId(priority)); + } + + /** + * Get the appropriate color id for the given log line priority. + * + * @param priority The log line priority. + * @return The color id. + */ + public static int getLogPriorityId(int priority) { + if (priority == ERROR) { + return LOG_ERROR; } - /** - * Create style for keywords. Convenient method to avoid calling {@link TextStyle#makeStyle(int, - * int, boolean, boolean, boolean)} - * - * @return The default style for keywords. - */ - public static long forKeyword() { - return TextStyle.makeStyle(KEYWORD, 0, true, false, false); + if (priority == WARNING) { + return LOG_WARNING; } - /** - * Create style for string literals. The returned style sets the {@link - * TextStyle#NO_COMPLETION_BIT}. - * - * @return The style for string literals. - */ - public static long forString() { - return TextStyle.makeStyle(LITERAL, true); + if (priority == INFO) { + return LOG_INFO; } - /** - * Create color style for default comments. - * - * @return The style for {@link #COMMENT}. - */ - public static long forComment() { - return TextStyle.makeStyle(COMMENT, true); - } + return DEBUG; + } - /** - * Create style for the given id without completions. - * - * @param id The id to create style for. - * @return The style for the id. - */ - public static long withoutCompletion(int id) { - return TextStyle.makeStyle(id, true); - } + @Override + public void applyDefault() { + // Apply default colors + super.applyDefault(); - /** - * Make style for the given log priority. - * - * @param priority The priority. - * @return The style. - */ - public static long forLogPriority(int priority) { - return get(getLogPriorityId(priority)); - } + // Apply customized colors + setColor(WHOLE_BACKGROUND, 0xff212121); + setColor(LINE_NUMBER_BACKGROUND, 0xff212121); + setColor(LINE_DIVIDER, Color.TRANSPARENT); + setColor(LINE_NUMBER, 0xffaaaaaa); + setColor(LINE_NUMBER_PANEL, 0xff000000); + setColor(LINE_NUMBER_PANEL_TEXT, 0xffffffff); + setColor(TEXT_NORMAL, 0xfff5f5f5); + setColor(SELECTED_TEXT_BACKGROUND, 0xff757575); + setColor(TEXT_SELECTED, 0xff424242); + setColor(SELECTION_INSERT, 0xffff5252); + setColor(SELECTION_HANDLE, 0xffff5252); + setColor(CURRENT_LINE, 0xff313131); + setColor(UNDERLINE, 0xffffffff); + setColor(SCROLL_BAR_THUMB, 0xff757575); + setColor(SCROLL_BAR_THUMB_PRESSED, 0xffff5252); + setColor(SCROLL_BAR_TRACK, 0xffbdbdbd); + setColor(BLOCK_LINE, 0xff424242); + setColor(BLOCK_LINE_CURRENT, 0xffffffff); + setColor(AUTO_COMP_PANEL_BG, 0xff757575); + setColor(AUTO_COMP_PANEL_CORNER, 0xff9e9e9e); + setColor(MATCHED_TEXT_BACKGROUND, 0xffFF8F00); + setColor(NON_PRINTABLE_CHAR, 0xffdddddd); + setColor(KEYWORD, 0xffff6060); + setColor(OPERATOR, 0xff4fc3f7); + setColor(LITERAL, 0xff8bc34a); + setColor(TYPE_NAME, 0xff4fc3f7); + setColor(LOCAL_VARIABLE, 0xfff5f5f5); + setColor(RESOURCE_VARIABLE, 0xfff5f5f5); + setColor(PARAMETER, 0xfff5f5f5); + setColor(EXCEPTION_PARAM, 0xfff5f5f5); + setColor(METHOD_DECLARATION, 0xfff5f5f5); + setColor(METHOD_INVOCATION, 0xfff5f5f5); + setColor(CONSTRUCTOR, 0xfff5f5f5); + setColor(ANNOTATION, 0xff4fc3f7); + setColor(PACKAGE_NAME, 0xfff0be4b); + setColor(FIELD, 0xfff0be4b); + setColor(STATIC_INIT, 0xfff0be4b); + setColor(INSTANCE_INIT, 0xfff0be4b); + setColor(TYPE_PARAM, 0xfff0be4b); + setColor(STATIC_FIELD, 0xffffb300); + setColor(ENUM_TYPE, 0xff4fc3f7); + setColor(INTERFACE, 0xff4fc3f7); + setColor(ENUM, 0xffffb300); + setColor(XML_TAG, 0xffff6060); - /** - * Get the appropriate color id for the given log line priority. - * - * @param priority The log line priority. - * @return The color id. - */ - public static int getLogPriorityId(int priority) { - if (priority == ERROR) { - return LOG_ERROR; - } + setColor(DIAGNOSTIC_ERROR, 0xfff44336); + setColor(DIAGNOSTIC_WARNING, 0xffFFEB3B); + setColor(DIAGNOSTIC_INFO, 0xff4CAF50); + setColor(DIAGNOSTIC_HINT, 0xffffffff); - if (priority == WARNING) { - return LOG_WARNING; - } + setColor(LOG_ERROR, 0xfff44336); + setColor(LOG_WARNING, 0xffFFEB3B); + setColor(LOG_INFO, 0xff4CAF50); + setColor(LOG_DEBUG, 0xfff5f5f5); - if (priority == INFO) { - return LOG_INFO; - } + setColor(STDERR, 0xfff44336); + setColor(STDOUT, 0xff4CAF50); - return DEBUG; - } - - @Override - public void applyDefault() { - // Apply default colors - super.applyDefault(); - - // Apply customized colors - setColor(WHOLE_BACKGROUND, 0xff212121); - setColor(LINE_NUMBER_BACKGROUND, 0xff212121); - setColor(LINE_DIVIDER, Color.TRANSPARENT); - setColor(LINE_NUMBER, 0xffaaaaaa); - setColor(LINE_NUMBER_PANEL, 0xff000000); - setColor(LINE_NUMBER_PANEL_TEXT, 0xffffffff); - setColor(TEXT_NORMAL, 0xfff5f5f5); - setColor(SELECTED_TEXT_BACKGROUND, 0xff757575); - setColor(TEXT_SELECTED, 0xff424242); - setColor(SELECTION_INSERT, 0xffff5252); - setColor(SELECTION_HANDLE, 0xffff5252); - setColor(CURRENT_LINE, 0xff313131); - setColor(UNDERLINE, 0xffffffff); - setColor(SCROLL_BAR_THUMB, 0xff757575); - setColor(SCROLL_BAR_THUMB_PRESSED, 0xffff5252); - setColor(SCROLL_BAR_TRACK, 0xffbdbdbd); - setColor(BLOCK_LINE, 0xff424242); - setColor(BLOCK_LINE_CURRENT, 0xffffffff); - setColor(AUTO_COMP_PANEL_BG, 0xff757575); - setColor(AUTO_COMP_PANEL_CORNER, 0xff9e9e9e); - setColor(MATCHED_TEXT_BACKGROUND, 0xffFF8F00); - setColor(NON_PRINTABLE_CHAR, 0xffdddddd); - setColor(KEYWORD, 0xffff6060); - setColor(OPERATOR, 0xff4fc3f7); - setColor(LITERAL, 0xff8bc34a); - setColor(TYPE_NAME, 0xff4fc3f7); - setColor(LOCAL_VARIABLE, 0xfff5f5f5); - setColor(RESOURCE_VARIABLE, 0xfff5f5f5); - setColor(PARAMETER, 0xfff5f5f5); - setColor(EXCEPTION_PARAM, 0xfff5f5f5); - setColor(METHOD_DECLARATION, 0xfff5f5f5); - setColor(METHOD_INVOCATION, 0xfff5f5f5); - setColor(CONSTRUCTOR, 0xfff5f5f5); - setColor(ANNOTATION, 0xff4fc3f7); - setColor(PACKAGE_NAME, 0xfff0be4b); - setColor(FIELD, 0xfff0be4b); - setColor(STATIC_INIT, 0xfff0be4b); - setColor(INSTANCE_INIT, 0xfff0be4b); - setColor(TYPE_PARAM, 0xfff0be4b); - setColor(STATIC_FIELD, 0xffffb300); - setColor(ENUM_TYPE, 0xff4fc3f7); - setColor(INTERFACE, 0xff4fc3f7); - setColor(ENUM, 0xffffb300); - setColor(XML_TAG, 0xffff6060); - - setColor(DIAGNOSTIC_ERROR, 0xfff44336); - setColor(DIAGNOSTIC_WARNING, 0xffFFEB3B); - setColor(DIAGNOSTIC_INFO, 0xff4CAF50); - setColor(DIAGNOSTIC_HINT, 0xffffffff); - - setColor(LOG_ERROR, 0xfff44336); - setColor(LOG_WARNING, 0xffFFEB3B); - setColor(LOG_INFO, 0xff4CAF50); - setColor(LOG_DEBUG, 0xfff5f5f5); - - setColor(STDERR, 0xfff44336); - setColor(STDOUT, 0xff4CAF50); - - setColor(TODO_COMMENT, 0xffffc400); - setColor(FIXME_COMMENT, 0xffffab00); - setColor(COMMENT, 0xffbdbdbd); - } + setColor(TODO_COMMENT, 0xffffc400); + setColor(FIXME_COMMENT, 0xffffab00); + setColor(COMMENT, 0xffbdbdbd); + } } diff --git a/app/src/main/java/com/itsaky/androidide/syntax/highlighters/Highlighter.java b/app/src/main/java/com/itsaky/androidide/syntax/highlighters/Highlighter.java index 659c2f9d5..6aae2b4a0 100644 --- a/app/src/main/java/com/itsaky/androidide/syntax/highlighters/Highlighter.java +++ b/app/src/main/java/com/itsaky/androidide/syntax/highlighters/Highlighter.java @@ -28,5 +28,5 @@ import android.text.SpannableStringBuilder; * an EditorLanguage instead */ public interface Highlighter { - SpannableStringBuilder highlight(String code, String match) throws Exception; + SpannableStringBuilder highlight(String code, String match) throws Exception; } diff --git a/app/src/main/java/com/itsaky/androidide/syntax/highlighters/JavaHighlighter.java b/app/src/main/java/com/itsaky/androidide/syntax/highlighters/JavaHighlighter.java index 853de6281..bd86cf9f3 100644 --- a/app/src/main/java/com/itsaky/androidide/syntax/highlighters/JavaHighlighter.java +++ b/app/src/main/java/com/itsaky/androidide/syntax/highlighters/JavaHighlighter.java @@ -34,178 +34,172 @@ import io.github.rosemoe.sora.widget.schemes.EditorColorScheme; public class JavaHighlighter implements Highlighter { - @Override - public SpannableStringBuilder highlight(String code, String match) throws Exception { - final JavaLexer lexer = new JavaLexer(CharStreams.fromReader(new StringReader(code))); - final SpannableStringBuilder sb = new SpannableStringBuilder(); - final EditorColorScheme scheme = new SchemeAndroidIDE(); - Token token; - while ((token = lexer.nextToken()) != null) { - if (token.getType() == JavaLexer.EOF) break; - switch (token.getType()) { - case JavaLexer.WS: - sb.append(token.getText()); - break; - case JavaLexer.ABSTRACT: - case JavaLexer.ASSERT: - case JavaLexer.BREAK: - case JavaLexer.CASE: - case JavaLexer.CATCH: - case JavaLexer.CLASS: - case JavaLexer.CONST: - case JavaLexer.CONTINUE: - case JavaLexer.DEFAULT: - case JavaLexer.DO: - case JavaLexer.ELSE: - case JavaLexer.EXTENDS: - case JavaLexer.FINAL: - case JavaLexer.FINALLY: - case JavaLexer.FOR: - case JavaLexer.IF: - case JavaLexer.GOTO: - case JavaLexer.IMPLEMENTS: - case JavaLexer.IMPORT: - case JavaLexer.INSTANCEOF: - case JavaLexer.INTERFACE: - case JavaLexer.NATIVE: - case JavaLexer.NEW: - case JavaLexer.PACKAGE: - case JavaLexer.PRIVATE: - case JavaLexer.PROTECTED: - case JavaLexer.PUBLIC: - case JavaLexer.RETURN: - case JavaLexer.STATIC: - case JavaLexer.STRICTFP: - case JavaLexer.SUPER: - case JavaLexer.SWITCH: - case JavaLexer.SYNCHRONIZED: - case JavaLexer.THIS: - case JavaLexer.THROW: - case JavaLexer.THROWS: - case JavaLexer.TRANSIENT: - case JavaLexer.TRY: - case JavaLexer.VOID: - case JavaLexer.VOLATILE: - case JavaLexer.WHILE: - sb.append( - token.getText(), - new ForegroundColorSpan(scheme.getColor(EditorColorScheme.KEYWORD)), - SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); - break; - case JavaLexer.DECIMAL_LITERAL: - case JavaLexer.HEX_LITERAL: - case JavaLexer.OCT_LITERAL: - case JavaLexer.BINARY_LITERAL: - case JavaLexer.FLOAT_LITERAL: - case JavaLexer.HEX_FLOAT_LITERAL: - case JavaLexer.BOOL_LITERAL: - case JavaLexer.CHAR_LITERAL: - case JavaLexer.NULL_LITERAL: - case JavaLexer.STRING_LITERAL: - sb.append( - token.getText(), - new ForegroundColorSpan(scheme.getColor(EditorColorScheme.LITERAL)), - SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); - break; - case JavaLexer.LPAREN: - case JavaLexer.RPAREN: - case JavaLexer.LBRACK: - case JavaLexer.RBRACK: - case JavaLexer.SEMI: - case JavaLexer.COMMA: - case JavaLexer.ASSIGN: - case JavaLexer.GT: - case JavaLexer.LT: - case JavaLexer.BANG: - case JavaLexer.TILDE: - case JavaLexer.QUESTION: - case JavaLexer.COLON: - case JavaLexer.EQUAL: - case JavaLexer.GE: - case JavaLexer.LE: - case JavaLexer.NOTEQUAL: - case JavaLexer.AND: - case JavaLexer.OR: - case JavaLexer.INC: - case JavaLexer.DEC: - case JavaLexer.ADD: - case JavaLexer.SUB: - case JavaLexer.MUL: - case JavaLexer.DIV: - case JavaLexer.BITAND: - case JavaLexer.BITOR: - case JavaLexer.CARET: - case JavaLexer.MOD: - case JavaLexer.ADD_ASSIGN: - case JavaLexer.SUB_ASSIGN: - case JavaLexer.MUL_ASSIGN: - case JavaLexer.DIV_ASSIGN: - case JavaLexer.AND_ASSIGN: - case JavaLexer.OR_ASSIGN: - case JavaLexer.XOR_ASSIGN: - case JavaLexer.MOD_ASSIGN: - case JavaLexer.LSHIFT_ASSIGN: - case JavaLexer.RSHIFT_ASSIGN: - case JavaLexer.URSHIFT_ASSIGN: - case JavaLexer.ARROW: - case JavaLexer.COLONCOLON: - case JavaLexer.ELLIPSIS: - case JavaLexer.DOT: - case JavaLexer.LBRACE: - case JavaLexer.RBRACE: - case JavaLexer.AT: - sb.append( - token.getText(), - new ForegroundColorSpan(scheme.getColor(EditorColorScheme.OPERATOR)), - SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); - break; - case JavaLexer.BOOLEAN: - case JavaLexer.BYTE: - case JavaLexer.CHAR: - case JavaLexer.DOUBLE: - case JavaLexer.ENUM: - case JavaLexer.FLOAT: - case JavaLexer.INT: - case JavaLexer.LONG: - case JavaLexer.SHORT: - sb.append( - token.getText(), - new ForegroundColorSpan(scheme.getColor(SchemeAndroidIDE.TYPE_NAME)), - SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); - break; - case JavaLexer.BLOCK_COMMENT: - case JavaLexer.LINE_COMMENT: - sb.append( - token.getText(), - new ForegroundColorSpan(scheme.getColor(EditorColorScheme.COMMENT)), - SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); - break; - default: - sb.append( - token.getText(), - new ForegroundColorSpan(scheme.getColor(EditorColorScheme.TEXT_NORMAL)), - SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); - break; - } - } - final BackgroundColorSpan bg = new BackgroundColorSpan(0xffffff00); - final ForegroundColorSpan fg = new ForegroundColorSpan(0xff000000); - - Pattern pattern = Pattern.compile(Pattern.quote(match)); - Matcher matcher = pattern.matcher(code); - while (matcher.find()) { - sb.setSpan( - bg, - matcher.start(), - matcher.end(), - SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); - sb.setSpan( - fg, - matcher.start(), - matcher.end(), - SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); - } - - return sb; + @Override + public SpannableStringBuilder highlight(String code, String match) throws Exception { + final JavaLexer lexer = new JavaLexer(CharStreams.fromReader(new StringReader(code))); + final SpannableStringBuilder sb = new SpannableStringBuilder(); + final EditorColorScheme scheme = new SchemeAndroidIDE(); + Token token; + while ((token = lexer.nextToken()) != null) { + if (token.getType() == JavaLexer.EOF) break; + switch (token.getType()) { + case JavaLexer.WS: + sb.append(token.getText()); + break; + case JavaLexer.ABSTRACT: + case JavaLexer.ASSERT: + case JavaLexer.BREAK: + case JavaLexer.CASE: + case JavaLexer.CATCH: + case JavaLexer.CLASS: + case JavaLexer.CONST: + case JavaLexer.CONTINUE: + case JavaLexer.DEFAULT: + case JavaLexer.DO: + case JavaLexer.ELSE: + case JavaLexer.EXTENDS: + case JavaLexer.FINAL: + case JavaLexer.FINALLY: + case JavaLexer.FOR: + case JavaLexer.IF: + case JavaLexer.GOTO: + case JavaLexer.IMPLEMENTS: + case JavaLexer.IMPORT: + case JavaLexer.INSTANCEOF: + case JavaLexer.INTERFACE: + case JavaLexer.NATIVE: + case JavaLexer.NEW: + case JavaLexer.PACKAGE: + case JavaLexer.PRIVATE: + case JavaLexer.PROTECTED: + case JavaLexer.PUBLIC: + case JavaLexer.RETURN: + case JavaLexer.STATIC: + case JavaLexer.STRICTFP: + case JavaLexer.SUPER: + case JavaLexer.SWITCH: + case JavaLexer.SYNCHRONIZED: + case JavaLexer.THIS: + case JavaLexer.THROW: + case JavaLexer.THROWS: + case JavaLexer.TRANSIENT: + case JavaLexer.TRY: + case JavaLexer.VOID: + case JavaLexer.VOLATILE: + case JavaLexer.WHILE: + sb.append( + token.getText(), + new ForegroundColorSpan(scheme.getColor(EditorColorScheme.KEYWORD)), + SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); + break; + case JavaLexer.DECIMAL_LITERAL: + case JavaLexer.HEX_LITERAL: + case JavaLexer.OCT_LITERAL: + case JavaLexer.BINARY_LITERAL: + case JavaLexer.FLOAT_LITERAL: + case JavaLexer.HEX_FLOAT_LITERAL: + case JavaLexer.BOOL_LITERAL: + case JavaLexer.CHAR_LITERAL: + case JavaLexer.NULL_LITERAL: + case JavaLexer.STRING_LITERAL: + sb.append( + token.getText(), + new ForegroundColorSpan(scheme.getColor(EditorColorScheme.LITERAL)), + SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); + break; + case JavaLexer.LPAREN: + case JavaLexer.RPAREN: + case JavaLexer.LBRACK: + case JavaLexer.RBRACK: + case JavaLexer.SEMI: + case JavaLexer.COMMA: + case JavaLexer.ASSIGN: + case JavaLexer.GT: + case JavaLexer.LT: + case JavaLexer.BANG: + case JavaLexer.TILDE: + case JavaLexer.QUESTION: + case JavaLexer.COLON: + case JavaLexer.EQUAL: + case JavaLexer.GE: + case JavaLexer.LE: + case JavaLexer.NOTEQUAL: + case JavaLexer.AND: + case JavaLexer.OR: + case JavaLexer.INC: + case JavaLexer.DEC: + case JavaLexer.ADD: + case JavaLexer.SUB: + case JavaLexer.MUL: + case JavaLexer.DIV: + case JavaLexer.BITAND: + case JavaLexer.BITOR: + case JavaLexer.CARET: + case JavaLexer.MOD: + case JavaLexer.ADD_ASSIGN: + case JavaLexer.SUB_ASSIGN: + case JavaLexer.MUL_ASSIGN: + case JavaLexer.DIV_ASSIGN: + case JavaLexer.AND_ASSIGN: + case JavaLexer.OR_ASSIGN: + case JavaLexer.XOR_ASSIGN: + case JavaLexer.MOD_ASSIGN: + case JavaLexer.LSHIFT_ASSIGN: + case JavaLexer.RSHIFT_ASSIGN: + case JavaLexer.URSHIFT_ASSIGN: + case JavaLexer.ARROW: + case JavaLexer.COLONCOLON: + case JavaLexer.ELLIPSIS: + case JavaLexer.DOT: + case JavaLexer.LBRACE: + case JavaLexer.RBRACE: + case JavaLexer.AT: + sb.append( + token.getText(), + new ForegroundColorSpan(scheme.getColor(EditorColorScheme.OPERATOR)), + SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); + break; + case JavaLexer.BOOLEAN: + case JavaLexer.BYTE: + case JavaLexer.CHAR: + case JavaLexer.DOUBLE: + case JavaLexer.ENUM: + case JavaLexer.FLOAT: + case JavaLexer.INT: + case JavaLexer.LONG: + case JavaLexer.SHORT: + sb.append( + token.getText(), + new ForegroundColorSpan(scheme.getColor(SchemeAndroidIDE.TYPE_NAME)), + SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); + break; + case JavaLexer.BLOCK_COMMENT: + case JavaLexer.LINE_COMMENT: + sb.append( + token.getText(), + new ForegroundColorSpan(scheme.getColor(EditorColorScheme.COMMENT)), + SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); + break; + default: + sb.append( + token.getText(), + new ForegroundColorSpan(scheme.getColor(EditorColorScheme.TEXT_NORMAL)), + SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); + break; + } } + final BackgroundColorSpan bg = new BackgroundColorSpan(0xffffff00); + final ForegroundColorSpan fg = new ForegroundColorSpan(0xff000000); + + Pattern pattern = Pattern.compile(Pattern.quote(match)); + Matcher matcher = pattern.matcher(code); + while (matcher.find()) { + sb.setSpan( + bg, matcher.start(), matcher.end(), SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); + sb.setSpan( + fg, matcher.start(), matcher.end(), SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + return sb; + } } diff --git a/app/src/main/java/com/itsaky/androidide/tasks/TaskExecutor.java b/app/src/main/java/com/itsaky/androidide/tasks/TaskExecutor.java index 91da942e6..6ddf40d26 100755 --- a/app/src/main/java/com/itsaky/androidide/tasks/TaskExecutor.java +++ b/app/src/main/java/com/itsaky/androidide/tasks/TaskExecutor.java @@ -27,54 +27,48 @@ import java.util.concurrent.CompletionException; public class TaskExecutor { - private final ILogger LOG = ILogger.newInstance("TaskExecutor"); + private final ILogger LOG = ILogger.newInstance("TaskExecutor"); - public static void execAsync(Callable callable, Callback callback) { - new TaskExecutor().executeAsync(callable, callback); - } + public static void execAsync(Callable callable, Callback callback) { + new TaskExecutor().executeAsync(callable, callback); + } - public void executeAsync(Callable callable, Callback callback) { - CompletableFuture.supplyAsync( - () -> { - try { - return callable.call(); - } catch (Throwable th) { - LOG.error( - "An error occurred while executing Callable in background" - + " thread.", - th); - return null; - } - }) - .whenComplete( - (result, throwable) -> - ThreadUtils.runOnUiThread(() -> callback.complete(result))); - } + public void executeAsync(Callable callable, Callback callback) { + CompletableFuture.supplyAsync( + () -> { + try { + return callable.call(); + } catch (Throwable th) { + LOG.error( + "An error occurred while executing Callable in background" + " thread.", th); + return null; + } + }) + .whenComplete( + (result, throwable) -> ThreadUtils.runOnUiThread(() -> callback.complete(result))); + } - public void executeAsyncProvideError(Callable callable, CallbackWithError callback) { - CompletableFuture.supplyAsync( - () -> { - try { - return callable.call(); - } catch (Throwable th) { - LOG.error( - "An error occurred while executing Callable in background" - + " thread.", - th); - throw new CompletionException(th); - } - }) - .whenComplete( - (result, throwable) -> - ThreadUtils.runOnUiThread( - () -> callback.complete(result, throwable))); - } + public void executeAsyncProvideError(Callable callable, CallbackWithError callback) { + CompletableFuture.supplyAsync( + () -> { + try { + return callable.call(); + } catch (Throwable th) { + LOG.error( + "An error occurred while executing Callable in background" + " thread.", th); + throw new CompletionException(th); + } + }) + .whenComplete( + (result, throwable) -> + ThreadUtils.runOnUiThread(() -> callback.complete(result, throwable))); + } - public interface Callback { - void complete(R result); - } + public interface Callback { + void complete(R result); + } - public interface CallbackWithError { - void complete(R result, Throwable error); - } + public interface CallbackWithError { + void complete(R result, Throwable error); + } } diff --git a/app/src/main/java/com/itsaky/androidide/tasks/callables/FileTreeCallable.java b/app/src/main/java/com/itsaky/androidide/tasks/callables/FileTreeCallable.java index 49404db51..4befb9014 100755 --- a/app/src/main/java/com/itsaky/androidide/tasks/callables/FileTreeCallable.java +++ b/app/src/main/java/com/itsaky/androidide/tasks/callables/FileTreeCallable.java @@ -32,53 +32,53 @@ import java.util.Comparator; import java.util.concurrent.Callable; public class FileTreeCallable implements Callable { - private final Context ctx; - private TreeNode parent; - private File file; + private final Context ctx; + private TreeNode parent; + private File file; - public FileTreeCallable(Context ctx, TreeNode parent, File file) { - this.ctx = ctx; - this.parent = parent; - this.file = file; + public FileTreeCallable(Context ctx, TreeNode parent, File file) { + this.ctx = ctx; + this.parent = parent; + this.file = file; + } + + @Override + public Boolean call() throws Exception { + getNodeFromArray(file.listFiles(/*new HiddenFilesFilter()*/ ), parent); + return true; + } + + private void getNodeFromArray(File[] files, TreeNode parent) { + Arrays.sort(files, new SortFileName()); + Arrays.sort(files, new SortFolder()); + for (File file : files) { + TreeNode node = new TreeNode(file); + node.setViewHolder(new FileTreeViewHolder(ctx)); + parent.addChild(node); } + } + + public static class HiddenFilesFilter implements FileFilter { @Override - public Boolean call() throws Exception { - getNodeFromArray(file.listFiles(/*new HiddenFilesFilter()*/ ), parent); - return true; + public boolean accept(File p1) { + return !p1.getName().startsWith("."); } + } - private void getNodeFromArray(File[] files, TreeNode parent) { - Arrays.sort(files, new SortFileName()); - Arrays.sort(files, new SortFolder()); - for (File file : files) { - TreeNode node = new TreeNode(file); - node.setViewHolder(new FileTreeViewHolder(ctx)); - parent.addChild(node); - } + public static class SortFileName implements Comparator { + @Override + public int compare(File f1, File f2) { + return f1.getName().compareTo(f2.getName()); } + } - public static class HiddenFilesFilter implements FileFilter { - - @Override - public boolean accept(File p1) { - return !p1.getName().startsWith("."); - } - } - - public static class SortFileName implements Comparator { - @Override - public int compare(File f1, File f2) { - return f1.getName().compareTo(f2.getName()); - } - } - - public static class SortFolder implements Comparator { - @Override - public int compare(File f1, File f2) { - if (f1.isDirectory() == f2.isDirectory()) return 0; - else if (f1.isDirectory() && !f2.isDirectory()) return -1; - else return 1; - } + public static class SortFolder implements Comparator { + @Override + public int compare(File f1, File f2) { + if (f1.isDirectory() == f2.isDirectory()) return 0; + else if (f1.isDirectory() && !f2.isDirectory()) return -1; + else return 1; } + } } diff --git a/app/src/main/java/com/itsaky/androidide/tasks/callables/LicenseReader.java b/app/src/main/java/com/itsaky/androidide/tasks/callables/LicenseReader.java index 5be6c8f69..492fa6217 100755 --- a/app/src/main/java/com/itsaky/androidide/tasks/callables/LicenseReader.java +++ b/app/src/main/java/com/itsaky/androidide/tasks/callables/LicenseReader.java @@ -33,22 +33,22 @@ import java.util.concurrent.Callable; public class LicenseReader implements Callable> { - private final Context ctx; + private final Context ctx; - public LicenseReader(Context ctx) { - this.ctx = ctx; - } + public LicenseReader(Context ctx) { + this.ctx = ctx; + } - @Override - public List call() throws Exception { - BufferedReader reader = - new BufferedReader(new InputStreamReader(ctx.getAssets().open("licenses.json"))); - String line; - StringBuilder sb = new StringBuilder(); - while ((line = reader.readLine()) != null) { - sb.append(line); - } - TypeToken> token = new TypeToken>() {}; - return new Gson().fromJson(sb.toString(), token.getType()); + @Override + public List call() throws Exception { + BufferedReader reader = + new BufferedReader(new InputStreamReader(ctx.getAssets().open("licenses.json"))); + String line; + StringBuilder sb = new StringBuilder(); + while ((line = reader.readLine()) != null) { + sb.append(line); } + TypeToken> token = new TypeToken>() {}; + return new Gson().fromJson(sb.toString(), token.getType()); + } } diff --git a/app/src/main/java/com/itsaky/androidide/tasks/callables/ListDirectoryCallable.java b/app/src/main/java/com/itsaky/androidide/tasks/callables/ListDirectoryCallable.java index 99690adfa..8d5fad31c 100755 --- a/app/src/main/java/com/itsaky/androidide/tasks/callables/ListDirectoryCallable.java +++ b/app/src/main/java/com/itsaky/androidide/tasks/callables/ListDirectoryCallable.java @@ -29,25 +29,23 @@ import java.io.FileFilter; public class ListDirectoryCallable implements java.util.concurrent.Callable { - private File file; + private File file; - public ListDirectoryCallable(File file) { - this.file = file; - } + public ListDirectoryCallable(File file) { + this.file = file; + } - @Override - public String call() throws Exception { - return TextUtils.join( - "\n", FileUtils.listFilesInDirWithFilter(file, ARCHIVE_FILTER, false)); - } + @Override + public String call() throws Exception { + return TextUtils.join("\n", FileUtils.listFilesInDirWithFilter(file, ARCHIVE_FILTER, false)); + } - private final FileFilter ARCHIVE_FILTER = - new FileFilter() { + private final FileFilter ARCHIVE_FILTER = + new FileFilter() { - @Override - public boolean accept(File p1) { - return p1.isFile() - && (p1.getName().endsWith(".tar.xz") || p1.getName().endsWith(".zip")); - } - }; + @Override + public boolean accept(File p1) { + return p1.isFile() && (p1.getName().endsWith(".tar.xz") || p1.getName().endsWith(".zip")); + } + }; } diff --git a/app/src/main/java/com/itsaky/androidide/tasks/callables/ProjectCreatorCallable.java b/app/src/main/java/com/itsaky/androidide/tasks/callables/ProjectCreatorCallable.java index f63030b87..40737b8d5 100755 --- a/app/src/main/java/com/itsaky/androidide/tasks/callables/ProjectCreatorCallable.java +++ b/app/src/main/java/com/itsaky/androidide/tasks/callables/ProjectCreatorCallable.java @@ -29,20 +29,20 @@ import java.util.concurrent.Callable; public class ProjectCreatorCallable implements Callable { - private ProjectTemplate template; - private NewProjectDetails details; - private ProjectWriterCallback callback; + private ProjectTemplate template; + private NewProjectDetails details; + private ProjectWriterCallback callback; - public ProjectCreatorCallable( - ProjectTemplate template, NewProjectDetails details, ProjectWriterCallback callback) { - this.template = template; - this.details = details; - this.callback = callback; - } + public ProjectCreatorCallable( + ProjectTemplate template, NewProjectDetails details, ProjectWriterCallback callback) { + this.template = template; + this.details = details; + this.callback = callback; + } - @Override - public Void call() throws Exception { - ProjectWriter.write(template, details, callback); - return null; - } + @Override + public Void call() throws Exception { + ProjectWriter.write(template, details, callback); + return null; + } } diff --git a/app/src/main/java/com/itsaky/androidide/tasks/callables/UnzipCallable.java b/app/src/main/java/com/itsaky/androidide/tasks/callables/UnzipCallable.java index 0016e1223..79a830f7a 100755 --- a/app/src/main/java/com/itsaky/androidide/tasks/callables/UnzipCallable.java +++ b/app/src/main/java/com/itsaky/androidide/tasks/callables/UnzipCallable.java @@ -28,16 +28,16 @@ import java.util.concurrent.Callable; public final class UnzipCallable implements Callable> { - private File src; - private File dest; + private File src; + private File dest; - public UnzipCallable(File src, File dest) { - this.src = src; - this.dest = dest; - } + public UnzipCallable(File src, File dest) { + this.src = src; + this.dest = dest; + } - @Override - public List call() throws Exception { - return ZipUtils.unzipFile(src, dest); - } + @Override + public List call() throws Exception { + return ZipUtils.unzipFile(src, dest); + } } diff --git a/app/src/main/java/com/itsaky/androidide/ui/WidgetDragData.java b/app/src/main/java/com/itsaky/androidide/ui/WidgetDragData.java index ed237b4fc..f61489793 100755 --- a/app/src/main/java/com/itsaky/androidide/ui/WidgetDragData.java +++ b/app/src/main/java/com/itsaky/androidide/ui/WidgetDragData.java @@ -27,29 +27,29 @@ import com.itsaky.inflater.IView; */ public class WidgetDragData { - /** - * Set this to true if the view that is being dragged was already inflated in the layout. If - * this is set to {@code true}, {@link #alreadyInflatedView} must NOT be {@code null}. - * - *

If this is {@code false}, then {@link #newDragData} must be not be null. - */ - public boolean isAlreadyInflated; + /** + * Set this to true if the view that is being dragged was already inflated in the layout. If this + * is set to {@code true}, {@link #alreadyInflatedView} must NOT be {@code null}. + * + *

If this is {@code false}, then {@link #newDragData} must be not be null. + */ + public boolean isAlreadyInflated; - /** - * The view that was already inflated and is being relocated. This must not be null if {@link - * #isAlreadyInflated} is {@code true}. - */ - public IView alreadyInflatedView; + /** + * The view that was already inflated and is being relocated. This must not be null if {@link + * #isAlreadyInflated} is {@code true}. + */ + public IView alreadyInflatedView; - /** The UIWidget that is being dragged. */ - public UIWidget newDragData; + /** The UIWidget that is being dragged. */ + public UIWidget newDragData; - private WidgetDragData() {} + private WidgetDragData() {} - public WidgetDragData( - boolean isAlreadyInflated, IView alreadyInflatedView, UIWidget newDragData) { - this.isAlreadyInflated = isAlreadyInflated; - this.alreadyInflatedView = alreadyInflatedView; - this.newDragData = newDragData; - } + public WidgetDragData( + boolean isAlreadyInflated, IView alreadyInflatedView, UIWidget newDragData) { + this.isAlreadyInflated = isAlreadyInflated; + this.alreadyInflatedView = alreadyInflatedView; + this.newDragData = newDragData; + } } diff --git a/app/src/main/java/com/itsaky/androidide/ui/WidgetDragListener.java b/app/src/main/java/com/itsaky/androidide/ui/WidgetDragListener.java index 5d2a9671a..bbdb32155 100755 --- a/app/src/main/java/com/itsaky/androidide/ui/WidgetDragListener.java +++ b/app/src/main/java/com/itsaky/androidide/ui/WidgetDragListener.java @@ -48,185 +48,182 @@ import org.jetbrains.annotations.Contract; */ public class WidgetDragListener implements View.OnDragListener { - private final IView placeholder; - private final IViewGroup viewGroup; - private final OnViewAddedListener addedListener; + private final IView placeholder; + private final IViewGroup viewGroup; + private final OnViewAddedListener addedListener; - private final int PLACEHOLDER_HEIGHT = 20; // in dp - private final int PLACEHOLDER_WIDTH = 40; // in dp + private final int PLACEHOLDER_HEIGHT = 20; // in dp + private final int PLACEHOLDER_WIDTH = 40; // in dp - public static final String ANDROID_NS = "android"; + public static final String ANDROID_NS = "android"; - private static final ILogger LOG = ILogger.newInstance("WidgetDragListener"); + private static final ILogger LOG = ILogger.newInstance("WidgetDragListener"); - public WidgetDragListener( - @NonNull Context context, IViewGroup viewGroup, OnViewAddedListener addedListener) { - this.placeholder = new UiView("android.view.View", new View(context), true); - this.viewGroup = viewGroup; - this.addedListener = addedListener; - this.placeholder.asView().setBackgroundResource(R.drawable.bg_widget_drag_placeholder); - this.placeholder - .asView() - .setLayoutParams( - new ViewGroup.LayoutParams( - SizeUtils.dp2px(PLACEHOLDER_WIDTH), - SizeUtils.dp2px(PLACEHOLDER_HEIGHT))); + public WidgetDragListener( + @NonNull Context context, IViewGroup viewGroup, OnViewAddedListener addedListener) { + this.placeholder = new UiView("android.view.View", new View(context), true); + this.viewGroup = viewGroup; + this.addedListener = addedListener; + this.placeholder.asView().setBackgroundResource(R.drawable.bg_widget_drag_placeholder); + this.placeholder + .asView() + .setLayoutParams( + new ViewGroup.LayoutParams( + SizeUtils.dp2px(PLACEHOLDER_WIDTH), SizeUtils.dp2px(PLACEHOLDER_HEIGHT))); + } + + @Override + public boolean onDrag(View v, @NonNull DragEvent event) { + + if (this.viewGroup instanceof ErrorUiView) { + return false; } - @Override - public boolean onDrag(View v, @NonNull DragEvent event) { + final var action = event.getAction(); + final var clipDesc = event.getClipDescription(); + final var dragData = (WidgetDragData) event.getLocalState(); - if (this.viewGroup instanceof ErrorUiView) { - return false; + var handled = true; + switch (action) { + case DragEvent.ACTION_DRAG_STARTED: + handled = clipDesc.hasMimeType(DesignerActivity.DRAGGING_WIDGET_MIME); + break; + case DragEvent.ACTION_DRAG_ENTERED: + case DragEvent.ACTION_DRAG_LOCATION: + this.placeholder.removeFromParent(); // If placeholder is added in another group, remove it. + this.viewGroup.addView(this.placeholder, findPlaceHolderIndex(event.getX(), event.getY())); + break; + case DragEvent.ACTION_DRAG_EXITED: + this.placeholder.removeFromParent(); + break; + case DragEvent.ACTION_DROP: + final var index = this.viewGroup.indexOfChild(this.placeholder); + this.placeholder.removeFromParent(); + + var view = + dragData.isAlreadyInflated + ? dragData.alreadyInflatedView + : createView(dragData.newDragData); + if (!dragData.isAlreadyInflated) { + applyBasicAttributes(view); } - final var action = event.getAction(); - final var clipDesc = event.getClipDescription(); - final var dragData = (WidgetDragData) event.getLocalState(); + // Remove from parent if it has a parent + view.removeFromParent(); + this.viewGroup.addView(view, index); - var handled = true; - switch (action) { - case DragEvent.ACTION_DRAG_STARTED: - handled = clipDesc.hasMimeType(DesignerActivity.DRAGGING_WIDGET_MIME); - break; - case DragEvent.ACTION_DRAG_ENTERED: - case DragEvent.ACTION_DRAG_LOCATION: - this.placeholder - .removeFromParent(); // If placeholder is added in another group, remove it. - this.viewGroup.addView( - this.placeholder, findPlaceHolderIndex(event.getX(), event.getY())); - break; - case DragEvent.ACTION_DRAG_EXITED: - this.placeholder.removeFromParent(); - break; - case DragEvent.ACTION_DROP: - final var index = this.viewGroup.indexOfChild(this.placeholder); - this.placeholder.removeFromParent(); - - var view = - dragData.isAlreadyInflated - ? dragData.alreadyInflatedView - : createView(dragData.newDragData); - if (!dragData.isAlreadyInflated) { - applyBasicAttributes(view); - } - - // Remove from parent if it has a parent - view.removeFromParent(); - this.viewGroup.addView(view, index); - - if (this.addedListener != null) { - this.addedListener.onViewAdded(view); - } - - break; + if (this.addedListener != null) { + this.addedListener.onViewAdded(view); } - return handled; + break; } - private void applyBasicAttributes(@NonNull IView view) { - view.addAttribute(layoutWidthAttr()); - view.addAttribute(layoutHeightAttr()); - } + return handled; + } - /** - * Finds the index at which the placeholder will be added in the current view group. - * - * @param x The x coordinate of the touch event - * @param y The y coordinate of the touch event - * @return The index at which the placeholder of the view should be added. The returned index is - * always valid index in the {@link #viewGroup}. - */ - private int findPlaceHolderIndex(final float x, final float y) { - final var count = this.viewGroup.getChildCount(); - for (int i = 0; i < this.viewGroup.getChildCount(); i++) { - final var child = this.viewGroup.getChildAt(i); - final var rect = getViewRect(child); - if (rect.contains(x, y)) { - final var top = topHalf(rect); - final var bottom = bottomHalf(rect); - if (top.contains(x, y)) { - return Math.max(0, i - 1); - } else if (bottom.contains(x, y)) { - return Math.min(count, i + 1); - } - } + private void applyBasicAttributes(@NonNull IView view) { + view.addAttribute(layoutWidthAttr()); + view.addAttribute(layoutHeightAttr()); + } + + /** + * Finds the index at which the placeholder will be added in the current view group. + * + * @param x The x coordinate of the touch event + * @param y The y coordinate of the touch event + * @return The index at which the placeholder of the view should be added. The returned index is + * always valid index in the {@link #viewGroup}. + */ + private int findPlaceHolderIndex(final float x, final float y) { + final var count = this.viewGroup.getChildCount(); + for (int i = 0; i < this.viewGroup.getChildCount(); i++) { + final var child = this.viewGroup.getChildAt(i); + final var rect = getViewRect(child); + if (rect.contains(x, y)) { + final var top = topHalf(rect); + final var bottom = bottomHalf(rect); + if (top.contains(x, y)) { + return Math.max(0, i - 1); + } else if (bottom.contains(x, y)) { + return Math.min(count, i + 1); } - - // If we don't find a suitable index, return the last index - return count; + } } - @NonNull - private RectF getViewRect(@NonNull IView view) { - final var v = view.asView(); + // If we don't find a suitable index, return the last index + return count; + } - final var rect = new RectF(); - rect.left = v.getLeft(); - rect.top = v.getTop(); - rect.right = rect.left + v.getWidth(); - rect.bottom = rect.top + v.getHeight(); + @NonNull + private RectF getViewRect(@NonNull IView view) { + final var v = view.asView(); - return rect; + final var rect = new RectF(); + rect.left = v.getLeft(); + rect.top = v.getTop(); + rect.right = rect.left + v.getWidth(); + rect.bottom = rect.top + v.getHeight(); + + return rect; + } + + private RectF topHalf(RectF src) { + final RectF result = new RectF(src); + result.bottom -= result.height() / 2; + return src; + } + + private RectF bottomHalf(RectF src) { + final RectF result = new RectF(src); + result.top += result.height() / 2; + return src; + } + + private RectF leftHalf(RectF src) { + final RectF result = new RectF(src); + result.right -= result.width() / 2; + return src; + } + + private RectF rightHalf(RectF src) { + final RectF result = new RectF(src); + result.left += result.width() / 2; + return src; + } + + @NonNull + @Contract(" -> new") + private IAttribute layoutHeightAttr() { + return new UiAttribute(INamespace.ANDROID, "layout_height", "wrap_content"); + } + + @NonNull + @Contract(" -> new") + private IAttribute layoutWidthAttr() { + return new UiAttribute(INamespace.ANDROID, "layout_width", "wrap_content"); + } + + @NonNull + private IView createView(@NonNull UIWidget widget) { + try { + final var clazz = widget.asClass(); + final var constructor = clazz.getConstructor(Context.class); + constructor.setAccessible(true); + final var instance = constructor.newInstance(this.viewGroup.asView().getContext()); + + if (instance instanceof ViewGroup) { + return new UiViewGroup(clazz.getName(), (ViewGroup) instance); + } else { + return new UiView(clazz.getName(), instance); + } + } catch (Throwable e) { + LOG.error("Unable to add widget", e); + throw new RuntimeException("Unable to add widget", e); } + } - private RectF topHalf(RectF src) { - final RectF result = new RectF(src); - result.bottom -= result.height() / 2; - return src; - } - - private RectF bottomHalf(RectF src) { - final RectF result = new RectF(src); - result.top += result.height() / 2; - return src; - } - - private RectF leftHalf(RectF src) { - final RectF result = new RectF(src); - result.right -= result.width() / 2; - return src; - } - - private RectF rightHalf(RectF src) { - final RectF result = new RectF(src); - result.left += result.width() / 2; - return src; - } - - @NonNull - @Contract(" -> new") - private IAttribute layoutHeightAttr() { - return new UiAttribute(INamespace.ANDROID, "layout_height", "wrap_content"); - } - - @NonNull - @Contract(" -> new") - private IAttribute layoutWidthAttr() { - return new UiAttribute(INamespace.ANDROID, "layout_width", "wrap_content"); - } - - @NonNull - private IView createView(@NonNull UIWidget widget) { - try { - final var clazz = widget.asClass(); - final var constructor = clazz.getConstructor(Context.class); - constructor.setAccessible(true); - final var instance = constructor.newInstance(this.viewGroup.asView().getContext()); - - if (instance instanceof ViewGroup) { - return new UiViewGroup(clazz.getName(), (ViewGroup) instance); - } else { - return new UiView(clazz.getName(), instance); - } - } catch (Throwable e) { - LOG.error("Unable to add widget", e); - throw new RuntimeException("Unable to add widget", e); - } - } - - public static interface OnViewAddedListener { - void onViewAdded(IView view); - } + public static interface OnViewAddedListener { + void onViewAdded(IView view); + } } diff --git a/app/src/main/java/com/itsaky/androidide/ui/WidgetDragShadowBuilder.java b/app/src/main/java/com/itsaky/androidide/ui/WidgetDragShadowBuilder.java index 30cfa5ca2..cc87c0d85 100755 --- a/app/src/main/java/com/itsaky/androidide/ui/WidgetDragShadowBuilder.java +++ b/app/src/main/java/com/itsaky/androidide/ui/WidgetDragShadowBuilder.java @@ -22,21 +22,21 @@ import android.view.View; public class WidgetDragShadowBuilder extends View.DragShadowBuilder { - private final double TOUCH_Y_OFFSET = 0.3; + private final double TOUCH_Y_OFFSET = 0.3; - public WidgetDragShadowBuilder(View view) { - super(view); - } + public WidgetDragShadowBuilder(View view) { + super(view); + } - @Override - public void onProvideShadowMetrics(Point outShadowSize, Point outShadowTouchPoint) { - if (getView() != null) { - final var width = getView().getWidth(); - final var height = getView().getHeight(); - outShadowSize.set(width, height); - outShadowTouchPoint.set(width / 2, height + (int) (height * TOUCH_Y_OFFSET)); - } else { - super.onProvideShadowMetrics(outShadowSize, outShadowTouchPoint); - } + @Override + public void onProvideShadowMetrics(Point outShadowSize, Point outShadowTouchPoint) { + if (getView() != null) { + final var width = getView().getWidth(); + final var height = getView().getHeight(); + outShadowSize.set(width, height); + outShadowTouchPoint.set(width / 2, height + (int) (height * TOUCH_Y_OFFSET)); + } else { + super.onProvideShadowMetrics(outShadowSize, outShadowTouchPoint); } + } } diff --git a/app/src/main/java/com/itsaky/androidide/ui/WidgetTouchListener.java b/app/src/main/java/com/itsaky/androidide/ui/WidgetTouchListener.java index 296a89258..bbfaf7caa 100644 --- a/app/src/main/java/com/itsaky/androidide/ui/WidgetTouchListener.java +++ b/app/src/main/java/com/itsaky/androidide/ui/WidgetTouchListener.java @@ -38,73 +38,73 @@ import com.itsaky.inflater.IView; * @author Akash Yadav */ public class WidgetTouchListener extends GestureDetector.SimpleOnGestureListener - implements View.OnTouchListener { + implements View.OnTouchListener { - private final GestureDetector mGestureDetector; - private final OnClickListener clickListener; - private final OnLongClickListener longClickListener; + private final GestureDetector mGestureDetector; + private final OnClickListener clickListener; + private final OnLongClickListener longClickListener; - private final IView mView; + private final IView mView; - public WidgetTouchListener( - @NonNull IView view, - @Nullable OnClickListener clickListener, - @Nullable OnLongClickListener longClickListener) { - this.mView = view; - this.mGestureDetector = new GestureDetector(view.asView().getContext(), this); - this.clickListener = clickListener; - this.longClickListener = longClickListener; + public WidgetTouchListener( + @NonNull IView view, + @Nullable OnClickListener clickListener, + @Nullable OnLongClickListener longClickListener) { + this.mView = view; + this.mGestureDetector = new GestureDetector(view.asView().getContext(), this); + this.clickListener = clickListener; + this.longClickListener = longClickListener; + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouch(View view, @NonNull MotionEvent motionEvent) { + return mGestureDetector.onTouchEvent(motionEvent); + } + + @Override + public boolean onDown(MotionEvent e) { + return true; // We would like to get notified about further events. + } + + // TODO Should we use onSingleTapConfirmed instead? + @Override + public boolean onSingleTapUp(MotionEvent e) { + if (this.clickListener != null) { + this.clickListener.onClick(this.mView); } - @SuppressLint("ClickableViewAccessibility") - @Override - public boolean onTouch(View view, @NonNull MotionEvent motionEvent) { - return mGestureDetector.onTouchEvent(motionEvent); + return true; + } + + @Override + public void onLongPress(MotionEvent e) { + + if (this.mView.getParent() == null) { + // This is the root layout so this should not be draggable + return; } - @Override - public boolean onDown(MotionEvent e) { - return true; // We would like to get notified about further events. + if (this.longClickListener != null && this.longClickListener.onLongClick(this.mView)) { + this.mView.asView().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); } + } - // TODO Should we use onSingleTapConfirmed instead? - @Override - public boolean onSingleTapUp(MotionEvent e) { - if (this.clickListener != null) { - this.clickListener.onClick(this.mView); - } + /** + * A listener which is invoked when we detect a single click event. + * + * @author Akash Yadav + */ + public interface OnClickListener { + void onClick(IView view); + } - return true; - } - - @Override - public void onLongPress(MotionEvent e) { - - if (this.mView.getParent() == null) { - // This is the root layout so this should not be draggable - return; - } - - if (this.longClickListener != null && this.longClickListener.onLongClick(this.mView)) { - this.mView.asView().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - } - } - - /** - * A listener which is invoked when we detect a single click event. - * - * @author Akash Yadav - */ - public interface OnClickListener { - void onClick(IView view); - } - - /** - * A listener which is invoked when we detect a long click event. - * - * @author Akash Yadav - */ - public interface OnLongClickListener { - boolean onLongClick(IView view); - } + /** + * A listener which is invoked when we detect a long click event. + * + * @author Akash Yadav + */ + public interface OnLongClickListener { + boolean onLongClick(IView view); + } } diff --git a/app/src/main/java/com/itsaky/androidide/ui/edit/BaseSuggestionProvider.java b/app/src/main/java/com/itsaky/androidide/ui/edit/BaseSuggestionProvider.java index e427c1255..7d4ad86ff 100644 --- a/app/src/main/java/com/itsaky/androidide/ui/edit/BaseSuggestionProvider.java +++ b/app/src/main/java/com/itsaky/androidide/ui/edit/BaseSuggestionProvider.java @@ -32,27 +32,27 @@ import java.util.regex.Pattern; */ public abstract class BaseSuggestionProvider implements IValueSuggestionProvider { - protected final File resDir; - protected final File layout; + protected final File resDir; + protected final File layout; - protected BaseSuggestionProvider(File layout) { - Objects.requireNonNull(layout); + protected BaseSuggestionProvider(File layout) { + Objects.requireNonNull(layout); - final var path = layout.getAbsolutePath(); - if (!Pattern.compile(".*/src/.*/res/layout/(\\w|_)+\\.xml").matcher(path).matches()) { - throw new IllegalArgumentException("Given layout file is not valid"); - } - - this.layout = layout; - this.resDir = Objects.requireNonNull(layout.getParentFile()).getParentFile(); - Objects.requireNonNull(this.resDir); + final var path = layout.getAbsolutePath(); + if (!Pattern.compile(".*/src/.*/res/layout/(\\w|_)+\\.xml").matcher(path).matches()) { + throw new IllegalArgumentException("Given layout file is not valid"); } - protected ValuesTable getValuesTable() { - return ValuesTableFactory.getTable(this.resDir); - } + this.layout = layout; + this.resDir = Objects.requireNonNull(layout.getParentFile()).getParentFile(); + Objects.requireNonNull(this.resDir); + } - protected IResourceTable getResourceTable() { - return StudioApp.getInstance().getResourceTable(); - } + protected ValuesTable getValuesTable() { + return ValuesTableFactory.getTable(this.resDir); + } + + protected IResourceTable getResourceTable() { + return StudioApp.getInstance().getResourceTable(); + } } diff --git a/app/src/main/java/com/itsaky/androidide/ui/edit/DimensionSuggestionProvider.java b/app/src/main/java/com/itsaky/androidide/ui/edit/DimensionSuggestionProvider.java index 7e3adc477..60bebb0fa 100644 --- a/app/src/main/java/com/itsaky/androidide/ui/edit/DimensionSuggestionProvider.java +++ b/app/src/main/java/com/itsaky/androidide/ui/edit/DimensionSuggestionProvider.java @@ -33,56 +33,56 @@ import java.util.List; */ public class DimensionSuggestionProvider extends BaseSuggestionProvider { - /** The dimension units that will be suggested. */ - private final List dimensions = new ArrayList<>(); + /** The dimension units that will be suggested. */ + private final List dimensions = new ArrayList<>(); - public DimensionSuggestionProvider(File layout) { - super(layout); + public DimensionSuggestionProvider(File layout) { + super(layout); - this.dimensions.add("dp"); - this.dimensions.add("sp"); - this.dimensions.add("px"); - this.dimensions.add("pt"); - this.dimensions.add("in"); - this.dimensions.add("mm"); - } + this.dimensions.add("dp"); + this.dimensions.add("sp"); + this.dimensions.add("px"); + this.dimensions.add("pt"); + this.dimensions.add("in"); + this.dimensions.add("mm"); + } - @Override - public boolean checkFormat(int format) { - return (format & Attr.DIMENSION) != 0; - } + @Override + public boolean checkFormat(int format) { + return (format & Attr.DIMENSION) != 0; + } - @Override - public @NonNull List suggest(IAttribute attribute, @NonNull String prefix) { - final var list = new ArrayList(); - if (prefix.length() > 0 && Character.isDigit(prefix.charAt(0))) { - // Fixed dimension values - // e.g. 10dp + @Override + public @NonNull List suggest(IAttribute attribute, @NonNull String prefix) { + final var list = new ArrayList(); + if (prefix.length() > 0 && Character.isDigit(prefix.charAt(0))) { + // Fixed dimension values + // e.g. 10dp - var val = extractDimensionVal(prefix); - for (var unit : this.dimensions) { - final var dimension = val + unit; - if (StringUtils.matchesFuzzy (dimension, prefix, true)) { - list.add(dimension); - } - } + var val = extractDimensionVal(prefix); + for (var unit : this.dimensions) { + final var dimension = val + unit; + if (StringUtils.matchesFuzzy(dimension, prefix, true)) { + list.add(dimension); } - - return list; + } } - private int extractDimensionVal(@NonNull String prefix) { - StringBuilder s = new StringBuilder(); - var i = 0; - while (i < prefix.length()) { - if (Character.isDigit(prefix.charAt(i))) { - s.append(prefix.charAt(i)); - } else { - break; - } - i++; - } + return list; + } - return Integer.parseInt(s.toString()); + private int extractDimensionVal(@NonNull String prefix) { + StringBuilder s = new StringBuilder(); + var i = 0; + while (i < prefix.length()) { + if (Character.isDigit(prefix.charAt(i))) { + s.append(prefix.charAt(i)); + } else { + break; + } + i++; } + + return Integer.parseInt(s.toString()); + } } diff --git a/app/src/main/java/com/itsaky/androidide/ui/edit/IValueSuggestionProvider.java b/app/src/main/java/com/itsaky/androidide/ui/edit/IValueSuggestionProvider.java index d145aad88..665f47207 100644 --- a/app/src/main/java/com/itsaky/androidide/ui/edit/IValueSuggestionProvider.java +++ b/app/src/main/java/com/itsaky/androidide/ui/edit/IValueSuggestionProvider.java @@ -30,22 +30,22 @@ import java.util.List; */ public interface IValueSuggestionProvider { - /** - * Check if the given format is supported by this provider. - * - * @param format The formats to check. - * @return true if the given formats contain a supported format, false - * otherwise. - */ - boolean checkFormat(int format); + /** + * Check if the given format is supported by this provider. + * + * @param format The formats to check. + * @return true if the given formats contain a supported format, false + * otherwise. + */ + boolean checkFormat(int format); - /** - * Provide suggestions for the given attribute. - * - * @param attribute The attribute to provide suggestions for. - * @param prefix The prefix (partial identifier) to filter the suggestions. - * @return The suggestion items. Must not be null. - */ - @NonNull - List suggest(IAttribute attribute, String prefix); + /** + * Provide suggestions for the given attribute. + * + * @param attribute The attribute to provide suggestions for. + * @param prefix The prefix (partial identifier) to filter the suggestions. + * @return The suggestion items. Must not be null. + */ + @NonNull + List suggest(IAttribute attribute, String prefix); } diff --git a/app/src/main/java/com/itsaky/androidide/utils/DialogUtils.java b/app/src/main/java/com/itsaky/androidide/utils/DialogUtils.java index ce858ee53..a03f3fdde 100644 --- a/app/src/main/java/com/itsaky/androidide/utils/DialogUtils.java +++ b/app/src/main/java/com/itsaky/androidide/utils/DialogUtils.java @@ -34,68 +34,68 @@ import org.jetbrains.annotations.Contract; */ public class DialogUtils { - /** - * Creates a new MaterialAlertDialogBuilder with the app's default style. - * - * @param context The context for the dialog builder. - * @return The new MaterialAlertDialogBuilder instance. - */ - @NonNull - @Contract("_ -> new") - public static MaterialAlertDialogBuilder newMaterialDialogBuilder(Context context) { - return new MaterialAlertDialogBuilder(context, R.style.AppTheme_MaterialAlertDialog); - } + /** + * Creates a new MaterialAlertDialogBuilder with the app's default style. + * + * @param context The context for the dialog builder. + * @return The new MaterialAlertDialogBuilder instance. + */ + @NonNull + @Contract("_ -> new") + public static MaterialAlertDialogBuilder newMaterialDialogBuilder(Context context) { + return new MaterialAlertDialogBuilder(context, R.style.AppTheme_MaterialAlertDialog); + } - /** - * Create a new alert dialog with two buttons: Yes and No. This method - * simply calls {@link #newYesNoDialog(Context, String, String, DialogInterface.OnClickListener, - * DialogInterface.OnClickListener)} with default values for title and message. - * - * @param context The context for the dialog. - * @param positiveClickListener A listener that will be invoked on the Yes button - * click. - * @param negativeClickListener A listener that will be invoked on the No button - * click. - * @return The newly created dialog. - */ - @NonNull - public static MaterialAlertDialogBuilder newYesNoDialog( - Context context, - DialogInterface.OnClickListener positiveClickListener, - DialogInterface.OnClickListener negativeClickListener) { - return newYesNoDialog( - context, - context.getString(R.string.msg_yesno_def_title), - context.getString(R.string.msg_yesno_def_message), - positiveClickListener, - negativeClickListener); - } + /** + * Create a new alert dialog with two buttons: Yes and No. This method + * simply calls {@link #newYesNoDialog(Context, String, String, DialogInterface.OnClickListener, + * DialogInterface.OnClickListener)} with default values for title and message. + * + * @param context The context for the dialog. + * @param positiveClickListener A listener that will be invoked on the Yes button + * click. + * @param negativeClickListener A listener that will be invoked on the No button + * click. + * @return The newly created dialog. + */ + @NonNull + public static MaterialAlertDialogBuilder newYesNoDialog( + Context context, + DialogInterface.OnClickListener positiveClickListener, + DialogInterface.OnClickListener negativeClickListener) { + return newYesNoDialog( + context, + context.getString(R.string.msg_yesno_def_title), + context.getString(R.string.msg_yesno_def_message), + positiveClickListener, + negativeClickListener); + } - /** - * Create a new alert dialog with two buttons: Yes and No. - * - * @param context The context for the dialog. - * @param title The title of the dialog. - * @param message The message of the dialog. - * @param positiveClickListener A listener that will be invoked on the Yes button - * click. - * @param negativeClickListener A listener that will be invoked on the No button - * click. - * @return The newly created dialog instance. - */ - @NonNull - public static MaterialAlertDialogBuilder newYesNoDialog( - Context context, - String title, - String message, - DialogInterface.OnClickListener positiveClickListener, - DialogInterface.OnClickListener negativeClickListener) { - final var builder = DialogUtils.newMaterialDialogBuilder(context); - builder.setTitle(title); - builder.setMessage(message); - builder.setPositiveButton(R.string.yes, positiveClickListener); - builder.setNegativeButton(R.string.no, negativeClickListener); + /** + * Create a new alert dialog with two buttons: Yes and No. + * + * @param context The context for the dialog. + * @param title The title of the dialog. + * @param message The message of the dialog. + * @param positiveClickListener A listener that will be invoked on the Yes button + * click. + * @param negativeClickListener A listener that will be invoked on the No button + * click. + * @return The newly created dialog instance. + */ + @NonNull + public static MaterialAlertDialogBuilder newYesNoDialog( + Context context, + String title, + String message, + DialogInterface.OnClickListener positiveClickListener, + DialogInterface.OnClickListener negativeClickListener) { + final var builder = DialogUtils.newMaterialDialogBuilder(context); + builder.setTitle(title); + builder.setMessage(message); + builder.setPositiveButton(R.string.yes, positiveClickListener); + builder.setNegativeButton(R.string.no, negativeClickListener); - return builder; - } + return builder; + } } diff --git a/app/src/main/java/com/itsaky/androidide/utils/EditorBottomSheetBehavior.java b/app/src/main/java/com/itsaky/androidide/utils/EditorBottomSheetBehavior.java index 2873fb93a..ce7919600 100644 --- a/app/src/main/java/com/itsaky/androidide/utils/EditorBottomSheetBehavior.java +++ b/app/src/main/java/com/itsaky/androidide/utils/EditorBottomSheetBehavior.java @@ -31,36 +31,36 @@ import com.itsaky.androidide.databinding.LayoutEditorBottomSheetBinding; public class EditorBottomSheetBehavior extends BottomSheetBehavior { - private LayoutEditorBottomSheetBinding binding; - private static final ILogger LOG = ILogger.newInstance("EditorBottomSheetBehavior"); + private LayoutEditorBottomSheetBinding binding; + private static final ILogger LOG = ILogger.newInstance("EditorBottomSheetBehavior"); - public EditorBottomSheetBehavior(Context context, AttributeSet attributeSet) { - super(context, attributeSet); - } + public EditorBottomSheetBehavior(Context context, AttributeSet attributeSet) { + super(context, attributeSet); + } - public void setBinding(LayoutEditorBottomSheetBinding binding) { - this.binding = binding; - } + public void setBinding(LayoutEditorBottomSheetBinding binding) { + this.binding = binding; + } - @Override - public boolean onInterceptTouchEvent( - @NonNull CoordinatorLayout parent, @NonNull V child, @NonNull MotionEvent event) { - if (binding != null) { - final var rect = getPagerRect(); - if (rect.contains(event.getX(), event.getY())) { - return false; - } - } - return super.onInterceptTouchEvent(parent, child, event); + @Override + public boolean onInterceptTouchEvent( + @NonNull CoordinatorLayout parent, @NonNull V child, @NonNull MotionEvent event) { + if (binding != null) { + final var rect = getPagerRect(); + if (rect.contains(event.getX(), event.getY())) { + return false; + } } + return super.onInterceptTouchEvent(parent, child, event); + } - @NonNull - private RectF getPagerRect() { - final var rect = new RectF(); - rect.left = binding.pager.getX(); - rect.top = binding.pager.getY(); - rect.right = rect.left + binding.pager.getWidth(); - rect.bottom = rect.top + binding.pager.getHeight(); - return rect; - } + @NonNull + private RectF getPagerRect() { + final var rect = new RectF(); + rect.left = binding.pager.getX(); + rect.top = binding.pager.getY(); + rect.right = rect.left + binding.pager.getWidth(); + rect.bottom = rect.top + binding.pager.getHeight(); + return rect; + } } diff --git a/app/src/main/java/com/itsaky/androidide/utils/HighlightRangeHelper.java b/app/src/main/java/com/itsaky/androidide/utils/HighlightRangeHelper.java index 7a6d96efc..838f7a9fa 100644 --- a/app/src/main/java/com/itsaky/androidide/utils/HighlightRangeHelper.java +++ b/app/src/main/java/com/itsaky/androidide/utils/HighlightRangeHelper.java @@ -44,113 +44,113 @@ import java.util.List; public class HighlightRangeHelper { - private final List highlights; + private final List highlights; - public HighlightRangeHelper(List highlights) { - this.highlights = highlights; + public HighlightRangeHelper(List highlights) { + this.highlights = highlights; + } + + public boolean isEnumType(int line, int column) { + return isOfKind(line, column, ENUM_TYPE); + } + + public boolean isAnnotationType(int line, int column) { + return isOfKind(line, column, ANNOTATION); + } + + public boolean isInterface(int line, int column) { + return isOfKind(line, column, INTERFACE); + } + + public boolean isEnum(int line, int column) { + return isOfKind(line, column, ENUM); + } + + public boolean isParameter(int line, int column) { + return isOfKind(line, column, PARAMETER); + } + + public boolean isExceptionParam(int line, int column) { + return isOfKind(line, column, EXCEPTION_PARAMETER); + } + + public boolean isConstructor(int line, int column) { + return isOfKind(line, column, CONSTRUCTOR); + } + + public boolean isStaticInit(int line, int column) { + return isOfKind(line, column, STATIC_INIT); + } + + public boolean isInstanceInit(int line, int column) { + return isOfKind(line, column, INSTANCE_INIT); + } + + public boolean isTypeParam(int line, int column) { + return isOfKind(line, column, TYPE_PARAMETER); + } + + public boolean isResourceVariable(int line, int column) { + return isOfKind(line, column, RESOURCE_VARIABLE); + } + + public boolean isPackageName(int line, int column) { + return isOfKind(line, column, PACKAGE_NAME); + } + + public boolean isClassName(int line, int column) { + return isOfKind(line, column, TYPE_NAME); + } + + public boolean isField(int line, int column) { + return isOfKind(line, column, FIELD); + } + + public boolean isStaticField(int line, int column) { + return isOfKind(line, column, STATIC_FIELD); + } + + public boolean isMethodDeclaration(int line, int column) { + return isOfKind(line, column, METHOD_DECLARATION); + } + + public boolean isMethodInvocation(int line, int column) { + return isOfKind(line, column, METHOD_INVOCATION); + } + + public boolean isLocal(int line, int column) { + return isOfKind(line, column, LOCAL_VARIABLE); + } + + /** + * Check if the token at the given index has the given token kind. + * + * @param line The line to check. + * @param column The column to check. + * @param kind The kind to check. + * @return {@code true} if the token at the given position has the given kind, {@code false} + * otherwise. + */ + public boolean isOfKind(int line, int column, HighlightTokenKind kind) { + final var position = new Position(line, column); + int left = 0; + int right = this.highlights.size(); + int mid; + while (left < right) { + mid = (left + right) / 2; + var token = this.highlights.get(mid); + var range = token.getRange(); + var pos = range.getStart().compareTo(position); + if (pos < 0) { + left = mid + 1; + } else if (pos > 0) { + right = mid - 1; + } else { + return token.getKind() == kind; + } } + return false; + } - public boolean isEnumType(int line, int column) { - return isOfKind(line, column, ENUM_TYPE); - } - - public boolean isAnnotationType(int line, int column) { - return isOfKind(line, column, ANNOTATION); - } - - public boolean isInterface(int line, int column) { - return isOfKind(line, column, INTERFACE); - } - - public boolean isEnum(int line, int column) { - return isOfKind(line, column, ENUM); - } - - public boolean isParameter(int line, int column) { - return isOfKind(line, column, PARAMETER); - } - - public boolean isExceptionParam(int line, int column) { - return isOfKind(line, column, EXCEPTION_PARAMETER); - } - - public boolean isConstructor(int line, int column) { - return isOfKind(line, column, CONSTRUCTOR); - } - - public boolean isStaticInit(int line, int column) { - return isOfKind(line, column, STATIC_INIT); - } - - public boolean isInstanceInit(int line, int column) { - return isOfKind(line, column, INSTANCE_INIT); - } - - public boolean isTypeParam(int line, int column) { - return isOfKind(line, column, TYPE_PARAMETER); - } - - public boolean isResourceVariable(int line, int column) { - return isOfKind(line, column, RESOURCE_VARIABLE); - } - - public boolean isPackageName(int line, int column) { - return isOfKind(line, column, PACKAGE_NAME); - } - - public boolean isClassName(int line, int column) { - return isOfKind(line, column, TYPE_NAME); - } - - public boolean isField(int line, int column) { - return isOfKind(line, column, FIELD); - } - - public boolean isStaticField(int line, int column) { - return isOfKind(line, column, STATIC_FIELD); - } - - public boolean isMethodDeclaration(int line, int column) { - return isOfKind(line, column, METHOD_DECLARATION); - } - - public boolean isMethodInvocation(int line, int column) { - return isOfKind(line, column, METHOD_INVOCATION); - } - - public boolean isLocal(int line, int column) { - return isOfKind(line, column, LOCAL_VARIABLE); - } - - /** - * Check if the token at the given index has the given token kind. - * - * @param line The line to check. - * @param column The column to check. - * @param kind The kind to check. - * @return {@code true} if the token at the given position has the given kind, {@code false} - * otherwise. - */ - public boolean isOfKind(int line, int column, HighlightTokenKind kind) { - final var position = new Position(line, column); - int left = 0; - int right = this.highlights.size(); - int mid; - while (left < right) { - mid = (left + right) / 2; - var token = this.highlights.get(mid); - var range = token.getRange(); - var pos = range.getStart().compareTo(position); - if (pos < 0) { - left = mid + 1; - } else if (pos > 0) { - right = mid - 1; - } else { - return token.getKind() == kind; - } - } - return false; - } - - private static final ILogger LOG = ILogger.newInstance("HighlightRangeHelper"); + private static final ILogger LOG = ILogger.newInstance("HighlightRangeHelper"); } diff --git a/app/src/main/java/com/itsaky/androidide/utils/JSONUtility.java b/app/src/main/java/com/itsaky/androidide/utils/JSONUtility.java index 316b267ec..140ea2fc8 100755 --- a/app/src/main/java/com/itsaky/androidide/utils/JSONUtility.java +++ b/app/src/main/java/com/itsaky/androidide/utils/JSONUtility.java @@ -22,13 +22,13 @@ import com.google.gson.GsonBuilder; import com.itsaky.androidide.tooling.api.util.ToolingApiLauncher; public class JSONUtility { - public static final Gson gson = new Gson(); - public static final Gson toolingGson; - public static final Gson prettyPrinter = new GsonBuilder().setPrettyPrinting().create(); + public static final Gson gson = new Gson(); + public static final Gson toolingGson; + public static final Gson prettyPrinter = new GsonBuilder().setPrettyPrinting().create(); - static { - final var builder = new GsonBuilder(); - ToolingApiLauncher.configureGson(builder); - toolingGson = builder.create(); - } + static { + final var builder = new GsonBuilder(); + ToolingApiLauncher.configureGson(builder); + toolingGson = builder.create(); + } } diff --git a/app/src/main/java/com/itsaky/androidide/utils/LSPUtils.java b/app/src/main/java/com/itsaky/androidide/utils/LSPUtils.java index b102dbf23..6454649fe 100755 --- a/app/src/main/java/com/itsaky/androidide/utils/LSPUtils.java +++ b/app/src/main/java/com/itsaky/androidide/utils/LSPUtils.java @@ -29,129 +29,129 @@ import java.util.List; public class LSPUtils { - public static final Position Position_ofZero; - public static final Range Range_ofZero; + public static final Position Position_ofZero; + public static final Range Range_ofZero; - static { - Position_ofZero = new Position(0, 0); - Range_ofZero = new Range(Position_ofZero, Position_ofZero); - } + static { + Position_ofZero = new Position(0, 0); + Range_ofZero = new Range(Position_ofZero, Position_ofZero); + } - public static boolean isEqual(Position p1, Position p2) { - if (p1 == null) return false; - if (p2 == null) return false; + public static boolean isEqual(Position p1, Position p2) { + if (p1 == null) return false; + if (p2 == null) return false; - return p1.getLine() == p2.getLine() && p1.getColumn() == p2.getColumn(); - } + return p1.getLine() == p2.getLine() && p1.getColumn() == p2.getColumn(); + } - public static boolean isInRange(Range range, Position position) { - final int line = position.getLine(); - final int column = position.getColumn(); - return isInRange(range, line, column); - } + public static boolean isInRange(Range range, Position position) { + final int line = position.getLine(); + final int column = position.getColumn(); + return isInRange(range, line, column); + } - public static boolean isInRange(Range range, int line, int column) { - final int startLine = range.getStart().getLine(); - final int startCol = range.getStart().getColumn(); - final int endLine = range.getEnd().getLine(); - final int endCol = range.getEnd().getColumn(); + public static boolean isInRange(Range range, int line, int column) { + final int startLine = range.getStart().getLine(); + final int startCol = range.getStart().getColumn(); + final int endLine = range.getEnd().getLine(); + final int endCol = range.getEnd().getColumn(); - return startLine <= line && line <= endLine && startCol <= column && column <= endCol; - } + return startLine <= line && line <= endLine && startCol <= column && column <= endCol; + } - public static boolean isEqual(Range r1, Range r2) { - if (r1 == null) return false; - if (r2 == null) return false; + public static boolean isEqual(Range r1, Range r2) { + if (r1 == null) return false; + if (r2 == null) return false; - return LSPUtils.isEqual(r1.getStart(), r2.getStart()) - && LSPUtils.isEqual(r1.getEnd(), r2.getEnd()); - } + return LSPUtils.isEqual(r1.getStart(), r2.getStart()) + && LSPUtils.isEqual(r1.getEnd(), r2.getEnd()); + } - public static DiagnosticItem newInfoDiagnostic( - int line, int column, int length, String message, String text) { - final var diag = new DiagnosticItem(); - diag.setCode("todo"); - diag.setMessage(message); - diag.setRange(getSingleLineRange(line, column, length)); - diag.setSeverity(DiagnosticSeverity.INFO); - diag.setSource(text); - return diag; - } + public static DiagnosticItem newInfoDiagnostic( + int line, int column, int length, String message, String text) { + final var diag = new DiagnosticItem(); + diag.setCode("todo"); + diag.setMessage(message); + diag.setRange(getSingleLineRange(line, column, length)); + diag.setSeverity(DiagnosticSeverity.INFO); + diag.setSource(text); + return diag; + } - public static DiagnosticItem newWarningDiagnostic( - int line, int column, int length, String message, String text) { - final var diag = new DiagnosticItem(); - diag.setCode("custom_warning"); - diag.setMessage(message); - diag.setRange(getSingleLineRange(line, column, length)); - diag.setSeverity(DiagnosticSeverity.WARNING); - diag.setSource(text); - return diag; - } + public static DiagnosticItem newWarningDiagnostic( + int line, int column, int length, String message, String text) { + final var diag = new DiagnosticItem(); + diag.setCode("custom_warning"); + diag.setMessage(message); + diag.setRange(getSingleLineRange(line, column, length)); + diag.setSeverity(DiagnosticSeverity.WARNING); + diag.setSource(text); + return diag; + } - public static Range getSingleLineRange(int line, int column, int length) { - final Range range = new Range(); - range.setStart(new Position(line, column)); - range.setEnd(new Position(line, column + length)); - return range; - } + public static Range getSingleLineRange(int line, int column, int length) { + final Range range = new Range(); + range.setStart(new Position(line, column)); + range.setEnd(new Position(line, column + length)); + return range; + } - public static int binarySearchStartPosition(List ranges, int line, int column) { - int left = 0, right = ranges.size() - 1; - while (left <= right) { - int mid = (left + right) / 2; - final Range range = ranges.get(mid); - final int l = range.getStart().getLine(); - final int c = range.getStart().getColumn(); + public static int binarySearchStartPosition(List ranges, int line, int column) { + int left = 0, right = ranges.size() - 1; + while (left <= right) { + int mid = (left + right) / 2; + final Range range = ranges.get(mid); + final int l = range.getStart().getLine(); + final int c = range.getStart().getColumn(); - // Compare lines - if (l < line) { - left = mid + 1; - } else if (l > line) { - right = mid - 1; - } else { - // Lines are same. Compare by columns - if (c < column) { - left = mid + 1; - } else if (c > column) { - right = mid - 1; - } else { - // Found! - return mid; - } - } + // Compare lines + if (l < line) { + left = mid + 1; + } else if (l > line) { + right = mid - 1; + } else { + // Lines are same. Compare by columns + if (c < column) { + left = mid + 1; + } else if (c > column) { + right = mid - 1; + } else { + // Found! + return mid; } - - return -1; + } } - public static final Comparator RANGE_START_COMPARATOR = - (r1, r2) -> { - // Check if ranges are null - if (r1 == null && r2 != null) { - return 1; - } else if (r1 != null && r2 == null) { - return -1; - } else if (r1 == null) { - return 0; - } else { - final int r1StartLine = r1.getStart().getLine(); - final int r1StartCol = r1.getStart().getColumn(); - final int r2StartLine = r2.getStart().getLine(); - final int r2StartCol = r2.getStart().getColumn(); + return -1; + } - // Compare by lines - if (r1StartLine < r2StartLine) { - return -1; - } else if (r1StartLine > r2StartLine) { - return 1; - } else { + public static final Comparator RANGE_START_COMPARATOR = + (r1, r2) -> { + // Check if ranges are null + if (r1 == null && r2 != null) { + return 1; + } else if (r1 != null && r2 == null) { + return -1; + } else if (r1 == null) { + return 0; + } else { + final int r1StartLine = r1.getStart().getLine(); + final int r1StartCol = r1.getStart().getColumn(); + final int r2StartLine = r2.getStart().getLine(); + final int r2StartCol = r2.getStart().getColumn(); - // Lines are same - // Compare by columns - // Both ranges have same start position - return Integer.compare(r1StartCol, r2StartCol); - } - } - }; + // Compare by lines + if (r1StartLine < r2StartLine) { + return -1; + } else if (r1StartLine > r2StartLine) { + return 1; + } else { + + // Lines are same + // Compare by columns + // Both ranges have same start position + return Integer.compare(r1StartCol, r2StartCol); + } + } + }; } diff --git a/app/src/main/java/com/itsaky/androidide/utils/ProjectWriter.java b/app/src/main/java/com/itsaky/androidide/utils/ProjectWriter.java index 570b00419..d089ad8d9 100755 --- a/app/src/main/java/com/itsaky/androidide/utils/ProjectWriter.java +++ b/app/src/main/java/com/itsaky/androidide/utils/ProjectWriter.java @@ -39,186 +39,184 @@ import java.util.regex.Pattern; public class ProjectWriter { - private static ProjectWriterCallback callback; + private static ProjectWriterCallback callback; - private static final String XML_TEMPLATE_PATH = "templates/xml"; + private static final String XML_TEMPLATE_PATH = "templates/xml"; - private static final String[] FILE_TO_CHANGE = { - "build.gradle", - "settings.gradle", - "app/src/main/AndroidManifest.xml", - "app/src/main/res/values/strings.xml", - "app/src/main/java/$package_name/MainActivity.java", - "app/src/main/java/$package_name/MainActivity.kt", - "app/build.gradle", - "app/src/main/java/$package_name/AndroidLauncher.java", - "app/res/values/strings.xml", - "core/build.gradle", - "core/src/main/java/$package_name/GameSuperClass.java", - "app/AndroidManifest.xml" - }; + private static final String[] FILE_TO_CHANGE = { + "build.gradle", + "settings.gradle", + "app/src/main/AndroidManifest.xml", + "app/src/main/res/values/strings.xml", + "app/src/main/java/$package_name/MainActivity.java", + "app/src/main/java/$package_name/MainActivity.kt", + "app/build.gradle", + "app/src/main/java/$package_name/AndroidLauncher.java", + "app/res/values/strings.xml", + "core/build.gradle", + "core/src/main/java/$package_name/GameSuperClass.java", + "app/AndroidManifest.xml" + }; - private static final String JAVA_PATH_REGEX = "/.*/src/.*/java|kt"; - private static final String APP_NAME = "$app_name", - PACKAGE_NAME = "$package_name", - MIN_SDK = "$min_sdk", - TARGET_SDK = "$target_sdk"; + private static final String JAVA_PATH_REGEX = "/.*/src/.*/java|kt"; + private static final String APP_NAME = "$app_name", + PACKAGE_NAME = "$package_name", + MIN_SDK = "$min_sdk", + TARGET_SDK = "$target_sdk"; - @NonNull - public static String createMenu() { - return ResourceUtils.readAssets2String(XML_TEMPLATE_PATH + "/menu.xml"); + @NonNull + public static String createMenu() { + return ResourceUtils.readAssets2String(XML_TEMPLATE_PATH + "/menu.xml"); + } + + @NonNull + public static String createDrawable() { + return ResourceUtils.readAssets2String(XML_TEMPLATE_PATH + "/drawable.xml"); + } + + @NonNull + public static String createLayout() { + return ResourceUtils.readAssets2String(XML_TEMPLATE_PATH + "/layout.xml"); + } + + public static String getPackageName(File parentPath) { + Matcher pkgMatcher = Pattern.compile(JAVA_PATH_REGEX).matcher(parentPath.getAbsolutePath()); + if (pkgMatcher.find()) { + int end = pkgMatcher.end(); + if (end <= 0) return ""; + String name = parentPath.getAbsolutePath().substring(pkgMatcher.end()); + if (name.startsWith(File.separator)) name = name.substring(1); + return name.replace(File.separator, "."); } + return null; + } - @NonNull - public static String createDrawable() { - return ResourceUtils.readAssets2String(XML_TEMPLATE_PATH + "/drawable.xml"); + public static String createJavaClass(String packageName, String className) { + return ClassBuilder.createClass(packageName, className); + } + + public static String createJavaInterface(String packageName, String className) { + return ClassBuilder.createInterface(packageName, className); + } + + public static String createJavaEnum(String packageName, String className) { + return ClassBuilder.createEnum(packageName, className); + } + + public static String createActivity(String packageName, String className) { + return ClassBuilder.createActivity(packageName, className); + } + + public static void write( + ProjectTemplate template, NewProjectDetails details, ProjectWriterCallback listener) + throws Exception { + write(template.getId(), details, listener); + } + + public static void write(int id, NewProjectDetails details, ProjectWriterCallback listener) + throws Exception { + callback = listener; + notifyBegin(); + final StudioApp instance = StudioApp.getInstance(); + final File temp = instance.getTempProjectDir(); + final File projectDir = new File(instance.getProjectsDir(), details.name); + if (projectDir.exists()) { + notifyFailed(instance.getString(R.string.project_exists)); + return; } + if (temp == null) notifyFailed(instance.getString(R.string.cannot_create_temp)); + if (!FileUtils.delete(temp) || !Environment.mkdirIfNotExits(temp).exists()) + notifyFailed(instance.getString(R.string.cannot_create_temp)); + notifyTask(instance.getString(R.string.copying_assets)); + projectDir.mkdirs(); + File destZip = new File(Environment.TMP_DIR, "templates/" + id + ".zip"); + Environment.mkdirIfNotExits(destZip.getParentFile()); + if (ResourceUtils.copyFileFromAssets( + "templates/" + destZip.getName(), destZip.getAbsolutePath())) { + ZipUtils.unzipFile(destZip, temp); + notifyTask(instance.getString(R.string.writing_files)); + for (String s : FILE_TO_CHANGE) { + File file = new File(temp, s); + if (file.exists() && s.contains(PACKAGE_NAME)) { + s = s.replace(PACKAGE_NAME, details.packageName.replace(".", "/")); + File f = new File(temp, s); + FileUtils.move(file, f); - @NonNull - public static String createLayout() { - return ResourceUtils.readAssets2String(XML_TEMPLATE_PATH + "/layout.xml"); - } - - public static String getPackageName(File parentPath) { - Matcher pkgMatcher = Pattern.compile(JAVA_PATH_REGEX).matcher(parentPath.getAbsolutePath()); - if (pkgMatcher.find()) { - int end = pkgMatcher.end(); - if (end <= 0) return ""; - String name = parentPath.getAbsolutePath().substring(pkgMatcher.end()); - if (name.startsWith(File.separator)) name = name.substring(1); - return name.replace(File.separator, "."); - } - return null; - } - - public static String createJavaClass(String packageName, String className) { - return ClassBuilder.createClass(packageName, className); - } - - public static String createJavaInterface(String packageName, String className) { - return ClassBuilder.createInterface(packageName, className); - } - - public static String createJavaEnum(String packageName, String className) { - return ClassBuilder.createEnum(packageName, className); - } - - public static String createActivity(String packageName, String className) { - return ClassBuilder.createActivity(packageName, className); - } - - public static void write( - ProjectTemplate template, NewProjectDetails details, ProjectWriterCallback listener) - throws Exception { - write(template.getId(), details, listener); - } - - public static void write(int id, NewProjectDetails details, ProjectWriterCallback listener) - throws Exception { - callback = listener; - notifyBegin(); - final StudioApp instance = StudioApp.getInstance(); - final File temp = instance.getTempProjectDir(); - final File projectDir = new File(instance.getProjectsDir(), details.name); - if (projectDir.exists()) { - notifyFailed(instance.getString(R.string.project_exists)); - return; - } - if (temp == null) notifyFailed(instance.getString(R.string.cannot_create_temp)); - if (!FileUtils.delete(temp) || !Environment.mkdirIfNotExits(temp).exists()) - notifyFailed(instance.getString(R.string.cannot_create_temp)); - notifyTask(instance.getString(R.string.copying_assets)); - projectDir.mkdirs(); - File destZip = new File(Environment.TMP_DIR, "templates/" + id + ".zip"); - Environment.mkdirIfNotExits(destZip.getParentFile()); - if (ResourceUtils.copyFileFromAssets( - "templates/" + destZip.getName(), destZip.getAbsolutePath())) { - ZipUtils.unzipFile(destZip, temp); - notifyTask(instance.getString(R.string.writing_files)); - for (String s : FILE_TO_CHANGE) { - File file = new File(temp, s); - if (file.exists() && s.contains(PACKAGE_NAME)) { - s = s.replace(PACKAGE_NAME, details.packageName.replace(".", "/")); - File f = new File(temp, s); - FileUtils.move(file, f); - - try { - File f2 = file.getParentFile(); - while (f2 != null && !f2.getName().contains(PACKAGE_NAME)) { - f2 = f2.getParentFile(); - } - - FileUtils.delete(f2); - } catch (Throwable t) { - // ignored - } - file = f; - } - if (file.exists()) { - String read = FileIOUtils.readFile2String(file); - read = - read.replace(APP_NAME, details.name) - .replace(PACKAGE_NAME, details.packageName) - .replace(MIN_SDK, String.valueOf(details.minSdk)) - .replace(TARGET_SDK, String.valueOf(details.targetSdk)); - if (!FileIOUtils.writeFileFromString(file, read, false)) { - notifyFailed( - instance.getString(R.string.failed_write_file, file.getName())); - } - } + try { + File f2 = file.getParentFile(); + while (f2 != null && !f2.getName().contains(PACKAGE_NAME)) { + f2 = f2.getParentFile(); } - notifyTask(instance.getString(R.string.copying_files)); - if (FileUtils.createOrExistsDir(projectDir)) { - boolean success = true; - final var files = temp == null ? null : temp.listFiles(); - if (files == null) { - success = false; - } else { - for (File f : files) { - if (!(success &= FileUtils.copy(f, new File(projectDir, f.getName())))) { - notifyFailed( - instance.getString(R.string.failed_write_file, f.getName())); - break; - } - } - } - if (success) { - notifySuccess(projectDir); - } - } else { - notifyFailed(instance.getString(R.string.failed_create_project_dir)); - } + FileUtils.delete(f2); + } catch (Throwable t) { + // ignored + } + file = f; + } + if (file.exists()) { + String read = FileIOUtils.readFile2String(file); + read = + read.replace(APP_NAME, details.name) + .replace(PACKAGE_NAME, details.packageName) + .replace(MIN_SDK, String.valueOf(details.minSdk)) + .replace(TARGET_SDK, String.valueOf(details.targetSdk)); + if (!FileIOUtils.writeFileFromString(file, read, false)) { + notifyFailed(instance.getString(R.string.failed_write_file, file.getName())); + } + } + } + notifyTask(instance.getString(R.string.copying_files)); + if (FileUtils.createOrExistsDir(projectDir)) { + boolean success = true; + final var files = temp == null ? null : temp.listFiles(); + if (files == null) { + success = false; } else { - notifyFailed(instance.getString(R.string.asset_copy_failed)); + for (File f : files) { + if (!(success &= FileUtils.copy(f, new File(projectDir, f.getName())))) { + notifyFailed(instance.getString(R.string.failed_write_file, f.getName())); + break; + } + } } - } - private static void notifyBegin() { - ThreadUtils.runOnUiThread( - () -> { - if (callback != null) callback.beforeBegin(); - }); + if (success) { + notifySuccess(projectDir); + } + } else { + notifyFailed(instance.getString(R.string.failed_create_project_dir)); + } + } else { + notifyFailed(instance.getString(R.string.asset_copy_failed)); } + } - private static void notifyTask(String name) { - ThreadUtils.runOnUiThread( - () -> { - if (callback != null) callback.onProcessTask(name); - }); - } + private static void notifyBegin() { + ThreadUtils.runOnUiThread( + () -> { + if (callback != null) callback.beforeBegin(); + }); + } - private static void notifySuccess(File root) { - ThreadUtils.runOnUiThread( - () -> { - if (callback != null) callback.onSuccess(root); - }); - } + private static void notifyTask(String name) { + ThreadUtils.runOnUiThread( + () -> { + if (callback != null) callback.onProcessTask(name); + }); + } - private static void notifyFailed(String reason) { - ThreadUtils.runOnUiThread( - () -> { - if (callback != null) callback.onFailed(reason); - }); - } + private static void notifySuccess(File root) { + ThreadUtils.runOnUiThread( + () -> { + if (callback != null) callback.onSuccess(root); + }); + } + + private static void notifyFailed(String reason) { + ThreadUtils.runOnUiThread( + () -> { + if (callback != null) callback.onFailed(reason); + }); + } } diff --git a/app/src/main/java/com/itsaky/androidide/utils/RecursiveFileSearcher.java b/app/src/main/java/com/itsaky/androidide/utils/RecursiveFileSearcher.java index cbf42e3e0..77315f1e5 100755 --- a/app/src/main/java/com/itsaky/androidide/utils/RecursiveFileSearcher.java +++ b/app/src/main/java/com/itsaky/androidide/utils/RecursiveFileSearcher.java @@ -46,124 +46,118 @@ import io.github.rosemoe.sora.text.Content; */ public class RecursiveFileSearcher { - private static final ILogger logger = ILogger.newInstance("RecursiveFileSearcher"); + private static final ILogger logger = ILogger.newInstance("RecursiveFileSearcher"); - /** - * Search the given text in files recursively in given search directories - * - * @param text Text to search - * @param exts Extentions of file to search. Maybe null. - * @param searchDirs Directories to search in. Subdirectories will be included - * @param callback A listener that will listen to the search result - */ - public static void searchRecursiveAsync( - String text, List exts, List searchDirs, Callback callback) { - // Cannot search empty or null text - if (text == null || text.isEmpty()) { - return; - } - - // If there is no listener to the search, search is meaningless - if (callback == null) { - return; - } - - // Avoid searching if no directories are specified - if (searchDirs == null || searchDirs.isEmpty()) { - return; - } - - new TaskExecutor() - .executeAsync( - new Searcher(text, exts, searchDirs), result -> callback.onResult(result)); + /** + * Search the given text in files recursively in given search directories + * + * @param text Text to search + * @param exts Extentions of file to search. Maybe null. + * @param searchDirs Directories to search in. Subdirectories will be included + * @param callback A listener that will listen to the search result + */ + public static void searchRecursiveAsync( + String text, List exts, List searchDirs, Callback callback) { + // Cannot search empty or null text + if (text == null || text.isEmpty()) { + return; } - private static class Searcher implements Callable>> { - - private final String query; - private final List exts; - private final List dirs; - - public Searcher(String query, List exts, List dirs) { - this.query = query; - this.exts = exts; - this.dirs = dirs; - } - - @Override - public Map> call() throws Exception { - final Map> result = new HashMap<>(); - for (int i = 0; i < dirs.size(); i++) { - final File dir = dirs.get(i); - final List files = - FileUtils.listFilesInDirWithFilter(dir, new MultiFileFilter(exts), true); - for (int j = 0; files != null && j < files.size(); j++) { - final File file = files.get(j); - if (file.isDirectory()) continue; - final String text = FileIOUtils.readFile2String(file); - if (text == null || text.trim().isEmpty()) continue; - final Content content = new Content(text); - final List ranges = new ArrayList<>(); - Matcher matcher = Pattern.compile(Pattern.quote(this.query)).matcher(text); - while (matcher.find()) { - final Range range = new Range(); - final CharPosition start = - content.getIndexer().getCharPosition(matcher.start()); - final CharPosition end = - content.getIndexer().getCharPosition(matcher.end()); - range.setStart(new Position(start.line, start.column)); - range.setEnd(new Position(end.line, end.column)); - String sub = - "..." - .concat( - text.substring( - Math.max(0, matcher.start() - 30), - Math.min( - matcher.end() + 31, text.length()))) - .trim() - .concat("..."); - String match = - content.subContent(start.line, start.column, end.line, end.column) - .toString(); - ranges.add( - new SearchResult(range, file, sub.replaceAll("\\s+", " "), match)); - } - if (ranges.size() > 0) { - result.put(file, ranges); - } - } - } - return result; - } + // If there is no listener to the search, search is meaningless + if (callback == null) { + return; } - private static class MultiFileFilter implements FileFilter { - - private final List exts; - - public MultiFileFilter(List exts) { - this.exts = exts; - } - - @Override - public boolean accept(File file) { - boolean accept = false; - if (exts == null || exts.isEmpty() || file.isDirectory()) { - accept = true; - } else { - for (String ext : exts) { - if (file.getName().endsWith(ext)) { - accept = true; - break; - } - } - } - - return accept && FileUtils.isUtf8(file); - } + // Avoid searching if no directories are specified + if (searchDirs == null || searchDirs.isEmpty()) { + return; } - public static interface Callback { - void onResult(Map> results); + new TaskExecutor() + .executeAsync(new Searcher(text, exts, searchDirs), result -> callback.onResult(result)); + } + + private static class Searcher implements Callable>> { + + private final String query; + private final List exts; + private final List dirs; + + public Searcher(String query, List exts, List dirs) { + this.query = query; + this.exts = exts; + this.dirs = dirs; } + + @Override + public Map> call() throws Exception { + final Map> result = new HashMap<>(); + for (int i = 0; i < dirs.size(); i++) { + final File dir = dirs.get(i); + final List files = + FileUtils.listFilesInDirWithFilter(dir, new MultiFileFilter(exts), true); + for (int j = 0; files != null && j < files.size(); j++) { + final File file = files.get(j); + if (file.isDirectory()) continue; + final String text = FileIOUtils.readFile2String(file); + if (text == null || text.trim().isEmpty()) continue; + final Content content = new Content(text); + final List ranges = new ArrayList<>(); + Matcher matcher = Pattern.compile(Pattern.quote(this.query)).matcher(text); + while (matcher.find()) { + final Range range = new Range(); + final CharPosition start = content.getIndexer().getCharPosition(matcher.start()); + final CharPosition end = content.getIndexer().getCharPosition(matcher.end()); + range.setStart(new Position(start.line, start.column)); + range.setEnd(new Position(end.line, end.column)); + String sub = + "..." + .concat( + text.substring( + Math.max(0, matcher.start() - 30), + Math.min(matcher.end() + 31, text.length()))) + .trim() + .concat("..."); + String match = + content.subContent(start.line, start.column, end.line, end.column).toString(); + ranges.add(new SearchResult(range, file, sub.replaceAll("\\s+", " "), match)); + } + if (ranges.size() > 0) { + result.put(file, ranges); + } + } + } + return result; + } + } + + private static class MultiFileFilter implements FileFilter { + + private final List exts; + + public MultiFileFilter(List exts) { + this.exts = exts; + } + + @Override + public boolean accept(File file) { + boolean accept = false; + if (exts == null || exts.isEmpty() || file.isDirectory()) { + accept = true; + } else { + for (String ext : exts) { + if (file.getName().endsWith(ext)) { + accept = true; + break; + } + } + } + + return accept && FileUtils.isUtf8(file); + } + } + + public static interface Callback { + void onResult(Map> results); + } } diff --git a/app/src/main/java/com/itsaky/androidide/utils/Symbols.java b/app/src/main/java/com/itsaky/androidide/utils/Symbols.java index b8e8f5b26..452e09966 100755 --- a/app/src/main/java/com/itsaky/androidide/utils/Symbols.java +++ b/app/src/main/java/com/itsaky/androidide/utils/Symbols.java @@ -26,83 +26,83 @@ import java.io.File; public class Symbols { - public static Symbol[] forFile(@Nullable File file) { - if (file == null) { - return new Symbol[0]; - } - - if (file.isFile()) { - if (file.getName().endsWith(".java") || file.getName().endsWith(".gradle")) - return javaSymbols(); - - if (file.getName().endsWith(".xml")) return xmlSymbols(); - } - - return new Symbol[0]; + public static Symbol[] forFile(@Nullable File file) { + if (file == null) { + return new Symbol[0]; } - public static String createTabSpaces() { - int size = StudioApp.getInstance().getPrefManager().getEditorTabSize(); - StringBuilder tab = new StringBuilder(); - for (int i = 1; i <= size; i++) { - tab.append(" "); - } - return tab.toString(); + if (file.isFile()) { + if (file.getName().endsWith(".java") || file.getName().endsWith(".gradle")) + return javaSymbols(); + + if (file.getName().endsWith(".xml")) return xmlSymbols(); } - public static Symbol[] javaSymbols() { - return new Symbol[] { - new Symbol("↹", "\t"), - new Symbol("{", "{}"), - new Symbol("}"), - new Symbol("(", "()"), - new Symbol(")"), - new Symbol(";"), - new Symbol("="), - new Symbol("\"", "\"\""), - new Symbol("|"), - new Symbol("&"), - new Symbol("!"), - new Symbol("[", "[]"), - new Symbol("]"), - new Symbol("<", "<>"), - new Symbol(">"), - new Symbol("+"), - new Symbol("-"), - new Symbol("/"), - new Symbol("*"), - new Symbol("?"), - new Symbol(":"), - new Symbol("_") - }; - } + return new Symbol[0]; + } - public static Symbol[] xmlSymbols() { - return new Symbol[] { - new Symbol("↹", "\t"), - new Symbol("<", "<>"), - new Symbol(">"), - new Symbol("/"), - new Symbol("="), - new Symbol("\"", "\"\""), - new Symbol(":"), - new Symbol("@"), - new Symbol("+"), - new Symbol("(", "()"), - new Symbol(")"), - new Symbol(";"), - new Symbol(","), - new Symbol("."), - new Symbol("?"), - new Symbol("|"), - new Symbol("\\"), - new Symbol("&"), - new Symbol("[", "[]"), - new Symbol("]"), - new Symbol("{", "{}"), - new Symbol("}"), - new Symbol("_"), - new Symbol("-") - }; + public static String createTabSpaces() { + int size = StudioApp.getInstance().getPrefManager().getEditorTabSize(); + StringBuilder tab = new StringBuilder(); + for (int i = 1; i <= size; i++) { + tab.append(" "); } + return tab.toString(); + } + + public static Symbol[] javaSymbols() { + return new Symbol[] { + new Symbol("↹", "\t"), + new Symbol("{", "{}"), + new Symbol("}"), + new Symbol("(", "()"), + new Symbol(")"), + new Symbol(";"), + new Symbol("="), + new Symbol("\"", "\"\""), + new Symbol("|"), + new Symbol("&"), + new Symbol("!"), + new Symbol("[", "[]"), + new Symbol("]"), + new Symbol("<", "<>"), + new Symbol(">"), + new Symbol("+"), + new Symbol("-"), + new Symbol("/"), + new Symbol("*"), + new Symbol("?"), + new Symbol(":"), + new Symbol("_") + }; + } + + public static Symbol[] xmlSymbols() { + return new Symbol[] { + new Symbol("↹", "\t"), + new Symbol("<", "<>"), + new Symbol(">"), + new Symbol("/"), + new Symbol("="), + new Symbol("\"", "\"\""), + new Symbol(":"), + new Symbol("@"), + new Symbol("+"), + new Symbol("(", "()"), + new Symbol(")"), + new Symbol(";"), + new Symbol(","), + new Symbol("."), + new Symbol("?"), + new Symbol("|"), + new Symbol("\\"), + new Symbol("&"), + new Symbol("[", "[]"), + new Symbol("]"), + new Symbol("{", "{}"), + new Symbol("}"), + new Symbol("_"), + new Symbol("-") + }; + } } diff --git a/app/src/main/java/com/itsaky/androidide/utils/TextWatcherAdapter.java b/app/src/main/java/com/itsaky/androidide/utils/TextWatcherAdapter.java index 7063d2f79..81b7ca762 100644 --- a/app/src/main/java/com/itsaky/androidide/utils/TextWatcherAdapter.java +++ b/app/src/main/java/com/itsaky/androidide/utils/TextWatcherAdapter.java @@ -27,12 +27,12 @@ import androidx.annotation.NonNull; */ public class TextWatcherAdapter implements TextWatcher { - @Override - public void beforeTextChanged(@NonNull CharSequence s, int start, int count, int after) {} + @Override + public void beforeTextChanged(@NonNull CharSequence s, int start, int count, int after) {} - @Override - public void onTextChanged(@NonNull CharSequence s, int start, int before, int count) {} + @Override + public void onTextChanged(@NonNull CharSequence s, int start, int before, int count) {} - @Override - public void afterTextChanged(@NonNull Editable s) {} + @Override + public void afterTextChanged(@NonNull Editable s) {} } diff --git a/app/src/main/java/com/itsaky/androidide/utils/TransformUtils.java b/app/src/main/java/com/itsaky/androidide/utils/TransformUtils.java index 0916a95c5..30aec1a30 100755 --- a/app/src/main/java/com/itsaky/androidide/utils/TransformUtils.java +++ b/app/src/main/java/com/itsaky/androidide/utils/TransformUtils.java @@ -27,19 +27,19 @@ import com.google.android.material.transition.MaterialContainerTransform; public class TransformUtils { - public static MaterialContainerTransform createContainerTransformFor( - View start, View end, View drawingView) { - MaterialContainerTransform transform = new MaterialContainerTransform(); - transform.setStartView(start); - transform.setEndView(end); - transform.addTarget(end); - transform.setDrawingViewId(drawingView.getId()); - transform.setAllContainerColors( - ContextCompat.getColor( - drawingView.getContext(), com.itsaky.androidide.R.color.primaryDarkColor)); - transform.setElevationShadowEnabled(true); - transform.setPathMotion(new MaterialArcMotion()); - transform.setScrimColor(Color.TRANSPARENT); - return transform; - } + public static MaterialContainerTransform createContainerTransformFor( + View start, View end, View drawingView) { + MaterialContainerTransform transform = new MaterialContainerTransform(); + transform.setStartView(start); + transform.setEndView(end); + transform.addTarget(end); + transform.setDrawingViewId(drawingView.getId()); + transform.setAllContainerColors( + ContextCompat.getColor( + drawingView.getContext(), com.itsaky.androidide.R.color.primaryDarkColor)); + transform.setElevationShadowEnabled(true); + transform.setPathMotion(new MaterialArcMotion()); + transform.setScrimColor(Color.TRANSPARENT); + return transform; + } } diff --git a/app/src/main/java/com/itsaky/androidide/viewmodel/EditorViewModel.java b/app/src/main/java/com/itsaky/androidide/viewmodel/EditorViewModel.java index 98a5117fa..84bf7df1e 100644 --- a/app/src/main/java/com/itsaky/androidide/viewmodel/EditorViewModel.java +++ b/app/src/main/java/com/itsaky/androidide/viewmodel/EditorViewModel.java @@ -33,107 +33,107 @@ import java.util.Objects; /** ViewModel for data used in {@link com.itsaky.androidide.EditorActivity} */ public class EditorViewModel extends ViewModel { - private final MutableLiveData> mFiles = new MutableLiveData<>(new ArrayList<>()); - private final MutableLiveData mFilesModified = new MutableLiveData<>(false); + private final MutableLiveData> mFiles = new MutableLiveData<>(new ArrayList<>()); + private final MutableLiveData mFilesModified = new MutableLiveData<>(false); - /** - * Holds information about the currently selected editor fragment. First value in the pair is - * the index of the editor opened. Second value is the file that is opened. - */ - private final MutableLiveData> mCurrentFile = new MutableLiveData<>(null); + /** + * Holds information about the currently selected editor fragment. First value in the pair is the + * index of the editor opened. Second value is the file that is opened. + */ + private final MutableLiveData> mCurrentFile = new MutableLiveData<>(null); - /** - * Add the given file to the list of opened files. - * - * @param file The file that has been opened. - */ - public void addFile(final File file) { - final var files = mFiles.getValue(); - Objects.requireNonNull(files).add(file); - mFiles.setValue(files); + /** + * Add the given file to the list of opened files. + * + * @param file The file that has been opened. + */ + public void addFile(final File file) { + final var files = mFiles.getValue(); + Objects.requireNonNull(files).add(file); + mFiles.setValue(files); + } + + /** + * Remove the file at the given index from the list of opened files. + * + * @param index The index of the closed file. + */ + public void removeFile(int index) { + final var files = mFiles.getValue(); + Objects.requireNonNull(files).remove(index); + mFiles.setValue(files); + } + + public void removeAllFiles() { + mFiles.setValue(new ArrayList<>()); + setCurrentFile(-1, null); + } + + public void setCurrentFile(final int index, @Nullable final File file) { + mCurrentFile.setValue(Pair.create(index, file)); + } + + /** + * Get the opened file at the given index. + * + * @param index The index of the file. + * @return The file at the given index. + */ + public File getOpenedFile(final int index) { + return Objects.requireNonNull(mFiles.getValue()).get(index); + } + + /** + * Get the number of files opened. + * + * @return The number of files opened. + */ + public int getOpenedFileCount() { + return Objects.requireNonNull(mFiles.getValue()).size(); + } + + /** + * Get the list of currently opened files. + * + * @return The list of opened files. + */ + @NonNull + public List getOpenedFiles() { + return mFiles.getValue() == null ? new ArrayList<>() : mFiles.getValue(); + } + + /** + * Add an observer to the list of opened files. + * + * @param lifecycleOwner The lifecycle owner. + * @param observer The observer. + */ + public void observeFiles(LifecycleOwner lifecycleOwner, Observer> observer) { + this.mFiles.observe(lifecycleOwner, observer); + } + + public int getCurrentFileIndex() { + if (mCurrentFile.getValue() == null) { + return -1; } - /** - * Remove the file at the given index from the list of opened files. - * - * @param index The index of the closed file. - */ - public void removeFile(int index) { - final var files = mFiles.getValue(); - Objects.requireNonNull(files).remove(index); - mFiles.setValue(files); + return mCurrentFile.getValue().first; + } + + public File getCurrentFile() { + if (mCurrentFile.getValue() == null) { + return null; } - public void removeAllFiles() { - mFiles.setValue(new ArrayList<>()); - setCurrentFile(-1, null); - } + return mCurrentFile.getValue().second; + } - public void setCurrentFile(final int index, @Nullable final File file) { - mCurrentFile.setValue(Pair.create(index, file)); - } + public void setFilesModified(boolean modified) { + this.mFilesModified.setValue(modified); + } - /** - * Get the opened file at the given index. - * - * @param index The index of the file. - * @return The file at the given index. - */ - public File getOpenedFile(final int index) { - return Objects.requireNonNull(mFiles.getValue()).get(index); - } - - /** - * Get the number of files opened. - * - * @return The number of files opened. - */ - public int getOpenedFileCount() { - return Objects.requireNonNull(mFiles.getValue()).size(); - } - - /** - * Get the list of currently opened files. - * - * @return The list of opened files. - */ - @NonNull - public List getOpenedFiles() { - return mFiles.getValue() == null ? new ArrayList<>() : mFiles.getValue(); - } - - /** - * Add an observer to the list of opened files. - * - * @param lifecycleOwner The lifecycle owner. - * @param observer The observer. - */ - public void observeFiles(LifecycleOwner lifecycleOwner, Observer> observer) { - this.mFiles.observe(lifecycleOwner, observer); - } - - public int getCurrentFileIndex() { - if (mCurrentFile.getValue() == null) { - return -1; - } - - return mCurrentFile.getValue().first; - } - - public File getCurrentFile() { - if (mCurrentFile.getValue() == null) { - return null; - } - - return mCurrentFile.getValue().second; - } - - public void setFilesModified(boolean modified) { - this.mFilesModified.setValue(modified); - } - - public boolean areFilesModified() { - final var modified = this.mFilesModified.getValue(); - return modified != null && modified; - } + public boolean areFilesModified() { + final var modified = this.mFilesModified.getValue(); + return modified != null && modified; + } } diff --git a/app/src/main/java/com/itsaky/androidide/views/DesignerLayoutContainer.java b/app/src/main/java/com/itsaky/androidide/views/DesignerLayoutContainer.java index a8a4e00c9..382f35f31 100644 --- a/app/src/main/java/com/itsaky/androidide/views/DesignerLayoutContainer.java +++ b/app/src/main/java/com/itsaky/androidide/views/DesignerLayoutContainer.java @@ -38,55 +38,53 @@ import com.itsaky.androidide.R; */ public class DesignerLayoutContainer extends LinearLayout { - private final TextView mMessage; + private final TextView mMessage; - public DesignerLayoutContainer(Context context) { - this(context, null); + public DesignerLayoutContainer(Context context) { + this(context, null); + } + + public DesignerLayoutContainer(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public DesignerLayoutContainer(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public DesignerLayoutContainer( + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + this.mMessage = new TextView(context); + this.mMessage.setText(context.getString(R.string.msg_empty_ui_layout)); + this.mMessage.setLayoutParams( + new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + this.mMessage.setGravity(Gravity.CENTER); + this.mMessage.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16); + } + + @Override + public void onViewAdded(View child) { + if (child != this.mMessage) { + removeView(this.mMessage); + } else { + super.onViewAdded(child); } + } - public DesignerLayoutContainer(Context context, @Nullable AttributeSet attrs) { - this(context, attrs, 0); + @Override + public void onViewRemoved(View child) { + if (child != this.mMessage && getChildCount() <= 0) { + addView(mMessage); + } else { + super.onViewRemoved(child); } + } - public DesignerLayoutContainer( - Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - this(context, attrs, defStyleAttr, 0); - } - - public DesignerLayoutContainer( - Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - - this.mMessage = new TextView(context); - this.mMessage.setText(context.getString(R.string.msg_empty_ui_layout)); - this.mMessage.setLayoutParams( - new LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); - this.mMessage.setGravity(Gravity.CENTER); - this.mMessage.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16); - } - - @Override - public void onViewAdded(View child) { - if (child != this.mMessage) { - removeView(this.mMessage); - } else { - super.onViewAdded(child); - } - } - - @Override - public void onViewRemoved(View child) { - if (child != this.mMessage && getChildCount() <= 0) { - addView(mMessage); - } else { - super.onViewRemoved(child); - } - } - - // This is needed by XMLLayoutInflater - @Override - protected LayoutParams generateDefaultLayoutParams() { - return super.generateDefaultLayoutParams(); - } + // This is needed by XMLLayoutInflater + @Override + protected LayoutParams generateDefaultLayoutParams() { + return super.generateDefaultLayoutParams(); + } } diff --git a/app/src/main/java/com/itsaky/androidide/views/EmptyView.java b/app/src/main/java/com/itsaky/androidide/views/EmptyView.java index 8185cf703..adad04919 100755 --- a/app/src/main/java/com/itsaky/androidide/views/EmptyView.java +++ b/app/src/main/java/com/itsaky/androidide/views/EmptyView.java @@ -30,38 +30,38 @@ import androidx.core.content.ContextCompat; public class EmptyView extends RelativeLayout { - public EmptyView(Context context) { - this(context, null); - } + public EmptyView(Context context) { + this(context, null); + } - public EmptyView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } + public EmptyView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } - public EmptyView(Context context, AttributeSet attrs, int defStyleAttr) { - this(context, attrs, defStyleAttr, 0); - } + public EmptyView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } - public EmptyView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); + public EmptyView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); - init(); - } + init(); + } - private void init() { + private void init() { - removeAllViews(); + removeAllViews(); - TextView text = new TextView(getContext()); - text.setText(com.itsaky.androidide.R.string.msg_empty_view); - text.setTextColor( - ContextCompat.getColor( - getContext(), com.itsaky.androidide.R.color.secondaryTextColor_light)); - text.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14); + TextView text = new TextView(getContext()); + text.setText(com.itsaky.androidide.R.string.msg_empty_view); + text.setTextColor( + ContextCompat.getColor( + getContext(), com.itsaky.androidide.R.color.secondaryTextColor_light)); + text.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14); - LayoutParams params = new LayoutParams(-2, -2); - params.addRule(CENTER_IN_PARENT); + LayoutParams params = new LayoutParams(-2, -2); + params.addRule(CENTER_IN_PARENT); - addView(text, params); - } + addView(text, params); + } } diff --git a/app/src/main/java/com/itsaky/androidide/views/MaterialBanner.java b/app/src/main/java/com/itsaky/androidide/views/MaterialBanner.java index 614f982d8..dd0c8a205 100755 --- a/app/src/main/java/com/itsaky/androidide/views/MaterialBanner.java +++ b/app/src/main/java/com/itsaky/androidide/views/MaterialBanner.java @@ -37,173 +37,173 @@ import com.itsaky.androidide.databinding.LayoutBannerBinding; public class MaterialBanner extends CoordinatorLayout { - private LayoutBannerBinding binding; - private OnPositiveClickListener positiveListener; - private OnNegativeClickListener negativeListener; + private LayoutBannerBinding binding; + private OnPositiveClickListener positiveListener; + private OnNegativeClickListener negativeListener; - private String contentText; - private String leftButtonText; - private String rightButtonText; - private Drawable iconDrawable; - private int backgroundColor; - private int contentTextColor; - private int buttonTextColor; + private String contentText; + private String leftButtonText; + private String rightButtonText; + private Drawable iconDrawable; + private int backgroundColor; + private int contentTextColor; + private int buttonTextColor; - public MaterialBanner(Context context) { - this(context, null); - } + public MaterialBanner(Context context) { + this(context, null); + } - public MaterialBanner(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } + public MaterialBanner(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } - public MaterialBanner(Context context, AttributeSet attrs, int style) { - super(context, attrs, style); - init(); - } + public MaterialBanner(Context context, AttributeSet attrs, int style) { + super(context, attrs, style); + init(); + } - private void init() { - binding = LayoutBannerBinding.inflate(LayoutInflater.from(getContext()), this, true); - } + private void init() { + binding = LayoutBannerBinding.inflate(LayoutInflater.from(getContext()), this, true); + } - public void show() { - this.binding.contentTextView.setText(contentText); - this.binding.contentIconView.setImageDrawable(iconDrawable); - this.binding.rightButton.setText(rightButtonText); - this.binding.leftButton.setText(leftButtonText); - this.binding.getRoot().setBackgroundColor(backgroundColor); - this.binding.contentTextView.setTextColor(contentTextColor); - this.binding.leftButton.setTextColor(buttonTextColor); - this.binding.rightButton.setTextColor(buttonTextColor); - this.binding.rightButton.setOnClickListener( - v -> { - if (positiveListener == null) dismiss(); - else { - positiveListener.onClick(MaterialBanner.this); - dismiss(); - } - }); - this.binding.leftButton.setOnClickListener( - v -> { - if (negativeListener == null) dismiss(); - else { - negativeListener.onClick(MaterialBanner.this); - dismiss(); - } - }); + public void show() { + this.binding.contentTextView.setText(contentText); + this.binding.contentIconView.setImageDrawable(iconDrawable); + this.binding.rightButton.setText(rightButtonText); + this.binding.leftButton.setText(leftButtonText); + this.binding.getRoot().setBackgroundColor(backgroundColor); + this.binding.contentTextView.setTextColor(contentTextColor); + this.binding.leftButton.setTextColor(buttonTextColor); + this.binding.rightButton.setTextColor(buttonTextColor); + this.binding.rightButton.setOnClickListener( + v -> { + if (positiveListener == null) dismiss(); + else { + positiveListener.onClick(MaterialBanner.this); + dismiss(); + } + }); + this.binding.leftButton.setOnClickListener( + v -> { + if (negativeListener == null) dismiss(); + else { + negativeListener.onClick(MaterialBanner.this); + dismiss(); + } + }); - final ConstraintLayout root = this.binding.getRoot(); - root.measure(-1, -2); - final int height = root.getMeasuredHeight(); - final LayoutParams p = (LayoutParams) root.getLayoutParams(); - p.height = 0; - root.setLayoutParams(p); - root.setVisibility(VISIBLE); - setVisibility(VISIBLE); + final ConstraintLayout root = this.binding.getRoot(); + root.measure(-1, -2); + final int height = root.getMeasuredHeight(); + final LayoutParams p = (LayoutParams) root.getLayoutParams(); + p.height = 0; + root.setLayoutParams(p); + root.setVisibility(VISIBLE); + setVisibility(VISIBLE); - Animation anim = - new Animation() { - @Override - public void applyTransformation(float time, Transformation t) { - p.height = time == 1f ? -2 : (int) (height * time); - root.setLayoutParams(p); - root.requestLayout(); - } + Animation anim = + new Animation() { + @Override + public void applyTransformation(float time, Transformation t) { + p.height = time == 1f ? -2 : (int) (height * time); + root.setLayoutParams(p); + root.requestLayout(); + } - @Override - public boolean willChangeBounds() { - return true; - } - }; + @Override + public boolean willChangeBounds() { + return true; + } + }; - anim.setDuration(200); - root.startAnimation(anim); - } + anim.setDuration(200); + root.startAnimation(anim); + } - public void dismiss() { - final ConstraintLayout root = this.binding.getRoot(); - final LayoutParams p = (LayoutParams) root.getLayoutParams(); - final int initialHeight = getMeasuredHeight(); - Animation animation = - new Animation() { - @Override - public void applyTransformation(float interpolatedTime, Transformation t) { - if (interpolatedTime == 1f) { - root.setVisibility(GONE); - setVisibility(GONE); - } else { - p.height = (int) (initialHeight - (initialHeight * interpolatedTime)); - root.requestLayout(); - } - } + public void dismiss() { + final ConstraintLayout root = this.binding.getRoot(); + final LayoutParams p = (LayoutParams) root.getLayoutParams(); + final int initialHeight = getMeasuredHeight(); + Animation animation = + new Animation() { + @Override + public void applyTransformation(float interpolatedTime, Transformation t) { + if (interpolatedTime == 1f) { + root.setVisibility(GONE); + setVisibility(GONE); + } else { + p.height = (int) (initialHeight - (initialHeight * interpolatedTime)); + root.requestLayout(); + } + } - @Override - public boolean willChangeBounds() { - return true; - } - }; - animation.setDuration((long) (initialHeight / getResources().getDisplayMetrics().density)); - root.startAnimation(animation); - } + @Override + public boolean willChangeBounds() { + return true; + } + }; + animation.setDuration((long) (initialHeight / getResources().getDisplayMetrics().density)); + root.startAnimation(animation); + } - public MaterialBanner setIcon(@DrawableRes int icon) { - return setIcon(ContextCompat.getDrawable(getContext(), icon)); - } + public MaterialBanner setIcon(@DrawableRes int icon) { + return setIcon(ContextCompat.getDrawable(getContext(), icon)); + } - public MaterialBanner setPositive(@StringRes int text, OnPositiveClickListener listener) { - return setPositive(getContext().getString(text), listener); - } + public MaterialBanner setPositive(@StringRes int text, OnPositiveClickListener listener) { + return setPositive(getContext().getString(text), listener); + } - public MaterialBanner setNegative(@StringRes int text, OnNegativeClickListener listener) { - return setNegative(getContext().getString(text), listener); - } + public MaterialBanner setNegative(@StringRes int text, OnNegativeClickListener listener) { + return setNegative(getContext().getString(text), listener); + } - public MaterialBanner setContentText(@StringRes int text) { - return setContentText(getContext().getString(text)); - } + public MaterialBanner setContentText(@StringRes int text) { + return setContentText(getContext().getString(text)); + } - public MaterialBanner setContentText(String contentText) { - this.contentText = contentText; - return this; - } + public MaterialBanner setContentText(String contentText) { + this.contentText = contentText; + return this; + } - public MaterialBanner setIcon(Drawable icon) { - this.iconDrawable = icon; - return this; - } + public MaterialBanner setIcon(Drawable icon) { + this.iconDrawable = icon; + return this; + } - public MaterialBanner setPositive(String text, OnPositiveClickListener listener) { - this.rightButtonText = text; - this.positiveListener = listener; - return this; - } + public MaterialBanner setPositive(String text, OnPositiveClickListener listener) { + this.rightButtonText = text; + this.positiveListener = listener; + return this; + } - public MaterialBanner setNegative(String text, OnNegativeClickListener listener) { - this.leftButtonText = text; - this.negativeListener = listener; - return this; - } + public MaterialBanner setNegative(String text, OnNegativeClickListener listener) { + this.leftButtonText = text; + this.negativeListener = listener; + return this; + } - public MaterialBanner setBannerBackgroundColor(int backgroundColor) { - this.backgroundColor = backgroundColor; - return this; - } + public MaterialBanner setBannerBackgroundColor(int backgroundColor) { + this.backgroundColor = backgroundColor; + return this; + } - public MaterialBanner setContentTextColor(int contentTextColor) { - this.contentTextColor = contentTextColor; - return this; - } + public MaterialBanner setContentTextColor(int contentTextColor) { + this.contentTextColor = contentTextColor; + return this; + } - public MaterialBanner setButtonTextColor(int buttonTextColor) { - this.buttonTextColor = buttonTextColor; - return this; - } + public MaterialBanner setButtonTextColor(int buttonTextColor) { + this.buttonTextColor = buttonTextColor; + return this; + } - public static interface OnPositiveClickListener { - public void onClick(MaterialBanner banner); - } + public static interface OnPositiveClickListener { + public void onClick(MaterialBanner banner); + } - public static interface OnNegativeClickListener { - public void onClick(MaterialBanner banner); - } + public static interface OnNegativeClickListener { + public void onClick(MaterialBanner banner); + } } diff --git a/app/src/main/java/com/itsaky/androidide/views/SymbolInputView.java b/app/src/main/java/com/itsaky/androidide/views/SymbolInputView.java index 540801d21..8d9b6f0dd 100755 --- a/app/src/main/java/com/itsaky/androidide/views/SymbolInputView.java +++ b/app/src/main/java/com/itsaky/androidide/views/SymbolInputView.java @@ -28,56 +28,54 @@ import com.itsaky.androidide.views.editor.IDEEditor; public class SymbolInputView extends RecyclerView { - private SymbolInputAdapter adapter; + private SymbolInputAdapter adapter; - public SymbolInputView(Context context) { - this(context, null); + public SymbolInputView(Context context) { + this(context, null); + } + + public SymbolInputView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public SymbolInputView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false)); + } + + public void bindEditor(IDEEditor editor) { + adapter = new SymbolInputAdapter(editor); + setAdapter(adapter); + setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false)); + } + + public void setSymbols(Symbol... symbols) { + if (adapter != null) { + adapter.setSymbols(true, symbols); + } + } + + public static class Symbol { + public String label; + public String commit; + public int offset; + + public Symbol(String both) { + this(both, 1); } - public SymbolInputView(Context context, AttributeSet attrs) { - this(context, attrs, 0); + public Symbol(String both, int offset) { + this(both, both, offset); } - public SymbolInputView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - setLayoutManager( - new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false)); + public Symbol(String label, String commit) { + this(label, commit, 1); } - public void bindEditor(IDEEditor editor) { - adapter = new SymbolInputAdapter(editor); - setAdapter(adapter); - setLayoutManager( - new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false)); - } - - public void setSymbols(Symbol... symbols) { - if (adapter != null) { - adapter.setSymbols(true, symbols); - } - } - - public static class Symbol { - public String label; - public String commit; - public int offset; - - public Symbol(String both) { - this(both, 1); - } - - public Symbol(String both, int offset) { - this(both, both, offset); - } - - public Symbol(String label, String commit) { - this(label, commit, 1); - } - - public Symbol(String label, String commit, int offset) { - this.label = label; - this.commit = commit; - this.offset = offset; - } + public Symbol(String label, String commit, int offset) { + this.label = label; + this.commit = commit; + this.offset = offset; } + } } diff --git a/app/src/main/java/com/itsaky/androidide/views/editor/BaseEditorWindow.java b/app/src/main/java/com/itsaky/androidide/views/editor/BaseEditorWindow.java index ba343bc1d..9d8f7bb39 100644 --- a/app/src/main/java/com/itsaky/androidide/views/editor/BaseEditorWindow.java +++ b/app/src/main/java/com/itsaky/androidide/views/editor/BaseEditorWindow.java @@ -40,81 +40,77 @@ import io.github.rosemoe.sora.widget.base.EditorPopupWindow; */ public abstract class BaseEditorWindow extends EditorPopupWindow { - protected final TextView text; + protected final TextView text; - /** - * Create a popup window for editor - * - * @param editor The editor - * @see #FEATURE_SCROLL_AS_CONTENT - * @see #FEATURE_SHOW_OUTSIDE_VIEW_ALLOWED - * @see #FEATURE_HIDE_WHEN_FAST_SCROLL - */ - public BaseEditorWindow(@NonNull IDEEditor editor) { - super(editor, getFeatureFlags()); + /** + * Create a popup window for editor + * + * @param editor The editor + * @see #FEATURE_SCROLL_AS_CONTENT + * @see #FEATURE_SHOW_OUTSIDE_VIEW_ALLOWED + * @see #FEATURE_HIDE_WHEN_FAST_SCROLL + */ + public BaseEditorWindow(@NonNull IDEEditor editor) { + super(editor, getFeatureFlags()); - this.text = onCreateTextView(editor); - setContentView(onCreateContentView(editor.getContext())); - } + this.text = onCreateTextView(editor); + setContentView(onCreateContentView(editor.getContext())); + } - private static int getFeatureFlags() { - return FEATURE_SCROLL_AS_CONTENT | FEATURE_SHOW_OUTSIDE_VIEW_ALLOWED; - } + private static int getFeatureFlags() { + return FEATURE_SCROLL_AS_CONTENT | FEATURE_SHOW_OUTSIDE_VIEW_ALLOWED; + } - protected View onCreateContentView(@NonNull Context context) { - return this.text; - } + protected View onCreateContentView(@NonNull Context context) { + return this.text; + } - protected TextView onCreateTextView(@NonNull IDEEditor editor) { - final var context = editor.getContext(); - final var dp4 = SizeUtils.dp2px(4); - final var dp8 = dp4 * 2; + protected TextView onCreateTextView(@NonNull IDEEditor editor) { + final var context = editor.getContext(); + final var dp4 = SizeUtils.dp2px(4); + final var dp8 = dp4 * 2; - final var text = new TextView(context); - text.setBackground(createBackground()); - text.setTextColor(ContextCompat.getColor(context, R.color.primaryTextColor)); - text.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14); - text.setClickable(false); - text.setFocusable(false); - text.setPaddingRelative(dp8, dp4, dp8, dp4); - text.setLayoutParams( - new ViewGroup.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - return text; - } + final var text = new TextView(context); + text.setBackground(createBackground()); + text.setTextColor(ContextCompat.getColor(context, R.color.primaryTextColor)); + text.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14); + text.setClickable(false); + text.setFocusable(false); + text.setPaddingRelative(dp8, dp4, dp8, dp4); + text.setLayoutParams( + new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + return text; + } - protected View getRootView() { - return this.text; - } + protected View getRootView() { + return this.text; + } - public void displayWindow() { - this.getRootView().measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); - this.setSize(this.getRootView().getMeasuredWidth(), this.getRootView().getMeasuredHeight()); + public void displayWindow() { + this.getRootView().measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); + this.setSize(this.getRootView().getMeasuredWidth(), this.getRootView().getMeasuredHeight()); - final var line = getEditor().getCursor().getLeftLine(); - final var column = getEditor().getCursor().getLeftColumn(); - int x = (int) ((getEditor().getOffset(line, column) - (getWidth() / 2))); - int y = - (int) (getEditor().getRowHeight() * line) - - getEditor().getOffsetY() - - getHeight() - - 5; - setLocationAbsolutely(x, y); - show(); - } + final var line = getEditor().getCursor().getLeftLine(); + final var column = getEditor().getCursor().getLeftColumn(); + int x = (int) ((getEditor().getOffset(line, column) - (getWidth() / 2))); + int y = (int) (getEditor().getRowHeight() * line) - getEditor().getOffsetY() - getHeight() - 5; + setLocationAbsolutely(x, y); + show(); + } - protected Drawable createBackground() { - GradientDrawable background = new GradientDrawable(); - background.setShape(GradientDrawable.RECTANGLE); - background.setColor(0xff212121); - background.setStroke(1, 0xffffffff); - background.setCornerRadius(8); - return background; - } + protected Drawable createBackground() { + GradientDrawable background = new GradientDrawable(); + background.setShape(GradientDrawable.RECTANGLE); + background.setColor(0xff212121); + background.setStroke(1, 0xffffffff); + background.setCornerRadius(8); + return background; + } - @NonNull - @Override - public IDEEditor getEditor() { - return (IDEEditor) super.getEditor(); - } + @NonNull + @Override + public IDEEditor getEditor() { + return (IDEEditor) super.getEditor(); + } } diff --git a/app/src/main/java/com/itsaky/androidide/views/editor/CodeEditorView.java b/app/src/main/java/com/itsaky/androidide/views/editor/CodeEditorView.java index 9abee6d85..89fb13f7f 100644 --- a/app/src/main/java/com/itsaky/androidide/views/editor/CodeEditorView.java +++ b/app/src/main/java/com/itsaky/androidide/views/editor/CodeEditorView.java @@ -86,356 +86,354 @@ import io.github.rosemoe.sora.widget.component.Magnifier; @SuppressLint("ViewConstructor") // This view is always dynamically created. public class CodeEditorView extends FrameLayout { - private static final ILogger LOG = ILogger.newInstance("CodeEditorView"); - private final File file; - private final LayoutCodeEditorBinding binding; - private boolean isModified; - private boolean isFirstCreate; + private static final ILogger LOG = ILogger.newInstance("CodeEditorView"); + private final File file; + private final LayoutCodeEditorBinding binding; + private boolean isModified; + private boolean isFirstCreate; - public CodeEditorView( - @NonNull Context context, @NonNull File file, final @NonNull Range selection) { - super(context); - this.file = file; - this.isModified = false; - this.isFirstCreate = true; + public CodeEditorView( + @NonNull Context context, @NonNull File file, final @NonNull Range selection) { + super(context); + this.file = file; + this.isModified = false; + this.isFirstCreate = true; - this.binding = LayoutCodeEditorBinding.inflate(LayoutInflater.from(context)); - this.binding.editor.setTypefaceText(TypefaceUtils.jetbrainsMono()); - this.binding.editor.setHighlightCurrentBlock(true); - this.binding.editor.getProps().autoCompletionOnComposing = true; - this.binding.editor.setAutoCompletionItemAdapter(new CompletionListAdapter()); - this.binding.editor.setDividerWidth(SizeUtils.dp2px(1)); - this.binding.editor.setColorScheme(new SchemeAndroidIDE()); - this.binding.editor.subscribeEvent( - ContentChangeEvent.class, ((event, unsubscribe) -> handleContentChange(event))); + this.binding = LayoutCodeEditorBinding.inflate(LayoutInflater.from(context)); + this.binding.editor.setTypefaceText(TypefaceUtils.jetbrainsMono()); + this.binding.editor.setHighlightCurrentBlock(true); + this.binding.editor.getProps().autoCompletionOnComposing = true; + this.binding.editor.setAutoCompletionItemAdapter(new CompletionListAdapter()); + this.binding.editor.setDividerWidth(SizeUtils.dp2px(1)); + this.binding.editor.setColorScheme(new SchemeAndroidIDE()); + this.binding.editor.subscribeEvent( + ContentChangeEvent.class, ((event, unsubscribe) -> handleContentChange(event))); - this.binding.diagnosticTextContainer.setVisibility(GONE); + this.binding.diagnosticTextContainer.setVisibility(GONE); - removeAllViews(); - addView(this.binding.getRoot()); + removeAllViews(); + addView(this.binding.getRoot()); - selection.validate(); - CompletableFuture.runAsync( - () -> { - final var contents = FileIOUtils.readFile2String(file); - binding.editor.post( - () -> { - binding.editor.setText(contents, createEditorArgs()); - postRead(); - if (LSPUtils.isEqual(selection.getStart(), selection.getEnd())) { - getEditor() - .setSelection( - selection.getStart().getLine(), - selection.getStart().getColumn()); - } else { - getEditor().setSelection(selection); - } - }); - }); - - configureEditorIfNeeded(); - } - - @NonNull - private Bundle createEditorArgs() { - var bundle = new Bundle(); - if (getFile() == null) { - return bundle; - } - - bundle.putString(IDEEditor.KEY_FILE, getFile().getAbsolutePath()); - return bundle; - } - - private void handleContentChange(@NonNull ContentChangeEvent event) { - isModified = true; - - if (event.getAction() == ContentChangeEvent.ACTION_INSERT) { - final var editor = event.getEditor(); - final var content = event.getChangedText(); - final var endLine = event.getChangeEnd().line; - final var endColumn = event.getChangeEnd().column; - if (file.getName().endsWith(".xml")) { - boolean isOpen = false; - try { - isOpen = editor.getText().charAt(editor.getCursor().getLeft() - 2) == '<'; - } catch (Throwable th) { - LOG.error(th); - } - - if (isOpen && "/".contentEquals(content)) { - closeCurrentTag(editor.getText().toString(), endLine, endColumn); - } - } - } - } - - public boolean save() { - final var file = getFile(); - if (file == null) { - LOG.error("Cannot save file. File instance is null."); - return false; - } - - if (!isModified() && file.exists()) { - LOG.info(file.getName()); - LOG.info("File was not modified. Skipping save operation."); - return false; - } - - final var text = getEditor().getText().toString(); - - try { - FileUtil.writeFile(file, text); - notifySaved(); - isModified = false; - return true; - } catch (IOException io) { - LOG.error("Failed to save file", file, io); - return false; - } - } - - @NonNull - private List getLines(Content text) { - final var count = text.getLineCount(); - final var result = new ArrayList(); - - for (int i = 0; i < count; i++) { - result.add(text.getLine(i)); - } - - return result; - } - - public IDEEditor getEditor() { - return binding.editor; - } - - public LayoutCodeEditorBinding getBinding() { - return binding; - } - - @Nullable - public File getFile() { - return file; - } - - public String getText() { - return binding.editor.getText().toString(); - } - - public boolean isModified() { - return isModified; - } - - public void onPause() { - // unimplemented - } - - public void onResume() { - configureEditorIfNeeded(); - } - - public void onEditorSelected() { - final var editor = getEditor(); - if (editor != null) { - editor.onEditorSelected(); - } - } - - public void undo() { - if (binding.editor.canUndo()) { - binding.editor.undo(); - } - } - - public void redo() { - if (binding.editor.canRedo()) { - binding.editor.redo(); - } - } - - public void findDefinition() { - binding.editor.findDefinition(); - } - - public void findReferences() { - binding.editor.findReferences(); - } - - public void commentLine() { - binding.editor.commentLine(); - } - - public void uncommentLine() { - binding.editor.uncommentLine(); - } - - public void beginSearch() { - binding.editor.beginSearchMode(); - } - - protected void postRead() { - if (file.isFile() && file.getName().endsWith(".java")) { - binding.editor.setEditorLanguage(new JavaLanguage()); - binding.editor.setLanguageServer(StudioApp.getInstance().getJavaLanguageServer()); - } else if (file.isFile() && file.getName().endsWith(".xml")) { - binding.editor.setEditorLanguage(new XMLLanguage()); - binding.editor.setLanguageServer(StudioApp.getInstance().getXMLLanguageServer()); - } else if (file.isFile() && file.getName().endsWith(".gradle")) { - binding.editor.setEditorLanguage(new GroovyLanguage()); - } else { - binding.editor.setEditorLanguage(new EmptyLanguage()); - } - - // File must be set only after setting the language server - // This will make sure that textDocument/didOpen is sent - binding.editor.setFile(getFile()); - - if (getContext() instanceof Activity) { - ((Activity) getContext()).invalidateOptionsMenu(); - } - } - - private void configureEditorIfNeeded() { - boolean sizeChanged = isFirstCreate || ConstantsBridge.EDITOR_PREF_SIZE_CHANGED; - boolean ligaturesChanged = isFirstCreate || ConstantsBridge.EDITOR_PREF_LIGATURES_CHANGED; - boolean flagsChanged = isFirstCreate || ConstantsBridge.EDITOR_PREF_FLAGS_CHANGED; - boolean drawHexChanged = isFirstCreate || ConstantsBridge.EDITOR_PREF_DRAW_HEX_CHANGED; - boolean inputFlagsChanged = - isFirstCreate || ConstantsBridge.EDITOR_PREF_VISIBLE_PASSWORD_CHANGED; - boolean wordWrapChanged = isFirstCreate || ConstantsBridge.EDITOR_PREF_WORD_WRAP_CHANGED; - boolean magnifierChanged = - isFirstCreate || ConstantsBridge.EDITOR_PREF_USE_MAGNIFIER_CHANGED; - - final PreferenceManager prefs = StudioApp.getInstance().getPrefManager(); - - if (sizeChanged) { - float textSize = prefs.getFloat(PreferenceManager.KEY_EDITOR_FONT_SIZE); - if (textSize < 6 || textSize > 32) { - textSize = 14; - } - - binding.editor.setTextSize(textSize); - ConstantsBridge.EDITOR_PREF_SIZE_CHANGED = false; - } - - if (ligaturesChanged) { - var enabled = prefs.getBoolean(PreferenceManager.KEY_EDITOR_FONT_LIGATURES, true); - binding.editor.setLigatureEnabled(enabled); - ConstantsBridge.EDITOR_PREF_LIGATURES_CHANGED = false; - } - - if (flagsChanged) { - int flags = 0; - if (prefs.getBoolean(PreferenceManager.KEY_EDITOR_FLAG_WS_LEADING, true)) { - flags |= IDEEditor.FLAG_DRAW_WHITESPACE_LEADING; - } - - if (prefs.getBoolean(PreferenceManager.KEY_EDITOR_FLAG_WS_TRAILING, false)) { - flags |= IDEEditor.FLAG_DRAW_WHITESPACE_TRAILING; - } - - if (prefs.getBoolean(PreferenceManager.KEY_EDITOR_FLAG_WS_INNER, true)) { - flags |= IDEEditor.FLAG_DRAW_WHITESPACE_INNER; - } - - if (prefs.getBoolean(PreferenceManager.KEY_EDITOR_FLAG_WS_EMPTY_LINE, true)) { - flags |= IDEEditor.FLAG_DRAW_WHITESPACE_FOR_EMPTY_LINE; - } - - if (prefs.getBoolean(PreferenceManager.KEY_EDITOR_FLAG_LINE_BREAK, true)) { - flags |= IDEEditor.FLAG_DRAW_LINE_SEPARATOR; - } - - binding.editor.setNonPrintablePaintingFlags(flags); - ConstantsBridge.EDITOR_PREF_FLAGS_CHANGED = false; - } - - if (inputFlagsChanged) { - binding.editor.setInputType(IDEEditor.createInputFlags()); - ConstantsBridge.EDITOR_PREF_VISIBLE_PASSWORD_CHANGED = false; - } - - if (drawHexChanged) { - // TODO Do something about this - // Maybe use spans... - // binding.editor.setLineColorsEnabled (prefs.getBoolean - // (PreferenceManager.KEY_EDITOR_DRAW_HEX, true)); - ConstantsBridge.EDITOR_PREF_DRAW_HEX_CHANGED = false; - } - - if (wordWrapChanged) { - var enabled = prefs.getBoolean(PreferenceManager.KEY_EDITOR_WORD_WRAP, false); - binding.editor.setWordwrap(enabled); - ConstantsBridge.EDITOR_PREF_WORD_WRAP_CHANGED = false; - } - - if (magnifierChanged) { - var enabled = prefs.getBoolean(PreferenceManager.KEY_EDITOR_USE_MAGNIFER, true); - binding.editor.getComponent(Magnifier.class).setEnabled(enabled); - ConstantsBridge.EDITOR_PREF_USE_MAGNIFIER_CHANGED = false; - } - - isFirstCreate = false; - } - - private void closeCurrentTag(String text, int line, int col) { - try { - XMLLexer lexer = new XMLLexer(CharStreams.fromReader(new StringReader(text))); - Token token; - boolean wasSlash = false, wasOpen = false; - ArrayList currentNames = new ArrayList<>(); - while (((token = lexer.nextToken()) != null && token.getType() != token.EOF)) { - final int type = token.getType(); - if (type == XMLLexer.OPEN) { - wasOpen = true; - } else if (type == XMLLexer.Name) { - if (wasOpen && wasSlash && currentNames.size() > 0) { - currentNames.remove(0); - } else if (wasOpen) { - currentNames.add(0, token.getText()); - wasOpen = false; - } - } else if (type == XMLLexer.OPEN_SLASH) { - int l = token.getLine() - 1; - int c = token.getCharPositionInLine(); - if (l == line && c == col) { - break; - } else if (currentNames.size() > 0) { - currentNames.remove(0); - } - } else if (type == XMLLexer.SLASH_CLOSE || type == XMLLexer.SPECIAL_CLOSE) { - if (currentNames.size() > 0 && token.getText().trim().endsWith("/>")) { - currentNames.remove(0); - } - } else if (type == XMLLexer.SLASH) { - wasSlash = true; + selection.validate(); + CompletableFuture.runAsync( + () -> { + final var contents = FileIOUtils.readFile2String(file); + binding.editor.post( + () -> { + binding.editor.setText(contents, createEditorArgs()); + postRead(); + if (LSPUtils.isEqual(selection.getStart(), selection.getEnd())) { + getEditor() + .setSelection( + selection.getStart().getLine(), selection.getStart().getColumn()); } else { - wasOpen = wasSlash = false; + getEditor().setSelection(selection); } - } + }); + }); - if (currentNames.size() > 0) { - binding.editor.getText().insert(line, col + 2, currentNames.get(0)); - } + configureEditorIfNeeded(); + } + + @NonNull + private Bundle createEditorArgs() { + var bundle = new Bundle(); + if (getFile() == null) { + return bundle; + } + + bundle.putString(IDEEditor.KEY_FILE, getFile().getAbsolutePath()); + return bundle; + } + + private void handleContentChange(@NonNull ContentChangeEvent event) { + isModified = true; + + if (event.getAction() == ContentChangeEvent.ACTION_INSERT) { + final var editor = event.getEditor(); + final var content = event.getChangedText(); + final var endLine = event.getChangeEnd().line; + final var endColumn = event.getChangeEnd().column; + if (file.getName().endsWith(".xml")) { + boolean isOpen = false; + try { + isOpen = editor.getText().charAt(editor.getCursor().getLeft() - 2) == '<'; } catch (Throwable th) { - LOG.error("Unable to close current tag", th); + LOG.error(th); } - } - private void notifySaved() { - binding.editor.didSave(); - - if (Objects.requireNonNull(getFile()).getName().endsWith(".xml")) { - ValuesTableFactory.syncWithFile(getFile()); + if (isOpen && "/".contentEquals(content)) { + closeCurrentTag(editor.getText().toString(), endLine, endColumn); } + } + } + } + + public boolean save() { + final var file = getFile(); + if (file == null) { + LOG.error("Cannot save file. File instance is null."); + return false; } - /** Mark this files as saved. Even if it not saved. */ - public void markAsSaved() { - isModified = false; - notifySaved(); + if (!isModified() && file.exists()) { + LOG.info(file.getName()); + LOG.info("File was not modified. Skipping save operation."); + return false; } + + final var text = getEditor().getText().toString(); + + try { + FileUtil.writeFile(file, text); + notifySaved(); + isModified = false; + return true; + } catch (IOException io) { + LOG.error("Failed to save file", file, io); + return false; + } + } + + @NonNull + private List getLines(Content text) { + final var count = text.getLineCount(); + final var result = new ArrayList(); + + for (int i = 0; i < count; i++) { + result.add(text.getLine(i)); + } + + return result; + } + + public IDEEditor getEditor() { + return binding.editor; + } + + public LayoutCodeEditorBinding getBinding() { + return binding; + } + + @Nullable + public File getFile() { + return file; + } + + public String getText() { + return binding.editor.getText().toString(); + } + + public boolean isModified() { + return isModified; + } + + public void onPause() { + // unimplemented + } + + public void onResume() { + configureEditorIfNeeded(); + } + + public void onEditorSelected() { + final var editor = getEditor(); + if (editor != null) { + editor.onEditorSelected(); + } + } + + public void undo() { + if (binding.editor.canUndo()) { + binding.editor.undo(); + } + } + + public void redo() { + if (binding.editor.canRedo()) { + binding.editor.redo(); + } + } + + public void findDefinition() { + binding.editor.findDefinition(); + } + + public void findReferences() { + binding.editor.findReferences(); + } + + public void commentLine() { + binding.editor.commentLine(); + } + + public void uncommentLine() { + binding.editor.uncommentLine(); + } + + public void beginSearch() { + binding.editor.beginSearchMode(); + } + + protected void postRead() { + if (file.isFile() && file.getName().endsWith(".java")) { + binding.editor.setEditorLanguage(new JavaLanguage()); + binding.editor.setLanguageServer(StudioApp.getInstance().getJavaLanguageServer()); + } else if (file.isFile() && file.getName().endsWith(".xml")) { + binding.editor.setEditorLanguage(new XMLLanguage()); + binding.editor.setLanguageServer(StudioApp.getInstance().getXMLLanguageServer()); + } else if (file.isFile() && file.getName().endsWith(".gradle")) { + binding.editor.setEditorLanguage(new GroovyLanguage()); + } else { + binding.editor.setEditorLanguage(new EmptyLanguage()); + } + + // File must be set only after setting the language server + // This will make sure that textDocument/didOpen is sent + binding.editor.setFile(getFile()); + + if (getContext() instanceof Activity) { + ((Activity) getContext()).invalidateOptionsMenu(); + } + } + + private void configureEditorIfNeeded() { + boolean sizeChanged = isFirstCreate || ConstantsBridge.EDITOR_PREF_SIZE_CHANGED; + boolean ligaturesChanged = isFirstCreate || ConstantsBridge.EDITOR_PREF_LIGATURES_CHANGED; + boolean flagsChanged = isFirstCreate || ConstantsBridge.EDITOR_PREF_FLAGS_CHANGED; + boolean drawHexChanged = isFirstCreate || ConstantsBridge.EDITOR_PREF_DRAW_HEX_CHANGED; + boolean inputFlagsChanged = + isFirstCreate || ConstantsBridge.EDITOR_PREF_VISIBLE_PASSWORD_CHANGED; + boolean wordWrapChanged = isFirstCreate || ConstantsBridge.EDITOR_PREF_WORD_WRAP_CHANGED; + boolean magnifierChanged = isFirstCreate || ConstantsBridge.EDITOR_PREF_USE_MAGNIFIER_CHANGED; + + final PreferenceManager prefs = StudioApp.getInstance().getPrefManager(); + + if (sizeChanged) { + float textSize = prefs.getFloat(PreferenceManager.KEY_EDITOR_FONT_SIZE); + if (textSize < 6 || textSize > 32) { + textSize = 14; + } + + binding.editor.setTextSize(textSize); + ConstantsBridge.EDITOR_PREF_SIZE_CHANGED = false; + } + + if (ligaturesChanged) { + var enabled = prefs.getBoolean(PreferenceManager.KEY_EDITOR_FONT_LIGATURES, true); + binding.editor.setLigatureEnabled(enabled); + ConstantsBridge.EDITOR_PREF_LIGATURES_CHANGED = false; + } + + if (flagsChanged) { + int flags = 0; + if (prefs.getBoolean(PreferenceManager.KEY_EDITOR_FLAG_WS_LEADING, true)) { + flags |= IDEEditor.FLAG_DRAW_WHITESPACE_LEADING; + } + + if (prefs.getBoolean(PreferenceManager.KEY_EDITOR_FLAG_WS_TRAILING, false)) { + flags |= IDEEditor.FLAG_DRAW_WHITESPACE_TRAILING; + } + + if (prefs.getBoolean(PreferenceManager.KEY_EDITOR_FLAG_WS_INNER, true)) { + flags |= IDEEditor.FLAG_DRAW_WHITESPACE_INNER; + } + + if (prefs.getBoolean(PreferenceManager.KEY_EDITOR_FLAG_WS_EMPTY_LINE, true)) { + flags |= IDEEditor.FLAG_DRAW_WHITESPACE_FOR_EMPTY_LINE; + } + + if (prefs.getBoolean(PreferenceManager.KEY_EDITOR_FLAG_LINE_BREAK, true)) { + flags |= IDEEditor.FLAG_DRAW_LINE_SEPARATOR; + } + + binding.editor.setNonPrintablePaintingFlags(flags); + ConstantsBridge.EDITOR_PREF_FLAGS_CHANGED = false; + } + + if (inputFlagsChanged) { + binding.editor.setInputType(IDEEditor.createInputFlags()); + ConstantsBridge.EDITOR_PREF_VISIBLE_PASSWORD_CHANGED = false; + } + + if (drawHexChanged) { + // TODO Do something about this + // Maybe use spans... + // binding.editor.setLineColorsEnabled (prefs.getBoolean + // (PreferenceManager.KEY_EDITOR_DRAW_HEX, true)); + ConstantsBridge.EDITOR_PREF_DRAW_HEX_CHANGED = false; + } + + if (wordWrapChanged) { + var enabled = prefs.getBoolean(PreferenceManager.KEY_EDITOR_WORD_WRAP, false); + binding.editor.setWordwrap(enabled); + ConstantsBridge.EDITOR_PREF_WORD_WRAP_CHANGED = false; + } + + if (magnifierChanged) { + var enabled = prefs.getBoolean(PreferenceManager.KEY_EDITOR_USE_MAGNIFER, true); + binding.editor.getComponent(Magnifier.class).setEnabled(enabled); + ConstantsBridge.EDITOR_PREF_USE_MAGNIFIER_CHANGED = false; + } + + isFirstCreate = false; + } + + private void closeCurrentTag(String text, int line, int col) { + try { + XMLLexer lexer = new XMLLexer(CharStreams.fromReader(new StringReader(text))); + Token token; + boolean wasSlash = false, wasOpen = false; + ArrayList currentNames = new ArrayList<>(); + while (((token = lexer.nextToken()) != null && token.getType() != token.EOF)) { + final int type = token.getType(); + if (type == XMLLexer.OPEN) { + wasOpen = true; + } else if (type == XMLLexer.Name) { + if (wasOpen && wasSlash && currentNames.size() > 0) { + currentNames.remove(0); + } else if (wasOpen) { + currentNames.add(0, token.getText()); + wasOpen = false; + } + } else if (type == XMLLexer.OPEN_SLASH) { + int l = token.getLine() - 1; + int c = token.getCharPositionInLine(); + if (l == line && c == col) { + break; + } else if (currentNames.size() > 0) { + currentNames.remove(0); + } + } else if (type == XMLLexer.SLASH_CLOSE || type == XMLLexer.SPECIAL_CLOSE) { + if (currentNames.size() > 0 && token.getText().trim().endsWith("/>")) { + currentNames.remove(0); + } + } else if (type == XMLLexer.SLASH) { + wasSlash = true; + } else { + wasOpen = wasSlash = false; + } + } + + if (currentNames.size() > 0) { + binding.editor.getText().insert(line, col + 2, currentNames.get(0)); + } + } catch (Throwable th) { + LOG.error("Unable to close current tag", th); + } + } + + private void notifySaved() { + binding.editor.didSave(); + + if (Objects.requireNonNull(getFile()).getName().endsWith(".xml")) { + ValuesTableFactory.syncWithFile(getFile()); + } + } + + /** Mark this files as saved. Even if it not saved. */ + public void markAsSaved() { + isModified = false; + notifySaved(); + } } diff --git a/app/src/main/java/com/itsaky/androidide/views/editor/DiagnosticWindow.java b/app/src/main/java/com/itsaky/androidide/views/editor/DiagnosticWindow.java index 4d2d88b74..7db13905f 100644 --- a/app/src/main/java/com/itsaky/androidide/views/editor/DiagnosticWindow.java +++ b/app/src/main/java/com/itsaky/androidide/views/editor/DiagnosticWindow.java @@ -29,35 +29,35 @@ import com.itsaky.lsp.models.DiagnosticItem; */ public class DiagnosticWindow extends BaseEditorWindow { - /** - * Create a popup window for editor - * - * @param editor The editor - * @see #FEATURE_SCROLL_AS_CONTENT - * @see #FEATURE_SHOW_OUTSIDE_VIEW_ALLOWED - * @see #FEATURE_HIDE_WHEN_FAST_SCROLL - */ - public DiagnosticWindow(@NonNull IDEEditor editor) { - super(editor); + /** + * Create a popup window for editor + * + * @param editor The editor + * @see #FEATURE_SCROLL_AS_CONTENT + * @see #FEATURE_SHOW_OUTSIDE_VIEW_ALLOWED + * @see #FEATURE_HIDE_WHEN_FAST_SCROLL + */ + public DiagnosticWindow(@NonNull IDEEditor editor) { + super(editor); + } + + /** + * Show the given diagnostic item. + * + * @param diagnostic The diagnostic item to show. + */ + public void showDiagnostic(@Nullable DiagnosticItem diagnostic) { + if (diagnostic == null) { + if (isShowing()) { + dismiss(); + } + + return; } - /** - * Show the given diagnostic item. - * - * @param diagnostic The diagnostic item to show. - */ - public void showDiagnostic(@Nullable DiagnosticItem diagnostic) { - if (diagnostic == null) { - if (isShowing()) { - dismiss(); - } + final var message = diagnostic.getMessage(); + this.text.setText(message); - return; - } - - final var message = diagnostic.getMessage(); - this.text.setText(message); - - displayWindow(); - } + displayWindow(); + } } diff --git a/app/src/main/java/com/itsaky/androidide/views/editor/IDEEditor.java b/app/src/main/java/com/itsaky/androidide/views/editor/IDEEditor.java index 37ba6f0cc..3e3b49ebc 100644 --- a/app/src/main/java/com/itsaky/androidide/views/editor/IDEEditor.java +++ b/app/src/main/java/com/itsaky/androidide/views/editor/IDEEditor.java @@ -70,814 +70,790 @@ import io.github.rosemoe.sora.widget.component.EditorTextActionWindow; public class IDEEditor extends CodeEditor { - public static final String KEY_FILE = "editor_file"; - private static final ILogger LOG = ILogger.newInstance("IDEEditor"); - private final EditorActionsMenu mActionsPopup; - private IDEEditorSearcher mSearcher; - private int mFileVersion; - private File file; - private ILanguageServer mLanguageServer; - private IDELanguageClientImpl mLanguageClient; - private SignatureHelpWindow mSignatureHelpWindow; - private DiagnosticWindow mDiagnosticWindow; + public static final String KEY_FILE = "editor_file"; + private static final ILogger LOG = ILogger.newInstance("IDEEditor"); + private final EditorActionsMenu mActionsPopup; + private IDEEditorSearcher mSearcher; + private int mFileVersion; + private File file; + private ILanguageServer mLanguageServer; + private IDELanguageClientImpl mLanguageClient; + private SignatureHelpWindow mSignatureHelpWindow; + private DiagnosticWindow mDiagnosticWindow; - public IDEEditor(Context context) { - this(context, null); + public IDEEditor(Context context) { + this(context, null); + } + + public IDEEditor(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public IDEEditor(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public IDEEditor(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + mActionsPopup = new EditorActionsMenu(this); + mActionsPopup.init(); + + setColorScheme(new SchemeAndroidIDE()); + setSearcher(new IDEEditorSearcher(this)); + getComponent(EditorTextActionWindow.class).setEnabled(false); + subscribeEvent(SelectionChangeEvent.class, this::handleSelectionChange); + subscribeEvent(ContentChangeEvent.class, this::handleContentChange); + + setInputType(createInputFlags()); + } + + private void handleSelectionChange(@NonNull SelectionChangeEvent event, Unsubscribe unsubscribe) { + if (event.isSelected() || !(getEditorLanguage() instanceof IDELanguage)) { + // do not show diagnostics when text is selected + // or if we cannot get diagnostics + return; } - public IDEEditor(Context context, AttributeSet attrs) { - this(context, attrs, 0); + final var diagnostics = ((IDELanguage) getEditorLanguage()).getDiagnostics(); + final var line = event.getLeft().line; + final var column = event.getLeft().column; + + // diagnostics are expected to be sorted, so, we do a binary search + final var diagnostic = binarySearchDiagnostic(diagnostics, line, column); + getDiagnosticWindow().showDiagnostic(diagnostic); + } + + /** + * Notify the language server that the content of this file has been changed. + * + * @param event The content change event. + */ + private void handleContentChange(ContentChangeEvent event, Unsubscribe unsubscribe) { + if (getContext() instanceof Activity) { + ((Activity) getContext()).invalidateOptionsMenu(); } - public IDEEditor(Context context, AttributeSet attrs, int defStyleAttr) { - this(context, attrs, defStyleAttr, 0); + if (getFile() == null || mLanguageServer == null) { + return; } - public IDEEditor(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - - mActionsPopup = new EditorActionsMenu(this); - mActionsPopup.init(); - - setColorScheme(new SchemeAndroidIDE()); - setSearcher(new IDEEditorSearcher(this)); - getComponent(EditorTextActionWindow.class).setEnabled(false); - subscribeEvent(SelectionChangeEvent.class, this::handleSelectionChange); - subscribeEvent(ContentChangeEvent.class, this::handleContentChange); - - setInputType(createInputFlags()); + final var documentHandler = mLanguageServer.getDocumentHandler(); + final var file = getFile().toPath(); + if (documentHandler.accepts(file)) { + documentHandler.onContentChange(new DocumentChangeEvent(file, getText(), mFileVersion + 1)); } - private void handleSelectionChange( - @NonNull SelectionChangeEvent event, Unsubscribe unsubscribe) { - if (event.isSelected() || !(getEditorLanguage() instanceof IDELanguage)) { - // do not show diagnostics when text is selected - // or if we cannot get diagnostics - return; - } + checkForSignatureHelp(event); + } - final var diagnostics = ((IDELanguage) getEditorLanguage()).getDiagnostics(); - final var line = event.getLeft().line; - final var column = event.getLeft().column; - - // diagnostics are expected to be sorted, so, we do a binary search - final var diagnostic = binarySearchDiagnostic(diagnostics, line, column); - getDiagnosticWindow().showDiagnostic(diagnostic); + public static int createInputFlags() { + var flags = + EditorInfo.TYPE_CLASS_TEXT + | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE + | EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS; + if (StudioApp.getInstance() + .getPrefManager() + .getBoolean(PreferenceManager.KEY_EDITOR_FLAG_PASSWORD, true)) { + flags |= EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD; } - /** - * Notify the language server that the content of this file has been changed. - * - * @param event The content change event. - */ - private void handleContentChange(ContentChangeEvent event, Unsubscribe unsubscribe) { - if (getContext() instanceof Activity) { - ((Activity) getContext()).invalidateOptionsMenu(); - } + return flags; + } - if (getFile() == null || mLanguageServer == null) { - return; - } + @Nullable + private DiagnosticItem binarySearchDiagnostic( + @NonNull List diagnostics, int line, int column) { + return DiagnosticUtil.binarySearchDiagnostic(diagnostics, line, column); + } - final var documentHandler = mLanguageServer.getDocumentHandler(); - final var file = getFile().toPath(); - if (documentHandler.accepts(file)) { - documentHandler.onContentChange( - new DocumentChangeEvent(file, getText(), mFileVersion + 1)); - } - - checkForSignatureHelp(event); + private DiagnosticWindow getDiagnosticWindow() { + if (mDiagnosticWindow == null) { + mDiagnosticWindow = new DiagnosticWindow(this); } - public static int createInputFlags() { - var flags = - EditorInfo.TYPE_CLASS_TEXT - | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE - | EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS; - if (StudioApp.getInstance() - .getPrefManager() - .getBoolean(PreferenceManager.KEY_EDITOR_FLAG_PASSWORD, true)) { - flags |= EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD; - } - - return flags; + return mDiagnosticWindow; + } + + /** + * Get the file that this editor is currently editing. + * + * @return The file instance. + */ + public File getFile() { + return file; + } + + /** + * Set the file that this editor is currently editing. + * + * @param file The file to set. + */ + public void setFile(File file) { + this.file = file; + + if (file != null && mLanguageServer != null) { + final var documentHandler = mLanguageServer.getDocumentHandler(); + if (documentHandler.accepts(file.toPath())) { + final var text = getText().toString(); + final var event = new DocumentOpenEvent(file.toPath(), text, mFileVersion = 0); + documentHandler.onFileOpened(event); + } + + // request diagnostics + analyze(); } - @Nullable - private DiagnosticItem binarySearchDiagnostic( - @NonNull List diagnostics, int line, int column) { - return DiagnosticUtil.binarySearchDiagnostic(diagnostics, line, column); + if (file != null) { + getExtraArguments().putString(KEY_FILE, file.getAbsolutePath()); + } else { + getExtraArguments().remove(KEY_FILE); + } + } + + public void analyze() { + if (mLanguageServer != null + && getFile() != null + && getEditorLanguage() instanceof IDELanguage) { + CompletableFuture.supplyAsync(() -> mLanguageServer.analyze(getFile().toPath())) + .whenComplete( + (diagnostics, throwable) -> { + final var lang = (IDELanguage) getEditorLanguage(); + final var analyzer = lang.getAnalyzeManager(); + if (analyzer instanceof IAnalyzeManager) { + ((IAnalyzeManager) analyzer).updateDiagnostics(diagnostics); + } + analyzer.rerun(); + }); + } + } + + /** + * Checks if the content change event should trigger signature help. Signature help trigger + * characters are : + * + *

    + *
  • '(' (parentheses) + *
  • ',' (comma) + *
+ * + * @param event The content change event. + */ + private void checkForSignatureHelp(@NonNull ContentChangeEvent event) { + if (event.getAction() != ContentChangeEvent.ACTION_INSERT + || event.getChangedText().length() != 1) { + return; } - private DiagnosticWindow getDiagnosticWindow() { - if (mDiagnosticWindow == null) { - mDiagnosticWindow = new DiagnosticWindow(this); - } - - return mDiagnosticWindow; + final var ch = event.getChangedText().charAt(0); + if (ch == '(' || ch == ',') { + signatureHelp(); } + } - /** - * Get the file that this editor is currently editing. - * - * @return The file instance. - */ - public File getFile() { - return file; - } + /** + * If any language server is set, requests signature help at the cursor's position. On a valid + * response, shows the signature help in a popup window. + */ + @SuppressWarnings("unused") // accessed using reflection in CompletionItem class + public void signatureHelp() { + if (mLanguageServer != null && getFile() != null) { + final CompletableFuture future = + CompletableFuture.supplyAsync( + () -> + mLanguageServer.signatureHelp( + new SignatureHelpParams( + getFile().toPath(), + new Position(getCursor().getLeftLine(), getCursor().getLeftColumn())))); - /** - * Set the file that this editor is currently editing. - * - * @param file The file to set. - */ - public void setFile(File file) { - this.file = file; - - if (file != null && mLanguageServer != null) { - final var documentHandler = mLanguageServer.getDocumentHandler(); - if (documentHandler.accepts(file.toPath())) { - final var text = getText().toString(); - final var event = new DocumentOpenEvent(file.toPath(), text, mFileVersion = 0); - documentHandler.onFileOpened(event); + future.whenComplete( + (help, error) -> { + if (help == null + || mLanguageClient == null + || future.isCancelled() + || future.isCompletedExceptionally()) { + LOG.error("An error occurred while finding signature help", error); + return; } - // request diagnostics - analyze(); - } + ThreadUtils.runOnUiThread(() -> showSignatureHelp(help)); + }); + } + } - if (file != null) { - getExtraArguments().putString(KEY_FILE, file.getAbsolutePath()); - } else { - getExtraArguments().remove(KEY_FILE); - } + /** + * Shows the given signature help in the editor. + * + * @param help The signature help data to show. + */ + public void showSignatureHelp(SignatureHelp help) { + getSignatureHelpWindow().setupAndDisplay(help); + } + + private SignatureHelpWindow getSignatureHelpWindow() { + if (mSignatureHelpWindow == null) { + mSignatureHelpWindow = new SignatureHelpWindow(this); } - public void analyze() { - if (mLanguageServer != null - && getFile() != null - && getEditorLanguage() instanceof IDELanguage) { - CompletableFuture.supplyAsync(() -> mLanguageServer.analyze(getFile().toPath())) - .whenComplete( - (diagnostics, throwable) -> { - final var lang = (IDELanguage) getEditorLanguage(); - final var analyzer = lang.getAnalyzeManager(); - if (analyzer instanceof IAnalyzeManager) { - ((IAnalyzeManager) analyzer).updateDiagnostics(diagnostics); - } - analyzer.rerun(); - }); - } + return mSignatureHelpWindow; + } + + /** + * Set the selection of this editor to the given position. + * + * @param position The position to select. + */ + public void setSelection(@NonNull Position position) { + setSelection(position.getLine(), position.getColumn()); + } + + /** + * Set the language server that this editor will connect with. If the language client is not set, + * it'll be set to {@link IDELanguageClientImpl}. + * + * @param server The server to set. Provide null to disable all the language server + * features. + */ + public void setLanguageServer(ILanguageServer server) { + this.mLanguageServer = server; + + if (mLanguageClient == null && IDELanguageClientImpl.isInitialized()) { + mLanguageClient = IDELanguageClientImpl.getInstance(); + } + } + + /** + * Append the given text at the end of the editor's content. + * + * @param text The text to append. + * @return The line at which the text was appended. + */ + public int append(CharSequence text) { + final var content = getText(); + if (getLineCount() <= 0) { + return 0; + } + final int line = getLineCount() - 1; + int col = content.getColumnCount(line); + if (col < 0) { + col = 0; + } + content.insert(line, col, text); + return line; + } + + /** Set the selection of the editor's cursor to the last line of the it's content. */ + public void goToEnd() { + final var line = getText().getLineCount() - 1; + setSelection(line, 0); + } + + /** Comment the line of the left cursor. */ + public void commentLine() { + if (getFile() == null) { + return; } - /** - * Checks if the content change event should trigger signature help. Signature help trigger - * characters are : - * - *
    - *
  • '(' (parentheses) - *
  • ',' (comma) - *
- * - * @param event The content change event. - */ - private void checkForSignatureHelp(@NonNull ContentChangeEvent event) { - if (event.getAction() != ContentChangeEvent.ACTION_INSERT - || event.getChangedText().length() != 1) { - return; + final var text = getText(); + final var name = getFile().getName(); + int line = getCursor().getLeftLine(); + if (name.endsWith(".java") || name.endsWith(".gradle")) { + while (line >= getCursor().getLeftLine() && line <= getCursor().getRightLine()) { + if (!text.getLineString(line).trim().startsWith("//")) { + text.insert(line, 0, "//"); } + line++; + } + } else if (name.endsWith(".xml")) { + while (line >= getCursor().getLeftLine() && line <= getCursor().getRightLine()) { + final String lineString = text.getLineString(line); + if (!lineString.trim().startsWith("")) { + text.replace( + line, 0, line, text.getColumnCount(line), "")); + } + line++; + } + } + } - final var ch = event.getChangedText().charAt(0); - if (ch == '(' || ch == ',') { - signatureHelp(); - } + /** Uncomment the current line */ + public void uncommentLine() { + if (getFile() == null) { + return; } - /** - * If any language server is set, requests signature help at the cursor's position. On a valid - * response, shows the signature help in a popup window. - */ - @SuppressWarnings("unused") // accessed using reflection in CompletionItem class - public void signatureHelp() { - if (mLanguageServer != null && getFile() != null) { - final CompletableFuture future = - CompletableFuture.supplyAsync( - () -> - mLanguageServer.signatureHelp( - new SignatureHelpParams( - getFile().toPath(), - new Position( - getCursor().getLeftLine(), - getCursor().getLeftColumn())))); - - future.whenComplete( - (help, error) -> { - if (help == null - || mLanguageClient == null - || future.isCancelled() - || future.isCompletedExceptionally()) { - LOG.error("An error occurred while finding signature help", error); - return; - } - - ThreadUtils.runOnUiThread(() -> showSignatureHelp(help)); - }); + final var text = getText(); + final String name = getFile().getName(); + int line = getCursor().getLeftLine(); + if (name.endsWith(".java") || name.endsWith(".gradle")) { + while (line >= getCursor().getLeftLine() && line <= getCursor().getRightLine()) { + String l = text.getLineString(line); + if (l.trim().startsWith("//")) { + int i = l.indexOf("//"); + text.delete(line, i, line, i + 2); } - } - - /** - * Shows the given signature help in the editor. - * - * @param help The signature help data to show. - */ - public void showSignatureHelp(SignatureHelp help) { - getSignatureHelpWindow().setupAndDisplay(help); - } - - private SignatureHelpWindow getSignatureHelpWindow() { - if (mSignatureHelpWindow == null) { - mSignatureHelpWindow = new SignatureHelpWindow(this); + line++; + } + } else if (name.endsWith(".xml")) { + final String commentStart = ""; + while (line >= getCursor().getLeftLine() && line <= getCursor().getRightLine()) { + String l = text.getLineString(line); + if (l.trim().startsWith(commentStart)) { + int i = l.indexOf(commentStart); + text.delete(line, i, line, i + commentStart.length()); } - - return mSignatureHelpWindow; - } - - /** - * Set the selection of this editor to the given position. - * - * @param position The position to select. - */ - public void setSelection(@NonNull Position position) { - setSelection(position.getLine(), position.getColumn()); - } - - /** - * Set the language server that this editor will connect with. If the language client is not - * set, it'll be set to {@link IDELanguageClientImpl}. - * - * @param server The server to set. Provide null to disable all the language server - * features. - */ - public void setLanguageServer(ILanguageServer server) { - this.mLanguageServer = server; - - if (mLanguageClient == null && IDELanguageClientImpl.isInitialized()) { - mLanguageClient = IDELanguageClientImpl.getInstance(); + if (l.trim().endsWith(commentEnd)) { + int count = text.getColumnCount(line); + text.delete(line, count - commentEnd.length(), line, count); } + line++; + } + } + } + + /** + * If any language server is set, asks the language server to find the definition of token at the + * cursor position. + * + *

If the server returns a valid response, and the file specified in the response is same the + * file in this editor, the range specified in the response will be selected. + */ + @SuppressWarnings("deprecation") + public void findDefinition() { + if (getFile() == null) { + return; } - /** - * Append the given text at the end of the editor's content. - * - * @param text The text to append. - * @return The line at which the text was appended. - */ - public int append(CharSequence text) { - final var content = getText(); - if (getLineCount() <= 0) { - return 0; - } - final int line = getLineCount() - 1; - int col = content.getColumnCount(line); - if (col < 0) { - col = 0; - } - content.insert(line, col, text); - return line; - } + final ProgressDialog pd = + ProgressDialog.show( + getContext(), null, getContext().getString(R.string.msg_finding_definition)); - /** Set the selection of the editor's cursor to the last line of the it's content. */ - public void goToEnd() { - final var line = getText().getLineCount() - 1; - setSelection(line, 0); - } + try { + final CompletableFuture future = + CompletableFuture.supplyAsync( + () -> { + final var params = + new com.itsaky.lsp.models.DefinitionParams( + getFile().toPath(), + new com.itsaky.lsp.models.Position( + getCursor().getLeftLine(), getCursor().getLeftColumn())); - /** Comment the line of the left cursor. */ - public void commentLine() { - if (getFile() == null) { - return; - } + return mLanguageServer.findDefinition(params); + }); - final var text = getText(); - final var name = getFile().getName(); - int line = getCursor().getLeftLine(); - if (name.endsWith(".java") || name.endsWith(".gradle")) { - while (line >= getCursor().getLeftLine() && line <= getCursor().getRightLine()) { - if (!text.getLineString(line).trim().startsWith("//")) { - text.insert(line, 0, "//"); - } - line++; + future.whenComplete( + (result, error) -> { + if (result == null + || mLanguageClient == null + || future.isCancelled() + || future.isCompletedExceptionally()) { + LOG.error("An error occurred while finding definition", error); + showDefinitionNotFound(pd); + return; } - } else if (name.endsWith(".xml")) { - while (line >= getCursor().getLeftLine() && line <= getCursor().getRightLine()) { - final String lineString = text.getLineString(line); - if (!lineString.trim().startsWith("")) { - text.replace( - line, - 0, - line, - text.getColumnCount(line), - "")); - } - line++; + + final var locations = result.getLocations(); + if (locations.size() <= 0) { + LOG.error("No definitions found", "Size:", locations.size()); + showDefinitionNotFound(pd); + return; } - } - } - /** Uncomment the current line */ - public void uncommentLine() { - if (getFile() == null) { - return; - } - - final var text = getText(); - final String name = getFile().getName(); - int line = getCursor().getLeftLine(); - if (name.endsWith(".java") || name.endsWith(".gradle")) { - while (line >= getCursor().getLeftLine() && line <= getCursor().getRightLine()) { - String l = text.getLineString(line); - if (l.trim().startsWith("//")) { - int i = l.indexOf("//"); - text.delete(line, i, line, i + 2); - } - line++; - } - } else if (name.endsWith(".xml")) { - final String commentStart = ""; - while (line >= getCursor().getLeftLine() && line <= getCursor().getRightLine()) { - String l = text.getLineString(line); - if (l.trim().startsWith(commentStart)) { - int i = l.indexOf(commentStart); - text.delete(line, i, line, i + commentStart.length()); - } - if (l.trim().endsWith(commentEnd)) { - int count = text.getColumnCount(line); - text.delete(line, count - commentEnd.length(), line, count); - } - line++; - } - } - } - - /** - * If any language server is set, asks the language server to find the definition of token at - * the cursor position. - * - *

If the server returns a valid response, and the file specified in the response is same the - * file in this editor, the range specified in the response will be selected. - */ - @SuppressWarnings("deprecation") - public void findDefinition() { - if (getFile() == null) { - return; - } - - final ProgressDialog pd = - ProgressDialog.show( - getContext(), - null, - getContext().getString(R.string.msg_finding_definition)); - - try { - final CompletableFuture future = - CompletableFuture.supplyAsync( - () -> { - final var params = - new com.itsaky.lsp.models.DefinitionParams( - getFile().toPath(), - new com.itsaky.lsp.models.Position( - getCursor().getLeftLine(), - getCursor().getLeftColumn())); - - return mLanguageServer.findDefinition(params); - }); - - future.whenComplete( - (result, error) -> { - if (result == null - || mLanguageClient == null - || future.isCancelled() - || future.isCompletedExceptionally()) { - LOG.error("An error occurred while finding definition", error); - showDefinitionNotFound(pd); - return; - } - - final var locations = result.getLocations(); - if (locations.size() <= 0) { - LOG.error("No definitions found", "Size:", locations.size()); - showDefinitionNotFound(pd); - return; - } - - ThreadUtils.runOnUiThread( - () -> { - if (locations.size() == 1) { - var location = locations.get(0); - if (PathUtils.isSameFile( - location.getFile(), getFile().toPath())) { - setSelection(location.getRange()); - return; - } - mLanguageClient.showDocument( - new ShowDocumentParams( - location.getFile(), location.getRange())); - } else { - mLanguageClient.showLocations(locations); - } - }); - - dismissOnUiThread(pd); - }); - } catch (Throwable th) { - LOG.error("An error occurred while finding definition", th); - showDefinitionNotFound(pd); - } - } - - /** - * Notify the user that no definitions can be found. - * - * @param pd The {@link ProgressDialog} that was shown when requesting the definitions. - */ - @SuppressWarnings("deprecation") - private void showDefinitionNotFound(final ProgressDialog pd) { - ThreadUtils.runOnUiThread( + ThreadUtils.runOnUiThread( () -> { - StudioApp.getInstance().toast(R.string.msg_no_definition, Toaster.Type.ERROR); - pd.dismiss(); - }); - } - - /** - * Set selection to the given range. - * - * @param range The range to select. - */ - public void setSelection(@NonNull Range range) { - if (isValidRange(range)) { - setSelectionRegion( - range.getStart().getLine(), - range.getStart().getColumn(), - range.getEnd().getLine(), - range.getEnd().getColumn()); - } - } - - /** - * Dismisses the given dialog on the UI thread. - * - * @param dialog The dialog to dismiss. - */ - private void dismissOnUiThread(@NonNull final Dialog dialog) { - ThreadUtils.runOnUiThread(dialog::dismiss); - } - - /** - * Checks if the given range is valid for this editor's text. - * - * @param range The range to check. - * @return true if valid, false otherwise. - */ - public boolean isValidRange(final Range range) { - if (range == null) { - return false; - } - - final var start = range.getStart(); - final var end = range.getEnd(); - - return isValidPosition(start) - && isValidPosition(end) - && start.compareTo(end) < 0; // make sure start position is before end position - } - - /** - * Checks if the given position is valid for this editor's text. - * - * @param position The position to check. - * @return true if valid, false otherwise. - */ - public boolean isValidPosition(final Position position) { - if (position == null) { - return false; - } - - return isValidLine(position.getLine()) - && isValidColumn(position.getLine(), position.getColumn()); - } - - /** - * Checks if the given line is valid for this editor's text. - * - * @param line The line to check. - * @return true if valid, false otherwise. - */ - public boolean isValidLine(int line) { - return line >= 0 && line < getText().getLineCount(); - } - - /** - * Checks if the given column is valid for this editor's text. - * - * @param line The line of the column to check. - * @param column The column to check. - * @return true if valid, false otherwise. - */ - public boolean isValidColumn(int line, int column) { - return column >= 0 && column < getText().getColumnCount(line); - } - - /** - * If any language server instance is set, finds the references to of the token at the current - * cursor position. - * - *

If the server returns a valid response, that response is forwarded to the {@link - * IDELanguageClientImpl}. - */ - public void findReferences() { - if (getFile() == null) { - return; - } - - @SuppressWarnings("deprecation") - final ProgressDialog pd = - ProgressDialog.show( - getContext(), - null, - getContext().getString(R.string.msg_finding_references)); - - try { - final CompletableFuture future = - CompletableFuture.supplyAsync( - () -> { - final var referenceParams = - new ReferenceParams( - getFile().toPath(), - new com.itsaky.lsp.models.Position( - getCursor().getLeftLine(), - getCursor().getLeftColumn()), - true); - return mLanguageServer.findReferences(referenceParams); - }); - - future.whenComplete( - (result, error) -> { - if (result == null - || mLanguageClient == null - || future.isCancelled() - || future.isCompletedExceptionally()) { - LOG.error("An error occurred while finding references", error); - showReferencesNotFound(pd); - return; - } - - if (result.getLocations().isEmpty()) { - showReferencesNotFound(pd); - return; - } else { - if (result.getLocations().size() == 1) { - final var loc = result.getLocations().get(0); - if (PathUtils.isSameFile(loc.getFile(), getFile().toPath())) { - setSelection(loc.getRange()); - return; - } - } - - ThreadUtils.runOnUiThread( - () -> mLanguageClient.showLocations(result.getLocations())); - } - - dismissOnUiThread(pd); - }); - } catch (Throwable th) { - LOG.error("An error occurred while finding references", th); - showReferencesNotFound(pd); - } - } - - /** - * Notify the user that no references were found for the selected token. - * - * @param pd The {@link ProgressDialog} that was shown when requesting references. - */ - @SuppressWarnings("deprecation") - private void showReferencesNotFound(final ProgressDialog pd) { - ThreadUtils.runOnUiThread( - () -> { - StudioApp.getInstance().toast(R.string.msg_no_references, Toaster.Type.ERROR); - pd.dismiss(); - }); - } - - /** If any language server is set, notify the server that the file in this editor was saved. */ - public void didSave() { - if (mLanguageServer != null && getFile() != null) { - final var documentHandler = mLanguageServer.getDocumentHandler(); - final var file = getFile().toPath(); - if (documentHandler.accepts(file)) { - documentHandler.onFileSaved(new DocumentSaveEvent(file)); - } - } - } - - /** Notify the language server that the file in this editor is about to be closed. */ - public void close() { - if (mLanguageServer != null && getFile() != null) { - final var documentHandler = mLanguageServer.getDocumentHandler(); - final var file = getFile().toPath(); - if (documentHandler.accepts(file)) { - documentHandler.onFileClosed(new DocumentCloseEvent(file)); - } - LOG.info("'textDocument/didClose' was sent to the language server."); - } else { - LOG.info("No language server is available for this file"); - } - - mActionsPopup.unsubscribeEvents(); - ensureWindowsDismissed(); - } - - /** Ensures that all the windows are dismissed. */ - public void ensureWindowsDismissed() { - if (getDiagnosticWindow().isShowing()) { - getDiagnosticWindow().dismiss(); - } - - if (getSignatureHelpWindow().isShowing()) { - getSignatureHelpWindow().dismiss(); - } - - if (mActionsPopup != null) { - if (mActionsPopup.isShowing()) { - mActionsPopup.dismiss(); - } - } - } - - public void onEditorSelected() { - if (getFile() == null) { - return; - } - - final var path = getFile().toPath(); - if (mLanguageServer != null) { - mLanguageServer.getDocumentHandler().onFileSelected(path); - } - } - - @SuppressWarnings("unused") - public void executeCommand(Command command) { - if (command == null) { - LOG.warn("Cannot execute command in editor. Command is null."); - return; - } - - LOG.info(String.format("Executing command '%s' for completion item.", command.getTitle())); - switch (command.getCommand()) { - case Command.TRIGGER_COMPLETION: - final var completion = getComponent(EditorAutoCompletion.class); - completion.requireCompletion(); - break; - case Command.TRIGGER_PARAMETER_HINTS: - signatureHelp(); - break; - case Command.FORMAT_CODE: - formatCodeAsync(); - break; - } - } - - /** - * Requests the language server to provided a semantically larger selection than the current - * selection. If a valid response is received, that range will be selected. - */ - public void expandSelection() { - if (mLanguageServer == null || getFile() == null) { - LOG.error("Cannot expand selection. Language server or file is null"); - return; - } - - //noinspection deprecation - final var pd = - ProgressDialog.show( - getContext(), - null, - getContext().getString(R.string.please_wait), - true, - false); - final CompletableFuture future = - CompletableFuture.supplyAsync( - () -> - mLanguageServer.expandSelection( - new ExpandSelectionParams( - getFile().toPath(), getCursorRange()))); - - future.whenComplete( - ((range, throwable) -> { - pd.dismiss(); - - if (throwable != null) { - LOG.error("Error computing expanded selection range", throwable); - return; + if (locations.size() == 1) { + var location = locations.get(0); + if (PathUtils.isSameFile(location.getFile(), getFile().toPath())) { + setSelection(location.getRange()); + return; } + mLanguageClient.showDocument( + new ShowDocumentParams(location.getFile(), location.getRange())); + } else { + mLanguageClient.showLocations(locations); + } + }); - ThreadUtils.runOnUiThread(() -> setSelection(range)); - })); + dismissOnUiThread(pd); + }); + } catch (Throwable th) { + LOG.error("An error occurred while finding definition", th); + showDefinitionNotFound(pd); + } + } + + /** + * Notify the user that no definitions can be found. + * + * @param pd The {@link ProgressDialog} that was shown when requesting the definitions. + */ + @SuppressWarnings("deprecation") + private void showDefinitionNotFound(final ProgressDialog pd) { + ThreadUtils.runOnUiThread( + () -> { + StudioApp.getInstance().toast(R.string.msg_no_definition, Toaster.Type.ERROR); + pd.dismiss(); + }); + } + + /** + * Set selection to the given range. + * + * @param range The range to select. + */ + public void setSelection(@NonNull Range range) { + if (isValidRange(range)) { + setSelectionRegion( + range.getStart().getLine(), + range.getStart().getColumn(), + range.getEnd().getLine(), + range.getEnd().getColumn()); + } + } + + /** + * Dismisses the given dialog on the UI thread. + * + * @param dialog The dialog to dismiss. + */ + private void dismissOnUiThread(@NonNull final Dialog dialog) { + ThreadUtils.runOnUiThread(dialog::dismiss); + } + + /** + * Checks if the given range is valid for this editor's text. + * + * @param range The range to check. + * @return true if valid, false otherwise. + */ + public boolean isValidRange(final Range range) { + if (range == null) { + return false; } - /** - * Get the cursor's selection range in the form of {@link Range}. - * - * @return The {@link Range} of the cursor. - */ - public Range getCursorRange() { - final var cursor = getCursor(); - final var start = new Position(cursor.getLeftLine(), cursor.getLeftColumn()); - final var end = new Position(cursor.getRightLine(), cursor.getRightColumn()); - return new Range(start, end); + final var start = range.getStart(); + final var end = range.getEnd(); + + return isValidPosition(start) + && isValidPosition(end) + && start.compareTo(end) < 0; // make sure start position is before end position + } + + /** + * Checks if the given position is valid for this editor's text. + * + * @param position The position to check. + * @return true if valid, false otherwise. + */ + public boolean isValidPosition(final Position position) { + if (position == null) { + return false; } - /** - * Get the cursor's position in the form of {@link Position}. - * - * @return The {@link Position} of the cursor. - */ - @SuppressWarnings("unused") - public Position getCursorAsLSPPosition() { - return new Position(getCursor().getLeftLine(), getCursor().getLeftColumn()); + return isValidLine(position.getLine()) + && isValidColumn(position.getLine(), position.getColumn()); + } + + /** + * Checks if the given line is valid for this editor's text. + * + * @param line The line to check. + * @return true if valid, false otherwise. + */ + public boolean isValidLine(int line) { + return line >= 0 && line < getText().getLineCount(); + } + + /** + * Checks if the given column is valid for this editor's text. + * + * @param line The line of the column to check. + * @param column The column to check. + * @return true if valid, false otherwise. + */ + public boolean isValidColumn(int line, int column) { + return column >= 0 && column < getText().getColumnCount(line); + } + + /** + * If any language server instance is set, finds the references to of the token at the current + * cursor position. + * + *

If the server returns a valid response, that response is forwarded to the {@link + * IDELanguageClientImpl}. + */ + public void findReferences() { + if (getFile() == null) { + return; } - /** - * Validates the range if it is invalid and returns a valid range. - * - * @param range Th range to validate. - * @return A new, validated range. - */ - public Range validateRange(@NonNull final Range range) { - final var start = range.getStart(); - final var end = range.getEnd(); + @SuppressWarnings("deprecation") + final ProgressDialog pd = + ProgressDialog.show( + getContext(), null, getContext().getString(R.string.msg_finding_references)); - if (start.getLine() < 0) { - start.setLine(0); - } else if (start.getLine() >= getText().getLineCount()) { - start.setLine(getText().getLineCount() - 1); - } + try { + final CompletableFuture future = + CompletableFuture.supplyAsync( + () -> { + final var referenceParams = + new ReferenceParams( + getFile().toPath(), + new com.itsaky.lsp.models.Position( + getCursor().getLeftLine(), getCursor().getLeftColumn()), + true); + return mLanguageServer.findReferences(referenceParams); + }); - if (end.getLine() < 0) { - end.setLine(0); - } else if (end.getLine() >= getText().getLineCount()) { - end.setLine(getText().getLineCount() - 1); - } + future.whenComplete( + (result, error) -> { + if (result == null + || mLanguageClient == null + || future.isCancelled() + || future.isCompletedExceptionally()) { + LOG.error("An error occurred while finding references", error); + showReferencesNotFound(pd); + return; + } - if (end.getLine() < start.getLine()) { - var l = end.getLine(); - var l2 = start.getLine(); - start.setLine(l); - end.setLine(l2); - } + if (result.getLocations().isEmpty()) { + showReferencesNotFound(pd); + return; + } else { + if (result.getLocations().size() == 1) { + final var loc = result.getLocations().get(0); + if (PathUtils.isSameFile(loc.getFile(), getFile().toPath())) { + setSelection(loc.getRange()); + return; + } + } - if (start.getColumn() < 0) { - start.setColumn(0); - } else if (start.getColumn() >= getText().getColumnCount(start.getLine())) { - start.setColumn(getText().getColumnCount(start.getLine()) - 1); - } + ThreadUtils.runOnUiThread(() -> mLanguageClient.showLocations(result.getLocations())); + } - if (end.getColumn() < 0) { - end.setColumn(0); - } else if (end.getColumn() >= getText().getColumnCount(end.getLine())) { - end.setColumn(getText().getColumnCount(end.getLine()) - 1); - } + dismissOnUiThread(pd); + }); + } catch (Throwable th) { + LOG.error("An error occurred while finding references", th); + showReferencesNotFound(pd); + } + } - if (end.getColumn() < start.getColumn()) { - final var c = start.getColumn(); - final var c2 = end.getColumn(); - start.setColumn(c2); - end.setColumn(c); - } + /** + * Notify the user that no references were found for the selected token. + * + * @param pd The {@link ProgressDialog} that was shown when requesting references. + */ + @SuppressWarnings("deprecation") + private void showReferencesNotFound(final ProgressDialog pd) { + ThreadUtils.runOnUiThread( + () -> { + StudioApp.getInstance().toast(R.string.msg_no_references, Toaster.Type.ERROR); + pd.dismiss(); + }); + } - return new Range(start, end); + /** If any language server is set, notify the server that the file in this editor was saved. */ + public void didSave() { + if (mLanguageServer != null && getFile() != null) { + final var documentHandler = mLanguageServer.getDocumentHandler(); + final var file = getFile().toPath(); + if (documentHandler.accepts(file)) { + documentHandler.onFileSaved(new DocumentSaveEvent(file)); + } + } + } + + /** Notify the language server that the file in this editor is about to be closed. */ + public void close() { + if (mLanguageServer != null && getFile() != null) { + final var documentHandler = mLanguageServer.getDocumentHandler(); + final var file = getFile().toPath(); + if (documentHandler.accepts(file)) { + documentHandler.onFileClosed(new DocumentCloseEvent(file)); + } + LOG.info("'textDocument/didClose' was sent to the language server."); + } else { + LOG.info("No language server is available for this file"); } - @Override - public IDEEditorSearcher getSearcher() { - return mSearcher; + mActionsPopup.unsubscribeEvents(); + ensureWindowsDismissed(); + } + + /** Ensures that all the windows are dismissed. */ + public void ensureWindowsDismissed() { + if (getDiagnosticWindow().isShowing()) { + getDiagnosticWindow().dismiss(); } - protected void setSearcher(@NonNull IDEEditorSearcher searcher) { - mSearcher = searcher; + if (getSignatureHelpWindow().isShowing()) { + getSignatureHelpWindow().dismiss(); } - @Override - public void beginSearchMode() { - final var callback = new SearchActionMode(this); - if (getContext() instanceof AppCompatActivity) { - startActionMode (callback); - } else { - LOG.error("Unable start search action mode. Activity must inherit AppCompatActivity."); - } + if (mActionsPopup != null) { + if (mActionsPopup.isShowing()) { + mActionsPopup.dismiss(); + } } + } + + public void onEditorSelected() { + if (getFile() == null) { + return; + } + + final var path = getFile().toPath(); + if (mLanguageServer != null) { + mLanguageServer.getDocumentHandler().onFileSelected(path); + } + } + + @SuppressWarnings("unused") + public void executeCommand(Command command) { + if (command == null) { + LOG.warn("Cannot execute command in editor. Command is null."); + return; + } + + LOG.info(String.format("Executing command '%s' for completion item.", command.getTitle())); + switch (command.getCommand()) { + case Command.TRIGGER_COMPLETION: + final var completion = getComponent(EditorAutoCompletion.class); + completion.requireCompletion(); + break; + case Command.TRIGGER_PARAMETER_HINTS: + signatureHelp(); + break; + case Command.FORMAT_CODE: + formatCodeAsync(); + break; + } + } + + /** + * Requests the language server to provided a semantically larger selection than the current + * selection. If a valid response is received, that range will be selected. + */ + public void expandSelection() { + if (mLanguageServer == null || getFile() == null) { + LOG.error("Cannot expand selection. Language server or file is null"); + return; + } + + //noinspection deprecation + final var pd = + ProgressDialog.show( + getContext(), null, getContext().getString(R.string.please_wait), true, false); + final CompletableFuture future = + CompletableFuture.supplyAsync( + () -> + mLanguageServer.expandSelection( + new ExpandSelectionParams(getFile().toPath(), getCursorRange()))); + + future.whenComplete( + ((range, throwable) -> { + pd.dismiss(); + + if (throwable != null) { + LOG.error("Error computing expanded selection range", throwable); + return; + } + + ThreadUtils.runOnUiThread(() -> setSelection(range)); + })); + } + + /** + * Get the cursor's selection range in the form of {@link Range}. + * + * @return The {@link Range} of the cursor. + */ + public Range getCursorRange() { + final var cursor = getCursor(); + final var start = new Position(cursor.getLeftLine(), cursor.getLeftColumn()); + final var end = new Position(cursor.getRightLine(), cursor.getRightColumn()); + return new Range(start, end); + } + + /** + * Get the cursor's position in the form of {@link Position}. + * + * @return The {@link Position} of the cursor. + */ + @SuppressWarnings("unused") + public Position getCursorAsLSPPosition() { + return new Position(getCursor().getLeftLine(), getCursor().getLeftColumn()); + } + + /** + * Validates the range if it is invalid and returns a valid range. + * + * @param range Th range to validate. + * @return A new, validated range. + */ + public Range validateRange(@NonNull final Range range) { + final var start = range.getStart(); + final var end = range.getEnd(); + + if (start.getLine() < 0) { + start.setLine(0); + } else if (start.getLine() >= getText().getLineCount()) { + start.setLine(getText().getLineCount() - 1); + } + + if (end.getLine() < 0) { + end.setLine(0); + } else if (end.getLine() >= getText().getLineCount()) { + end.setLine(getText().getLineCount() - 1); + } + + if (end.getLine() < start.getLine()) { + var l = end.getLine(); + var l2 = start.getLine(); + start.setLine(l); + end.setLine(l2); + } + + if (start.getColumn() < 0) { + start.setColumn(0); + } else if (start.getColumn() >= getText().getColumnCount(start.getLine())) { + start.setColumn(getText().getColumnCount(start.getLine()) - 1); + } + + if (end.getColumn() < 0) { + end.setColumn(0); + } else if (end.getColumn() >= getText().getColumnCount(end.getLine())) { + end.setColumn(getText().getColumnCount(end.getLine()) - 1); + } + + if (end.getColumn() < start.getColumn()) { + final var c = start.getColumn(); + final var c2 = end.getColumn(); + start.setColumn(c2); + end.setColumn(c); + } + + return new Range(start, end); + } + + @Override + public IDEEditorSearcher getSearcher() { + return mSearcher; + } + + protected void setSearcher(@NonNull IDEEditorSearcher searcher) { + mSearcher = searcher; + } + + @Override + public void beginSearchMode() { + final var callback = new SearchActionMode(this); + if (getContext() instanceof AppCompatActivity) { + startActionMode(callback); + } else { + LOG.error("Unable start search action mode. Activity must inherit AppCompatActivity."); + } + } } - - diff --git a/app/src/main/java/com/itsaky/androidide/views/editor/SignatureHelpWindow.java b/app/src/main/java/com/itsaky/androidide/views/editor/SignatureHelpWindow.java index e347828c5..1a568036c 100644 --- a/app/src/main/java/com/itsaky/androidide/views/editor/SignatureHelpWindow.java +++ b/app/src/main/java/com/itsaky/androidide/views/editor/SignatureHelpWindow.java @@ -38,148 +38,142 @@ import io.github.rosemoe.sora.widget.base.EditorPopupWindow; */ public class SignatureHelpWindow extends BaseEditorWindow { - private static final ILogger LOG = ILogger.newInstance("SignatureHelpWindow"); + private static final ILogger LOG = ILogger.newInstance("SignatureHelpWindow"); - /** - * Create a signature help popup window for editor - * - * @param editor The editor. - */ - public SignatureHelpWindow(@NonNull IDEEditor editor) { - super(editor); + /** + * Create a signature help popup window for editor + * + * @param editor The editor. + */ + public SignatureHelpWindow(@NonNull IDEEditor editor) { + super(editor); - editor.subscribeEvent( - SelectionChangeEvent.class, - (event, unsubscribe) -> { - if (isShowing()) { - dismiss(); - } - }); + editor.subscribeEvent( + SelectionChangeEvent.class, + (event, unsubscribe) -> { + if (isShowing()) { + dismiss(); + } + }); + } + + public void setupAndDisplay(SignatureHelp signature) { + if (signature == null || signature.getSignatures().isEmpty()) { + if (isShowing()) { + dismiss(); + } + + return; } - public void setupAndDisplay(SignatureHelp signature) { - if (signature == null || signature.getSignatures().isEmpty()) { - if (isShowing()) { - dismiss(); - } + final var signatureText = createSignatureText(signature); - return; - } - - final var signatureText = createSignatureText(signature); - - if (signatureText == null) { - return; - } - - this.text.setText(signatureText); - displayWindow(); + if (signatureText == null) { + return; } - @Nullable - private CharSequence createSignatureText(@NonNull SignatureHelp signature) { - final var signatures = signature.getSignatures(); - final var activeSignature = signature.getActiveSignature(); - final var activeParameter = signature.getActiveParameter(); - final SpannableStringBuilder sb = new SpannableStringBuilder(); + this.text.setText(signatureText); + displayWindow(); + } - if (activeSignature < 0 || activeParameter < 0) { - LOG.debug("activeSignature:", activeSignature, "activeParameter:", activeParameter); - return null; - } + @Nullable + private CharSequence createSignatureText(@NonNull SignatureHelp signature) { + final var signatures = signature.getSignatures(); + final var activeSignature = signature.getActiveSignature(); + final var activeParameter = signature.getActiveParameter(); + final SpannableStringBuilder sb = new SpannableStringBuilder(); - var count = signatures.size(); - if (activeSignature >= count) { - LOG.debug("Active signature is invalid", "Size is " + count); - return null; - } - - // remove all with non-applicable signatures - signatures.removeIf( - info -> { - final var remove = activeParameter >= info.getParameters().size(); - if (remove) { - LOG.debug( - "Removing", - info, - "params=" + info.getParameters().size(), - "active=" + activeParameter); - } - return remove; - }); - - count = signatures.size(); - for (var i = 0; i < count; i++) { - final var info = signatures.get(i); - formatSignature(info, activeParameter, i == activeSignature, sb); - if (i != count - 1) { - sb.append('\n'); - } - } - - return sb; + if (activeSignature < 0 || activeParameter < 0) { + LOG.debug("activeSignature:", activeSignature, "activeParameter:", activeParameter); + return null; } - /** - * Formats (highlights) a method signature - * - * @param signature Signature information - * @param paramIndex Currently active parameter index - * @param isCurrentSignature true if the given signature is the active signature. - * @param result The builder to append spanned text to. - */ - private void formatSignature( - @NonNull SignatureInformation signature, - int paramIndex, - boolean isCurrentSignature, - SpannableStringBuilder result) { + var count = signatures.size(); + if (activeSignature >= count) { + LOG.debug("Active signature is invalid", "Size is " + count); + return null; + } - final var currentIndex = Math.max(0, result.length() - 1); - String name = signature.getLabel(); - name = name.substring(0, name.indexOf("(")); + // remove all with non-applicable signatures + signatures.removeIf( + info -> { + final var remove = activeParameter >= info.getParameters().size(); + if (remove) { + LOG.debug( + "Removing", + info, + "params=" + info.getParameters().size(), + "active=" + activeParameter); + } + return remove; + }); + count = signatures.size(); + for (var i = 0; i < count; i++) { + final var info = signatures.get(i); + formatSignature(info, activeParameter, i == activeSignature, sb); + if (i != count - 1) { + sb.append('\n'); + } + } + + return sb; + } + + /** + * Formats (highlights) a method signature + * + * @param signature Signature information + * @param paramIndex Currently active parameter index + * @param isCurrentSignature true if the given signature is the active signature. + * @param result The builder to append spanned text to. + */ + private void formatSignature( + @NonNull SignatureInformation signature, + int paramIndex, + boolean isCurrentSignature, + SpannableStringBuilder result) { + + final var currentIndex = Math.max(0, result.length() - 1); + String name = signature.getLabel(); + name = name.substring(0, name.indexOf("(")); + + result.append( + name, new ForegroundColorSpan(0xffffffff), SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); + result.append( + "(", new ForegroundColorSpan(0xff4fc3f7), SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); + + var params = signature.getParameters(); + for (int i = 0; i < params.size(); i++) { + int color = i == paramIndex ? 0xffff6060 : 0xffffffff; + final var info = params.get(i); + if (i == params.size() - 1) { result.append( - name, - new ForegroundColorSpan(0xffffffff), - SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); + info.getLabel() + "", + new ForegroundColorSpan(color), + SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); + } else { result.append( - "(", - new ForegroundColorSpan(0xff4fc3f7), - SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); - - var params = signature.getParameters(); - for (int i = 0; i < params.size(); i++) { - int color = i == paramIndex ? 0xffff6060 : 0xffffffff; - final var info = params.get(i); - if (i == params.size() - 1) { - result.append( - info.getLabel() + "", - new ForegroundColorSpan(color), - SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); - } else { - result.append( - info.getLabel() + "", - new ForegroundColorSpan(color), - SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); - result.append( - ",", - new ForegroundColorSpan(0xff4fc3f7), - SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); - result.append(" "); - } - } + info.getLabel() + "", + new ForegroundColorSpan(color), + SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); result.append( - ")", - new ForegroundColorSpan(0xff4fc3f7), - SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); - - if (isCurrentSignature) { - // set background span to little light color than the text's background - result.setSpan( - new BackgroundColorSpan(0xff373737), - currentIndex, - result.length(), - SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); - } + ",", + new ForegroundColorSpan(0xff4fc3f7), + SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); + result.append(" "); + } } + result.append( + ")", new ForegroundColorSpan(0xff4fc3f7), SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); + + if (isCurrentSignature) { + // set background span to little light color than the text's background + result.setSpan( + new BackgroundColorSpan(0xff373737), + currentIndex, + result.length(), + SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } } diff --git a/app/src/main/java/com/itsaky/androidide/views/transition/ArcMotionPlus.java b/app/src/main/java/com/itsaky/androidide/views/transition/ArcMotionPlus.java index d6ccd4f7c..80ca304c9 100755 --- a/app/src/main/java/com/itsaky/androidide/views/transition/ArcMotionPlus.java +++ b/app/src/main/java/com/itsaky/androidide/views/transition/ArcMotionPlus.java @@ -46,88 +46,88 @@ import androidx.transition.PathMotion; public class ArcMotionPlus extends PathMotion { - private static final float DEFAULT_ARC_ANGLE = 90f; - private static final boolean DEFAULT_REFLECT = false; + private static final float DEFAULT_ARC_ANGLE = 90f; + private static final boolean DEFAULT_REFLECT = false; - private boolean isReflectedArc = DEFAULT_REFLECT; - private float arcAngle = DEFAULT_ARC_ANGLE; + private boolean isReflectedArc = DEFAULT_REFLECT; + private float arcAngle = DEFAULT_ARC_ANGLE; - public ArcMotionPlus() {} + public ArcMotionPlus() {} - public ArcMotionPlus(Context context, AttributeSet attrs) { - super(context, attrs); + public ArcMotionPlus(Context context, AttributeSet attrs) { + super(context, attrs); + } + + /** + * Returns if we are reflecting the arc + * + *

The default value is false. + * + * @return Is the arc reflected + */ + public boolean isReflectedArc() { + return isReflectedArc; + } + + /** + * Sets whether we should reflect the arc about the line drawn between the start and end points + * + *

The default value is false. + * + * @param isReflectedArc Is the arc reflected about the line between start and end points + */ + public void setReflectedArc(boolean isReflectedArc) { + this.isReflectedArc = isReflectedArc; + } + + /** + * Returns the arc angle that describes the curve. The larger the angle the more pronounce the + * curve will be. Min value is 1, Max value si 179 + * + *

The default value is 90. + * + * @return The angle of the arc on a circle describing the Path between two points. + */ + public float getArcAngle() { + return arcAngle; + } + + /** + * Sets the arc angle that describes the curve. The larger the angle the more pronounce the curve + * will be. Min value is 1, Max value si 179 + * + *

The default value is 90. + * + * @param angle The angle of the arc on a circle describing the Path between two points. + */ + public ArcMotionPlus setArcAngle(float angle) { + arcAngle = angle; + return this; + } + + @Override + public Path getPath(float startX, float startY, float endX, float endY) { + float controlP1X, controlP1Y, controlP2X, controlP2Y; + + Path path = new Path(); + path.moveTo(startX, startY); + + CubicBezierArc cubicBezierArc = new CubicBezierArc(arcAngle, startX, startY, endX, endY); + + if (isReflectedArc) { + controlP1X = cubicBezierArc.getReflectedControlPoint1().x; + controlP1Y = cubicBezierArc.getReflectedControlPoint1().y; + controlP2X = cubicBezierArc.getReflectedControlPoint2().x; + controlP2Y = cubicBezierArc.getReflectedControlPoint2().y; + + } else { + controlP1X = cubicBezierArc.getControlPoint1().x; + controlP1Y = cubicBezierArc.getControlPoint1().y; + controlP2X = cubicBezierArc.getControlPoint2().x; + controlP2Y = cubicBezierArc.getControlPoint2().y; } - /** - * Returns if we are reflecting the arc - * - *

The default value is false. - * - * @return Is the arc reflected - */ - public boolean isReflectedArc() { - return isReflectedArc; - } - - /** - * Sets whether we should reflect the arc about the line drawn between the start and end points - * - *

The default value is false. - * - * @param isReflectedArc Is the arc reflected about the line between start and end points - */ - public void setReflectedArc(boolean isReflectedArc) { - this.isReflectedArc = isReflectedArc; - } - - /** - * Returns the arc angle that describes the curve. The larger the angle the more pronounce the - * curve will be. Min value is 1, Max value si 179 - * - *

The default value is 90. - * - * @return The angle of the arc on a circle describing the Path between two points. - */ - public float getArcAngle() { - return arcAngle; - } - - /** - * Sets the arc angle that describes the curve. The larger the angle the more pronounce the - * curve will be. Min value is 1, Max value si 179 - * - *

The default value is 90. - * - * @param angle The angle of the arc on a circle describing the Path between two points. - */ - public ArcMotionPlus setArcAngle(float angle) { - arcAngle = angle; - return this; - } - - @Override - public Path getPath(float startX, float startY, float endX, float endY) { - float controlP1X, controlP1Y, controlP2X, controlP2Y; - - Path path = new Path(); - path.moveTo(startX, startY); - - CubicBezierArc cubicBezierArc = new CubicBezierArc(arcAngle, startX, startY, endX, endY); - - if (isReflectedArc) { - controlP1X = cubicBezierArc.getReflectedControlPoint1().x; - controlP1Y = cubicBezierArc.getReflectedControlPoint1().y; - controlP2X = cubicBezierArc.getReflectedControlPoint2().x; - controlP2Y = cubicBezierArc.getReflectedControlPoint2().y; - - } else { - controlP1X = cubicBezierArc.getControlPoint1().x; - controlP1Y = cubicBezierArc.getControlPoint1().y; - controlP2X = cubicBezierArc.getControlPoint2().x; - controlP2Y = cubicBezierArc.getControlPoint2().y; - } - - path.cubicTo(controlP1X, controlP1Y, controlP2X, controlP2Y, endX, endY); - return path; - } + path.cubicTo(controlP1X, controlP1Y, controlP2X, controlP2Y, endX, endY); + return path; + } } diff --git a/app/src/main/java/com/itsaky/androidide/views/transition/CubicBezierArc.java b/app/src/main/java/com/itsaky/androidide/views/transition/CubicBezierArc.java index bb4a7dfd1..dc49823b4 100755 --- a/app/src/main/java/com/itsaky/androidide/views/transition/CubicBezierArc.java +++ b/app/src/main/java/com/itsaky/androidide/views/transition/CubicBezierArc.java @@ -24,154 +24,154 @@ package com.itsaky.androidide.views.transition; */ public class CubicBezierArc { - private Point startPoint; - private Point endPoint; - private Point controlPoint1; - private Point controlPoint2; - private Point reflectedControlPoint1; - private Point reflectedControlPoint2; + private Point startPoint; + private Point endPoint; + private Point controlPoint1; + private Point controlPoint2; + private Point reflectedControlPoint1; + private Point reflectedControlPoint2; - /** - * Create a CubicBezierArc class with arc angle and start and end points - * - * @param angle The angle used to describe the arc, the greater the angle the more curved the - * arc will be (min = 1 , max = 179) - * @param startX the x coord of the start point - * @param startY the y coord of the start point - * @param endX the x coord of the end point - * @param endY the y coord of the end point - */ - public CubicBezierArc(float angle, float startX, float startY, float endX, float endY) { + /** + * Create a CubicBezierArc class with arc angle and start and end points + * + * @param angle The angle used to describe the arc, the greater the angle the more curved the arc + * will be (min = 1 , max = 179) + * @param startX the x coord of the start point + * @param startY the y coord of the start point + * @param endX the x coord of the end point + * @param endY the y coord of the end point + */ + public CubicBezierArc(float angle, float startX, float startY, float endX, float endY) { - Point start = new Point(startX, startY); - Point end = new Point(endX, endY); + Point start = new Point(startX, startY); + Point end = new Point(endX, endY); - if (start.x == end.x && start.y == end.y) { - throw new IllegalArgumentException("Start and end points cannot be the same"); - } - if (angle < 1 || angle > 179) { - throw new IllegalArgumentException("Arc angle must be between 1 and 179 degrees"); - } - startPoint = start; - endPoint = end; - calculateControlPoints(angle, start, end); + if (start.x == end.x && start.y == end.y) { + throw new IllegalArgumentException("Start and end points cannot be the same"); } - - /** - * Returns the start point - * - * @return The start point - */ - public Point getStartPoint() { - return startPoint; + if (angle < 1 || angle > 179) { + throw new IllegalArgumentException("Arc angle must be between 1 and 179 degrees"); } + startPoint = start; + endPoint = end; + calculateControlPoints(angle, start, end); + } - /** - * Returns the end point - * - * @return The end point - */ - public Point getEndPoint() { - return endPoint; + /** + * Returns the start point + * + * @return The start point + */ + public Point getStartPoint() { + return startPoint; + } + + /** + * Returns the end point + * + * @return The end point + */ + public Point getEndPoint() { + return endPoint; + } + + /** + * Returns the first control point + * + * @return The first control point + */ + public Point getControlPoint1() { + return controlPoint1; + } + + /** + * Returns the second control point + * + * @return The second control point + */ + public Point getControlPoint2() { + return controlPoint2; + } + + /** + * Returns the first control point when reflected about line between start and end points + * + * @return Reflected control point 1 + */ + public Point getReflectedControlPoint1() { + return reflectedControlPoint1; + } + + /** + * Returns the second control point when reflected about line between start and end points + * + * @return Reflected control point 2 + */ + public Point getReflectedControlPoint2() { + return reflectedControlPoint2; + } + + public static class Point { + public float x; + public float y; + + public Point() {} + + public Point(float x, float y) { + this.x = x; + this.y = y; } + } - /** - * Returns the first control point - * - * @return The first control point - */ - public Point getControlPoint1() { - return controlPoint1; + private Point getTrianglePoint(float angle, Point a, Point b) { + double angleRadians = Math.toRadians(angle); + Point thirdPoint = new Point(); + thirdPoint.x = (float) Math.tan(angleRadians) * (b.y - a.y) * -1; + thirdPoint.y = (float) Math.tan(angleRadians) * (b.x - a.x); + thirdPoint.x = thirdPoint.x + a.x; + thirdPoint.y = thirdPoint.y + a.y; + return thirdPoint; + } + + private Point getReflectedPointAboutLine(Point start, Point end, Point reflect) { + Point reflectedPoint = new Point(); + if (start.x != end.x) { + float m = (start.y - end.y) / (start.x - end.x); + float c = end.y - (m * end.x); + float d = (reflect.x + (reflect.y - c) * m) / (1 + (m * m)); + reflectedPoint.x = (2 * d) - reflect.x; + reflectedPoint.y = (2 * d * m) - reflect.y + (2 * c); + } else { + reflectedPoint.y = reflect.y; + reflectedPoint.x = start.x - (reflect.x - start.x); } + return reflectedPoint; + } - /** - * Returns the second control point - * - * @return The second control point - */ - public Point getControlPoint2() { - return controlPoint2; - } + private void calculateControlPoints(float angle, Point start, Point end) { + double angleRadians = Math.toRadians(angle); - /** - * Returns the first control point when reflected about line between start and end points - * - * @return Reflected control point 1 - */ - public Point getReflectedControlPoint1() { - return reflectedControlPoint1; - } + float deltaX = start.x - end.x; + float deltaY = start.y - end.y; + float halfChordLength = (float) Math.sqrt(deltaX * deltaX + deltaY * deltaY) / 2; + float radius = halfChordLength / (float) Math.sin(angleRadians / 2.0f); + // The length of the line from the start or end point to the control point + float controlLength = (float) ((4f / 3f) * Math.tan(angleRadians / 4)) * radius; - /** - * Returns the second control point when reflected about line between start and end points - * - * @return Reflected control point 2 - */ - public Point getReflectedControlPoint2() { - return reflectedControlPoint2; - } + float angleToControl = (float) Math.toDegrees(Math.atan(controlLength / radius)); - public static class Point { - public float x; - public float y; + // The mid point of the line from start to end + Point midPointChord = new Point((start.x + end.x) / 2, (start.y + end.y) / 2); - public Point() {} + // Angle between line from circle centre to control point, and line between control points + float chordRadiusAngle = 180 - 90 - (angle / 2); - public Point(float x, float y) { - this.x = x; - this.y = y; - } - } + Point center = getTrianglePoint(chordRadiusAngle, midPointChord, end); + controlPoint2 = getTrianglePoint(angleToControl, end, center); + controlPoint1 = getReflectedPointAboutLine(center, midPointChord, controlPoint2); - private Point getTrianglePoint(float angle, Point a, Point b) { - double angleRadians = Math.toRadians(angle); - Point thirdPoint = new Point(); - thirdPoint.x = (float) Math.tan(angleRadians) * (b.y - a.y) * -1; - thirdPoint.y = (float) Math.tan(angleRadians) * (b.x - a.x); - thirdPoint.x = thirdPoint.x + a.x; - thirdPoint.y = thirdPoint.y + a.y; - return thirdPoint; - } - - private Point getReflectedPointAboutLine(Point start, Point end, Point reflect) { - Point reflectedPoint = new Point(); - if (start.x != end.x) { - float m = (start.y - end.y) / (start.x - end.x); - float c = end.y - (m * end.x); - float d = (reflect.x + (reflect.y - c) * m) / (1 + (m * m)); - reflectedPoint.x = (2 * d) - reflect.x; - reflectedPoint.y = (2 * d * m) - reflect.y + (2 * c); - } else { - reflectedPoint.y = reflect.y; - reflectedPoint.x = start.x - (reflect.x - start.x); - } - return reflectedPoint; - } - - private void calculateControlPoints(float angle, Point start, Point end) { - double angleRadians = Math.toRadians(angle); - - float deltaX = start.x - end.x; - float deltaY = start.y - end.y; - float halfChordLength = (float) Math.sqrt(deltaX * deltaX + deltaY * deltaY) / 2; - float radius = halfChordLength / (float) Math.sin(angleRadians / 2.0f); - // The length of the line from the start or end point to the control point - float controlLength = (float) ((4f / 3f) * Math.tan(angleRadians / 4)) * radius; - - float angleToControl = (float) Math.toDegrees(Math.atan(controlLength / radius)); - - // The mid point of the line from start to end - Point midPointChord = new Point((start.x + end.x) / 2, (start.y + end.y) / 2); - - // Angle between line from circle centre to control point, and line between control points - float chordRadiusAngle = 180 - 90 - (angle / 2); - - Point center = getTrianglePoint(chordRadiusAngle, midPointChord, end); - controlPoint2 = getTrianglePoint(angleToControl, end, center); - controlPoint1 = getReflectedPointAboutLine(center, midPointChord, controlPoint2); - - // Get reflected control points - reflectedControlPoint1 = getReflectedPointAboutLine(start, end, controlPoint1); - reflectedControlPoint2 = getReflectedPointAboutLine(start, end, controlPoint2); - } + // Get reflected control points + reflectedControlPoint1 = getReflectedPointAboutLine(start, end, controlPoint1); + reflectedControlPoint2 = getReflectedPointAboutLine(start, end, controlPoint2); + } } diff --git a/app/src/main/java/com/itsaky/androidide/views/transition/ProgressTransition.java b/app/src/main/java/com/itsaky/androidide/views/transition/ProgressTransition.java index 9bc24ee61..a87bece50 100755 --- a/app/src/main/java/com/itsaky/androidide/views/transition/ProgressTransition.java +++ b/app/src/main/java/com/itsaky/androidide/views/transition/ProgressTransition.java @@ -30,50 +30,50 @@ import com.itsaky.androidide.utils.IntProperty; public class ProgressTransition extends Transition { - private static final String PROPNAME_PROGRESS = "ProgressTransition:progress"; - private static final Property PROGRESS_PROPERTY = - new IntProperty(PROPNAME_PROGRESS) { + private static final String PROPNAME_PROGRESS = "ProgressTransition:progress"; + private static final Property PROGRESS_PROPERTY = + new IntProperty(PROPNAME_PROGRESS) { - @Override - public void setValue(ProgressBar progressBar, int value) { - progressBar.setProgress(value); - } - - @Override - public Integer get(ProgressBar progressBar) { - return progressBar.getProgress(); - } - }; - - @Override - public void captureStartValues(TransitionValues transitionValues) { - captureValues(transitionValues); - } - - @Override - public void captureEndValues(TransitionValues transitionValues) { - captureValues(transitionValues); - } - - private void captureValues(TransitionValues transitionValues) { - if (transitionValues.view instanceof ProgressBar) { - ProgressBar progressBar = ((ProgressBar) transitionValues.view); - transitionValues.values.put(PROPNAME_PROGRESS, progressBar.getProgress()); + @Override + public void setValue(ProgressBar progressBar, int value) { + progressBar.setProgress(value); } - } - @Override - public Animator createAnimator( - ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) { - if (startValues != null && endValues != null && endValues.view instanceof ProgressBar) { - ProgressBar progressBar = (ProgressBar) endValues.view; - int start = (Integer) startValues.values.get(PROPNAME_PROGRESS); - int end = (Integer) endValues.values.get(PROPNAME_PROGRESS); - if (start != end) { - progressBar.setProgress(start); - return ObjectAnimator.ofInt(progressBar, PROGRESS_PROPERTY, end); - } + @Override + public Integer get(ProgressBar progressBar) { + return progressBar.getProgress(); } - return null; + }; + + @Override + public void captureStartValues(TransitionValues transitionValues) { + captureValues(transitionValues); + } + + @Override + public void captureEndValues(TransitionValues transitionValues) { + captureValues(transitionValues); + } + + private void captureValues(TransitionValues transitionValues) { + if (transitionValues.view instanceof ProgressBar) { + ProgressBar progressBar = ((ProgressBar) transitionValues.view); + transitionValues.values.put(PROPNAME_PROGRESS, progressBar.getProgress()); } + } + + @Override + public Animator createAnimator( + ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) { + if (startValues != null && endValues != null && endValues.view instanceof ProgressBar) { + ProgressBar progressBar = (ProgressBar) endValues.view; + int start = (Integer) startValues.values.get(PROPNAME_PROGRESS); + int end = (Integer) endValues.values.get(PROPNAME_PROGRESS); + if (start != end) { + progressBar.setProgress(start); + return ObjectAnimator.ofInt(progressBar, PROGRESS_PROPERTY, end); + } + } + return null; + } } diff --git a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/diffutils/DiffUtils.java b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/diffutils/DiffUtils.java index 9d2e59f46..639995c1a 100644 --- a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/diffutils/DiffUtils.java +++ b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/diffutils/DiffUtils.java @@ -23,831 +23,759 @@ import com.itsaky.androidide.diffutils.structs.MatchingBlock; import com.itsaky.androidide.diffutils.structs.OpCode; /** - * This is a port of all the functions needed from python-levenshtein C implementation. - * The code was ported line by line but unfortunately it was mostly undocumented, - * so it is mostly non readable (eg. var names) + * This is a port of all the functions needed from python-levenshtein C implementation. The code was + * ported line by line but unfortunately it was mostly undocumented, so it is mostly non readable + * (eg. var names) */ public class DiffUtils { - public static EditOp[] getEditOps(String s1, String s2) { - return getEditOps(s1.length(), s1, s2.length(), s2); + public static EditOp[] getEditOps(String s1, String s2) { + return getEditOps(s1.length(), s1, s2.length(), s2); + } + + private static EditOp[] getEditOps(int len1, String s1, int len2, String s2) { + + int len1o, len2o; + int i; + + int[] matrix; + + char[] c1 = s1.toCharArray(); + char[] c2 = s2.toCharArray(); + + int p1 = 0; + int p2 = 0; + + len1o = 0; + + while (len1 > 0 && len2 > 0 && c1[p1] == c2[p2]) { + len1--; + len2--; + + p1++; + p2++; + + len1o++; } + len2o = len1o; - private static EditOp[] getEditOps(int len1, String s1, int len2, String s2) { - - int len1o, len2o; - int i; - - int[] matrix; - - char[] c1 = s1.toCharArray(); - char[] c2 = s2.toCharArray(); - - int p1 = 0; - int p2 = 0; - - len1o = 0; - - while (len1 > 0 && len2 > 0 && c1[p1] == c2[p2]) { - len1--; - len2--; - - p1++; - p2++; - - len1o++; - } - - len2o = len1o; - - /* strip common suffix */ - while (len1 > 0 && len2 > 0 && c1[p1 + len1 - 1] == c2[p2 + len2 - 1]) { - len1--; - len2--; - } - - len1++; - len2++; - - matrix = new int[len2 * len1]; - - for (i = 0; i < len2; i++) - matrix[i] = i; - for (i = 1; i < len1; i++) - matrix[len2 * i] = i; - - for (i = 1; i < len1; i++) { - - int ptrPrev = (i - 1) * len2; - int ptrC = i * len2; - int ptrEnd = ptrC + len2 - 1; - - char char1 = c1[p1 + i - 1]; - int ptrChar2 = p2; - - int x = i; - - ptrC++; - - while (ptrC <= ptrEnd) { - - int c3 = matrix[ptrPrev++] + (char1 != c2[ptrChar2++] ? 1 : 0); - x++; - - if (x > c3) { - x = c3; - } - - c3 = matrix[ptrPrev] + 1; - - if (x > c3) { - x = c3; - } - - matrix[ptrC++] = x; - - } - - } - - - return editOpsFromCostMatrix(len1, c1, p1, len1o, len2, c2, p2, len2o, matrix); + /* strip common suffix */ + while (len1 > 0 && len2 > 0 && c1[p1 + len1 - 1] == c2[p2 + len2 - 1]) { + len1--; + len2--; } + len1++; + len2++; - private static EditOp[] editOpsFromCostMatrix(int len1, char[] c1, int p1, int o1, - int len2, char[] c2, int p2, int o2, - int[] matrix) { + matrix = new int[len2 * len1]; - int i, j, pos; + for (i = 0; i < len2; i++) matrix[i] = i; + for (i = 1; i < len1; i++) matrix[len2 * i] = i; - int ptr; + for (i = 1; i < len1; i++) { - EditOp[] ops; + int ptrPrev = (i - 1) * len2; + int ptrC = i * len2; + int ptrEnd = ptrC + len2 - 1; - int dir = 0; + char char1 = c1[p1 + i - 1]; + int ptrChar2 = p2; - pos = matrix[len1 * len2 - 1]; + int x = i; - ops = new EditOp[pos]; + ptrC++; - i = len1 - 1; - j = len2 - 1; + while (ptrC <= ptrEnd) { - ptr = len1 * len2 - 1; - - while (i > 0 || j > 0) { - - if (i != 0 && j != 0 && matrix[ptr] == matrix[ptr - len2 - 1] - && c1[p1 + i - 1] == c2[p2 + j - 1]) { - - i--; - j--; - ptr -= len2 + 1; - dir = 0; - - continue; - - } - - if (dir < 0 && j != 0 && matrix[ptr] == matrix[ptr - 1] + 1) { - - EditOp eop = new EditOp(); - - pos--; - ops[pos] = eop; - eop.type = EditType.INSERT; - eop.spos = i + o1; - eop.dpos = --j + o2; - ptr--; - - continue; - } - - if (dir > 0 && i != 0 && matrix[ptr] == matrix[ptr - len2] + 1) { - - EditOp eop = new EditOp(); - - pos--; - ops[pos] = eop; - eop.type = EditType.DELETE; - eop.spos = --i + o1; - eop.dpos = j + o2; - ptr -= len2; - - continue; - - } - - if (i != 0 && j != 0 && matrix[ptr] == matrix[ptr - len2 - 1] + 1) { - - pos--; - - EditOp eop = new EditOp(); - ops[pos] = eop; - - eop.type = EditType.REPLACE; - eop.spos = --i + o1; - eop.dpos = --j + o2; - - ptr -= len2 + 1; - dir = 0; - continue; - - } - - if (dir == 0 && j != 0 && matrix[ptr] == matrix[ptr - 1] + 1) { - - pos--; - EditOp eop = new EditOp(); - ops[pos] = eop; - eop.type = EditType.INSERT; - eop.spos = i + o1; - eop.dpos = --j + o2; - ptr--; - dir = -1; - - continue; - } - - if (dir == 0 && i != 0 && matrix[ptr] == matrix[ptr - len2] + 1) { - pos--; - EditOp eop = new EditOp(); - ops[pos] = eop; - - eop.type = EditType.DELETE; - eop.spos = --i + o1; - eop.dpos = j + o2; - ptr -= len2; - dir = 1; - continue; - } - - assert false; + int c3 = matrix[ptrPrev++] + (char1 != c2[ptrChar2++] ? 1 : 0); + x++; + if (x > c3) { + x = c3; } - return ops; - - } - - public static MatchingBlock[] getMatchingBlocks(String s1, String s2) { - - return getMatchingBlocks(s1.length(), s2.length(), getEditOps(s1, s2)); - - } - - public static MatchingBlock[] getMatchingBlocks(int len1, int len2, OpCode[] ops) { - - int n = ops.length; - - int noOfMB, i; - int o = 0; - - noOfMB = 0; - - for (i = n; i-- != 0; o++) { - - if (ops[o].type == EditType.KEEP) { - - noOfMB++; - - while (i != 0 && ops[o].type == EditType.KEEP) { - i--; - o++; - } - - if (i == 0) - break; - - } + c3 = matrix[ptrPrev] + 1; + if (x > c3) { + x = c3; } - MatchingBlock[] matchingBlocks = new MatchingBlock[noOfMB + 1]; - int mb = 0; - o = 0; + matrix[ptrC++] = x; + } + } + + return editOpsFromCostMatrix(len1, c1, p1, len1o, len2, c2, p2, len2o, matrix); + } + + private static EditOp[] editOpsFromCostMatrix( + int len1, char[] c1, int p1, int o1, int len2, char[] c2, int p2, int o2, int[] matrix) { + + int i, j, pos; + + int ptr; + + EditOp[] ops; + + int dir = 0; + + pos = matrix[len1 * len2 - 1]; + + ops = new EditOp[pos]; + + i = len1 - 1; + j = len2 - 1; + + ptr = len1 * len2 - 1; + + while (i > 0 || j > 0) { + + if (i != 0 + && j != 0 + && matrix[ptr] == matrix[ptr - len2 - 1] + && c1[p1 + i - 1] == c2[p2 + j - 1]) { + + i--; + j--; + ptr -= len2 + 1; + dir = 0; + + continue; + } + + if (dir < 0 && j != 0 && matrix[ptr] == matrix[ptr - 1] + 1) { + + EditOp eop = new EditOp(); + + pos--; + ops[pos] = eop; + eop.type = EditType.INSERT; + eop.spos = i + o1; + eop.dpos = --j + o2; + ptr--; + + continue; + } + + if (dir > 0 && i != 0 && matrix[ptr] == matrix[ptr - len2] + 1) { + + EditOp eop = new EditOp(); + + pos--; + ops[pos] = eop; + eop.type = EditType.DELETE; + eop.spos = --i + o1; + eop.dpos = j + o2; + ptr -= len2; + + continue; + } + + if (i != 0 && j != 0 && matrix[ptr] == matrix[ptr - len2 - 1] + 1) { + + pos--; + + EditOp eop = new EditOp(); + ops[pos] = eop; + + eop.type = EditType.REPLACE; + eop.spos = --i + o1; + eop.dpos = --j + o2; + + ptr -= len2 + 1; + dir = 0; + continue; + } + + if (dir == 0 && j != 0 && matrix[ptr] == matrix[ptr - 1] + 1) { + + pos--; + EditOp eop = new EditOp(); + ops[pos] = eop; + eop.type = EditType.INSERT; + eop.spos = i + o1; + eop.dpos = --j + o2; + ptr--; + dir = -1; + + continue; + } + + if (dir == 0 && i != 0 && matrix[ptr] == matrix[ptr - len2] + 1) { + pos--; + EditOp eop = new EditOp(); + ops[pos] = eop; + + eop.type = EditType.DELETE; + eop.spos = --i + o1; + eop.dpos = j + o2; + ptr -= len2; + dir = 1; + continue; + } + + assert false; + } + + return ops; + } + + public static MatchingBlock[] getMatchingBlocks(String s1, String s2) { + + return getMatchingBlocks(s1.length(), s2.length(), getEditOps(s1, s2)); + } + + public static MatchingBlock[] getMatchingBlocks(int len1, int len2, OpCode[] ops) { + + int n = ops.length; + + int noOfMB, i; + int o = 0; + + noOfMB = 0; + + for (i = n; i-- != 0; o++) { + + if (ops[o].type == EditType.KEEP) { + + noOfMB++; + + while (i != 0 && ops[o].type == EditType.KEEP) { + i--; + o++; + } + + if (i == 0) break; + } + } + + MatchingBlock[] matchingBlocks = new MatchingBlock[noOfMB + 1]; + int mb = 0; + o = 0; + matchingBlocks[mb] = new MatchingBlock(); + + for (i = n; i != 0; i--, o++) { + + if (ops[o].type == EditType.KEEP) { + + matchingBlocks[mb].spos = ops[o].sbeg; + matchingBlocks[mb].dpos = ops[o].dbeg; + + while (i != 0 && ops[o].type == EditType.KEEP) { + i--; + o++; + } + + if (i == 0) { + matchingBlocks[mb].length = len1 - matchingBlocks[mb].spos; + mb++; + break; + } + + matchingBlocks[mb].length = ops[o].sbeg - matchingBlocks[mb].spos; + mb++; matchingBlocks[mb] = new MatchingBlock(); - - for (i = n; i != 0; i--, o++) { - - if (ops[o].type == EditType.KEEP) { - - - matchingBlocks[mb].spos = ops[o].sbeg; - matchingBlocks[mb].dpos = ops[o].dbeg; - - while (i != 0 && ops[o].type == EditType.KEEP) { - i--; - o++; - } - - if (i == 0) { - matchingBlocks[mb].length = len1 - matchingBlocks[mb].spos; - mb++; - break; - } - - matchingBlocks[mb].length = ops[o].sbeg - matchingBlocks[mb].spos; - mb++; - matchingBlocks[mb] = new MatchingBlock(); - } - - - } - - assert mb == noOfMB; - - MatchingBlock finalBlock = new MatchingBlock(); - finalBlock.spos = len1; - finalBlock.dpos = len2; - finalBlock.length = 0; - - matchingBlocks[mb] = finalBlock; - - return matchingBlocks; - - + } } + assert mb == noOfMB; - private static MatchingBlock[] getMatchingBlocks(int len1, int len2, EditOp[] ops) { + MatchingBlock finalBlock = new MatchingBlock(); + finalBlock.spos = len1; + finalBlock.dpos = len2; + finalBlock.length = 0; - int n = ops.length; + matchingBlocks[mb] = finalBlock; - int numberOfMatchingBlocks, i, spos, dpos; + return matchingBlocks; + } - numberOfMatchingBlocks = 0; + private static MatchingBlock[] getMatchingBlocks(int len1, int len2, EditOp[] ops) { - int o = 0; + int n = ops.length; - spos = dpos = 0; + int numberOfMatchingBlocks, i, spos, dpos; - EditType type; + numberOfMatchingBlocks = 0; - for (i = n; i != 0; ) { + int o = 0; + spos = dpos = 0; - while (ops[o].type == EditType.KEEP && --i != 0) { - o++; - } + EditType type; - if (i == 0) - break; + for (i = n; i != 0; ) { - if (spos < ops[o].spos || dpos < ops[o].dpos) { + while (ops[o].type == EditType.KEEP && --i != 0) { + o++; + } - numberOfMatchingBlocks++; - spos = ops[o].spos; - dpos = ops[o].dpos; + if (i == 0) break; - } + if (spos < ops[o].spos || dpos < ops[o].dpos) { - type = ops[o].type; + numberOfMatchingBlocks++; + spos = ops[o].spos; + dpos = ops[o].dpos; + } - switch (type) { - case REPLACE: - do { - spos++; - dpos++; - i--; - o++; - } while (i != 0 && ops[o].type == type && - spos == ops[o].spos && dpos == ops[o].dpos); - break; + type = ops[o].type; - case DELETE: - do { - spos++; - i--; - o++; - } while (i != 0 && ops[o].type == type && - spos == ops[o].spos && dpos == ops[o].dpos); - break; + switch (type) { + case REPLACE: + do { + spos++; + dpos++; + i--; + o++; + } while (i != 0 && ops[o].type == type && spos == ops[o].spos && dpos == ops[o].dpos); + break; - case INSERT: - do { - dpos++; - i--; - o++; - } while (i != 0 && ops[o].type == type && - spos == ops[o].spos && dpos == ops[o].dpos); - break; + case DELETE: + do { + spos++; + i--; + o++; + } while (i != 0 && ops[o].type == type && spos == ops[o].spos && dpos == ops[o].dpos); + break; - default: - break; - } - } + case INSERT: + do { + dpos++; + i--; + o++; + } while (i != 0 && ops[o].type == type && spos == ops[o].spos && dpos == ops[o].dpos); + break; - if (spos < len1 || dpos < len2) { - numberOfMatchingBlocks++; - } - - MatchingBlock[] matchingBlocks = new MatchingBlock[numberOfMatchingBlocks + 1]; - - o = 0; - spos = dpos = 0; - int mbIndex = 0; - - - for (i = n; i != 0; ) { - - while (ops[o].type == EditType.KEEP && --i != 0) - o++; - - if (i == 0) - break; - - if (spos < ops[o].spos || dpos < ops[o].dpos) { - MatchingBlock mb = new MatchingBlock(); - - mb.spos = spos; - mb.dpos = dpos; - mb.length = ops[o].spos - spos; - spos = ops[o].spos; - dpos = ops[o].dpos; - - matchingBlocks[mbIndex++] = mb; - - } - - type = ops[o].type; - - switch (type) { - case REPLACE: - do { - spos++; - dpos++; - i--; - o++; - } while (i != 0 && ops[o].type == type && - spos == ops[o].spos && dpos == ops[o].dpos); - break; - - case DELETE: - do { - spos++; - i--; - o++; - } while (i != 0 && ops[o].type == type && - spos == ops[o].spos && dpos == ops[o].dpos); - break; - - case INSERT: - do { - dpos++; - i--; - o++; - } while (i != 0 && ops[o].type == type && - spos == ops[o].spos && dpos == ops[o].dpos); - break; - - default: - break; - } - } - - if (spos < len1 || dpos < len2) { - assert len1 - spos == len2 - dpos; - - MatchingBlock mb = new MatchingBlock(); - mb.spos = spos; - mb.dpos = dpos; - mb.length = len1 - spos; - - matchingBlocks[mbIndex++] = mb; - } - - assert numberOfMatchingBlocks == mbIndex; - - MatchingBlock finalBlock = new MatchingBlock(); - finalBlock.spos = len1; - finalBlock.dpos = len2; - finalBlock.length = 0; - - matchingBlocks[mbIndex] = finalBlock; - - - return matchingBlocks; + default: + break; + } } - - private static OpCode[] editOpsToOpCodes(EditOp[] ops, int len1, int len2) { - - int n = ops.length; - int noOfBlocks, i, spos, dpos; - int o = 0; - EditType type; - - noOfBlocks = 0; - spos = dpos = 0; - - for (i = n; i != 0; ) { - - while (ops[o].type == EditType.KEEP && --i != 0) { - o++; - } - - if (i == 0) - break; - - if (spos < ops[o].spos || dpos < ops[o].dpos) { - - noOfBlocks++; - spos = ops[o].spos; - dpos = ops[o].dpos; - - } - - // TODO: Is this right? - noOfBlocks++; - type = ops[o].type; - - switch (type) { - case REPLACE: - do { - spos++; - dpos++; - i--; - o++; - } while (i != 0 && ops[o].type == type && - spos == ops[o].spos && dpos == ops[o].dpos); - break; - - case DELETE: - do { - spos++; - i--; - o++; - } while (i != 0 && ops[o].type == type && - spos == ops[o].spos && dpos == ops[o].dpos); - break; - - case INSERT: - do { - dpos++; - i--; - o++; - } while (i != 0 && ops[o].type == type && - spos == ops[o].spos && dpos == ops[o].dpos); - break; - - default: - break; - } - } - - if (spos < len1 || dpos < len2) - noOfBlocks++; - - OpCode[] opCodes = new OpCode[noOfBlocks]; - - o = 0; - spos = dpos = 0; - int oIndex = 0; - - for (i = n; i != 0; ) { - - while (ops[o].type == EditType.KEEP && --i != 0) - o++; - - if (i == 0) - break; - - OpCode oc = new OpCode(); - opCodes[oIndex] = oc; - oc.sbeg = spos; - oc.dbeg = dpos; - - if (spos < ops[o].spos || dpos < ops[o].dpos) { - - oc.type = EditType.KEEP; - spos = oc.send = ops[o].spos; - dpos = oc.dend = ops[o].dpos; - - oIndex++; - OpCode oc2 = new OpCode(); - opCodes[oIndex] = oc2; - oc2.sbeg = spos; - oc2.dbeg = dpos; - - } - - type = ops[o].type; - - switch (type) { - case REPLACE: - do { - spos++; - dpos++; - i--; - o++; - } while (i != 0 && ops[o].type == type && - spos == ops[o].spos && dpos == ops[o].dpos); - break; - - case DELETE: - do { - spos++; - i--; - o++; - } while (i != 0 && ops[o].type == type && - spos == ops[o].spos && dpos == ops[o].dpos); - break; - - case INSERT: - do { - dpos++; - i--; - o++; - } while (i != 0 && ops[o].type == type && - spos == ops[o].spos && dpos == ops[o].dpos); - break; - - default: - break; - } - - opCodes[oIndex].type = type; - opCodes[oIndex].send = spos; - opCodes[oIndex].dend = dpos; - oIndex++; - } - - if (spos < len1 || dpos < len2) { - - assert len1 - spos == len2 - dpos; - if (opCodes[oIndex] == null) - opCodes[oIndex] = new OpCode(); - opCodes[oIndex].type = EditType.KEEP; - opCodes[oIndex].sbeg = spos; - opCodes[oIndex].dbeg = dpos; - opCodes[oIndex].send = len1; - opCodes[oIndex].dend = len2; - - oIndex++; - - } - - assert oIndex == noOfBlocks; - - return opCodes; - + if (spos < len1 || dpos < len2) { + numberOfMatchingBlocks++; } - public static int levEditDistance(String s1, String s2, int xcost) { + MatchingBlock[] matchingBlocks = new MatchingBlock[numberOfMatchingBlocks + 1]; - int i; - int half; + o = 0; + spos = dpos = 0; + int mbIndex = 0; - char[] c1 = s1.toCharArray(); - char[] c2 = s2.toCharArray(); + for (i = n; i != 0; ) { - int str1 = 0; - int str2 = 0; + while (ops[o].type == EditType.KEEP && --i != 0) o++; - int len1 = s1.length(); - int len2 = s2.length(); + if (i == 0) break; - /* strip common prefix */ - while (len1 > 0 && len2 > 0 && c1[str1] == c2[str2]) { + if (spos < ops[o].spos || dpos < ops[o].dpos) { + MatchingBlock mb = new MatchingBlock(); - len1--; - len2--; - str1++; - str2++; + mb.spos = spos; + mb.dpos = dpos; + mb.length = ops[o].spos - spos; + spos = ops[o].spos; + dpos = ops[o].dpos; + matchingBlocks[mbIndex++] = mb; + } + + type = ops[o].type; + + switch (type) { + case REPLACE: + do { + spos++; + dpos++; + i--; + o++; + } while (i != 0 && ops[o].type == type && spos == ops[o].spos && dpos == ops[o].dpos); + break; + + case DELETE: + do { + spos++; + i--; + o++; + } while (i != 0 && ops[o].type == type && spos == ops[o].spos && dpos == ops[o].dpos); + break; + + case INSERT: + do { + dpos++; + i--; + o++; + } while (i != 0 && ops[o].type == type && spos == ops[o].spos && dpos == ops[o].dpos); + break; + + default: + break; + } + } + + if (spos < len1 || dpos < len2) { + assert len1 - spos == len2 - dpos; + + MatchingBlock mb = new MatchingBlock(); + mb.spos = spos; + mb.dpos = dpos; + mb.length = len1 - spos; + + matchingBlocks[mbIndex++] = mb; + } + + assert numberOfMatchingBlocks == mbIndex; + + MatchingBlock finalBlock = new MatchingBlock(); + finalBlock.spos = len1; + finalBlock.dpos = len2; + finalBlock.length = 0; + + matchingBlocks[mbIndex] = finalBlock; + + return matchingBlocks; + } + + private static OpCode[] editOpsToOpCodes(EditOp[] ops, int len1, int len2) { + + int n = ops.length; + int noOfBlocks, i, spos, dpos; + int o = 0; + EditType type; + + noOfBlocks = 0; + spos = dpos = 0; + + for (i = n; i != 0; ) { + + while (ops[o].type == EditType.KEEP && --i != 0) { + o++; + } + + if (i == 0) break; + + if (spos < ops[o].spos || dpos < ops[o].dpos) { + + noOfBlocks++; + spos = ops[o].spos; + dpos = ops[o].dpos; + } + + // TODO: Is this right? + noOfBlocks++; + type = ops[o].type; + + switch (type) { + case REPLACE: + do { + spos++; + dpos++; + i--; + o++; + } while (i != 0 && ops[o].type == type && spos == ops[o].spos && dpos == ops[o].dpos); + break; + + case DELETE: + do { + spos++; + i--; + o++; + } while (i != 0 && ops[o].type == type && spos == ops[o].spos && dpos == ops[o].dpos); + break; + + case INSERT: + do { + dpos++; + i--; + o++; + } while (i != 0 && ops[o].type == type && spos == ops[o].spos && dpos == ops[o].dpos); + break; + + default: + break; + } + } + + if (spos < len1 || dpos < len2) noOfBlocks++; + + OpCode[] opCodes = new OpCode[noOfBlocks]; + + o = 0; + spos = dpos = 0; + int oIndex = 0; + + for (i = n; i != 0; ) { + + while (ops[o].type == EditType.KEEP && --i != 0) o++; + + if (i == 0) break; + + OpCode oc = new OpCode(); + opCodes[oIndex] = oc; + oc.sbeg = spos; + oc.dbeg = dpos; + + if (spos < ops[o].spos || dpos < ops[o].dpos) { + + oc.type = EditType.KEEP; + spos = oc.send = ops[o].spos; + dpos = oc.dend = ops[o].dpos; + + oIndex++; + OpCode oc2 = new OpCode(); + opCodes[oIndex] = oc2; + oc2.sbeg = spos; + oc2.dbeg = dpos; + } + + type = ops[o].type; + + switch (type) { + case REPLACE: + do { + spos++; + dpos++; + i--; + o++; + } while (i != 0 && ops[o].type == type && spos == ops[o].spos && dpos == ops[o].dpos); + break; + + case DELETE: + do { + spos++; + i--; + o++; + } while (i != 0 && ops[o].type == type && spos == ops[o].spos && dpos == ops[o].dpos); + break; + + case INSERT: + do { + dpos++; + i--; + o++; + } while (i != 0 && ops[o].type == type && spos == ops[o].spos && dpos == ops[o].dpos); + break; + + default: + break; + } + + opCodes[oIndex].type = type; + opCodes[oIndex].send = spos; + opCodes[oIndex].dend = dpos; + oIndex++; + } + + if (spos < len1 || dpos < len2) { + + assert len1 - spos == len2 - dpos; + if (opCodes[oIndex] == null) opCodes[oIndex] = new OpCode(); + opCodes[oIndex].type = EditType.KEEP; + opCodes[oIndex].sbeg = spos; + opCodes[oIndex].dbeg = dpos; + opCodes[oIndex].send = len1; + opCodes[oIndex].dend = len2; + + oIndex++; + } + + assert oIndex == noOfBlocks; + + return opCodes; + } + + public static int levEditDistance(String s1, String s2, int xcost) { + + int i; + int half; + + char[] c1 = s1.toCharArray(); + char[] c2 = s2.toCharArray(); + + int str1 = 0; + int str2 = 0; + + int len1 = s1.length(); + int len2 = s2.length(); + + /* strip common prefix */ + while (len1 > 0 && len2 > 0 && c1[str1] == c2[str2]) { + + len1--; + len2--; + str1++; + str2++; + } + + /* strip common suffix */ + while (len1 > 0 && len2 > 0 && c1[str1 + len1 - 1] == c2[str2 + len2 - 1]) { + len1--; + len2--; + } + + /* catch trivial cases */ + if (len1 == 0) return len2; + if (len2 == 0) return len1; + + /* make the inner cycle (i.e. str2) the longer one */ + if (len1 > len2) { + + int nx = len1; + int temp = str1; + + len1 = len2; + len2 = nx; + + str1 = str2; + str2 = temp; + + char[] t = c2; + c2 = c1; + c1 = t; + } + + /* check len1 == 1 separately */ + if (len1 == 1) { + if (xcost != 0) { + return len2 + 1 - 2 * memchr(c2, str2, c1[str1], len2); + } else { + return len2 - memchr(c2, str2, c1[str1], len2); + } + } + + len1++; + len2++; + half = len1 >> 1; + + int[] row = new int[len2]; + int end = len2 - 1; + + for (i = 0; i < len2 - (xcost != 0 ? 0 : half); i++) row[i] = i; + + /* go through the matrix and compute the costs. yes, this is an extremely + * obfuscated version, but also extremely memory-conservative and relatively + * fast. */ + + if (xcost != 0) { + + for (i = 1; i < len1; i++) { + + int p = 1; + + char ch1 = c1[str1 + i - 1]; + int c2p = str2; + + int D = i; + int x = i; + + while (p <= end) { + + if (ch1 == c2[c2p++]) { + x = --D; + } else { + x++; + } + D = row[p]; + D++; + + if (x > D) x = D; + row[p++] = x; } + } - /* strip common suffix */ - while (len1 > 0 && len2 > 0 && c1[str1 + len1 - 1] == c2[str2 + len2 - 1]) { - len1--; - len2--; - } + } else { - /* catch trivial cases */ - if (len1 == 0) - return len2; - if (len2 == 0) - return len1; + /* in this case we don't have to scan two corner triangles (of size len1/2) + * in the matrix because no best path can go throught them. note this + * breaks when len1 == len2 == 2 so the memchr() special case above is + * necessary */ - /* make the inner cycle (i.e. str2) the longer one */ - if (len1 > len2) { + row[0] = len1 - half - 1; + for (i = 1; i < len1; i++) { + int p; - int nx = len1; - int temp = str1; + char ch1 = c1[str1 + i - 1]; + int c2p; - len1 = len2; - len2 = nx; + int D, x; - str1 = str2; - str2 = temp; - - char[] t = c2; - c2 = c1; - c1 = t; - - } - - /* check len1 == 1 separately */ - if (len1 == 1) { - if (xcost != 0) { - return len2 + 1 - 2 * memchr(c2, str2, c1[str1], len2); - } else { - return len2 - memchr(c2, str2, c1[str1], len2); - } - } - - len1++; - len2++; - half = len1 >> 1; - - int[] row = new int[len2]; - int end = len2 - 1; - - for (i = 0; i < len2 - (xcost != 0 ? 0 : half); i++) - row[i] = i; - - - /* go through the matrix and compute the costs. yes, this is an extremely - * obfuscated version, but also extremely memory-conservative and relatively - * fast. */ - - if (xcost != 0) { - - for (i = 1; i < len1; i++) { - - int p = 1; - - char ch1 = c1[str1 + i - 1]; - int c2p = str2; - - int D = i; - int x = i; - - while (p <= end) { - - if (ch1 == c2[c2p++]) { - x = --D; - } else { - x++; - } - D = row[p]; - D++; - - if (x > D) - x = D; - row[p++] = x; - - } - - } + /* skip the upper triangle */ + if (i >= len1 - half) { + int offset = i - (len1 - half); + int c3; + c2p = str2 + offset; + p = offset; + c3 = row[p++] + ((ch1 != c2[c2p++]) ? 1 : 0); + x = row[p]; + x++; + D = x; + if (x > c3) { + x = c3; + } + row[p++] = x; } else { - - /* in this case we don't have to scan two corner triangles (of size len1/2) - * in the matrix because no best path can go throught them. note this - * breaks when len1 == len2 == 2 so the memchr() special case above is - * necessary */ - - row[0] = len1 - half - 1; - for (i = 1; i < len1; i++) { - int p; - - char ch1 = c1[str1 + i - 1]; - int c2p; - - int D, x; - - /* skip the upper triangle */ - if (i >= len1 - half) { - int offset = i - (len1 - half); - int c3; - - c2p = str2 + offset; - p = offset; - c3 = row[p++] + ((ch1 != c2[c2p++]) ? 1 : 0); - x = row[p]; - x++; - D = x; - if (x > c3) { - x = c3; - } - row[p++] = x; - } else { - p = 1; - c2p = str2; - D = x = i; - } - /* skip the lower triangle */ - if (i <= half + 1) - end = len2 + i - half - 2; - /* main */ - while (p <= end) { - int c3 = --D + ((ch1 != c2[c2p++]) ? 1 : 0); - x++; - if (x > c3) { - x = c3; - } - D = row[p]; - D++; - if (x > D) - x = D; - row[p++] = x; - - } - - /* lower triangle sentinel */ - if (i <= half) { - int c3 = --D + ((ch1 != c2[c2p]) ? 1 : 0); - x++; - if (x > c3) { - x = c3; - } - row[p] = x; - } - } + p = 1; + c2p = str2; + D = x = i; + } + /* skip the lower triangle */ + if (i <= half + 1) end = len2 + i - half - 2; + /* main */ + while (p <= end) { + int c3 = --D + ((ch1 != c2[c2p++]) ? 1 : 0); + x++; + if (x > c3) { + x = c3; + } + D = row[p]; + D++; + if (x > D) x = D; + row[p++] = x; } - i = row[end]; - - return i; - - } - - private static int memchr(char[] haystack, int offset, char needle, int num) { - - if (num != 0) { - int p = 0; - - do { - - if (haystack[offset + p] == needle) - return 1; - - p++; - - } while (--num != 0); - + /* lower triangle sentinel */ + if (i <= half) { + int c3 = --D + ((ch1 != c2[c2p]) ? 1 : 0); + x++; + if (x > c3) { + x = c3; + } + row[p] = x; } - return 0; - + } } + i = row[end]; - public static double getRatio(String s1, String s2) { + return i; + } - int len1 = s1.length(); - int len2 = s2.length(); - int lensum = len1 + len2; + private static int memchr(char[] haystack, int offset, char needle, int num) { - int editDistance = levEditDistance(s1, s2, 1); + if (num != 0) { + int p = 0; - return (lensum - editDistance) / (double) lensum; + do { + if (haystack[offset + p] == needle) return 1; + + p++; + + } while (--num != 0); } + return 0; + } + public static double getRatio(String s1, String s2) { + int len1 = s1.length(); + int len2 = s2.length(); + int lensum = len1 + len2; + int editDistance = levEditDistance(s1, s2, 1); + + return (lensum - editDistance) / (double) lensum; + } } diff --git a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/diffutils/structs/EditOp.java b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/diffutils/structs/EditOp.java index 2d2b6b670..8c03ef6a0 100644 --- a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/diffutils/structs/EditOp.java +++ b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/diffutils/structs/EditOp.java @@ -2,13 +2,12 @@ package com.itsaky.androidide.diffutils.structs; public final class EditOp { - public EditType type; - public int spos; // source block pos - public int dpos; // destination block pos - - @Override - public String toString() { - return type.name() + "(" + spos + "," + dpos + ")"; - } + public EditType type; + public int spos; // source block pos + public int dpos; // destination block pos + @Override + public String toString() { + return type.name() + "(" + spos + "," + dpos + ")"; + } } diff --git a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/diffutils/structs/EditType.java b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/diffutils/structs/EditType.java index 5c770b3c5..d5400e135 100644 --- a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/diffutils/structs/EditType.java +++ b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/diffutils/structs/EditType.java @@ -1,11 +1,9 @@ package com.itsaky.androidide.diffutils.structs; public enum EditType { - - DELETE, - EQUAL, - INSERT, - REPLACE, - KEEP - + DELETE, + EQUAL, + INSERT, + REPLACE, + KEEP } diff --git a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/diffutils/structs/MatchingBlock.java b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/diffutils/structs/MatchingBlock.java index c5238bdfb..8553978af 100644 --- a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/diffutils/structs/MatchingBlock.java +++ b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/diffutils/structs/MatchingBlock.java @@ -1,12 +1,12 @@ package com.itsaky.androidide.diffutils.structs; public final class MatchingBlock { - public int spos; - public int dpos; - public int length; + public int spos; + public int dpos; + public int length; - @Override - public String toString() { - return "(" + spos + "," + dpos + "," + length + ")"; - } + @Override + public String toString() { + return "(" + spos + "," + dpos + "," + length + ")"; + } } diff --git a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/diffutils/structs/OpCode.java b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/diffutils/structs/OpCode.java index e53cd831f..8df6c4416 100644 --- a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/diffutils/structs/OpCode.java +++ b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/diffutils/structs/OpCode.java @@ -2,13 +2,12 @@ package com.itsaky.androidide.diffutils.structs; public final class OpCode { - public EditType type; - public int sbeg, send; - public int dbeg, dend; + public EditType type; + public int sbeg, send; + public int dbeg, dend; - @Override - public String toString() { - return type.name() + "(" + sbeg + "," + send + "," - + dbeg + "," + dend + ")"; - } + @Override + public String toString() { + return type.name() + "(" + sbeg + "," + send + "," + dbeg + "," + dend + ")"; + } } diff --git a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/Applicable.java b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/Applicable.java index 1619001c8..c0b794617 100644 --- a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/Applicable.java +++ b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/Applicable.java @@ -17,18 +17,15 @@ package com.itsaky.androidide.fuzzysearch; -/** - * A ratio/algorithm that can be applied - */ +/** A ratio/algorithm that can be applied */ public interface Applicable { - /** - * Apply the ratio/algorithm to the input strings - * - * @param s1 Input string - * @param s2 Input string - * @return The score of similarity - */ - int apply(String s1, String s2); - + /** + * Apply the ratio/algorithm to the input strings + * + * @param s1 Input string + * @param s2 Input string + * @return The score of similarity + */ + int apply(String s1, String s2); } diff --git a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/Extractor.java b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/Extractor.java index 4fc72167c..cd26dc4aa 100644 --- a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/Extractor.java +++ b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/Extractor.java @@ -28,192 +28,197 @@ import java.util.List; public class Extractor { - private int cutoff; + private int cutoff; - public Extractor() { - this.cutoff = 0; + public Extractor() { + this.cutoff = 0; + } + + public Extractor(int cutoff) { + this.cutoff = cutoff; + } + + public Extractor with(int cutoff) { + this.setCutoff(cutoff); + return this; + } + + /** + * Returns the list of choices with their associated scores of similarity in a list of {@link + * ExtractedResult} + * + * @param query The query string + * @param choices The list of choices + * @param func The function to apply + * @return The list of results + */ + public List extractWithoutOrder( + String query, Collection choices, Applicable func) { + List yields = new ArrayList<>(); + int index = 0; + + for (String s : choices) { + + int score = func.apply(query, s); + + if (score >= cutoff) { + yields.add(new ExtractedResult(s, score, index)); + } + index++; } - public Extractor(int cutoff) { - this.cutoff = cutoff; + return yields; + } + + /** + * Returns the list of choices with their associated scores of similarity in a list of {@link + * ExtractedResult} + * + * @param query The query string + * @param choices The list of choices + * @param toStringFunction The ToStringFunction to be applied to all choices. + * @param func The function to apply + * @return The list of results + */ + public List> extractWithoutOrder( + String query, Collection choices, ToStringFunction toStringFunction, Applicable func) { + + List> yields = new ArrayList<>(); + int index = 0; + + for (T t : choices) { + + String s = toStringFunction.apply(t); + int score = func.apply(query, s); + + if (score >= cutoff) { + yields.add(new BoundExtractedResult<>(t, s, score, index)); + } + index++; } - public Extractor with(int cutoff) { - this.setCutoff(cutoff); - return this; - } + return yields; + } - /** - * Returns the list of choices with their associated scores of similarity in a list - * of {@link ExtractedResult} - * - * @param query The query string - * @param choices The list of choices - * @param func The function to apply - * @return The list of results - */ - public List extractWithoutOrder(String query, Collection choices, - Applicable func) { - List yields = new ArrayList<>(); - int index = 0; + /** + * Find the single best match above a score in a list of choices. + * + * @param query A string to match against + * @param choices A list of choices + * @param func Scoring function + * @return An object containing the best match and it's score + */ + public ExtractedResult extractOne(String query, Collection choices, Applicable func) { + List extracted = extractWithoutOrder(query, choices, func); - for (String s : choices) { + return Collections.max(extracted); + } - int score = func.apply(query, s); + /** + * Find the single best match above a score in a list of choices. + * + * @param query A string to match against + * @param choices A list of choices + * @param toStringFunction The ToStringFunction to be applied to all choices. + * @param func Scoring function + * @return An object containing the best match and it's score + */ + public BoundExtractedResult extractOne( + String query, Collection choices, ToStringFunction toStringFunction, Applicable func) { - if (score >= cutoff) { - yields.add(new ExtractedResult(s, score, index)); - } - index++; - } + List> extracted = + extractWithoutOrder(query, choices, toStringFunction, func); - return yields; - } + return Collections.max(extracted); + } - /** - * Returns the list of choices with their associated scores of similarity in a list - * of {@link ExtractedResult} - * - * @param query The query string - * @param choices The list of choices - * @param toStringFunction The ToStringFunction to be applied to all choices. - * @param func The function to apply - * @return The list of results - */ - public List> extractWithoutOrder(String query, Collection choices, - ToStringFunction toStringFunction, Applicable func) { + /** + * Creates a sorted list of {@link ExtractedResult} which contain the top @param limit most + * similar choices + * + * @param query The query string + * @param choices A list of choices + * @param func The scoring function + * @return A list of the results + */ + public List extractTop( + String query, Collection choices, Applicable func) { + List best = extractWithoutOrder(query, choices, func); + Collections.sort(best, Collections.reverseOrder()); - List> yields = new ArrayList<>(); - int index = 0; + return best; + } - for (T t : choices) { + /** + * Creates a sorted list of {@link ExtractedResult} which contain the top @param limit most + * similar choices + * + * @param query The query string + * @param choices A list of choices + * @param toStringFunction The ToStringFunction to be applied to all choices. + * @param func The scoring function + * @return A list of the results + */ + public List> extractTop( + String query, Collection choices, ToStringFunction toStringFunction, Applicable func) { - String s = toStringFunction.apply(t); - int score = func.apply(query, s); + List> best = + extractWithoutOrder(query, choices, toStringFunction, func); + Collections.sort(best, Collections.>reverseOrder()); - if (score >= cutoff) { - yields.add(new BoundExtractedResult<>(t, s, score, index)); - } - index++; - } + return best; + } - return yields; + /** + * Creates a sorted list of {@link ExtractedResult} which contain the top @param limit most + * similar choices + * + * @param query The query string + * @param choices A list of choices + * @param limit Limits the number of results and speeds up the search (k-top heap sort) is used + * @return A list of the results + */ + public List extractTop( + String query, Collection choices, Applicable func, int limit) { + List best = extractWithoutOrder(query, choices, func); - } + List results = Utils.findTopKHeap(best, limit); + Collections.reverse(results); - /** - * Find the single best match above a score in a list of choices. - * - * @param query A string to match against - * @param choices A list of choices - * @param func Scoring function - * @return An object containing the best match and it's score - */ - public ExtractedResult extractOne(String query, Collection choices, Applicable func) { - List extracted = extractWithoutOrder(query, choices, func); + return results; + } - return Collections.max(extracted); - } + /** + * Creates a sorted list of {@link ExtractedResult} which contain the top @param limit most + * similar choices + * + * @param query The query string + * @param choices A list of choices + * @param toStringFunction The ToStringFunction to be applied to all choices. + * @param limit Limits the number of results and speeds up the search (k-top heap sort) is used + * @return A list of the results + */ + public List> extractTop( + String query, + Collection choices, + ToStringFunction toStringFunction, + Applicable func, + int limit) { - /** - * Find the single best match above a score in a list of choices. - * - * @param query A string to match against - * @param choices A list of choices - * @param toStringFunction The ToStringFunction to be applied to all choices. - * @param func Scoring function - * @return An object containing the best match and it's score - */ - public BoundExtractedResult extractOne(String query, Collection choices, ToStringFunction toStringFunction, - Applicable func) { + List> best = + extractWithoutOrder(query, choices, toStringFunction, func); - List> extracted = extractWithoutOrder(query, choices, toStringFunction, func); + List> results = Utils.findTopKHeap(best, limit); + Collections.reverse(results); - return Collections.max(extracted); + return results; + } - } + public int getCutoff() { + return cutoff; + } - /** - * Creates a sorted list of {@link ExtractedResult} which contain the - * top @param limit most similar choices - * - * @param query The query string - * @param choices A list of choices - * @param func The scoring function - * @return A list of the results - */ - public List extractTop(String query, Collection choices, Applicable func) { - List best = extractWithoutOrder(query, choices, func); - Collections.sort(best, Collections.reverseOrder()); - - return best; - } - - /** - * Creates a sorted list of {@link ExtractedResult} which contain the - * top @param limit most similar choices - * - * @param query The query string - * @param choices A list of choices - * @param toStringFunction The ToStringFunction to be applied to all choices. - * @param func The scoring function - * @return A list of the results - */ - public List> extractTop(String query, Collection choices, - ToStringFunction toStringFunction, Applicable func) { - - List> best = extractWithoutOrder(query, choices, toStringFunction, func); - Collections.sort(best, Collections.>reverseOrder()); - - return best; - } - - /** - * Creates a sorted list of {@link ExtractedResult} which contain the - * top @param limit most similar choices - * - * @param query The query string - * @param choices A list of choices - * @param limit Limits the number of results and speeds up - * the search (k-top heap sort) is used - * @return A list of the results - */ - public List extractTop(String query, Collection choices, Applicable func, int limit) { - List best = extractWithoutOrder(query, choices, func); - - List results = Utils.findTopKHeap(best, limit); - Collections.reverse(results); - - return results; - } - - /** - * Creates a sorted list of {@link ExtractedResult} which contain the - * top @param limit most similar choices - * - * @param query The query string - * @param choices A list of choices - * @param toStringFunction The ToStringFunction to be applied to all choices. - * @param limit Limits the number of results and speeds up - * the search (k-top heap sort) is used - * @return A list of the results - */ - public List> extractTop(String query, Collection choices, - ToStringFunction toStringFunction, Applicable func, int limit) { - - List> best = extractWithoutOrder(query, choices, toStringFunction, func); - - List> results = Utils.findTopKHeap(best, limit); - Collections.reverse(results); - - return results; - } - - public int getCutoff() { - return cutoff; - } - - public void setCutoff(int cutoff) { - this.cutoff = cutoff; - } + public void setCutoff(int cutoff) { + this.cutoff = cutoff; + } } diff --git a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/FuzzySearch.java b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/FuzzySearch.java index d0cd39e5e..724d70f24 100644 --- a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/FuzzySearch.java +++ b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/FuzzySearch.java @@ -32,644 +32,634 @@ import java.util.List; @SuppressWarnings("unused") public class FuzzySearch { - /** - * Calculates a Levenshtein simple ratio between the strings. This is indicates a measure of - * similarity - * - * @param s1 Input string - * @param s2 Input string - * @return The simple ratio - */ - public static int ratio(String s1, String s2) { - return new SimpleRatio().apply(s1, s2); - } + /** + * Calculates a Levenshtein simple ratio between the strings. This is indicates a measure of + * similarity + * + * @param s1 Input string + * @param s2 Input string + * @return The simple ratio + */ + public static int ratio(String s1, String s2) { + return new SimpleRatio().apply(s1, s2); + } - /** - * Calculates a Levenshtein simple ratio between the strings. This is indicates a measure of - * similarity - * - * @param s1 Input string - * @param s2 Input string - * @param stringFunction Functor which transforms strings before calculating the ratio - * @return The simple ratio - */ - public static int ratio(String s1, String s2, ToStringFunction stringFunction) { - return new SimpleRatio().apply(s1, s2, stringFunction); - } + /** + * Calculates a Levenshtein simple ratio between the strings. This is indicates a measure of + * similarity + * + * @param s1 Input string + * @param s2 Input string + * @param stringFunction Functor which transforms strings before calculating the ratio + * @return The simple ratio + */ + public static int ratio(String s1, String s2, ToStringFunction stringFunction) { + return new SimpleRatio().apply(s1, s2, stringFunction); + } - /** - * Inconsistent substrings lead to problems in matching. This ratio uses a heuristic called - * "best partial" for when two strings are of noticeably different lengths. - * - * @param s1 Input string - * @param s2 Input string - * @return The partial ratio - */ - public static int partialRatio(String s1, String s2) { - return new PartialRatio().apply(s1, s2); - } + /** + * Inconsistent substrings lead to problems in matching. This ratio uses a heuristic called "best + * partial" for when two strings are of noticeably different lengths. + * + * @param s1 Input string + * @param s2 Input string + * @return The partial ratio + */ + public static int partialRatio(String s1, String s2) { + return new PartialRatio().apply(s1, s2); + } - /** - * Inconsistent substrings lead to problems in matching. This ratio uses a heuristic called - * "best partial" for when two strings are of noticeably different lengths. - * - * @param s1 Input string - * @param s2 Input string - * @param stringFunction Functor which transforms strings before calculating the ratio - * @return The partial ratio - */ - public static int partialRatio(String s1, String s2, ToStringFunction stringFunction) { - return new PartialRatio().apply(s1, s2, stringFunction); - } + /** + * Inconsistent substrings lead to problems in matching. This ratio uses a heuristic called "best + * partial" for when two strings are of noticeably different lengths. + * + * @param s1 Input string + * @param s2 Input string + * @param stringFunction Functor which transforms strings before calculating the ratio + * @return The partial ratio + */ + public static int partialRatio(String s1, String s2, ToStringFunction stringFunction) { + return new PartialRatio().apply(s1, s2, stringFunction); + } - /** - * Find all alphanumeric tokens in the string and sort those tokens and then take ratio of - * resulting joined strings. - * - * @param s1 Input string - * @param s2 Input string - * @return The partial ratio of the strings - */ - public static int tokenSortPartialRatio(String s1, String s2) { - return new TokenSort().apply(s1, s2, new PartialRatio()); - } + /** + * Find all alphanumeric tokens in the string and sort those tokens and then take ratio of + * resulting joined strings. + * + * @param s1 Input string + * @param s2 Input string + * @return The partial ratio of the strings + */ + public static int tokenSortPartialRatio(String s1, String s2) { + return new TokenSort().apply(s1, s2, new PartialRatio()); + } - /** - * Find all alphanumeric tokens in the string and sort those tokens and then take ratio of - * resulting joined strings. - * - * @param s1 Input string - * @param s2 Input string - * @param stringFunction Functor which transforms strings before calculating the ratio - * @return The partial ratio of the strings - */ - public static int tokenSortPartialRatio( - String s1, String s2, ToStringFunction stringFunction) { - return new TokenSort().apply(s1, s2, new PartialRatio(), stringFunction); - } + /** + * Find all alphanumeric tokens in the string and sort those tokens and then take ratio of + * resulting joined strings. + * + * @param s1 Input string + * @param s2 Input string + * @param stringFunction Functor which transforms strings before calculating the ratio + * @return The partial ratio of the strings + */ + public static int tokenSortPartialRatio( + String s1, String s2, ToStringFunction stringFunction) { + return new TokenSort().apply(s1, s2, new PartialRatio(), stringFunction); + } - /** - * Find all alphanumeric tokens in the string and sort those tokens and then take ratio of - * resulting joined strings. - * - * @param s1 Input string - * @param s2 Input string - * @return The full ratio of the strings - */ - public static int tokenSortRatio(String s1, String s2) { - return new TokenSort().apply(s1, s2, new SimpleRatio()); - } + /** + * Find all alphanumeric tokens in the string and sort those tokens and then take ratio of + * resulting joined strings. + * + * @param s1 Input string + * @param s2 Input string + * @return The full ratio of the strings + */ + public static int tokenSortRatio(String s1, String s2) { + return new TokenSort().apply(s1, s2, new SimpleRatio()); + } - /** - * Find all alphanumeric tokens in the string and sort those tokens and then take ratio of - * resulting joined strings. - * - * @param s1 Input string - * @param s2 Input string - * @param stringFunction Functor which transforms strings before calculating the ratio - * @return The full ratio of the strings - */ - public static int tokenSortRatio( - String s1, String s2, ToStringFunction stringFunction) { - return new TokenSort().apply(s1, s2, new SimpleRatio(), stringFunction); - } + /** + * Find all alphanumeric tokens in the string and sort those tokens and then take ratio of + * resulting joined strings. + * + * @param s1 Input string + * @param s2 Input string + * @param stringFunction Functor which transforms strings before calculating the ratio + * @return The full ratio of the strings + */ + public static int tokenSortRatio(String s1, String s2, ToStringFunction stringFunction) { + return new TokenSort().apply(s1, s2, new SimpleRatio(), stringFunction); + } - /** - * Splits the strings into tokens and computes intersections and remainders between the tokens - * of the two strings. A comparison string is then built up and is compared using the simple - * ratio algorithm. Useful for strings where words appear redundantly. - * - * @param s1 Input string - * @param s2 Input string - * @return The ratio of similarity - */ - public static int tokenSetRatio(String s1, String s2) { - return new TokenSet().apply(s1, s2, new SimpleRatio()); - } + /** + * Splits the strings into tokens and computes intersections and remainders between the tokens of + * the two strings. A comparison string is then built up and is compared using the simple ratio + * algorithm. Useful for strings where words appear redundantly. + * + * @param s1 Input string + * @param s2 Input string + * @return The ratio of similarity + */ + public static int tokenSetRatio(String s1, String s2) { + return new TokenSet().apply(s1, s2, new SimpleRatio()); + } - /** - * Splits the strings into tokens and computes intersections and remainders between the tokens - * of the two strings. A comparison string is then built up and is compared using the simple - * ratio algorithm. Useful for strings where words appear redundantly. - * - * @param s1 Input string - * @param s2 Input string - * @param stringFunction Functor which transforms strings before calculating the ratio - * @return The ratio of similarity - */ - public static int tokenSetRatio(String s1, String s2, ToStringFunction stringFunction) { - return new TokenSet().apply(s1, s2, new SimpleRatio(), stringFunction); - } + /** + * Splits the strings into tokens and computes intersections and remainders between the tokens of + * the two strings. A comparison string is then built up and is compared using the simple ratio + * algorithm. Useful for strings where words appear redundantly. + * + * @param s1 Input string + * @param s2 Input string + * @param stringFunction Functor which transforms strings before calculating the ratio + * @return The ratio of similarity + */ + public static int tokenSetRatio(String s1, String s2, ToStringFunction stringFunction) { + return new TokenSet().apply(s1, s2, new SimpleRatio(), stringFunction); + } - /** - * Splits the strings into tokens and computes intersections and remainders between the tokens - * of the two strings. A comparison string is then built up and is compared using the simple - * ratio algorithm. Useful for strings where words appear redundantly. - * - * @param s1 Input string - * @param s2 Input string - * @return The ratio of similarity - */ - public static int tokenSetPartialRatio(String s1, String s2) { - return new TokenSet().apply(s1, s2, new PartialRatio()); - } + /** + * Splits the strings into tokens and computes intersections and remainders between the tokens of + * the two strings. A comparison string is then built up and is compared using the simple ratio + * algorithm. Useful for strings where words appear redundantly. + * + * @param s1 Input string + * @param s2 Input string + * @return The ratio of similarity + */ + public static int tokenSetPartialRatio(String s1, String s2) { + return new TokenSet().apply(s1, s2, new PartialRatio()); + } - /** - * Splits the strings into tokens and computes intersections and remainders between the tokens - * of the two strings. A comparison string is then built up and is compared using the simple - * ratio algorithm. Useful for strings where words appear redundantly. - * - * @param s1 Input string - * @param s2 Input string - * @param stringFunction Functor which transforms strings before calculating the ratio - * @return The ratio of similarity - */ - public static int tokenSetPartialRatio( - String s1, String s2, ToStringFunction stringFunction) { - return new TokenSet().apply(s1, s2, new PartialRatio(), stringFunction); - } + /** + * Splits the strings into tokens and computes intersections and remainders between the tokens of + * the two strings. A comparison string is then built up and is compared using the simple ratio + * algorithm. Useful for strings where words appear redundantly. + * + * @param s1 Input string + * @param s2 Input string + * @param stringFunction Functor which transforms strings before calculating the ratio + * @return The ratio of similarity + */ + public static int tokenSetPartialRatio( + String s1, String s2, ToStringFunction stringFunction) { + return new TokenSet().apply(s1, s2, new PartialRatio(), stringFunction); + } - /** - * Calculates a weighted ratio between the different algorithms for best results - * - * @param s1 Input string - * @param s2 Input string - * @return The ratio of similarity - */ - public static int weightedRatio(String s1, String s2) { - return new WeightedRatio().apply(s1, s2); - } + /** + * Calculates a weighted ratio between the different algorithms for best results + * + * @param s1 Input string + * @param s2 Input string + * @return The ratio of similarity + */ + public static int weightedRatio(String s1, String s2) { + return new WeightedRatio().apply(s1, s2); + } - /** - * Calculates a weighted ratio between the different algorithms for best results - * - * @param s1 Input string - * @param s2 Input string - * @param stringFunction Functor which transforms strings before calculating the ratio - * @return The ratio of similarity - */ - public static int weightedRatio(String s1, String s2, ToStringFunction stringFunction) { - return new WeightedRatio().apply(s1, s2, stringFunction); - } + /** + * Calculates a weighted ratio between the different algorithms for best results + * + * @param s1 Input string + * @param s2 Input string + * @param stringFunction Functor which transforms strings before calculating the ratio + * @return The ratio of similarity + */ + public static int weightedRatio(String s1, String s2, ToStringFunction stringFunction) { + return new WeightedRatio().apply(s1, s2, stringFunction); + } - /** - * Creates a sorted list of {@link ExtractedResult} which contain the top @param limit - * most similar choices - * - * @param query The query string - * @param choices A list of choices - * @param func The scoring function - * @return A list of the results - */ - public static List extractTop( - String query, Collection choices, Applicable func, int limit, int cutoff) { - Extractor extractor = new Extractor(cutoff); - return extractor.extractTop(query, choices, func, limit); - } + /** + * Creates a sorted list of {@link ExtractedResult} which contain the top @param limit most + * similar choices + * + * @param query The query string + * @param choices A list of choices + * @param func The scoring function + * @return A list of the results + */ + public static List extractTop( + String query, Collection choices, Applicable func, int limit, int cutoff) { + Extractor extractor = new Extractor(cutoff); + return extractor.extractTop(query, choices, func, limit); + } - /** - * Creates a sorted list of {@link ExtractedResult} which contain the top @param limit - * most similar choices - * - * @param query The query string - * @param choices A list of choices - * @param limit Limits the number of results and speeds up the search (k-top heap sort) is used - * @param cutoff Rejects any entries with score below this - * @return A list of the results - */ - public static List extractTop( - String query, Collection choices, int limit, int cutoff) { - Extractor extractor = new Extractor(cutoff); - return extractor.extractTop(query, choices, new WeightedRatio(), limit); - } + /** + * Creates a sorted list of {@link ExtractedResult} which contain the top @param limit most + * similar choices + * + * @param query The query string + * @param choices A list of choices + * @param limit Limits the number of results and speeds up the search (k-top heap sort) is used + * @param cutoff Rejects any entries with score below this + * @return A list of the results + */ + public static List extractTop( + String query, Collection choices, int limit, int cutoff) { + Extractor extractor = new Extractor(cutoff); + return extractor.extractTop(query, choices, new WeightedRatio(), limit); + } - /** - * Creates a sorted list of {@link ExtractedResult} which contain the top @param limit - * most similar choices - * - * @param query The query string - * @param choices A list of choices - * @param func The scoring function - * @param limit The number of results to return - * @return A list of the results - */ - public static List extractTop( - String query, Collection choices, Applicable func, int limit) { - Extractor extractor = new Extractor(); - return extractor.extractTop(query, choices, func, limit); - } + /** + * Creates a sorted list of {@link ExtractedResult} which contain the top @param limit most + * similar choices + * + * @param query The query string + * @param choices A list of choices + * @param func The scoring function + * @param limit The number of results to return + * @return A list of the results + */ + public static List extractTop( + String query, Collection choices, Applicable func, int limit) { + Extractor extractor = new Extractor(); + return extractor.extractTop(query, choices, func, limit); + } - /** - * Creates a sorted list of {@link ExtractedResult} which contain the top @param limit - * most similar choices - * - * @param query The query string - * @param choices A list of choices - * @param limit The number of results to return - * @return A list of the results - */ - public static List extractTop( - String query, Collection choices, int limit) { - Extractor extractor = new Extractor(); - return extractor.extractTop(query, choices, new WeightedRatio(), limit); - } + /** + * Creates a sorted list of {@link ExtractedResult} which contain the top @param limit most + * similar choices + * + * @param query The query string + * @param choices A list of choices + * @param limit The number of results to return + * @return A list of the results + */ + public static List extractTop( + String query, Collection choices, int limit) { + Extractor extractor = new Extractor(); + return extractor.extractTop(query, choices, new WeightedRatio(), limit); + } - /** - * Creates a sorted list of {@link ExtractedResult} which contain all the choices with - * their corresponding score where higher is more similar - * - * @param query The query string - * @param choices A list of choices - * @param func The scoring function - * @return A list of the results - */ - public static List extractSorted( - String query, Collection choices, Applicable func) { - Extractor extractor = new Extractor(); - return extractor.extractTop(query, choices, func); - } + /** + * Creates a sorted list of {@link ExtractedResult} which contain all the choices with + * their corresponding score where higher is more similar + * + * @param query The query string + * @param choices A list of choices + * @param func The scoring function + * @return A list of the results + */ + public static List extractSorted( + String query, Collection choices, Applicable func) { + Extractor extractor = new Extractor(); + return extractor.extractTop(query, choices, func); + } - /** - * Creates a sorted list of {@link ExtractedResult} which contain all the choices with - * their corresponding score where higher is more similar - * - * @param query The query string - * @param choices A list of choices - * @param func The scoring function - * @param cutoff Keep only scores above cutoff - * @return A list of the results - */ - public static List extractSorted( - String query, Collection choices, Applicable func, int cutoff) { - Extractor extractor = new Extractor(cutoff); - return extractor.extractTop(query, choices, func); - } + /** + * Creates a sorted list of {@link ExtractedResult} which contain all the choices with + * their corresponding score where higher is more similar + * + * @param query The query string + * @param choices A list of choices + * @param func The scoring function + * @param cutoff Keep only scores above cutoff + * @return A list of the results + */ + public static List extractSorted( + String query, Collection choices, Applicable func, int cutoff) { + Extractor extractor = new Extractor(cutoff); + return extractor.extractTop(query, choices, func); + } - /** - * Creates a sorted list of {@link ExtractedResult} which contain all the choices with - * their corresponding score where higher is more similar - * - * @param query The query string - * @param choices A list of choices - * @return A list of the results - */ - public static List extractSorted(String query, Collection choices) { - Extractor extractor = new Extractor(); - return extractor.extractTop(query, choices, new WeightedRatio()); - } + /** + * Creates a sorted list of {@link ExtractedResult} which contain all the choices with + * their corresponding score where higher is more similar + * + * @param query The query string + * @param choices A list of choices + * @return A list of the results + */ + public static List extractSorted(String query, Collection choices) { + Extractor extractor = new Extractor(); + return extractor.extractTop(query, choices, new WeightedRatio()); + } - /** - * Creates a sorted list of {@link ExtractedResult} which contain all the choices with - * their corresponding score where higher is more similar - * - * @param query The query string - * @param choices A list of choices - * @param cutoff Keep only scores above cutoff - * @return A list of the results - */ - public static List extractSorted( - String query, Collection choices, int cutoff) { - Extractor extractor = new Extractor(cutoff); - return extractor.extractTop(query, choices, new WeightedRatio()); - } + /** + * Creates a sorted list of {@link ExtractedResult} which contain all the choices with + * their corresponding score where higher is more similar + * + * @param query The query string + * @param choices A list of choices + * @param cutoff Keep only scores above cutoff + * @return A list of the results + */ + public static List extractSorted( + String query, Collection choices, int cutoff) { + Extractor extractor = new Extractor(cutoff); + return extractor.extractTop(query, choices, new WeightedRatio()); + } - /** - * Creates a list of {@link ExtractedResult} which contain all the choices with their - * corresponding score where higher is more similar - * - * @param query The query string - * @param choices A list of choices - * @param func The scoring function - * @return A list of the results - */ - public static List extractAll( - String query, Collection choices, Applicable func) { + /** + * Creates a list of {@link ExtractedResult} which contain all the choices with their + * corresponding score where higher is more similar + * + * @param query The query string + * @param choices A list of choices + * @param func The scoring function + * @return A list of the results + */ + public static List extractAll( + String query, Collection choices, Applicable func) { - Extractor extractor = new Extractor(); + Extractor extractor = new Extractor(); - return extractor.extractWithoutOrder(query, choices, func); - } + return extractor.extractWithoutOrder(query, choices, func); + } - /** - * Creates a list of {@link ExtractedResult} which contain all the choices with their - * corresponding score where higher is more similar - * - * @param query The query string - * @param choices A list of choices - * @param func The scoring function - * @param cutoff Keep only scores above cutoff - * @return A list of the results - */ - public static List extractAll( - String query, Collection choices, Applicable func, int cutoff) { - Extractor extractor = new Extractor(cutoff); - return extractor.extractWithoutOrder(query, choices, func); - } + /** + * Creates a list of {@link ExtractedResult} which contain all the choices with their + * corresponding score where higher is more similar + * + * @param query The query string + * @param choices A list of choices + * @param func The scoring function + * @param cutoff Keep only scores above cutoff + * @return A list of the results + */ + public static List extractAll( + String query, Collection choices, Applicable func, int cutoff) { + Extractor extractor = new Extractor(cutoff); + return extractor.extractWithoutOrder(query, choices, func); + } - /** - * Creates a list of {@link ExtractedResult} which contain all the choices with their - * corresponding score where higher is more similar - * - * @param query The query string - * @param choices A list of choices - * @return A list of the results - */ - public static List extractAll(String query, Collection choices) { - Extractor extractor = new Extractor(); - return extractor.extractWithoutOrder(query, choices, new WeightedRatio()); - } + /** + * Creates a list of {@link ExtractedResult} which contain all the choices with their + * corresponding score where higher is more similar + * + * @param query The query string + * @param choices A list of choices + * @return A list of the results + */ + public static List extractAll(String query, Collection choices) { + Extractor extractor = new Extractor(); + return extractor.extractWithoutOrder(query, choices, new WeightedRatio()); + } - /** - * Creates a list of {@link ExtractedResult} which contain all the choices with their - * corresponding score where higher is more similar - * - * @param query The query string - * @param choices A list of choices - * @param cutoff Keep only scores above cutoff - * @return A list of the results - */ - public static List extractAll( - String query, Collection choices, int cutoff) { - Extractor extractor = new Extractor(cutoff); - return extractor.extractWithoutOrder(query, choices, new WeightedRatio()); - } + /** + * Creates a list of {@link ExtractedResult} which contain all the choices with their + * corresponding score where higher is more similar + * + * @param query The query string + * @param choices A list of choices + * @param cutoff Keep only scores above cutoff + * @return A list of the results + */ + public static List extractAll( + String query, Collection choices, int cutoff) { + Extractor extractor = new Extractor(cutoff); + return extractor.extractWithoutOrder(query, choices, new WeightedRatio()); + } - /** - * Find the single best match above a score in a list of choices. - * - * @param query A string to match against - * @param choices A list of choices - * @param func Scoring function - * @return An object containing the best match and it's score - */ - public static ExtractedResult extractOne( - String query, Collection choices, Applicable func) { - Extractor extractor = new Extractor(); - return extractor.extractOne(query, choices, func); - } + /** + * Find the single best match above a score in a list of choices. + * + * @param query A string to match against + * @param choices A list of choices + * @param func Scoring function + * @return An object containing the best match and it's score + */ + public static ExtractedResult extractOne( + String query, Collection choices, Applicable func) { + Extractor extractor = new Extractor(); + return extractor.extractOne(query, choices, func); + } - /** - * Find the single best match above a score in a list of choices. - * - * @param query A string to match against - * @param choices A list of choices - * @return An object containing the best match and it's score - */ - public static ExtractedResult extractOne(String query, Collection choices) { - Extractor extractor = new Extractor(); - return extractor.extractOne(query, choices, new WeightedRatio()); - } + /** + * Find the single best match above a score in a list of choices. + * + * @param query A string to match against + * @param choices A list of choices + * @return An object containing the best match and it's score + */ + public static ExtractedResult extractOne(String query, Collection choices) { + Extractor extractor = new Extractor(); + return extractor.extractOne(query, choices, new WeightedRatio()); + } - /** - * Creates a sorted list of {@link ExtractedResult} which contain the top @param limit - * most similar choices - * - * @param query The query string - * @param choices A list of choices - * @param toStringFunction The ToStringFunction to be applied to all choices. - * @param func The scoring function - * @return A list of the results - */ - public static List> extractTop( - String query, - Collection choices, - ToStringFunction toStringFunction, - Applicable func, - int limit, - int cutoff) { - Extractor extractor = new Extractor(cutoff); - return extractor.extractTop(query, choices, toStringFunction, func, limit); - } + /** + * Creates a sorted list of {@link ExtractedResult} which contain the top @param limit most + * similar choices + * + * @param query The query string + * @param choices A list of choices + * @param toStringFunction The ToStringFunction to be applied to all choices. + * @param func The scoring function + * @return A list of the results + */ + public static List> extractTop( + String query, + Collection choices, + ToStringFunction toStringFunction, + Applicable func, + int limit, + int cutoff) { + Extractor extractor = new Extractor(cutoff); + return extractor.extractTop(query, choices, toStringFunction, func, limit); + } - /** - * Creates a sorted list of {@link ExtractedResult} which contain the top @param limit - * most similar choices - * - * @param query The query string - * @param choices A list of choices - * @param toStringFunction The ToStringFunction to be applied to all choices. - * @param limit Limits the number of results and speeds up the search (k-top heap sort) is used - * @param cutoff Rejects any entries with score below this - * @return A list of the results - */ - public static List> extractTop( - String query, - Collection choices, - ToStringFunction toStringFunction, - int limit, - int cutoff) { - Extractor extractor = new Extractor(cutoff); - return extractor.extractTop(query, choices, toStringFunction, new WeightedRatio(), limit); - } + /** + * Creates a sorted list of {@link ExtractedResult} which contain the top @param limit most + * similar choices + * + * @param query The query string + * @param choices A list of choices + * @param toStringFunction The ToStringFunction to be applied to all choices. + * @param limit Limits the number of results and speeds up the search (k-top heap sort) is used + * @param cutoff Rejects any entries with score below this + * @return A list of the results + */ + public static List> extractTop( + String query, + Collection choices, + ToStringFunction toStringFunction, + int limit, + int cutoff) { + Extractor extractor = new Extractor(cutoff); + return extractor.extractTop(query, choices, toStringFunction, new WeightedRatio(), limit); + } - /** - * Creates a sorted list of {@link ExtractedResult} which contain the top @param limit - * most similar choices - * - * @param query The query string - * @param choices A list of choices - * @param toStringFunction The ToStringFunction to be applied to all choices. - * @param func The scoring function - * @param limit The number of results to return - * @return A list of the results - */ - public static List> extractTop( - String query, - Collection choices, - ToStringFunction toStringFunction, - Applicable func, - int limit) { - Extractor extractor = new Extractor(); - return extractor.extractTop(query, choices, toStringFunction, func, limit); - } + /** + * Creates a sorted list of {@link ExtractedResult} which contain the top @param limit most + * similar choices + * + * @param query The query string + * @param choices A list of choices + * @param toStringFunction The ToStringFunction to be applied to all choices. + * @param func The scoring function + * @param limit The number of results to return + * @return A list of the results + */ + public static List> extractTop( + String query, + Collection choices, + ToStringFunction toStringFunction, + Applicable func, + int limit) { + Extractor extractor = new Extractor(); + return extractor.extractTop(query, choices, toStringFunction, func, limit); + } - /** - * Creates a sorted list of {@link ExtractedResult} which contain the top @param limit - * most similar choices - * - * @param query The query string - * @param choices A list of choices - * @param toStringFunction The ToStringFunction to be applied to all choices. - * @param limit The number of results to return - * @return A list of the results - */ - public static List> extractTop( - String query, Collection choices, ToStringFunction toStringFunction, int limit) { - Extractor extractor = new Extractor(); - return extractor.extractTop(query, choices, toStringFunction, new WeightedRatio(), limit); - } + /** + * Creates a sorted list of {@link ExtractedResult} which contain the top @param limit most + * similar choices + * + * @param query The query string + * @param choices A list of choices + * @param toStringFunction The ToStringFunction to be applied to all choices. + * @param limit The number of results to return + * @return A list of the results + */ + public static List> extractTop( + String query, Collection choices, ToStringFunction toStringFunction, int limit) { + Extractor extractor = new Extractor(); + return extractor.extractTop(query, choices, toStringFunction, new WeightedRatio(), limit); + } - /** - * Creates a sorted list of {@link ExtractedResult} which contain all the choices with - * their corresponding score where higher is more similar - * - * @param query The query string - * @param choices A list of choices - * @param toStringFunction The ToStringFunction to be applied to all choices. - * @param func The scoring function - * @return A list of the results - */ - public static List> extractSorted( - String query, - Collection choices, - ToStringFunction toStringFunction, - Applicable func) { - Extractor extractor = new Extractor(); - return extractor.extractTop(query, choices, toStringFunction, func); - } + /** + * Creates a sorted list of {@link ExtractedResult} which contain all the choices with + * their corresponding score where higher is more similar + * + * @param query The query string + * @param choices A list of choices + * @param toStringFunction The ToStringFunction to be applied to all choices. + * @param func The scoring function + * @return A list of the results + */ + public static List> extractSorted( + String query, Collection choices, ToStringFunction toStringFunction, Applicable func) { + Extractor extractor = new Extractor(); + return extractor.extractTop(query, choices, toStringFunction, func); + } - /** - * Creates a sorted list of {@link ExtractedResult} which contain all the choices with - * their corresponding score where higher is more similar - * - * @param query The query string - * @param choices A list of choices - * @param toStringFunction The ToStringFunction to be applied to all choices. - * @param func The scoring function - * @param cutoff Keep only scores above cutoff - * @return A list of the results - */ - public static List> extractSorted( - String query, - Collection choices, - ToStringFunction toStringFunction, - Applicable func, - int cutoff) { - Extractor extractor = new Extractor(cutoff); - return extractor.extractTop(query, choices, toStringFunction, func); - } + /** + * Creates a sorted list of {@link ExtractedResult} which contain all the choices with + * their corresponding score where higher is more similar + * + * @param query The query string + * @param choices A list of choices + * @param toStringFunction The ToStringFunction to be applied to all choices. + * @param func The scoring function + * @param cutoff Keep only scores above cutoff + * @return A list of the results + */ + public static List> extractSorted( + String query, + Collection choices, + ToStringFunction toStringFunction, + Applicable func, + int cutoff) { + Extractor extractor = new Extractor(cutoff); + return extractor.extractTop(query, choices, toStringFunction, func); + } - /** - * Creates a sorted list of {@link ExtractedResult} which contain all the choices with - * their corresponding score where higher is more similar - * - * @param query The query string - * @param choices A list of choices - * @param toStringFunction The ToStringFunction to be applied to all choices. - * @return A list of the results - */ - public static List> extractSorted( - String query, Collection choices, ToStringFunction toStringFunction) { - Extractor extractor = new Extractor(); - return extractor.extractTop(query, choices, toStringFunction, new WeightedRatio()); - } + /** + * Creates a sorted list of {@link ExtractedResult} which contain all the choices with + * their corresponding score where higher is more similar + * + * @param query The query string + * @param choices A list of choices + * @param toStringFunction The ToStringFunction to be applied to all choices. + * @return A list of the results + */ + public static List> extractSorted( + String query, Collection choices, ToStringFunction toStringFunction) { + Extractor extractor = new Extractor(); + return extractor.extractTop(query, choices, toStringFunction, new WeightedRatio()); + } - /** - * Creates a sorted list of {@link ExtractedResult} which contain all the choices with - * their corresponding score where higher is more similar - * - * @param query The query string - * @param choices A list of choices - * @param toStringFunction The ToStringFunction to be applied to all choices. - * @param cutoff Keep only scores above cutoff - * @return A list of the results - */ - public static List> extractSorted( - String query, Collection choices, ToStringFunction toStringFunction, int cutoff) { - Extractor extractor = new Extractor(cutoff); - return extractor.extractTop(query, choices, toStringFunction, new WeightedRatio()); - } + /** + * Creates a sorted list of {@link ExtractedResult} which contain all the choices with + * their corresponding score where higher is more similar + * + * @param query The query string + * @param choices A list of choices + * @param toStringFunction The ToStringFunction to be applied to all choices. + * @param cutoff Keep only scores above cutoff + * @return A list of the results + */ + public static List> extractSorted( + String query, Collection choices, ToStringFunction toStringFunction, int cutoff) { + Extractor extractor = new Extractor(cutoff); + return extractor.extractTop(query, choices, toStringFunction, new WeightedRatio()); + } - /** - * Creates a list of {@link ExtractedResult} which contain all the choices with their - * corresponding score where higher is more similar - * - * @param query The query string - * @param choices A list of choices - * @param toStringFunction The ToStringFunction to be applied to all choices. - * @param func The scoring function - * @return A list of the results - */ - public static List> extractAll( - String query, - Collection choices, - ToStringFunction toStringFunction, - Applicable func) { - Extractor extractor = new Extractor(); - return extractor.extractWithoutOrder(query, choices, toStringFunction, func); - } + /** + * Creates a list of {@link ExtractedResult} which contain all the choices with their + * corresponding score where higher is more similar + * + * @param query The query string + * @param choices A list of choices + * @param toStringFunction The ToStringFunction to be applied to all choices. + * @param func The scoring function + * @return A list of the results + */ + public static List> extractAll( + String query, Collection choices, ToStringFunction toStringFunction, Applicable func) { + Extractor extractor = new Extractor(); + return extractor.extractWithoutOrder(query, choices, toStringFunction, func); + } - /** - * Creates a list of {@link ExtractedResult} which contain all the choices with their - * corresponding score where higher is more similar - * - * @param query The query string - * @param choices A list of choices - * @param toStringFunction The ToStringFunction to be applied to all choices. - * @param func The scoring function - * @param cutoff Keep only scores above cutoff - * @return A list of the results - */ - public static List> extractAll( - String query, - Collection choices, - ToStringFunction toStringFunction, - Applicable func, - int cutoff) { - Extractor extractor = new Extractor(cutoff); - return extractor.extractWithoutOrder(query, choices, toStringFunction, func); - } + /** + * Creates a list of {@link ExtractedResult} which contain all the choices with their + * corresponding score where higher is more similar + * + * @param query The query string + * @param choices A list of choices + * @param toStringFunction The ToStringFunction to be applied to all choices. + * @param func The scoring function + * @param cutoff Keep only scores above cutoff + * @return A list of the results + */ + public static List> extractAll( + String query, + Collection choices, + ToStringFunction toStringFunction, + Applicable func, + int cutoff) { + Extractor extractor = new Extractor(cutoff); + return extractor.extractWithoutOrder(query, choices, toStringFunction, func); + } - /** - * Creates a list of {@link ExtractedResult} which contain all the choices with their - * corresponding score where higher is more similar - * - * @param query The query string - * @param choices A list of choices - * @param toStringFunction The ToStringFunction to be applied to all choices. - * @return A list of the results - */ - public static List> extractAll( - String query, Collection choices, ToStringFunction toStringFunction) { - Extractor extractor = new Extractor(); - return extractor.extractWithoutOrder(query, choices, toStringFunction, new WeightedRatio()); - } + /** + * Creates a list of {@link ExtractedResult} which contain all the choices with their + * corresponding score where higher is more similar + * + * @param query The query string + * @param choices A list of choices + * @param toStringFunction The ToStringFunction to be applied to all choices. + * @return A list of the results + */ + public static List> extractAll( + String query, Collection choices, ToStringFunction toStringFunction) { + Extractor extractor = new Extractor(); + return extractor.extractWithoutOrder(query, choices, toStringFunction, new WeightedRatio()); + } - /** - * Creates a list of {@link ExtractedResult} which contain all the choices with their - * corresponding score where higher is more similar - * - * @param query The query string - * @param choices A list of choices - * @param toStringFunction The ToStringFunction to be applied to all choices. - * @param cutoff Keep only scores above cutoff - * @return A list of the results - */ - public static List> extractAll( - String query, Collection choices, ToStringFunction toStringFunction, int cutoff) { - Extractor extractor = new Extractor(cutoff); - return extractor.extractWithoutOrder(query, choices, toStringFunction, new WeightedRatio()); - } + /** + * Creates a list of {@link ExtractedResult} which contain all the choices with their + * corresponding score where higher is more similar + * + * @param query The query string + * @param choices A list of choices + * @param toStringFunction The ToStringFunction to be applied to all choices. + * @param cutoff Keep only scores above cutoff + * @return A list of the results + */ + public static List> extractAll( + String query, Collection choices, ToStringFunction toStringFunction, int cutoff) { + Extractor extractor = new Extractor(cutoff); + return extractor.extractWithoutOrder(query, choices, toStringFunction, new WeightedRatio()); + } - /** - * Find the single best match above a score in a list of choices. - * - * @param query A string to match against - * @param choices A list of choices - * @param toStringFunction The ToStringFunction to be applied to all choices. - * @param func Scoring function - * @return An object containing the best match and it's score - */ - public static BoundExtractedResult extractOne( - String query, - Collection choices, - ToStringFunction toStringFunction, - Applicable func) { - Extractor extractor = new Extractor(); - return extractor.extractOne(query, choices, toStringFunction, func); - } + /** + * Find the single best match above a score in a list of choices. + * + * @param query A string to match against + * @param choices A list of choices + * @param toStringFunction The ToStringFunction to be applied to all choices. + * @param func Scoring function + * @return An object containing the best match and it's score + */ + public static BoundExtractedResult extractOne( + String query, Collection choices, ToStringFunction toStringFunction, Applicable func) { + Extractor extractor = new Extractor(); + return extractor.extractOne(query, choices, toStringFunction, func); + } - /** - * Find the single best match above a score in a list of choices. - * - * @param query A string to match against - * @param choices A list of choices - * @param toStringFunction The ToStringFunction to be applied to all choices. - * @return An object containing the best match and it's score - */ - public static BoundExtractedResult extractOne( - String query, Collection choices, ToStringFunction toStringFunction) { - Extractor extractor = new Extractor(); - return extractor.extractOne(query, choices, toStringFunction, new WeightedRatio()); - } + /** + * Find the single best match above a score in a list of choices. + * + * @param query A string to match against + * @param choices A list of choices + * @param toStringFunction The ToStringFunction to be applied to all choices. + * @return An object containing the best match and it's score + */ + public static BoundExtractedResult extractOne( + String query, Collection choices, ToStringFunction toStringFunction) { + Extractor extractor = new Extractor(); + return extractor.extractOne(query, choices, toStringFunction, new WeightedRatio()); + } } diff --git a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/Ratio.java b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/Ratio.java index 9c16e7053..28a6f7884 100644 --- a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/Ratio.java +++ b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/Ratio.java @@ -17,28 +17,25 @@ package com.itsaky.androidide.fuzzysearch; -/** - * Interface for the different ratios - */ +/** Interface for the different ratios */ public interface Ratio extends Applicable { - /** - * Applies the ratio between the two strings - * - * @param s1 Input string - * @param s2 Input string - * @return Integer representing ratio of similarity - */ - int apply(String s1, String s2); - - /** - * Applies the ratio between the two strings - * - * @param s1 Input string - * @param s2 Input string - * @param sp String processor to pre-process strings before calculating the ratio - * @return Integer representing ratio of similarity - */ - int apply(String s1, String s2, ToStringFunction sp); + /** + * Applies the ratio between the two strings + * + * @param s1 Input string + * @param s2 Input string + * @return Integer representing ratio of similarity + */ + int apply(String s1, String s2); + /** + * Applies the ratio between the two strings + * + * @param s1 Input string + * @param s2 Input string + * @param sp String processor to pre-process strings before calculating the ratio + * @return Integer representing ratio of similarity + */ + int apply(String s1, String s2, ToStringFunction sp); } diff --git a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/StringProcessor.java b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/StringProcessor.java index ed5da99a4..70503dc8d 100644 --- a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/StringProcessor.java +++ b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/StringProcessor.java @@ -24,19 +24,19 @@ package com.itsaky.androidide.fuzzysearch; */ @Deprecated public abstract class StringProcessor implements ToStringFunction { -// now abstract class because JDK1.7 does not allow default methods in interfaces - /** - * Transforms the input string - * - * @deprecated Use {@code ToStringFunction#apply(String)} instead. - * @param in Input string - * @return The processed string - */ - @Deprecated - public abstract String process(String in); + // now abstract class because JDK1.7 does not allow default methods in interfaces + /** + * Transforms the input string + * + * @deprecated Use {@code ToStringFunction#apply(String)} instead. + * @param in Input string + * @return The processed string + */ + @Deprecated + public abstract String process(String in); - @Override - public String apply(String item) { - return process(item); - } + @Override + public String apply(String item) { + return process(item); + } } diff --git a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/ToStringFunction.java b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/ToStringFunction.java index e0225185b..e34476fdc 100644 --- a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/ToStringFunction.java +++ b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/ToStringFunction.java @@ -23,22 +23,23 @@ package com.itsaky.androidide.fuzzysearch; * @param The type of the item to transform. */ public interface ToStringFunction { - /** - * Transforms the input item to a string. - * - * @param item The item to transform. - * @return A string to use for comparing the item. - */ - String apply(T item); + /** + * Transforms the input item to a string. + * + * @param item The item to transform. + * @return A string to use for comparing the item. + */ + String apply(T item); - /** - * A default ToStringFunction that returns the input string; - * used by methods that use plain strings in {@link FuzzySearch}. - */ - ToStringFunction NO_PROCESS = new ToStringFunction() { + /** + * A default ToStringFunction that returns the input string; used by methods that use plain + * strings in {@link FuzzySearch}. + */ + ToStringFunction NO_PROCESS = + new ToStringFunction() { @Override public String apply(String item) { - return item; + return item; } - }; + }; } diff --git a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/BasicAlgorithm.java b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/BasicAlgorithm.java index f4efc336d..e340dffba 100644 --- a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/BasicAlgorithm.java +++ b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/BasicAlgorithm.java @@ -5,39 +5,38 @@ import com.itsaky.androidide.fuzzysearch.ToStringFunction; public abstract class BasicAlgorithm implements Applicable { - private ToStringFunction stringFunction; + private ToStringFunction stringFunction; - public BasicAlgorithm() { - this.stringFunction = new DefaultStringFunction(); - } + public BasicAlgorithm() { + this.stringFunction = new DefaultStringFunction(); + } - public BasicAlgorithm(ToStringFunction stringFunction) { - this.stringFunction = stringFunction; - } + public BasicAlgorithm(ToStringFunction stringFunction) { + this.stringFunction = stringFunction; + } - public abstract int apply(String s1, String s2, ToStringFunction stringProcessor); + public abstract int apply(String s1, String s2, ToStringFunction stringProcessor); - public int apply(String s1, String s2){ + public int apply(String s1, String s2) { - return apply(s1, s2, this.stringFunction); + return apply(s1, s2, this.stringFunction); + } - } + public BasicAlgorithm with(ToStringFunction stringFunction) { + setStringFunction(stringFunction); + return this; + } - public BasicAlgorithm with(ToStringFunction stringFunction){ - setStringFunction(stringFunction); - return this; - } + public BasicAlgorithm noProcessor() { + this.stringFunction = ToStringFunction.NO_PROCESS; + return this; + } - public BasicAlgorithm noProcessor(){ - this.stringFunction = ToStringFunction.NO_PROCESS; - return this; - } + void setStringFunction(ToStringFunction stringFunction) { + this.stringFunction = stringFunction; + } - void setStringFunction(ToStringFunction stringFunction){ - this.stringFunction = stringFunction; - } - - public ToStringFunction getStringFunction() { - return stringFunction; - } + public ToStringFunction getStringFunction() { + return stringFunction; + } } diff --git a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/DefaultStringFunction.java b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/DefaultStringFunction.java index 06385a7e9..309efc24d 100644 --- a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/DefaultStringFunction.java +++ b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/DefaultStringFunction.java @@ -6,63 +6,58 @@ import com.itsaky.androidide.fuzzysearch.ToStringFunction; public class DefaultStringFunction implements ToStringFunction { - private final static String pattern = "(?ui)\\W"; - private final static Pattern r = compilePattern(); + private static final String pattern = "(?ui)\\W"; + private static final Pattern r = compilePattern(); + /** + * Substitute non alphanumeric characters. + * + * @param in The input string + * @param sub The string to substitute with + * @return The replaced string + */ + public static String subNonAlphaNumeric(String in, String sub) { - /** - * Substitute non alphanumeric characters. - * - * @param in The input string - * @param sub The string to substitute with - * @return The replaced string - */ - public static String subNonAlphaNumeric(String in, String sub) { + Matcher m = r.matcher(in); - Matcher m = r.matcher(in); + if (m.find()) { + return m.replaceAll(sub); + } else { + return in; + } + } - if(m.find()){ - return m.replaceAll(sub); - } else { - return in; - } - - } - - /** - * Performs the default string processing on the input string - * - * @param in Input string - * @return The processed string - */ - @Override - public String apply(String in) { - - in = subNonAlphaNumeric(in, " "); - in = in.toLowerCase(); - in = in.trim(); - - return in; - - } - - private static Pattern compilePattern(){ - - Pattern p; - - try{ - p = Pattern.compile(pattern, Pattern.UNICODE_CHARACTER_CLASS); - } catch (IllegalArgumentException e) { - // Even though Android supports the unicode pattern class - // for some reason it throws an IllegalArgumentException - // if we pass the flag like on standard Java runtime - // - // We catch this and recompile without the flag (unicode should still work) - p = Pattern.compile(pattern); - } - - return p; + /** + * Performs the default string processing on the input string + * + * @param in Input string + * @return The processed string + */ + @Override + public String apply(String in) { + in = subNonAlphaNumeric(in, " "); + in = in.toLowerCase(); + in = in.trim(); + + return in; + } + + private static Pattern compilePattern() { + + Pattern p; + + try { + p = Pattern.compile(pattern, Pattern.UNICODE_CHARACTER_CLASS); + } catch (IllegalArgumentException e) { + // Even though Android supports the unicode pattern class + // for some reason it throws an IllegalArgumentException + // if we pass the flag like on standard Java runtime + // + // We catch this and recompile without the flag (unicode should still work) + p = Pattern.compile(pattern); } + return p; + } } diff --git a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/DefaultStringProcessor.java b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/DefaultStringProcessor.java index 6cc955404..fcbd5a00d 100644 --- a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/DefaultStringProcessor.java +++ b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/DefaultStringProcessor.java @@ -10,63 +10,58 @@ import com.itsaky.androidide.fuzzysearch.StringProcessor; @Deprecated public class DefaultStringProcessor extends StringProcessor { - private final static String pattern = "[^\\p{Alnum}]"; - private final static Pattern r = compilePattern(); + private static final String pattern = "[^\\p{Alnum}]"; + private static final Pattern r = compilePattern(); + /** + * Substitute non alphanumeric characters. + * + * @param in The input string + * @param sub The string to substitute with + * @return The replaced string + */ + public static String subNonAlphaNumeric(String in, String sub) { - /** - * Substitute non alphanumeric characters. - * - * @param in The input string - * @param sub The string to substitute with - * @return The replaced string - */ - public static String subNonAlphaNumeric(String in, String sub) { + Matcher m = r.matcher(in); - Matcher m = r.matcher(in); + if (m.find()) { + return m.replaceAll(sub); + } else { + return in; + } + } - if(m.find()){ - return m.replaceAll(sub); - } else { - return in; - } - - } - - /** - * Performs the default string processing on the input string - * - * @param in Input string - * @return The processed string - */ - @Override - public String process(String in) { - - in = subNonAlphaNumeric(in, " "); - in = in.toLowerCase(); - in = in.trim(); - - return in; - - } - - private static Pattern compilePattern(){ - - Pattern p; - - try{ - p = Pattern.compile(pattern, Pattern.UNICODE_CHARACTER_CLASS); - } catch (IllegalArgumentException e) { - // Even though Android supports the unicode pattern class - // for some reason it throws an IllegalArgumentException - // if we pass the flag like on standard Java runtime - // - // We catch this and recompile without the flag (unicode should still work) - p = Pattern.compile(pattern); - } - - return p; + /** + * Performs the default string processing on the input string + * + * @param in Input string + * @return The processed string + */ + @Override + public String process(String in) { + in = subNonAlphaNumeric(in, " "); + in = in.toLowerCase(); + in = in.trim(); + + return in; + } + + private static Pattern compilePattern() { + + Pattern p; + + try { + p = Pattern.compile(pattern, Pattern.UNICODE_CHARACTER_CLASS); + } catch (IllegalArgumentException e) { + // Even though Android supports the unicode pattern class + // for some reason it throws an IllegalArgumentException + // if we pass the flag like on standard Java runtime + // + // We catch this and recompile without the flag (unicode should still work) + p = Pattern.compile(pattern); } + return p; + } } diff --git a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/NoProcess.java b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/NoProcess.java index 465652f3e..e9dcb18e4 100644 --- a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/NoProcess.java +++ b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/NoProcess.java @@ -8,10 +8,9 @@ import com.itsaky.androidide.fuzzysearch.StringProcessor; @Deprecated public class NoProcess extends StringProcessor { - @Override - @Deprecated - public String process(String in) { - return in; - } - + @Override + @Deprecated + public String process(String in) { + return in; + } } diff --git a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/PrimitiveUtils.java b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/PrimitiveUtils.java index 7d6dc11f1..2e4fbe6f1 100644 --- a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/PrimitiveUtils.java +++ b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/PrimitiveUtils.java @@ -2,38 +2,33 @@ package com.itsaky.androidide.fuzzysearch.algorithms; final class PrimitiveUtils { - static double max(double ... elems) { + static double max(double... elems) { - if (elems.length == 0) return 0; + if (elems.length == 0) return 0; - double best = elems[0]; - - for(double t : elems){ - if (t > best) { - best = t; - } - } - - return best; + double best = elems[0]; + for (double t : elems) { + if (t > best) { + best = t; + } } - static int max(int ... elems) { + return best; + } - if (elems.length == 0) return 0; + static int max(int... elems) { - int best = elems[0]; + if (elems.length == 0) return 0; - for(int t : elems){ - if (t > best) { - best = t; - } - } - - return best; + int best = elems[0]; + for (int t : elems) { + if (t > best) { + best = t; + } } - - - + + return best; + } } diff --git a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/RatioAlgorithm.java b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/RatioAlgorithm.java index 7138612e7..63f5a172c 100644 --- a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/RatioAlgorithm.java +++ b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/RatioAlgorithm.java @@ -6,49 +6,49 @@ import com.itsaky.androidide.fuzzysearch.ratios.SimpleRatio; public abstract class RatioAlgorithm extends BasicAlgorithm { - private Ratio ratio; + private Ratio ratio; - public RatioAlgorithm() { - super(); - this.ratio = new SimpleRatio(); - } + public RatioAlgorithm() { + super(); + this.ratio = new SimpleRatio(); + } - public RatioAlgorithm(ToStringFunction stringFunction) { - super(stringFunction); - } + public RatioAlgorithm(ToStringFunction stringFunction) { + super(stringFunction); + } - public RatioAlgorithm(Ratio ratio) { - super(); - this.ratio = ratio; - } + public RatioAlgorithm(Ratio ratio) { + super(); + this.ratio = ratio; + } + public RatioAlgorithm(ToStringFunction stringFunction, Ratio ratio) { + super(stringFunction); + this.ratio = ratio; + } - public RatioAlgorithm(ToStringFunction stringFunction, Ratio ratio) { - super(stringFunction); - this.ratio = ratio; - } + public abstract int apply( + String s1, String s2, Ratio ratio, ToStringFunction stringFunction); - public abstract int apply(String s1, String s2, Ratio ratio, ToStringFunction stringFunction); + public RatioAlgorithm with(Ratio ratio) { + setRatio(ratio); + return this; + } - public RatioAlgorithm with(Ratio ratio) { - setRatio(ratio); - return this; - } + public int apply(String s1, String s2, Ratio ratio) { + return apply(s1, s2, ratio, getStringFunction()); + } - public int apply(String s1, String s2, Ratio ratio) { - return apply(s1, s2, ratio, getStringFunction()); - } + @Override + public int apply(String s1, String s2, ToStringFunction stringFunction) { + return apply(s1, s2, getRatio(), stringFunction); + } - @Override - public int apply(String s1, String s2, ToStringFunction stringFunction) { - return apply(s1, s2, getRatio(), stringFunction); - } + public void setRatio(Ratio ratio) { + this.ratio = ratio; + } - public void setRatio(Ratio ratio) { - this.ratio = ratio; - } - - public Ratio getRatio() { - return ratio; - } + public Ratio getRatio() { + return ratio; + } } diff --git a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/SetUtils.java b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/SetUtils.java index c725750b4..54d0a76ab 100644 --- a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/SetUtils.java +++ b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/SetUtils.java @@ -5,22 +5,19 @@ import java.util.Set; final class SetUtils { - static Set intersection (Set s1, Set s2){ + static Set intersection(Set s1, Set s2) { - Set intersection = new HashSet<>(s1); - intersection.retainAll(s2); + Set intersection = new HashSet<>(s1); + intersection.retainAll(s2); - return intersection; + return intersection; + } - } + static Set difference(Set s1, Set s2) { - static Set difference (Set s1, Set s2) { - - Set difference = new HashSet<>(s1); - difference.removeAll(s2); - - return difference; - - } + Set difference = new HashSet<>(s1); + difference.removeAll(s2); + return difference; + } } diff --git a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/TokenSet.java b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/TokenSet.java index 40769148f..1109e2a95 100644 --- a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/TokenSet.java +++ b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/TokenSet.java @@ -10,31 +10,29 @@ import com.itsaky.androidide.fuzzysearch.ToStringFunction; public class TokenSet extends RatioAlgorithm { - @Override - public int apply(String s1, String s2, Ratio ratio, ToStringFunction stringFunction) { + @Override + public int apply(String s1, String s2, Ratio ratio, ToStringFunction stringFunction) { - s1 = stringFunction.apply(s1); - s2 = stringFunction.apply(s2); + s1 = stringFunction.apply(s1); + s2 = stringFunction.apply(s2); - Set tokens1 = Utils.tokenizeSet(s1); - Set tokens2 = Utils.tokenizeSet(s2); + Set tokens1 = Utils.tokenizeSet(s1); + Set tokens2 = Utils.tokenizeSet(s2); - Set intersection = SetUtils.intersection(tokens1, tokens2); - Set diff1to2 = SetUtils.difference(tokens1, tokens2); - Set diff2to1 = SetUtils.difference(tokens2, tokens1); + Set intersection = SetUtils.intersection(tokens1, tokens2); + Set diff1to2 = SetUtils.difference(tokens1, tokens2); + Set diff2to1 = SetUtils.difference(tokens2, tokens1); - String sortedInter = Utils.sortAndJoin(intersection, " ").trim(); - String sorted1to2 = (sortedInter + " " + Utils.sortAndJoin(diff1to2, " ")).trim(); - String sorted2to1 = (sortedInter + " " + Utils.sortAndJoin(diff2to1, " ")).trim(); + String sortedInter = Utils.sortAndJoin(intersection, " ").trim(); + String sorted1to2 = (sortedInter + " " + Utils.sortAndJoin(diff1to2, " ")).trim(); + String sorted2to1 = (sortedInter + " " + Utils.sortAndJoin(diff2to1, " ")).trim(); - List results = new ArrayList<>(); + List results = new ArrayList<>(); - results.add(ratio.apply(sortedInter, sorted1to2)); - results.add(ratio.apply(sortedInter, sorted2to1)); - results.add(ratio.apply(sorted1to2, sorted2to1)); - - return Collections.max(results); - - } + results.add(ratio.apply(sortedInter, sorted1to2)); + results.add(ratio.apply(sortedInter, sorted2to1)); + results.add(ratio.apply(sorted1to2, sorted2to1)); + return Collections.max(results); + } } diff --git a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/TokenSort.java b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/TokenSort.java index 1eeb6fa39..56f92020b 100644 --- a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/TokenSort.java +++ b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/TokenSort.java @@ -8,26 +8,23 @@ import com.itsaky.androidide.fuzzysearch.ToStringFunction; public class TokenSort extends RatioAlgorithm { - @Override - public int apply(String s1, String s2, Ratio ratio, ToStringFunction stringFunction) { + @Override + public int apply(String s1, String s2, Ratio ratio, ToStringFunction stringFunction) { - String sorted1 = processAndSort(s1, stringFunction); - String sorted2 = processAndSort(s2, stringFunction); + String sorted1 = processAndSort(s1, stringFunction); + String sorted2 = processAndSort(s2, stringFunction); - return ratio.apply(sorted1, sorted2); + return ratio.apply(sorted1, sorted2); + } - } + private static String processAndSort(String in, ToStringFunction stringProcessor) { - private static String processAndSort(String in, ToStringFunction stringProcessor) { + in = stringProcessor.apply(in); + String[] wordsArray = in.split("\\s+"); - in = stringProcessor.apply(in); - String[] wordsArray = in.split("\\s+"); - - List words = Arrays.asList(wordsArray); - String joined = Utils.sortAndJoin(words, " "); - - return joined.trim(); - - } + List words = Arrays.asList(wordsArray); + String joined = Utils.sortAndJoin(words, " "); + return joined.trim(); + } } diff --git a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/Utils.java b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/Utils.java index 6a42f0395..b4aa6f346 100644 --- a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/Utils.java +++ b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/Utils.java @@ -2,88 +2,77 @@ package com.itsaky.androidide.fuzzysearch.algorithms; import java.util.*; -final public class Utils { +public final class Utils { + static List tokenize(String in) { - static List tokenize(String in){ + return Arrays.asList(in.split("\\s+")); + } - return Arrays.asList(in.split("\\s+")); + static Set tokenizeSet(String in) { + return new HashSet<>(tokenize(in)); + } + + static String sortAndJoin(List col, String sep) { + + Collections.sort(col); + + return join(col, sep); + } + + static String join(List strings, String sep) { + final StringBuilder buf = new StringBuilder(strings.size() * 16); + + for (int i = 0; i < strings.size(); i++) { + + if (i < strings.size()) { + buf.append(sep); + } + + buf.append(strings.get(i)); } - static Set tokenizeSet(String in){ + return buf.toString().trim(); + } - return new HashSet<>(tokenize(in)); + static String sortAndJoin(Set col, String sep) { + return sortAndJoin(new ArrayList<>(col), sep); + } + + public static > List findTopKHeap(List arr, int k) { + PriorityQueue pq = new PriorityQueue(); + + for (T x : arr) { + if (pq.size() < k) pq.add(x); + else if (x.compareTo(pq.peek()) > 0) { + pq.poll(); + pq.add(x); + } + } + List res = new ArrayList<>(); + for (int i = k; i > 0; i--) { + T polled = pq.poll(); + if (polled != null) { + res.add(polled); + } + } + return res; + } + + static > T max(T... elems) { + + if (elems.length == 0) return null; + + T best = elems[0]; + + for (T t : elems) { + if (t.compareTo(best) > 0) { + best = t; + } } - static String sortAndJoin(List col, String sep){ - - Collections.sort(col); - - return join(col, sep); - - } - - static String join(List strings, String sep) { - final StringBuilder buf = new StringBuilder(strings.size() * 16); - - for(int i = 0; i < strings.size(); i++){ - - if(i < strings.size()) { - buf.append(sep); - } - - buf.append(strings.get(i)); - - } - - return buf.toString().trim(); - } - - static String sortAndJoin(Set col, String sep){ - - return sortAndJoin(new ArrayList<>(col), sep); - - } - - public static > List findTopKHeap(List arr, int k) { - PriorityQueue pq = new PriorityQueue(); - - for (T x : arr) { - if (pq.size() < k) pq.add(x); - else if (x.compareTo(pq.peek()) > 0) { - pq.poll(); - pq.add(x); - } - } - List res = new ArrayList<>(); - for (int i =k; i > 0; i--) { - T polled = pq.poll(); - if (polled != null) { - res.add(polled); - } - } - return res; - - } - - static > T max(T ... elems) { - - if (elems.length == 0) return null; - - T best = elems[0]; - - for(T t : elems){ - if (t.compareTo(best) > 0) { - best = t; - } - } - - return best; - - } - - - + return best; + } } diff --git a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/WeightedRatio.java b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/WeightedRatio.java index aea625f5f..49b557fc9 100644 --- a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/WeightedRatio.java +++ b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/algorithms/WeightedRatio.java @@ -6,56 +6,53 @@ import static com.itsaky.androidide.fuzzysearch.FuzzySearch.*; import static com.itsaky.androidide.fuzzysearch.algorithms.PrimitiveUtils.max; import static java.lang.Math.round; - @SuppressWarnings("WeakerAccess") -public class WeightedRatio extends BasicAlgorithm { +public class WeightedRatio extends BasicAlgorithm { - public static final double UNBASE_SCALE = .95; - public static final double PARTIAL_SCALE = .90; - public static final boolean TRY_PARTIALS = true; + public static final double UNBASE_SCALE = .95; + public static final double PARTIAL_SCALE = .90; + public static final boolean TRY_PARTIALS = true; + @Override + public int apply(String s1, String s2, ToStringFunction stringProcessor) { - @Override - public int apply(String s1, String s2, ToStringFunction stringProcessor) { + s1 = stringProcessor.apply(s1); + s2 = stringProcessor.apply(s2); - s1 = stringProcessor.apply(s1); - s2 = stringProcessor.apply(s2); - - int len1 = s1.length(); - int len2 = s2.length(); - - if (len1 == 0 || len2 == 0) { return 0; } - - boolean tryPartials = TRY_PARTIALS; - double unbaseScale = UNBASE_SCALE; - double partialScale = PARTIAL_SCALE; - - int base = ratio(s1, s2); - double lenRatio = ((double) Math.max(len1, len2)) / Math.min(len1, len2); - - // if strings are similar length don't use partials - if (lenRatio < 1.5) tryPartials = false; - - // if one string is much shorter than the other - if (lenRatio > 8) partialScale = .6; - - if (tryPartials) { - - double partial = partialRatio(s1, s2) * partialScale; - double partialSor = tokenSortPartialRatio(s1, s2) * unbaseScale * partialScale; - double partialSet = tokenSetPartialRatio(s1, s2) * unbaseScale * partialScale; - - return (int) round(max(base, partial, partialSor, partialSet)); - - } else { - - double tokenSort = tokenSortRatio(s1, s2) * unbaseScale; - double tokenSet = tokenSetRatio(s1, s2) * unbaseScale; - - return (int) round(max(base, tokenSort, tokenSet)); - - } + int len1 = s1.length(); + int len2 = s2.length(); + if (len1 == 0 || len2 == 0) { + return 0; } + boolean tryPartials = TRY_PARTIALS; + double unbaseScale = UNBASE_SCALE; + double partialScale = PARTIAL_SCALE; + + int base = ratio(s1, s2); + double lenRatio = ((double) Math.max(len1, len2)) / Math.min(len1, len2); + + // if strings are similar length don't use partials + if (lenRatio < 1.5) tryPartials = false; + + // if one string is much shorter than the other + if (lenRatio > 8) partialScale = .6; + + if (tryPartials) { + + double partial = partialRatio(s1, s2) * partialScale; + double partialSor = tokenSortPartialRatio(s1, s2) * unbaseScale * partialScale; + double partialSet = tokenSetPartialRatio(s1, s2) * unbaseScale * partialScale; + + return (int) round(max(base, partial, partialSor, partialSet)); + + } else { + + double tokenSort = tokenSortRatio(s1, s2) * unbaseScale; + double tokenSet = tokenSetRatio(s1, s2) * unbaseScale; + + return (int) round(max(base, tokenSort, tokenSet)); + } + } } diff --git a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/model/BoundExtractedResult.java b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/model/BoundExtractedResult.java index 500e5c1ec..e0754a7b2 100644 --- a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/model/BoundExtractedResult.java +++ b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/model/BoundExtractedResult.java @@ -2,45 +2,45 @@ package com.itsaky.androidide.fuzzysearch.model; public class BoundExtractedResult implements Comparable> { - private T referent; - private String string; - private int score; - private int index; + private T referent; + private String string; + private int score; + private int index; - public BoundExtractedResult(T referent, String string, int score, int index) { - this.referent = referent; - this.string = string; - this.score = score; - this.index = index; - } + public BoundExtractedResult(T referent, String string, int score, int index) { + this.referent = referent; + this.string = string; + this.score = score; + this.index = index; + } - public T getReferent() { - return referent; - } + public T getReferent() { + return referent; + } - public String getString() { - return string; - } + public String getString() { + return string; + } - public void setString(String string) { - this.string = string; - } + public void setString(String string) { + this.string = string; + } - public int getScore() { - return score; - } + public int getScore() { + return score; + } - public int getIndex() { - return index; - } + public int getIndex() { + return index; + } - @Override - public String toString() { - return "(string: " + string + ", score: " + score + ", index: " + index+ ")"; - } + @Override + public String toString() { + return "(string: " + string + ", score: " + score + ", index: " + index + ")"; + } - @Override - public int compareTo(BoundExtractedResult o) { - return Integer.compare(this.getScore(), o.getScore()); - } -} \ No newline at end of file + @Override + public int compareTo(BoundExtractedResult o) { + return Integer.compare(this.getScore(), o.getScore()); + } +} diff --git a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/model/ExtractedResult.java b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/model/ExtractedResult.java index 72e87d41e..239360789 100644 --- a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/model/ExtractedResult.java +++ b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/model/ExtractedResult.java @@ -2,39 +2,39 @@ package com.itsaky.androidide.fuzzysearch.model; public class ExtractedResult implements Comparable { - private String string; - private int score; - private int index; + private String string; + private int score; + private int index; - public ExtractedResult(String string, int score, int index) { - this.string = string; - this.score = score; - this.index = index; - } + public ExtractedResult(String string, int score, int index) { + this.string = string; + this.score = score; + this.index = index; + } - @Override - public int compareTo(ExtractedResult o) { - return Integer.compare(this.getScore(), o.getScore()); - } + @Override + public int compareTo(ExtractedResult o) { + return Integer.compare(this.getScore(), o.getScore()); + } - public String getString() { - return string; - } + public String getString() { + return string; + } - public void setString(String string) { - this.string = string; - } + public void setString(String string) { + this.string = string; + } - public int getScore() { - return score; - } + public int getScore() { + return score; + } - public int getIndex() { - return index; - } + public int getIndex() { + return index; + } - @Override - public String toString() { - return "(string: " + string + ", score: " + score + ", index: " + index+ ")"; - } -} \ No newline at end of file + @Override + public String toString() { + return "(string: " + string + ", score: " + score + ", index: " + index + ")"; + } +} diff --git a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/ratios/PartialRatio.java b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/ratios/PartialRatio.java index 0d924516b..3d85ca1d6 100644 --- a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/ratios/PartialRatio.java +++ b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/ratios/PartialRatio.java @@ -9,67 +9,62 @@ import java.util.Collections; import java.util.List; import com.itsaky.androidide.fuzzysearch.ToStringFunction; -/** - * Partial ratio of similarity - */ +/** Partial ratio of similarity */ public class PartialRatio implements Ratio { - /** - * Computes a partial ratio between the strings - * - * @param s1 Input string - * @param s2 Input string - * @return The partial ratio - */ - @Override - public int apply(String s1, String s2) { + /** + * Computes a partial ratio between the strings + * + * @param s1 Input string + * @param s2 Input string + * @return The partial ratio + */ + @Override + public int apply(String s1, String s2) { - String shorter; - String longer; + String shorter; + String longer; - if (s1.length() <= s2.length()){ + if (s1.length() <= s2.length()) { - shorter = s1; - longer = s2; + shorter = s1; + longer = s2; - } else { - - shorter = s2; - longer = s1; - - } - - MatchingBlock[] matchingBlocks = DiffUtils.getMatchingBlocks(shorter, longer); - - List scores = new ArrayList<>(); - - for (MatchingBlock mb : matchingBlocks) { - - int dist = mb.dpos - mb.spos; - - int long_start = Math.max (dist, 0); - int long_end = long_start + shorter.length(); - - if(long_end > longer.length()) long_end = longer.length(); - - String long_substr = longer.substring(long_start, long_end); - - double ratio = DiffUtils.getRatio(shorter, long_substr); - - if (ratio > .995) { - return 100; - } else { - scores.add(ratio); - } - - } - - return (int) Math.round(100 * Collections.max(scores)); + } else { + shorter = s2; + longer = s1; } - @Override - public int apply(String s1, String s2, ToStringFunction sp) { - return apply(sp.apply(s1), sp.apply(s2)); + MatchingBlock[] matchingBlocks = DiffUtils.getMatchingBlocks(shorter, longer); + + List scores = new ArrayList<>(); + + for (MatchingBlock mb : matchingBlocks) { + + int dist = mb.dpos - mb.spos; + + int long_start = Math.max(dist, 0); + int long_end = long_start + shorter.length(); + + if (long_end > longer.length()) long_end = longer.length(); + + String long_substr = longer.substring(long_start, long_end); + + double ratio = DiffUtils.getRatio(shorter, long_substr); + + if (ratio > .995) { + return 100; + } else { + scores.add(ratio); + } } + + return (int) Math.round(100 * Collections.max(scores)); + } + + @Override + public int apply(String s1, String s2, ToStringFunction sp) { + return apply(sp.apply(s1), sp.apply(s2)); + } } diff --git a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/ratios/SimpleRatio.java b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/ratios/SimpleRatio.java index a3360c00a..94075a574 100644 --- a/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/ratios/SimpleRatio.java +++ b/build-tools/fuzzysearch/src/main/java/com/itsaky/androidide/fuzzysearch/ratios/SimpleRatio.java @@ -6,22 +6,21 @@ import com.itsaky.androidide.fuzzysearch.ToStringFunction; public class SimpleRatio implements Ratio { - /** - * Computes a simple Levenshtein distance ratio between the strings - * - * @param s1 Input string - * @param s2 Input string - * @return The resulting ratio of similarity - */ - @Override - public int apply(String s1, String s2) { + /** + * Computes a simple Levenshtein distance ratio between the strings + * + * @param s1 Input string + * @param s2 Input string + * @return The resulting ratio of similarity + */ + @Override + public int apply(String s1, String s2) { - return (int) Math.round(100 * DiffUtils.getRatio(s1, s2)); + return (int) Math.round(100 * DiffUtils.getRatio(s1, s2)); + } - } - - @Override - public int apply(String s1, String s2, ToStringFunction sp) { - return apply(sp.apply(s1), sp.apply(s2)); - } + @Override + public int apply(String s1, String s2, ToStringFunction sp) { + return apply(sp.apply(s1), sp.apply(s2)); + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/CloseOp.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/CloseOp.java index d77f0028d..59e7e5a28 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/CloseOp.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/CloseOp.java @@ -22,24 +22,24 @@ import com.google.common.base.MoreObjects; * delimited by {@link OpenOp}-{@code CloseOp} pairs turn into nested {@link Doc.Level}s. */ public enum CloseOp implements Op { - CLOSE; + CLOSE; - /** - * Make a {@code CloseOp}, returning a singleton since they are all the same. - * - * @return the singleton {@code CloseOp} - */ - public static Op make() { - return CLOSE; - } + /** + * Make a {@code CloseOp}, returning a singleton since they are all the same. + * + * @return the singleton {@code CloseOp} + */ + public static Op make() { + return CLOSE; + } - @Override - public void add(DocBuilder builder) { - builder.close(); - } + @Override + public void add(DocBuilder builder) { + builder.close(); + } - @Override - public String toString() { - return MoreObjects.toStringHelper(this).toString(); - } + @Override + public String toString() { + return MoreObjects.toStringHelper(this).toString(); + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/CommentsHelper.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/CommentsHelper.java index ef308a671..45e507bbd 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/CommentsHelper.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/CommentsHelper.java @@ -19,13 +19,13 @@ package com.google.googlejavaformat; * com.google.googlejavaformat.java.JavaCommentsHelper JavaCommentsHelper}. */ public interface CommentsHelper { - /** - * Try to rewrite comments, returning rewritten text. - * - * @param tok the comment's tok - * @param maxWidth the line length for the output - * @param column0 the current column - * @return the rewritten comment - */ - String rewrite(Input.Tok tok, int maxWidth, int column0); + /** + * Try to rewrite comments, returning rewritten text. + * + * @param tok the comment's tok + * @param maxWidth the line length for the output + * @param column0 the current column + * @return the rewritten comment + */ + String rewrite(Input.Tok tok, int maxWidth, int column0); } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/Doc.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/Doc.java index 207032108..f399a24b8 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/Doc.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/Doc.java @@ -37,759 +37,738 @@ import java.util.Optional; * representing single spaces; and {@link Break}s, which represent optional line-breaks. */ public abstract class Doc { + /** + * Each {@link Break} in a {@link Level} is either {@link FillMode#UNIFIED} or {@link + * FillMode#INDEPENDENT}. + */ + public enum FillMode { /** - * Each {@link Break} in a {@link Level} is either {@link FillMode#UNIFIED} or {@link - * FillMode#INDEPENDENT}. + * If a {@link Level} will not fit on one line, all of its {@code UNIFIED} {@link Break}s will + * be broken. */ - public enum FillMode { - /** - * If a {@link Level} will not fit on one line, all of its {@code UNIFIED} {@link Break}s - * will be broken. - */ - UNIFIED, - - /** - * If a {@link Level} will not fit on one line, its {@code INDEPENDENT} {@link Break}s will - * be broken independently of each other, to fill in the {@link Level}. - */ - INDEPENDENT, - - /** - * A {@code FORCED} {@link Break} will always be broken, and a {@link Level} it appears in - * will not fit on one line. - */ - FORCED - } - - /** State for writing. */ - public static final class State { - final int lastIndent; - final int indent; - final int column; - final boolean mustBreak; - - State(int lastIndent, int indent, int column, boolean mustBreak) { - this.lastIndent = lastIndent; - this.indent = indent; - this.column = column; - this.mustBreak = mustBreak; - } - - public State(int indent0, int column0) { - this(indent0, indent0, column0, false); - } - - State withColumn(int column) { - return new State(lastIndent, indent, column, mustBreak); - } - - State withMustBreak(boolean mustBreak) { - return new State(lastIndent, indent, column, mustBreak); - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("lastIndent", lastIndent) - .add("indent", indent) - .add("column", column) - .add("mustBreak", mustBreak) - .toString(); - } - } - - private static final Range EMPTY_RANGE = Range.closedOpen(-1, -1); - private static final DiscreteDomain INTEGERS = DiscreteDomain.integers(); - - // Memoized width; Float.POSITIVE_INFINITY if contains forced breaks. - private boolean widthComputed = false; - private float width = 0.0F; - - // Memoized flat; not defined (and never computed) if contains forced breaks. - private boolean flatComputed = false; - private String flat = ""; - - // Memoized Range. - private boolean rangeComputed = false; - private Range range = EMPTY_RANGE; + UNIFIED, /** - * Return the width of a {@code Doc}, or {@code Float.POSITIVE_INFINITY} if it must be broken. + * If a {@link Level} will not fit on one line, its {@code INDEPENDENT} {@link Break}s will be + * broken independently of each other, to fill in the {@link Level}. + */ + INDEPENDENT, + + /** + * A {@code FORCED} {@link Break} will always be broken, and a {@link Level} it appears in will + * not fit on one line. + */ + FORCED + } + + /** State for writing. */ + public static final class State { + final int lastIndent; + final int indent; + final int column; + final boolean mustBreak; + + State(int lastIndent, int indent, int column, boolean mustBreak) { + this.lastIndent = lastIndent; + this.indent = indent; + this.column = column; + this.mustBreak = mustBreak; + } + + public State(int indent0, int column0) { + this(indent0, indent0, column0, false); + } + + State withColumn(int column) { + return new State(lastIndent, indent, column, mustBreak); + } + + State withMustBreak(boolean mustBreak) { + return new State(lastIndent, indent, column, mustBreak); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("lastIndent", lastIndent) + .add("indent", indent) + .add("column", column) + .add("mustBreak", mustBreak) + .toString(); + } + } + + private static final Range EMPTY_RANGE = Range.closedOpen(-1, -1); + private static final DiscreteDomain INTEGERS = DiscreteDomain.integers(); + + // Memoized width; Float.POSITIVE_INFINITY if contains forced breaks. + private boolean widthComputed = false; + private float width = 0.0F; + + // Memoized flat; not defined (and never computed) if contains forced breaks. + private boolean flatComputed = false; + private String flat = ""; + + // Memoized Range. + private boolean rangeComputed = false; + private Range range = EMPTY_RANGE; + + /** + * Return the width of a {@code Doc}, or {@code Float.POSITIVE_INFINITY} if it must be broken. + * + * @return the width + */ + final float getWidth() { + if (!widthComputed) { + width = computeWidth(); + widthComputed = true; + } + return width; + } + + /** + * Return a {@code Doc}'s flat-string value; not defined (and never called) if the (@code Doc} + * contains forced breaks. + * + * @return the flat-string value + */ + final String getFlat() { + if (!flatComputed) { + flat = computeFlat(); + flatComputed = true; + } + return flat; + } + + /** + * Return the {@link Range} of a {@code Doc}. + * + * @return the {@code Doc}'s {@link Range} + */ + final Range range() { + if (!rangeComputed) { + range = computeRange(); + rangeComputed = true; + } + return range; + } + + /** + * Compute the {@code Doc}'s width. + * + * @return the width, or {@code Float.POSITIVE_INFINITY} if it must be broken + */ + abstract float computeWidth(); + + /** + * Compute the {@code Doc}'s flat value. Not defined (and never called) if contains forced breaks. + * + * @return the flat value + */ + abstract String computeFlat(); + + /** + * Compute the {@code Doc}'s {@link Range} of {@link Input.Token}s. + * + * @return the {@link Range} + */ + abstract Range computeRange(); + + /** + * Make breaking decisions for a {@code Doc}. + * + * @param maxWidth the maximum line width + * @param state the current output state + * @return the new output state + */ + public abstract State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state); + + /** Write a {@code Doc} to an {@link Output}, after breaking decisions have been made. */ + public abstract void write(Output output); + + /** A {@code Level} inside a {@link Doc}. */ + static final class Level extends Doc { + private final Indent plusIndent; // The extra indent following breaks. + private final List docs = new ArrayList<>(); // The elements of the level. + + private Level(Indent plusIndent) { + this.plusIndent = plusIndent; + } + + /** + * Factory method for {@code Level}s. * - * @return the width + * @param plusIndent the extra indent inside the {@code Level} + * @return the new {@code Level} */ - final float getWidth() { - if (!widthComputed) { - width = computeWidth(); - widthComputed = true; - } - return width; + static Level make(Indent plusIndent) { + return new Level(plusIndent); } /** - * Return a {@code Doc}'s flat-string value; not defined (and never called) if the (@code Doc} - * contains forced breaks. + * Add a {@link Doc} to the {@code Level}. * - * @return the flat-string value + * @param doc the {@link Doc} to add */ - final String getFlat() { - if (!flatComputed) { - flat = computeFlat(); - flatComputed = true; + void add(Doc doc) { + docs.add(doc); + } + + @Override + float computeWidth() { + float thisWidth = 0.0F; + for (Doc doc : docs) { + thisWidth += doc.getWidth(); + } + return thisWidth; + } + + @Override + String computeFlat() { + StringBuilder builder = new StringBuilder(); + for (Doc doc : docs) { + builder.append(doc.getFlat()); + } + return builder.toString(); + } + + @Override + Range computeRange() { + Range docRange = EMPTY_RANGE; + for (Doc doc : docs) { + docRange = union(docRange, doc.range()); + } + return docRange; + } + + // State that needs to be preserved between calculating breaks and + // writing output. + // TODO(cushon): represent phases as separate immutable data. + + /** True if the entire {@link Level} fits on one line. */ + boolean oneLine = false; + + /** + * Groups of {@link Doc}s that are children of the current {@link Level}, separated by {@link + * Break}s. + */ + List> splits = new ArrayList<>(); + + /** {@link Break}s between {@link Doc}s in the current {@link Level}. */ + List breaks = new ArrayList<>(); + + @Override + public State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state) { + float thisWidth = getWidth(); + if (state.column + thisWidth <= maxWidth) { + oneLine = true; + return state.withColumn(state.column + (int) thisWidth); + } + State broken = + computeBroken( + commentsHelper, maxWidth, new State(state.indent + plusIndent.eval(), state.column)); + return state.withColumn(broken.column); + } + + private static void splitByBreaks(List docs, List> splits, List breaks) { + splits.clear(); + breaks.clear(); + splits.add(new ArrayList<>()); + for (Doc doc : docs) { + if (doc instanceof Break) { + breaks.add((Break) doc); + splits.add(new ArrayList<>()); + } else { + getLast(splits).add(doc); } - return flat; + } + } + + /** Compute breaks for a {@link Level} that spans multiple lines. */ + private State computeBroken(CommentsHelper commentsHelper, int maxWidth, State state) { + splitByBreaks(docs, splits, breaks); + + state = + computeBreakAndSplit( + commentsHelper, maxWidth, state, /* optBreakDoc= */ Optional.empty(), splits.get(0)); + + // Handle following breaks and split. + for (int i = 0; i < breaks.size(); i++) { + state = + computeBreakAndSplit( + commentsHelper, maxWidth, state, Optional.of(breaks.get(i)), splits.get(i + 1)); + } + return state; + } + + /** Lay out a Break-separated group of Docs in the current Level. */ + private static State computeBreakAndSplit( + CommentsHelper commentsHelper, + int maxWidth, + State state, + Optional optBreakDoc, + List split) { + float breakWidth = optBreakDoc.isPresent() ? optBreakDoc.get().getWidth() : 0.0F; + float splitWidth = getWidth(split); + boolean shouldBreak = + (optBreakDoc.isPresent() && optBreakDoc.get().fillMode == FillMode.UNIFIED) + || state.mustBreak + || state.column + breakWidth + splitWidth > maxWidth; + + if (optBreakDoc.isPresent()) { + state = optBreakDoc.get().computeBreaks(state, state.lastIndent, shouldBreak); + } + boolean enoughRoom = state.column + splitWidth <= maxWidth; + state = computeSplit(commentsHelper, maxWidth, split, state.withMustBreak(false)); + if (!enoughRoom) { + state = state.withMustBreak(true); // Break after, too. + } + return state; + } + + private static State computeSplit( + CommentsHelper commentsHelper, int maxWidth, List docs, State state) { + for (Doc doc : docs) { + state = doc.computeBreaks(commentsHelper, maxWidth, state); + } + return state; + } + + @Override + public void write(Output output) { + if (oneLine) { + output.append(getFlat(), range()); // This is defined because width is finite. + } else { + writeFilled(output); + } + } + + private void writeFilled(Output output) { + // Handle first split. + for (Doc doc : splits.get(0)) { + doc.write(output); + } + // Handle following breaks and split. + for (int i = 0; i < breaks.size(); i++) { + breaks.get(i).write(output); + for (Doc doc : splits.get(i + 1)) { + doc.write(output); + } + } } /** - * Return the {@link Range} of a {@code Doc}. + * Get the width of a sequence of {@link Doc}s. * - * @return the {@code Doc}'s {@link Range} + * @param docs the {@link Doc}s + * @return the width, or {@code Float.POSITIVE_INFINITY} if any {@link Doc} must be broken */ - final Range range() { - if (!rangeComputed) { - range = computeRange(); - rangeComputed = true; - } - return range; + static float getWidth(List docs) { + float width = 0.0F; + for (Doc doc : docs) { + width += doc.getWidth(); + } + return width; + } + + private static Range union(Range x, Range y) { + return x.isEmpty() ? y : y.isEmpty() ? x : x.span(y).canonical(INTEGERS); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("plusIndent", plusIndent) + .add("docs", docs) + .toString(); + } + } + + /** A leaf {@link Doc} for a token. */ + public static final class Token extends Doc implements Op { + /** Is a Token a real token, or imaginary (e.g., a token generated incorrectly, or an EOF)? */ + public enum RealOrImaginary { + REAL, + IMAGINARY; + + boolean isReal() { + return this == REAL; + } + } + + private final Input.Token token; + private final RealOrImaginary realOrImaginary; + private final Indent plusIndentCommentsBefore; + private final Optional breakAndIndentTrailingComment; + + private Token( + Input.Token token, + RealOrImaginary realOrImaginary, + Indent plusIndentCommentsBefore, + Optional breakAndIndentTrailingComment) { + this.token = token; + this.realOrImaginary = realOrImaginary; + this.plusIndentCommentsBefore = plusIndentCommentsBefore; + this.breakAndIndentTrailingComment = breakAndIndentTrailingComment; } /** - * Compute the {@code Doc}'s width. + * How much extra to indent comments before the {@code Token}. * - * @return the width, or {@code Float.POSITIVE_INFINITY} if it must be broken + * @return the extra indent */ - abstract float computeWidth(); + Indent getPlusIndentCommentsBefore() { + return plusIndentCommentsBefore; + } + + /** Force a line break and indent trailing javadoc or block comments. */ + Optional breakAndIndentTrailingComment() { + return breakAndIndentTrailingComment; + } /** - * Compute the {@code Doc}'s flat value. Not defined (and never called) if contains forced - * breaks. + * Make a {@code Token}. * - * @return the flat value + * @param token the {@link Input.Token} to wrap + * @param realOrImaginary did this {@link Input.Token} appear in the input, or was it generated + * incorrectly? + * @param plusIndentCommentsBefore extra {@code plusIndent} for comments just before this token + * @return the new {@code Token} */ - abstract String computeFlat(); + static Op make( + Input.Token token, + Doc.Token.RealOrImaginary realOrImaginary, + Indent plusIndentCommentsBefore, + Optional breakAndIndentTrailingComment) { + return new Token( + token, realOrImaginary, plusIndentCommentsBefore, breakAndIndentTrailingComment); + } /** - * Compute the {@code Doc}'s {@link Range} of {@link Input.Token}s. + * Return the wrapped {@link Input.Token}. * - * @return the {@link Range} + * @return the {@link Input.Token} */ - abstract Range computeRange(); + Input.Token getToken() { + return token; + } /** - * Make breaking decisions for a {@code Doc}. + * Is the token good? That is, does it match an {@link Input.Token}? * - * @param maxWidth the maximum line width - * @param state the current output state - * @return the new output state + * @return whether the @code Token} is good */ - public abstract State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state); - - /** Write a {@code Doc} to an {@link Output}, after breaking decisions have been made. */ - public abstract void write(Output output); - - /** A {@code Level} inside a {@link Doc}. */ - static final class Level extends Doc { - private final Indent plusIndent; // The extra indent following breaks. - private final List docs = new ArrayList<>(); // The elements of the level. - - private Level(Indent plusIndent) { - this.plusIndent = plusIndent; - } - - /** - * Factory method for {@code Level}s. - * - * @param plusIndent the extra indent inside the {@code Level} - * @return the new {@code Level} - */ - static Level make(Indent plusIndent) { - return new Level(plusIndent); - } - - /** - * Add a {@link Doc} to the {@code Level}. - * - * @param doc the {@link Doc} to add - */ - void add(Doc doc) { - docs.add(doc); - } - - @Override - float computeWidth() { - float thisWidth = 0.0F; - for (Doc doc : docs) { - thisWidth += doc.getWidth(); - } - return thisWidth; - } - - @Override - String computeFlat() { - StringBuilder builder = new StringBuilder(); - for (Doc doc : docs) { - builder.append(doc.getFlat()); - } - return builder.toString(); - } - - @Override - Range computeRange() { - Range docRange = EMPTY_RANGE; - for (Doc doc : docs) { - docRange = union(docRange, doc.range()); - } - return docRange; - } - - // State that needs to be preserved between calculating breaks and - // writing output. - // TODO(cushon): represent phases as separate immutable data. - - /** True if the entire {@link Level} fits on one line. */ - boolean oneLine = false; - - /** - * Groups of {@link Doc}s that are children of the current {@link Level}, separated by - * {@link Break}s. - */ - List> splits = new ArrayList<>(); - - /** {@link Break}s between {@link Doc}s in the current {@link Level}. */ - List breaks = new ArrayList<>(); - - @Override - public State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state) { - float thisWidth = getWidth(); - if (state.column + thisWidth <= maxWidth) { - oneLine = true; - return state.withColumn(state.column + (int) thisWidth); - } - State broken = - computeBroken( - commentsHelper, - maxWidth, - new State(state.indent + plusIndent.eval(), state.column)); - return state.withColumn(broken.column); - } - - private static void splitByBreaks( - List docs, List> splits, List breaks) { - splits.clear(); - breaks.clear(); - splits.add(new ArrayList<>()); - for (Doc doc : docs) { - if (doc instanceof Break) { - breaks.add((Break) doc); - splits.add(new ArrayList<>()); - } else { - getLast(splits).add(doc); - } - } - } - - /** Compute breaks for a {@link Level} that spans multiple lines. */ - private State computeBroken(CommentsHelper commentsHelper, int maxWidth, State state) { - splitByBreaks(docs, splits, breaks); - - state = - computeBreakAndSplit( - commentsHelper, - maxWidth, - state, - /* optBreakDoc= */ Optional.empty(), - splits.get(0)); - - // Handle following breaks and split. - for (int i = 0; i < breaks.size(); i++) { - state = - computeBreakAndSplit( - commentsHelper, - maxWidth, - state, - Optional.of(breaks.get(i)), - splits.get(i + 1)); - } - return state; - } - - /** Lay out a Break-separated group of Docs in the current Level. */ - private static State computeBreakAndSplit( - CommentsHelper commentsHelper, - int maxWidth, - State state, - Optional optBreakDoc, - List split) { - float breakWidth = optBreakDoc.isPresent() ? optBreakDoc.get().getWidth() : 0.0F; - float splitWidth = getWidth(split); - boolean shouldBreak = - (optBreakDoc.isPresent() && optBreakDoc.get().fillMode == FillMode.UNIFIED) - || state.mustBreak - || state.column + breakWidth + splitWidth > maxWidth; - - if (optBreakDoc.isPresent()) { - state = optBreakDoc.get().computeBreaks(state, state.lastIndent, shouldBreak); - } - boolean enoughRoom = state.column + splitWidth <= maxWidth; - state = computeSplit(commentsHelper, maxWidth, split, state.withMustBreak(false)); - if (!enoughRoom) { - state = state.withMustBreak(true); // Break after, too. - } - return state; - } - - private static State computeSplit( - CommentsHelper commentsHelper, int maxWidth, List docs, State state) { - for (Doc doc : docs) { - state = doc.computeBreaks(commentsHelper, maxWidth, state); - } - return state; - } - - @Override - public void write(Output output) { - if (oneLine) { - output.append(getFlat(), range()); // This is defined because width is finite. - } else { - writeFilled(output); - } - } - - private void writeFilled(Output output) { - // Handle first split. - for (Doc doc : splits.get(0)) { - doc.write(output); - } - // Handle following breaks and split. - for (int i = 0; i < breaks.size(); i++) { - breaks.get(i).write(output); - for (Doc doc : splits.get(i + 1)) { - doc.write(output); - } - } - } - - /** - * Get the width of a sequence of {@link Doc}s. - * - * @param docs the {@link Doc}s - * @return the width, or {@code Float.POSITIVE_INFINITY} if any {@link Doc} must be broken - */ - static float getWidth(List docs) { - float width = 0.0F; - for (Doc doc : docs) { - width += doc.getWidth(); - } - return width; - } - - private static Range union(Range x, Range y) { - return x.isEmpty() ? y : y.isEmpty() ? x : x.span(y).canonical(INTEGERS); - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("plusIndent", plusIndent) - .add("docs", docs) - .toString(); - } + RealOrImaginary realOrImaginary() { + return realOrImaginary; } - /** A leaf {@link Doc} for a token. */ - public static final class Token extends Doc implements Op { - /** - * Is a Token a real token, or imaginary (e.g., a token generated incorrectly, or an EOF)? - */ - public enum RealOrImaginary { - REAL, - IMAGINARY; - - boolean isReal() { - return this == REAL; - } - } - - private final Input.Token token; - private final RealOrImaginary realOrImaginary; - private final Indent plusIndentCommentsBefore; - private final Optional breakAndIndentTrailingComment; - - private Token( - Input.Token token, - RealOrImaginary realOrImaginary, - Indent plusIndentCommentsBefore, - Optional breakAndIndentTrailingComment) { - this.token = token; - this.realOrImaginary = realOrImaginary; - this.plusIndentCommentsBefore = plusIndentCommentsBefore; - this.breakAndIndentTrailingComment = breakAndIndentTrailingComment; - } - - /** - * How much extra to indent comments before the {@code Token}. - * - * @return the extra indent - */ - Indent getPlusIndentCommentsBefore() { - return plusIndentCommentsBefore; - } - - /** Force a line break and indent trailing javadoc or block comments. */ - Optional breakAndIndentTrailingComment() { - return breakAndIndentTrailingComment; - } - - /** - * Make a {@code Token}. - * - * @param token the {@link Input.Token} to wrap - * @param realOrImaginary did this {@link Input.Token} appear in the input, or was it - * generated incorrectly? - * @param plusIndentCommentsBefore extra {@code plusIndent} for comments just before this - * token - * @return the new {@code Token} - */ - static Op make( - Input.Token token, - Doc.Token.RealOrImaginary realOrImaginary, - Indent plusIndentCommentsBefore, - Optional breakAndIndentTrailingComment) { - return new Token( - token, - realOrImaginary, - plusIndentCommentsBefore, - breakAndIndentTrailingComment); - } - - /** - * Return the wrapped {@link Input.Token}. - * - * @return the {@link Input.Token} - */ - Input.Token getToken() { - return token; - } - - /** - * Is the token good? That is, does it match an {@link Input.Token}? - * - * @return whether the @code Token} is good - */ - RealOrImaginary realOrImaginary() { - return realOrImaginary; - } - - @Override - public void add(DocBuilder builder) { - builder.add(this); - } - - @Override - float computeWidth() { - return token.getTok().length(); - } - - @Override - String computeFlat() { - return token.getTok().getOriginalText(); - } - - @Override - Range computeRange() { - return Range.singleton(token.getTok().getIndex()).canonical(INTEGERS); - } - - @Override - public State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state) { - String text = token.getTok().getOriginalText(); - return state.withColumn(state.column + text.length()); - } - - @Override - public void write(Output output) { - String text = token.getTok().getOriginalText(); - output.append(text, range()); - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("token", token) - .add("realOrImaginary", realOrImaginary) - .add("plusIndentCommentsBefore", plusIndentCommentsBefore) - .toString(); - } + @Override + public void add(DocBuilder builder) { + builder.add(this); } - /** A Leaf node in a {@link Doc} for a non-breaking space. */ - static final class Space extends Doc implements Op { - private static final Space SPACE = new Space(); - - private Space() {} - - /** - * Factor method for {@code Space}. - * - * @return the new {@code Space} - */ - static Space make() { - return SPACE; - } - - @Override - public void add(DocBuilder builder) { - builder.add(this); - } - - @Override - float computeWidth() { - return 1.0F; - } - - @Override - String computeFlat() { - return " "; - } - - @Override - Range computeRange() { - return EMPTY_RANGE; - } - - @Override - public State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state) { - return state.withColumn(state.column + 1); - } - - @Override - public void write(Output output) { - output.append(" ", range()); - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this).toString(); - } + @Override + float computeWidth() { + return token.getTok().length(); } - /** A leaf node in a {@link Doc} for an optional break. */ - public static final class Break extends Doc implements Op { - private final FillMode fillMode; - private final String flat; - private final Indent plusIndent; - private final Optional optTag; - - private Break( - FillMode fillMode, String flat, Indent plusIndent, Optional optTag) { - this.fillMode = fillMode; - this.flat = flat; - this.plusIndent = plusIndent; - this.optTag = optTag; - } - - /** - * Make a {@code Break}. - * - * @param fillMode the {@link FillMode} - * @param flat the text when not broken - * @param plusIndent extra indent if taken - * @return the new {@code Break} - */ - public static Break make(FillMode fillMode, String flat, Indent plusIndent) { - return new Break(fillMode, flat, plusIndent, /* optTag= */ Optional.empty()); - } - - /** - * Make a {@code Break}. - * - * @param fillMode the {@link FillMode} - * @param flat the text when not broken - * @param plusIndent extra indent if taken - * @param optTag an optional tag for remembering whether the break was taken - * @return the new {@code Break} - */ - public static Break make( - FillMode fillMode, String flat, Indent plusIndent, Optional optTag) { - return new Break(fillMode, flat, plusIndent, optTag); - } - - /** - * Make a forced {@code Break}. - * - * @return the new forced {@code Break} - */ - public static Break makeForced() { - return make(FillMode.FORCED, "", Indent.Const.ZERO); - } - - /** - * Return the {@code Break}'s extra indent. - * - * @return the extra indent - */ - int getPlusIndent() { - return plusIndent.eval(); - } - - /** - * Is the {@code Break} forced? - * - * @return whether the {@code Break} is forced - */ - boolean isForced() { - return fillMode == FillMode.FORCED; - } - - @Override - public void add(DocBuilder builder) { - builder.breakDoc(this); - } - - @Override - float computeWidth() { - return isForced() ? Float.POSITIVE_INFINITY : (float) flat.length(); - } - - @Override - String computeFlat() { - return flat; - } - - @Override - Range computeRange() { - return EMPTY_RANGE; - } - - /** Was this break taken? */ - boolean broken; - - /** New indent after this break. */ - int newIndent; - - public State computeBreaks(State state, int lastIndent, boolean broken) { - if (optTag.isPresent()) { - optTag.get().recordBroken(broken); - } - - if (broken) { - this.broken = true; - this.newIndent = max(lastIndent + plusIndent.eval(), 0); - return state.withColumn(newIndent); - } else { - this.broken = false; - this.newIndent = -1; - return state.withColumn(state.column + flat.length()); - } - } - - @Override - public State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state) { - // Updating the state for {@link Break}s requires deciding if the break - // should be taken. - // TODO(cushon): this hierarchy is wrong, create a separate interface - // for unbreakable Docs? - throw new UnsupportedOperationException( - "Did you mean computeBreaks(State, int, boolean)?"); - } - - @Override - public void write(Output output) { - if (broken) { - output.append("\n", EMPTY_RANGE); - output.indent(newIndent); - } else { - output.append(flat, range()); - } - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("fillMode", fillMode) - .add("flat", flat) - .add("plusIndent", plusIndent) - .add("optTag", optTag) - .toString(); - } + @Override + String computeFlat() { + return token.getTok().getOriginalText(); } - /** A leaf node in a {@link Doc} for a non-token. */ - static final class Tok extends Doc implements Op { - private final Input.Tok tok; - - private Tok(Input.Tok tok) { - this.tok = tok; - } - - /** - * Factory method for a {@code Tok}. - * - * @param tok the {@link Input.Tok} to wrap - * @return the new {@code Tok} - */ - static Tok make(Input.Tok tok) { - return new Tok(tok); - } - - @Override - public void add(DocBuilder builder) { - builder.add(this); - } - - @Override - float computeWidth() { - int idx = Newlines.firstBreak(tok.getOriginalText()); - // only count the first line of multi-line block comments - if (tok.isComment()) { - if (idx > 0) { - return idx; - } else if (tok.isSlashSlashComment() && !tok.getOriginalText().startsWith("// ")) { - // Account for line comments with missing spaces, see computeFlat. - return tok.length() + 1; - } else { - return tok.length(); - } - } - return idx != -1 ? Float.POSITIVE_INFINITY : (float) tok.length(); - } - - @Override - String computeFlat() { - // TODO(cushon): commentsHelper.rewrite doesn't get called for spans that fit in a - // single - // line. That's fine for multi-line comment reflowing, but problematic for adding - // missing - // spaces in line comments. - if (tok.isSlashSlashComment() && !tok.getOriginalText().startsWith("// ")) { - return "// " + tok.getOriginalText().substring("//".length()); - } - return tok.getOriginalText(); - } - - @Override - Range computeRange() { - return Range.singleton(tok.getIndex()).canonical(INTEGERS); - } - - String text; - - @Override - public State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state) { - text = commentsHelper.rewrite(tok, maxWidth, state.column); - int firstLineLength = - text.length() - Iterators.getLast(Newlines.lineOffsetIterator(text)); - return state.withColumn(state.column + firstLineLength); - } - - @Override - public void write(Output output) { - output.append(text, range()); - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this).add("tok", tok).toString(); - } + @Override + Range computeRange() { + return Range.singleton(token.getTok().getIndex()).canonical(INTEGERS); } + + @Override + public State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state) { + String text = token.getTok().getOriginalText(); + return state.withColumn(state.column + text.length()); + } + + @Override + public void write(Output output) { + String text = token.getTok().getOriginalText(); + output.append(text, range()); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("token", token) + .add("realOrImaginary", realOrImaginary) + .add("plusIndentCommentsBefore", plusIndentCommentsBefore) + .toString(); + } + } + + /** A Leaf node in a {@link Doc} for a non-breaking space. */ + static final class Space extends Doc implements Op { + private static final Space SPACE = new Space(); + + private Space() {} + + /** + * Factor method for {@code Space}. + * + * @return the new {@code Space} + */ + static Space make() { + return SPACE; + } + + @Override + public void add(DocBuilder builder) { + builder.add(this); + } + + @Override + float computeWidth() { + return 1.0F; + } + + @Override + String computeFlat() { + return " "; + } + + @Override + Range computeRange() { + return EMPTY_RANGE; + } + + @Override + public State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state) { + return state.withColumn(state.column + 1); + } + + @Override + public void write(Output output) { + output.append(" ", range()); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this).toString(); + } + } + + /** A leaf node in a {@link Doc} for an optional break. */ + public static final class Break extends Doc implements Op { + private final FillMode fillMode; + private final String flat; + private final Indent plusIndent; + private final Optional optTag; + + private Break(FillMode fillMode, String flat, Indent plusIndent, Optional optTag) { + this.fillMode = fillMode; + this.flat = flat; + this.plusIndent = plusIndent; + this.optTag = optTag; + } + + /** + * Make a {@code Break}. + * + * @param fillMode the {@link FillMode} + * @param flat the text when not broken + * @param plusIndent extra indent if taken + * @return the new {@code Break} + */ + public static Break make(FillMode fillMode, String flat, Indent plusIndent) { + return new Break(fillMode, flat, plusIndent, /* optTag= */ Optional.empty()); + } + + /** + * Make a {@code Break}. + * + * @param fillMode the {@link FillMode} + * @param flat the text when not broken + * @param plusIndent extra indent if taken + * @param optTag an optional tag for remembering whether the break was taken + * @return the new {@code Break} + */ + public static Break make( + FillMode fillMode, String flat, Indent plusIndent, Optional optTag) { + return new Break(fillMode, flat, plusIndent, optTag); + } + + /** + * Make a forced {@code Break}. + * + * @return the new forced {@code Break} + */ + public static Break makeForced() { + return make(FillMode.FORCED, "", Indent.Const.ZERO); + } + + /** + * Return the {@code Break}'s extra indent. + * + * @return the extra indent + */ + int getPlusIndent() { + return plusIndent.eval(); + } + + /** + * Is the {@code Break} forced? + * + * @return whether the {@code Break} is forced + */ + boolean isForced() { + return fillMode == FillMode.FORCED; + } + + @Override + public void add(DocBuilder builder) { + builder.breakDoc(this); + } + + @Override + float computeWidth() { + return isForced() ? Float.POSITIVE_INFINITY : (float) flat.length(); + } + + @Override + String computeFlat() { + return flat; + } + + @Override + Range computeRange() { + return EMPTY_RANGE; + } + + /** Was this break taken? */ + boolean broken; + + /** New indent after this break. */ + int newIndent; + + public State computeBreaks(State state, int lastIndent, boolean broken) { + if (optTag.isPresent()) { + optTag.get().recordBroken(broken); + } + + if (broken) { + this.broken = true; + this.newIndent = max(lastIndent + plusIndent.eval(), 0); + return state.withColumn(newIndent); + } else { + this.broken = false; + this.newIndent = -1; + return state.withColumn(state.column + flat.length()); + } + } + + @Override + public State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state) { + // Updating the state for {@link Break}s requires deciding if the break + // should be taken. + // TODO(cushon): this hierarchy is wrong, create a separate interface + // for unbreakable Docs? + throw new UnsupportedOperationException("Did you mean computeBreaks(State, int, boolean)?"); + } + + @Override + public void write(Output output) { + if (broken) { + output.append("\n", EMPTY_RANGE); + output.indent(newIndent); + } else { + output.append(flat, range()); + } + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("fillMode", fillMode) + .add("flat", flat) + .add("plusIndent", plusIndent) + .add("optTag", optTag) + .toString(); + } + } + + /** A leaf node in a {@link Doc} for a non-token. */ + static final class Tok extends Doc implements Op { + private final Input.Tok tok; + + private Tok(Input.Tok tok) { + this.tok = tok; + } + + /** + * Factory method for a {@code Tok}. + * + * @param tok the {@link Input.Tok} to wrap + * @return the new {@code Tok} + */ + static Tok make(Input.Tok tok) { + return new Tok(tok); + } + + @Override + public void add(DocBuilder builder) { + builder.add(this); + } + + @Override + float computeWidth() { + int idx = Newlines.firstBreak(tok.getOriginalText()); + // only count the first line of multi-line block comments + if (tok.isComment()) { + if (idx > 0) { + return idx; + } else if (tok.isSlashSlashComment() && !tok.getOriginalText().startsWith("// ")) { + // Account for line comments with missing spaces, see computeFlat. + return tok.length() + 1; + } else { + return tok.length(); + } + } + return idx != -1 ? Float.POSITIVE_INFINITY : (float) tok.length(); + } + + @Override + String computeFlat() { + // TODO(cushon): commentsHelper.rewrite doesn't get called for spans that fit in a + // single + // line. That's fine for multi-line comment reflowing, but problematic for adding + // missing + // spaces in line comments. + if (tok.isSlashSlashComment() && !tok.getOriginalText().startsWith("// ")) { + return "// " + tok.getOriginalText().substring("//".length()); + } + return tok.getOriginalText(); + } + + @Override + Range computeRange() { + return Range.singleton(tok.getIndex()).canonical(INTEGERS); + } + + String text; + + @Override + public State computeBreaks(CommentsHelper commentsHelper, int maxWidth, State state) { + text = commentsHelper.rewrite(tok, maxWidth, state.column); + int firstLineLength = text.length() - Iterators.getLast(Newlines.lineOffsetIterator(text)); + return state.withColumn(state.column + firstLineLength); + } + + @Override + public void write(Output output) { + output.append(text, range()); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this).add("tok", tok).toString(); + } + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/DocBuilder.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/DocBuilder.java index c5dccc9c2..bf6059e23 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/DocBuilder.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/DocBuilder.java @@ -21,95 +21,95 @@ import java.util.List; /** A {@code DocBuilder} converts a sequence of {@link Op}s into a {@link Doc}. */ public final class DocBuilder { - private final Doc.Level base = Doc.Level.make(Indent.Const.ZERO); - private final ArrayDeque stack = new ArrayDeque<>(); + private final Doc.Level base = Doc.Level.make(Indent.Const.ZERO); + private final ArrayDeque stack = new ArrayDeque<>(); - /** - * A possibly earlier {@link Doc.Level} for appending text, à la Philip Wadler. - * - *

Processing {@link Doc}s presents a subtle problem. Suppose we have a {@link Doc} for to an - * assignment node, {@code a = b}, with an optional {@link Doc.Break} following the {@code =}. - * Suppose we have 5 characters to write it, so that we think we don't need the break. - * Unfortunately, this {@link Doc} lies in an expression statement {@link Doc} for the statement - * {@code a = b;} and this statement does not fit in 3 characters. This is why many formatters - * sometimes emit lines that are too long, or cheat by using a narrower line length to avoid - * such problems. - * - *

One solution to this problem is not to decide whether a {@link Doc.Level} should be broken - * until later (in this case, after the semicolon has been seen). A simpler approach is to - * rewrite the {@link Doc} as here, so that the semicolon moves inside the inner {@link Doc}, - * and we can decide whether to break that {@link Doc} without seeing later text. - */ - private Doc.Level appendLevel = base; + /** + * A possibly earlier {@link Doc.Level} for appending text, à la Philip Wadler. + * + *

Processing {@link Doc}s presents a subtle problem. Suppose we have a {@link Doc} for to an + * assignment node, {@code a = b}, with an optional {@link Doc.Break} following the {@code =}. + * Suppose we have 5 characters to write it, so that we think we don't need the break. + * Unfortunately, this {@link Doc} lies in an expression statement {@link Doc} for the statement + * {@code a = b;} and this statement does not fit in 3 characters. This is why many formatters + * sometimes emit lines that are too long, or cheat by using a narrower line length to avoid such + * problems. + * + *

One solution to this problem is not to decide whether a {@link Doc.Level} should be broken + * until later (in this case, after the semicolon has been seen). A simpler approach is to rewrite + * the {@link Doc} as here, so that the semicolon moves inside the inner {@link Doc}, and we can + * decide whether to break that {@link Doc} without seeing later text. + */ + private Doc.Level appendLevel = base; - /** Start to build a {@code DocBuilder}. */ - public DocBuilder() { - stack.addLast(base); + /** Start to build a {@code DocBuilder}. */ + public DocBuilder() { + stack.addLast(base); + } + + /** + * Add a list of {@link Op}s to the {@link OpsBuilder}. + * + * @param ops the {@link Op}s + * @return the {@link OpsBuilder} + */ + public DocBuilder withOps(List ops) { + for (Op op : ops) { + op.add(this); // These operations call the operations below to build the doc. } + return this; + } - /** - * Add a list of {@link Op}s to the {@link OpsBuilder}. - * - * @param ops the {@link Op}s - * @return the {@link OpsBuilder} - */ - public DocBuilder withOps(List ops) { - for (Op op : ops) { - op.add(this); // These operations call the operations below to build the doc. - } - return this; - } + /** + * Open a new {@link Doc.Level}. + * + * @param plusIndent the extra indent for the {@link Doc.Level} + */ + void open(Indent plusIndent) { + Doc.Level level = Doc.Level.make(plusIndent); + stack.addLast(level); + } - /** - * Open a new {@link Doc.Level}. - * - * @param plusIndent the extra indent for the {@link Doc.Level} - */ - void open(Indent plusIndent) { - Doc.Level level = Doc.Level.make(plusIndent); - stack.addLast(level); - } + /** Close the current {@link Doc.Level}. */ + void close() { + Doc.Level top = stack.removeLast(); + stack.peekLast().add(top); + } - /** Close the current {@link Doc.Level}. */ - void close() { - Doc.Level top = stack.removeLast(); - stack.peekLast().add(top); - } + /** + * Add a {@link Doc} to the current {@link Doc.Level}. + * + * @param doc the {@link Doc} + */ + void add(Doc doc) { + appendLevel.add(doc); + } - /** - * Add a {@link Doc} to the current {@link Doc.Level}. - * - * @param doc the {@link Doc} - */ - void add(Doc doc) { - appendLevel.add(doc); - } + /** + * Add a {@link Doc.Break} to the current {@link Doc.Level}. + * + * @param breakDoc the {@link Doc.Break} + */ + void breakDoc(Doc.Break breakDoc) { + appendLevel = stack.peekLast(); + appendLevel.add(breakDoc); + } - /** - * Add a {@link Doc.Break} to the current {@link Doc.Level}. - * - * @param breakDoc the {@link Doc.Break} - */ - void breakDoc(Doc.Break breakDoc) { - appendLevel = stack.peekLast(); - appendLevel.add(breakDoc); - } + /** + * Return the {@link Doc}. + * + * @return the {@link Doc} + */ + public Doc build() { + return base; + } - /** - * Return the {@link Doc}. - * - * @return the {@link Doc} - */ - public Doc build() { - return base; - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("base", base) - .add("stack", stack) - .add("appendLevel", appendLevel) - .toString(); - } + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("base", base) + .add("stack", stack) + .add("appendLevel", appendLevel) + .toString(); + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/FormatterDiagnostic.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/FormatterDiagnostic.java index 758dd1ed2..0a9f9ec32 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/FormatterDiagnostic.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/FormatterDiagnostic.java @@ -19,62 +19,62 @@ import static com.google.common.base.Preconditions.checkNotNull; /** An error that prevented formatting from succeeding. */ public class FormatterDiagnostic { - private final int lineNumber; - private final String message; - private final int column; + private final int lineNumber; + private final String message; + private final int column; - public static FormatterDiagnostic create(String message) { - return new FormatterDiagnostic(-1, -1, message); - } + public static FormatterDiagnostic create(String message) { + return new FormatterDiagnostic(-1, -1, message); + } - public static FormatterDiagnostic create(int lineNumber, int column, String message) { - checkArgument(lineNumber >= 0); - checkArgument(column >= 0); - checkNotNull(message); - return new FormatterDiagnostic(lineNumber, column, message); - } + public static FormatterDiagnostic create(int lineNumber, int column, String message) { + checkArgument(lineNumber >= 0); + checkArgument(column >= 0); + checkNotNull(message); + return new FormatterDiagnostic(lineNumber, column, message); + } - private FormatterDiagnostic(int lineNumber, int column, String message) { - this.lineNumber = lineNumber; - this.column = column; - this.message = message; - } + private FormatterDiagnostic(int lineNumber, int column, String message) { + this.lineNumber = lineNumber; + this.column = column; + this.message = message; + } - /** - * Returns the line number on which the error occurred, or {@code -1} if the error does not have - * a line number. - */ - public int line() { - return lineNumber; - } + /** + * Returns the line number on which the error occurred, or {@code -1} if the error does not have a + * line number. + */ + public int line() { + return lineNumber; + } - /** - * Returns the 0-indexed column number on which the error occurred, or {@code -1} if the error - * does not have a column. - */ - public int column() { - return column; - } + /** + * Returns the 0-indexed column number on which the error occurred, or {@code -1} if the error + * does not have a column. + */ + public int column() { + return column; + } - /** Returns a description of the problem that prevented formatting from succeeding. */ - public String message() { - return message; - } + /** Returns a description of the problem that prevented formatting from succeeding. */ + public String message() { + return message; + } - public String toString() { - StringBuilder sb = new StringBuilder(); - if (lineNumber >= 0) { - sb.append(lineNumber).append(':'); - } - if (column >= 0) { - // internal column numbers are 0-based, but diagnostics use 1-based indexing by - // convention - sb.append(column + 1).append(':'); - } - if (lineNumber >= 0 || column >= 0) { - sb.append(' '); - } - sb.append("error: ").append(message); - return sb.toString(); + public String toString() { + StringBuilder sb = new StringBuilder(); + if (lineNumber >= 0) { + sb.append(lineNumber).append(':'); } + if (column >= 0) { + // internal column numbers are 0-based, but diagnostics use 1-based indexing by + // convention + sb.append(column + 1).append(':'); + } + if (lineNumber >= 0 || column >= 0) { + sb.append(' '); + } + sb.append("error: ").append(message); + return sb.toString(); + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/FormattingError.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/FormattingError.java index 68b2fcbdb..50381d0cd 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/FormattingError.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/FormattingError.java @@ -20,18 +20,18 @@ import com.google.common.collect.ImmutableList; /** An unchecked formatting error. */ public class FormattingError extends Error { - private final ImmutableList diagnostics; + private final ImmutableList diagnostics; - public FormattingError(FormatterDiagnostic diagnostic) { - this(ImmutableList.of(diagnostic)); - } + public FormattingError(FormatterDiagnostic diagnostic) { + this(ImmutableList.of(diagnostic)); + } - public FormattingError(Iterable diagnostics) { - super(Joiner.on("\n").join(diagnostics) + "\n"); - this.diagnostics = ImmutableList.copyOf(diagnostics); - } + public FormattingError(Iterable diagnostics) { + super(Joiner.on("\n").join(diagnostics) + "\n"); + this.diagnostics = ImmutableList.copyOf(diagnostics); + } - public ImmutableList diagnostics() { - return diagnostics; - } + public ImmutableList diagnostics() { + return diagnostics; + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/Indent.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/Indent.java index 0d5b067b5..44b56cdd6 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/Indent.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/Indent.java @@ -24,61 +24,61 @@ import com.google.googlejavaformat.Output.BreakTag; */ public abstract class Indent { - abstract int eval(); + abstract int eval(); - /** A constant function, returning a constant indent. */ - public static final class Const extends Indent { - private final int n; + /** A constant function, returning a constant indent. */ + public static final class Const extends Indent { + private final int n; - public static final Const ZERO = new Const(+0); + public static final Const ZERO = new Const(+0); - private Const(int n) { - this.n = n; - } - - public static Const make(int n, int indentMultiplier) { - return new Const(n * indentMultiplier); - } - - @Override - int eval() { - return n; - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this).add("n", n).toString(); - } + private Const(int n) { + this.n = n; } - /** A conditional function, whose value depends on whether a break was taken. */ - public static final class If extends Indent { - private final BreakTag condition; - private final Indent thenIndent; - private final Indent elseIndent; - - private If(BreakTag condition, Indent thenIndent, Indent elseIndent) { - this.condition = condition; - this.thenIndent = thenIndent; - this.elseIndent = elseIndent; - } - - public static If make(BreakTag condition, Indent thenIndent, Indent elseIndent) { - return new If(condition, thenIndent, elseIndent); - } - - @Override - int eval() { - return (condition.wasBreakTaken() ? thenIndent : elseIndent).eval(); - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("condition", condition) - .add("thenIndent", thenIndent) - .add("elseIndent", elseIndent) - .toString(); - } + public static Const make(int n, int indentMultiplier) { + return new Const(n * indentMultiplier); } + + @Override + int eval() { + return n; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this).add("n", n).toString(); + } + } + + /** A conditional function, whose value depends on whether a break was taken. */ + public static final class If extends Indent { + private final BreakTag condition; + private final Indent thenIndent; + private final Indent elseIndent; + + private If(BreakTag condition, Indent thenIndent, Indent elseIndent) { + this.condition = condition; + this.thenIndent = thenIndent; + this.elseIndent = elseIndent; + } + + public static If make(BreakTag condition, Indent thenIndent, Indent elseIndent) { + return new If(condition, thenIndent, elseIndent); + } + + @Override + int eval() { + return (condition.wasBreakTaken() ? thenIndent : elseIndent).eval(); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("condition", condition) + .add("thenIndent", thenIndent) + .add("elseIndent", elseIndent) + .toString(); + } + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/Input.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/Input.java index 554b997ca..66a392190 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/Input.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/Input.java @@ -21,127 +21,127 @@ import com.google.common.collect.ImmutableRangeMap; /** An input to the formatter. */ public abstract class Input extends InputOutput { + /** + * A {@code Tok} ("tock") is a token, or a comment, or a newline, or a maximal string of blanks. A + * token {@code Tok} underlies a {@link Token}, and each other {@code Tok} is attached to a single + * {@code Token}. Tokens and comments have indices; white space {@code Tok}s do not. + */ + public interface Tok { /** - * A {@code Tok} ("tock") is a token, or a comment, or a newline, or a maximal string of blanks. - * A token {@code Tok} underlies a {@link Token}, and each other {@code Tok} is attached to a - * single {@code Token}. Tokens and comments have indices; white space {@code Tok}s do not. - */ - public interface Tok { - /** - * Return the {@code Tok}'s index. - * - * @return its index - */ - int getIndex(); - - /** - * Return the {@code Tok}'s {@code 0}-based position. - * - * @return its position - */ - int getPosition(); - - /** - * Return the {@code Tok}'s {@code 0}-based column number. - * - * @return its column number - */ - int getColumn(); - - /** The {@code Tok}'s text. */ - String getText(); - - /** The {@code Tok}'s original text (before processing escapes). */ - String getOriginalText(); - - /** The length of the {@code Tok}'s original text. */ - int length(); - - /** Is the {@code Tok} a newline? */ - boolean isNewline(); - - /** Is the {@code Tok} a "//" comment? */ - boolean isSlashSlashComment(); - - /** Is the {@code Tok} a "/*" comment? */ - boolean isSlashStarComment(); - - /** Is the {@code Tok} a javadoc comment? */ - boolean isJavadocComment(); - - /** Is the {@code Tok} a comment? */ - boolean isComment(); - } - - /** A {@code Token} is a language-level token. */ - public interface Token { - /** - * Get the token's {@link Tok}. - * - * @return the token's {@link Tok} - */ - Tok getTok(); - - /** - * Get the earlier {@link Tok}s assigned to this {@code Token}. - * - * @return the earlier {@link Tok}s assigned to this {@code Token} - */ - ImmutableList getToksBefore(); - - /** - * Get the later {@link Tok}s assigned to this {@code Token}. - * - * @return the later {@link Tok}s assigned to this {@code Token} - */ - ImmutableList getToksAfter(); - } - - /** - * Get the input tokens. + * Return the {@code Tok}'s index. * - * @return the input tokens + * @return its index */ - public abstract ImmutableList getTokens(); - - /** A map from [start, end] position ranges to {@link Token}s. */ - public abstract ImmutableRangeMap getPositionTokenMap(); - - public abstract ImmutableMap getPositionToColumnMap(); - - public abstract String getText(); + int getIndex(); /** - * Get the number of toks. + * Return the {@code Tok}'s {@code 0}-based position. * - * @return the number of toks, excluding the EOF tok + * @return its position */ - public abstract int getkN(); + int getPosition(); /** - * Get the Token by index. + * Return the {@code Tok}'s {@code 0}-based column number. * - * @param k the Tok index + * @return its column number */ - public abstract Token getToken(int k); + int getColumn(); - @Override - public String toString() { - return MoreObjects.toStringHelper(this).add("super", super.toString()).toString(); - } + /** The {@code Tok}'s text. */ + String getText(); - /** Converts a character offset in the input to a line number. */ - public abstract int getLineNumber(int inputPosition); + /** The {@code Tok}'s original text (before processing escapes). */ + String getOriginalText(); - /** Converts a character offset in the input to a 0-based column number. */ - public abstract int getColumnNumber(int inputPosition); + /** The length of the {@code Tok}'s original text. */ + int length(); + + /** Is the {@code Tok} a newline? */ + boolean isNewline(); + + /** Is the {@code Tok} a "//" comment? */ + boolean isSlashSlashComment(); + + /** Is the {@code Tok} a "/*" comment? */ + boolean isSlashStarComment(); + + /** Is the {@code Tok} a javadoc comment? */ + boolean isJavadocComment(); + + /** Is the {@code Tok} a comment? */ + boolean isComment(); + } + + /** A {@code Token} is a language-level token. */ + public interface Token { + /** + * Get the token's {@link Tok}. + * + * @return the token's {@link Tok} + */ + Tok getTok(); /** - * Construct a diagnostic. Populates the input filename, and converts character offsets to - * numbers. + * Get the earlier {@link Tok}s assigned to this {@code Token}. + * + * @return the earlier {@link Tok}s assigned to this {@code Token} */ - public FormatterDiagnostic createDiagnostic(int inputPosition, String message) { - return FormatterDiagnostic.create( - getLineNumber(inputPosition), getColumnNumber(inputPosition), message); - } + ImmutableList getToksBefore(); + + /** + * Get the later {@link Tok}s assigned to this {@code Token}. + * + * @return the later {@link Tok}s assigned to this {@code Token} + */ + ImmutableList getToksAfter(); + } + + /** + * Get the input tokens. + * + * @return the input tokens + */ + public abstract ImmutableList getTokens(); + + /** A map from [start, end] position ranges to {@link Token}s. */ + public abstract ImmutableRangeMap getPositionTokenMap(); + + public abstract ImmutableMap getPositionToColumnMap(); + + public abstract String getText(); + + /** + * Get the number of toks. + * + * @return the number of toks, excluding the EOF tok + */ + public abstract int getkN(); + + /** + * Get the Token by index. + * + * @param k the Tok index + */ + public abstract Token getToken(int k); + + @Override + public String toString() { + return MoreObjects.toStringHelper(this).add("super", super.toString()).toString(); + } + + /** Converts a character offset in the input to a line number. */ + public abstract int getLineNumber(int inputPosition); + + /** Converts a character offset in the input to a 0-based column number. */ + public abstract int getColumnNumber(int inputPosition); + + /** + * Construct a diagnostic. Populates the input filename, and converts character offsets to + * numbers. + */ + public FormatterDiagnostic createDiagnostic(int inputPosition, String message) { + return FormatterDiagnostic.create( + getLineNumber(inputPosition), getColumnNumber(inputPosition), message); + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/InputOutput.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/InputOutput.java index be7237add..8ae10aaca 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/InputOutput.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/InputOutput.java @@ -25,96 +25,96 @@ import java.util.Map; /** This interface defines methods common to an {@link Input} or an {@link Output}. */ public abstract class InputOutput { - private ImmutableList lines = ImmutableList.of(); + private ImmutableList lines = ImmutableList.of(); - protected static final Range EMPTY_RANGE = Range.closedOpen(-1, -1); - private static final DiscreteDomain INTEGERS = DiscreteDomain.integers(); + protected static final Range EMPTY_RANGE = Range.closedOpen(-1, -1); + private static final DiscreteDomain INTEGERS = DiscreteDomain.integers(); - /** Set the lines. */ - protected final void setLines(ImmutableList lines) { - this.lines = lines; + /** Set the lines. */ + protected final void setLines(ImmutableList lines) { + this.lines = lines; + } + + /** + * Get the line count. + * + * @return the line count + */ + public final int getLineCount() { + return lines.size(); + } + + /** + * Get a line. + * + * @param lineI the line number + * @return the line + */ + public final String getLine(int lineI) { + return lines.get(lineI); + } + + /** The {@link Range}s of the tokens or comments lying on each line, in any part. */ + protected final List> ranges = new ArrayList<>(); + + private static void addToRanges(List> ranges, int i, int k) { + while (ranges.size() <= i) { + ranges.add(EMPTY_RANGE); } + Range oldValue = ranges.get(i); + ranges.set(i, Range.closedOpen(oldValue.isEmpty() ? k : oldValue.lowerEndpoint(), k + 1)); + } - /** - * Get the line count. - * - * @return the line count - */ - public final int getLineCount() { - return lines.size(); - } - - /** - * Get a line. - * - * @param lineI the line number - * @return the line - */ - public final String getLine(int lineI) { - return lines.get(lineI); - } - - /** The {@link Range}s of the tokens or comments lying on each line, in any part. */ - protected final List> ranges = new ArrayList<>(); - - private static void addToRanges(List> ranges, int i, int k) { - while (ranges.size() <= i) { - ranges.add(EMPTY_RANGE); + protected final void computeRanges(List toks) { + int lineI = 0; + for (Input.Tok tok : toks) { + String txt = tok.getOriginalText(); + int lineI0 = lineI; + lineI += Newlines.count(txt); + int k = tok.getIndex(); + if (k >= 0) { + for (int i = lineI0; i <= lineI; i++) { + addToRanges(ranges, i, k); } - Range oldValue = ranges.get(i); - ranges.set(i, Range.closedOpen(oldValue.isEmpty() ? k : oldValue.lowerEndpoint(), k + 1)); + } } + } - protected final void computeRanges(List toks) { - int lineI = 0; - for (Input.Tok tok : toks) { - String txt = tok.getOriginalText(); - int lineI0 = lineI; - lineI += Newlines.count(txt); - int k = tok.getIndex(); - if (k >= 0) { - for (int i = lineI0; i <= lineI; i++) { - addToRanges(ranges, i, k); - } - } + /** + * Given an {@code InputOutput}, compute the map from tok indices to line ranges. + * + * @param put the {@code InputOutput} + * @return the map from {@code com.google.googlejavaformat.java.JavaInput.Tok} indices to line + * ranges in this {@code put} + */ + public static Map> makeKToIJ(InputOutput put) { + Map> map = new HashMap<>(); + int ijN = put.getLineCount(); + for (int ij = 0; ij <= ijN; ij++) { + Range range = put.getRanges(ij).canonical(INTEGERS); + for (int k = range.lowerEndpoint(); k < range.upperEndpoint(); k++) { + if (map.containsKey(k)) { + map.put(k, Range.closedOpen(map.get(k).lowerEndpoint(), ij + 1)); + } else { + map.put(k, Range.closedOpen(ij, ij + 1)); } + } } + return map; + } - /** - * Given an {@code InputOutput}, compute the map from tok indices to line ranges. - * - * @param put the {@code InputOutput} - * @return the map from {@code com.google.googlejavaformat.java.JavaInput.Tok} indices to line - * ranges in this {@code put} - */ - public static Map> makeKToIJ(InputOutput put) { - Map> map = new HashMap<>(); - int ijN = put.getLineCount(); - for (int ij = 0; ij <= ijN; ij++) { - Range range = put.getRanges(ij).canonical(INTEGERS); - for (int k = range.lowerEndpoint(); k < range.upperEndpoint(); k++) { - if (map.containsKey(k)) { - map.put(k, Range.closedOpen(map.get(k).lowerEndpoint(), ij + 1)); - } else { - map.put(k, Range.closedOpen(ij, ij + 1)); - } - } - } - return map; - } + /** + * Get the {@link Range} of {@link Input.Tok}s lying in any part on a line. + * + * @param lineI the line number + * @return the {@link Range} of {@link Input.Tok}s on the specified line + */ + public final Range getRanges(int lineI) { + return 0 <= lineI && lineI < ranges.size() ? ranges.get(lineI) : EMPTY_RANGE; + } - /** - * Get the {@link Range} of {@link Input.Tok}s lying in any part on a line. - * - * @param lineI the line number - * @return the {@link Range} of {@link Input.Tok}s on the specified line - */ - public final Range getRanges(int lineI) { - return 0 <= lineI && lineI < ranges.size() ? ranges.get(lineI) : EMPTY_RANGE; - } - - @Override - public String toString() { - return "InputOutput{" + "lines=" + lines + ", ranges=" + ranges + '}'; - } + @Override + public String toString() { + return "InputOutput{" + "lines=" + lines + ", ranges=" + ranges + '}'; + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/Newlines.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/Newlines.java index e95adbcaf..9b87be209 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/Newlines.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/Newlines.java @@ -24,177 +24,177 @@ import java.util.NoSuchElementException; /** Platform-independent newline handling. */ public class Newlines { - /** Returns the number of line breaks in the input. */ - public static int count(String input) { - return Iterators.size(lineOffsetIterator(input)) - 1; + /** Returns the number of line breaks in the input. */ + public static int count(String input) { + return Iterators.size(lineOffsetIterator(input)) - 1; + } + + /** Returns the index of the first break in the input, or {@code -1}. */ + public static int firstBreak(String input) { + Iterator it = lineOffsetIterator(input); + it.next(); + return it.hasNext() ? it.next() : -1; + } + + private static final ImmutableSet BREAKS = ImmutableSet.of("\r\n", "\n", "\r"); + + /** Returns true if the entire input string is a recognized line break. */ + public static boolean isNewline(String input) { + return BREAKS.contains(input); + } + + /** Returns the length of the newline sequence at the current offset, or {@code -1}. */ + public static int hasNewlineAt(String input, int idx) { + for (String b : BREAKS) { + if (input.startsWith(b, idx)) { + return b.length(); + } + } + return -1; + } + + /** + * Returns the terminating line break in the input, or {@code null} if the input does not end in a + * break. + */ + public static String getLineEnding(String input) { + for (String b : BREAKS) { + if (input.endsWith(b)) { + return b; + } + } + return null; + } + + /** + * Returns the first line separator in the text, or {@code "\n"} if the text does not contain a + * single line separator. + */ + public static String guessLineSeparator(String text) { + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + switch (c) { + case '\r': + if (i + 1 < text.length() && text.charAt(i + 1) == '\n') { + return "\r\n"; + } + return "\r"; + case '\n': + return "\n"; + default: + break; + } + } + return "\n"; + } + + /** Returns true if the input contains any line breaks. */ + public static boolean containsBreaks(String text) { + return CharMatcher.anyOf("\n\r").matchesAnyOf(text); + } + + /** Returns an iterator over the start offsets of lines in the input. */ + public static Iterator lineOffsetIterator(String input) { + return new LineOffsetIterator(input); + } + + /** Returns an iterator over lines in the input, including trailing whitespace. */ + public static Iterator lineIterator(String input) { + return new LineIterator(input); + } + + private static class LineOffsetIterator implements Iterator { + + private int curr = 0; + private int idx = 0; + private final String input; + + private LineOffsetIterator(String input) { + this.input = input; } - /** Returns the index of the first break in the input, or {@code -1}. */ - public static int firstBreak(String input) { - Iterator it = lineOffsetIterator(input); - it.next(); - return it.hasNext() ? it.next() : -1; + @Override + public boolean hasNext() { + return curr != -1; } - private static final ImmutableSet BREAKS = ImmutableSet.of("\r\n", "\n", "\r"); - - /** Returns true if the entire input string is a recognized line break. */ - public static boolean isNewline(String input) { - return BREAKS.contains(input); + @Override + public Integer next() { + if (curr == -1) { + throw new NoSuchElementException(); + } + int result = curr; + advance(); + return result; } - /** Returns the length of the newline sequence at the current offset, or {@code -1}. */ - public static int hasNewlineAt(String input, int idx) { - for (String b : BREAKS) { - if (input.startsWith(b, idx)) { - return b.length(); + private void advance() { + for (; idx < input.length(); idx++) { + char c = input.charAt(idx); + switch (c) { + case '\r': + if (idx + 1 < input.length() && input.charAt(idx + 1) == '\n') { + idx++; } + // falls through + case '\n': + idx++; + curr = idx; + return; + default: + break; } - return -1; + } + curr = -1; } - /** - * Returns the terminating line break in the input, or {@code null} if the input does not end in - * a break. - */ - public static String getLineEnding(String input) { - for (String b : BREAKS) { - if (input.endsWith(b)) { - return b; - } - } - return null; + @Override + public void remove() { + throw new UnsupportedOperationException("remove"); + } + } + + private static class LineIterator implements Iterator { + + int idx; + String curr; + + private final String input; + private final Iterator indices; + + private LineIterator(String input) { + this.input = input; + this.indices = lineOffsetIterator(input); + idx = indices.next(); // read leading 0 } - /** - * Returns the first line separator in the text, or {@code "\n"} if the text does not contain a - * single line separator. - */ - public static String guessLineSeparator(String text) { - for (int i = 0; i < text.length(); i++) { - char c = text.charAt(i); - switch (c) { - case '\r': - if (i + 1 < text.length() && text.charAt(i + 1) == '\n') { - return "\r\n"; - } - return "\r"; - case '\n': - return "\n"; - default: - break; - } - } - return "\n"; + private void advance() { + int last = idx; + if (indices.hasNext()) { + idx = indices.next(); + } else if (hasNext()) { + // no terminal line break + idx = input.length(); + } else { + throw new NoSuchElementException(); + } + curr = input.substring(last, idx); } - /** Returns true if the input contains any line breaks. */ - public static boolean containsBreaks(String text) { - return CharMatcher.anyOf("\n\r").matchesAnyOf(text); + @Override + public boolean hasNext() { + return idx < input.length(); } - /** Returns an iterator over the start offsets of lines in the input. */ - public static Iterator lineOffsetIterator(String input) { - return new LineOffsetIterator(input); + @Override + public String next() { + advance(); + return curr; } - /** Returns an iterator over lines in the input, including trailing whitespace. */ - public static Iterator lineIterator(String input) { - return new LineIterator(input); - } - - private static class LineOffsetIterator implements Iterator { - - private int curr = 0; - private int idx = 0; - private final String input; - - private LineOffsetIterator(String input) { - this.input = input; - } - - @Override - public boolean hasNext() { - return curr != -1; - } - - @Override - public Integer next() { - if (curr == -1) { - throw new NoSuchElementException(); - } - int result = curr; - advance(); - return result; - } - - private void advance() { - for (; idx < input.length(); idx++) { - char c = input.charAt(idx); - switch (c) { - case '\r': - if (idx + 1 < input.length() && input.charAt(idx + 1) == '\n') { - idx++; - } - // falls through - case '\n': - idx++; - curr = idx; - return; - default: - break; - } - } - curr = -1; - } - - @Override - public void remove() { - throw new UnsupportedOperationException("remove"); - } - } - - private static class LineIterator implements Iterator { - - int idx; - String curr; - - private final String input; - private final Iterator indices; - - private LineIterator(String input) { - this.input = input; - this.indices = lineOffsetIterator(input); - idx = indices.next(); // read leading 0 - } - - private void advance() { - int last = idx; - if (indices.hasNext()) { - idx = indices.next(); - } else if (hasNext()) { - // no terminal line break - idx = input.length(); - } else { - throw new NoSuchElementException(); - } - curr = input.substring(last, idx); - } - - @Override - public boolean hasNext() { - return idx < input.length(); - } - - @Override - public String next() { - advance(); - return curr; - } - - @Override - public void remove() { - throw new UnsupportedOperationException("remove"); - } + @Override + public void remove() { + throw new UnsupportedOperationException("remove"); } + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/Op.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/Op.java index 1b3c036be..a48454179 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/Op.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/Op.java @@ -21,10 +21,10 @@ package com.google.googlejavaformat; * OpenOp}-{@link CloseOp} {@code Op}s. */ public interface Op { - /** - * Add an {@code Op} to a {@link DocBuilder}. - * - * @param builder the {@link DocBuilder} - */ - void add(DocBuilder builder); + /** + * Add an {@code Op} to a {@link DocBuilder}. + * + * @param builder the {@link DocBuilder} + */ + void add(DocBuilder builder); } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/OpenOp.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/OpenOp.java index 77b5cd743..649820131 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/OpenOp.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/OpenOp.java @@ -23,29 +23,29 @@ import com.google.common.base.MoreObjects; * Doc.Level}s. */ public final class OpenOp implements Op { - private final Indent plusIndent; + private final Indent plusIndent; - private OpenOp(Indent plusIndent) { - this.plusIndent = plusIndent; - } + private OpenOp(Indent plusIndent) { + this.plusIndent = plusIndent; + } - /** - * Make an ordinary {@code OpenOp}. - * - * @param plusIndent the indent for breaks at this level - * @return the {@code OpenOp} - */ - public static Op make(Indent plusIndent) { - return new OpenOp(plusIndent); - } + /** + * Make an ordinary {@code OpenOp}. + * + * @param plusIndent the indent for breaks at this level + * @return the {@code OpenOp} + */ + public static Op make(Indent plusIndent) { + return new OpenOp(plusIndent); + } - @Override - public void add(DocBuilder builder) { - builder.open(plusIndent); - } + @Override + public void add(DocBuilder builder) { + builder.open(plusIndent); + } - @Override - public String toString() { - return MoreObjects.toStringHelper(this).add("plusIndent", plusIndent).toString(); - } + @Override + public String toString() { + return MoreObjects.toStringHelper(this).add("plusIndent", plusIndent).toString(); + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/OpsBuilder.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/OpsBuilder.java index c3e85fec0..91ee3f072 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/OpsBuilder.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/OpsBuilder.java @@ -39,635 +39,626 @@ import java.util.Optional; */ public final class OpsBuilder { - /** - * @return the actual size of the AST node at position, including comments. - */ - public int actualSize(int position, int length) { - Token startToken = input.getPositionTokenMap().get(position); - int start = startToken.getTok().getPosition(); - for (Tok tok : startToken.getToksBefore()) { - if (tok.isComment()) { - start = min(start, tok.getPosition()); - } - } - Token endToken = input.getPositionTokenMap().get(position + length - 1); - int end = endToken.getTok().getPosition() + endToken.getTok().length(); - for (Tok tok : endToken.getToksAfter()) { - if (tok.isComment()) { - end = max(end, tok.getPosition() + tok.length()); - } - } - return end - start; + /** + * @return the actual size of the AST node at position, including comments. + */ + public int actualSize(int position, int length) { + Token startToken = input.getPositionTokenMap().get(position); + int start = startToken.getTok().getPosition(); + for (Tok tok : startToken.getToksBefore()) { + if (tok.isComment()) { + start = min(start, tok.getPosition()); + } } + Token endToken = input.getPositionTokenMap().get(position + length - 1); + int end = endToken.getTok().getPosition() + endToken.getTok().length(); + for (Tok tok : endToken.getToksAfter()) { + if (tok.isComment()) { + end = max(end, tok.getPosition() + tok.length()); + } + } + return end - start; + } - /** - * @return the start column of the token at {@code position}, including leading comments. - */ - public Integer actualStartColumn(int position) { - Token startToken = input.getPositionTokenMap().get(position); - int start = startToken.getTok().getPosition(); - int line0 = input.getLineNumber(start); - for (Tok tok : startToken.getToksBefore()) { - if (line0 != input.getLineNumber(tok.getPosition())) { - return start; - } - if (tok.isComment()) { - start = min(start, tok.getPosition()); - } - } + /** + * @return the start column of the token at {@code position}, including leading comments. + */ + public Integer actualStartColumn(int position) { + Token startToken = input.getPositionTokenMap().get(position); + int start = startToken.getTok().getPosition(); + int line0 = input.getLineNumber(start); + for (Tok tok : startToken.getToksBefore()) { + if (line0 != input.getLineNumber(tok.getPosition())) { return start; + } + if (tok.isComment()) { + start = min(start, tok.getPosition()); + } } + return start; + } - /** A request to add or remove a blank line in the output. */ - public abstract static class BlankLineWanted { + /** A request to add or remove a blank line in the output. */ + public abstract static class BlankLineWanted { - /** Always emit a blank line. */ - public static final BlankLineWanted YES = new SimpleBlankLine(Optional.of(true)); + /** Always emit a blank line. */ + public static final BlankLineWanted YES = new SimpleBlankLine(Optional.of(true)); - /** Never emit a blank line. */ - public static final BlankLineWanted NO = new SimpleBlankLine(Optional.of(false)); - - /** - * Explicitly preserve blank lines from the input (e.g. before the first member in a class - * declaration). Overrides conditional blank lines. - */ - public static final BlankLineWanted PRESERVE = - new SimpleBlankLine(/* wanted= */ Optional.empty()); - - /** Is the blank line wanted? */ - public abstract Optional wanted(); - - /** Merge this blank line request with another. */ - public abstract BlankLineWanted merge(BlankLineWanted wanted); - - /** Emit a blank line if the given break is taken. */ - public static BlankLineWanted conditional(BreakTag breakTag) { - return new ConditionalBlankLine(ImmutableList.of(breakTag)); - } - - private static final class SimpleBlankLine extends BlankLineWanted { - private final Optional wanted; - - SimpleBlankLine(Optional wanted) { - this.wanted = wanted; - } - - @Override - public Optional wanted() { - return wanted; - } - - @Override - public BlankLineWanted merge(BlankLineWanted other) { - return this; - } - } - - private static final class ConditionalBlankLine extends BlankLineWanted { - - private final ImmutableList tags; - - ConditionalBlankLine(Iterable tags) { - this.tags = ImmutableList.copyOf(tags); - } - - @Override - public Optional wanted() { - for (BreakTag tag : tags) { - if (tag.wasBreakTaken()) { - return Optional.of(true); - } - } - return Optional.empty(); - } - - @Override - public BlankLineWanted merge(BlankLineWanted other) { - if (!(other instanceof ConditionalBlankLine)) { - return other; - } - return new ConditionalBlankLine( - Iterables.concat(this.tags, ((ConditionalBlankLine) other).tags)); - } - } - } - - private final Input input; - private final List ops = new ArrayList<>(); - private final Output output; - private static final Indent.Const ZERO = Indent.Const.ZERO; - - private int tokenI = 0; - private int inputPosition = Integer.MIN_VALUE; - - /** The number of unclosed open ops in the input stream. */ - int depth = 0; - - /** Add an {@link Op}, and record open/close ops for later validation of unclosed levels. */ - private void add(Op op) { - if (op instanceof OpenOp) { - depth++; - } else if (op instanceof CloseOp) { - depth--; - if (depth < 0) { - throw new AssertionError(); - } - } - ops.add(op); - } - - /** Add a list of {@link Op}s. */ - public final void addAll(List ops) { - for (Op op : ops) { - add(op); - } - } + /** Never emit a blank line. */ + public static final BlankLineWanted NO = new SimpleBlankLine(Optional.of(false)); /** - * The {@code OpsBuilder} constructor. - * - * @param input the {@link Input}, used for retrieve information from the AST - * @param output the {@link Output}, used here only to record blank-line information + * Explicitly preserve blank lines from the input (e.g. before the first member in a class + * declaration). Overrides conditional blank lines. */ - public OpsBuilder(Input input, Output output) { - this.input = input; - this.output = output; + public static final BlankLineWanted PRESERVE = + new SimpleBlankLine(/* wanted= */ Optional.empty()); + + /** Is the blank line wanted? */ + public abstract Optional wanted(); + + /** Merge this blank line request with another. */ + public abstract BlankLineWanted merge(BlankLineWanted wanted); + + /** Emit a blank line if the given break is taken. */ + public static BlankLineWanted conditional(BreakTag breakTag) { + return new ConditionalBlankLine(ImmutableList.of(breakTag)); } - /** Get the {@code OpsBuilder}'s {@link Input}. */ - public final Input getInput() { - return input; + private static final class SimpleBlankLine extends BlankLineWanted { + private final Optional wanted; + + SimpleBlankLine(Optional wanted) { + this.wanted = wanted; + } + + @Override + public Optional wanted() { + return wanted; + } + + @Override + public BlankLineWanted merge(BlankLineWanted other) { + return this; + } } - /** Returns the number of unclosed open ops in the input stream. */ - public int depth() { - return depth; - } + private static final class ConditionalBlankLine extends BlankLineWanted { - /** - * Checks that all open ops in the op stream have matching close ops. - * - * @throws FormattingError if any ops were unclosed - */ - public void checkClosed(int previous) { - if (depth != previous) { - throw new FormattingError(diagnostic(String.format("saw %d unclosed ops", depth))); + private final ImmutableList tags; + + ConditionalBlankLine(Iterable tags) { + this.tags = ImmutableList.copyOf(tags); + } + + @Override + public Optional wanted() { + for (BreakTag tag : tags) { + if (tag.wasBreakTaken()) { + return Optional.of(true); + } } - } + return Optional.empty(); + } - /** Create a {@link FormatterDiagnostic} at the current position. */ - public FormatterDiagnostic diagnostic(String message) { - return input.createDiagnostic(inputPosition, message); - } - - /** - * Sync to position in the input. If we've skipped outputting any tokens that were present in - * the input tokens, output them here and optionally complain. - * - * @param inputPosition the {@code 0}-based input position - */ - public final void sync(int inputPosition) { - if (inputPosition > this.inputPosition) { - ImmutableList tokens = input.getTokens(); - int tokensN = tokens.size(); - this.inputPosition = inputPosition; - if (tokenI < tokensN && inputPosition > tokens.get(tokenI).getTok().getPosition()) { - // Found a missing input token. Insert it and mark it missing (usually not good). - Input.Token token = tokens.get(tokenI++); - throw new FormattingError( - diagnostic( - String.format( - "did not generate token \"%s\"", - token.getTok().getText()))); - } + @Override + public BlankLineWanted merge(BlankLineWanted other) { + if (!(other instanceof ConditionalBlankLine)) { + return other; } + return new ConditionalBlankLine( + Iterables.concat(this.tags, ((ConditionalBlankLine) other).tags)); + } } + } - /** Output any remaining tokens from the input stream (e.g. terminal whitespace). */ - public final void drain() { - int inputPosition = input.getText().length() + 1; - if (inputPosition > this.inputPosition) { - ImmutableList tokens = input.getTokens(); - int tokensN = tokens.size(); - while (tokenI < tokensN && inputPosition > tokens.get(tokenI).getTok().getPosition()) { - Input.Token token = tokens.get(tokenI++); - add( - Doc.Token.make( - token, - Doc.Token.RealOrImaginary.IMAGINARY, - ZERO, - /* breakAndIndentTrailingComment= */ Optional.empty())); - } - } - this.inputPosition = inputPosition; - checkClosed(0); + private final Input input; + private final List ops = new ArrayList<>(); + private final Output output; + private static final Indent.Const ZERO = Indent.Const.ZERO; + + private int tokenI = 0; + private int inputPosition = Integer.MIN_VALUE; + + /** The number of unclosed open ops in the input stream. */ + int depth = 0; + + /** Add an {@link Op}, and record open/close ops for later validation of unclosed levels. */ + private void add(Op op) { + if (op instanceof OpenOp) { + depth++; + } else if (op instanceof CloseOp) { + depth--; + if (depth < 0) { + throw new AssertionError(); + } } + ops.add(op); + } - /** - * Open a new level by emitting an {@link OpenOp}. - * - * @param plusIndent the extra indent for the new level - */ - public final void open(Indent plusIndent) { - add(OpenOp.make(plusIndent)); + /** Add a list of {@link Op}s. */ + public final void addAll(List ops) { + for (Op op : ops) { + add(op); } + } - /** Close the current level, by emitting a {@link CloseOp}. */ - public final void close() { - add(CloseOp.make()); + /** + * The {@code OpsBuilder} constructor. + * + * @param input the {@link Input}, used for retrieve information from the AST + * @param output the {@link Output}, used here only to record blank-line information + */ + public OpsBuilder(Input input, Output output) { + this.input = input; + this.output = output; + } + + /** Get the {@code OpsBuilder}'s {@link Input}. */ + public final Input getInput() { + return input; + } + + /** Returns the number of unclosed open ops in the input stream. */ + public int depth() { + return depth; + } + + /** + * Checks that all open ops in the op stream have matching close ops. + * + * @throws FormattingError if any ops were unclosed + */ + public void checkClosed(int previous) { + if (depth != previous) { + throw new FormattingError(diagnostic(String.format("saw %d unclosed ops", depth))); } + } - /** Return the text of the next {@link Input.Token}, or absent if there is none. */ - public final Optional peekToken() { - return peekToken(0); + /** Create a {@link FormatterDiagnostic} at the current position. */ + public FormatterDiagnostic diagnostic(String message) { + return input.createDiagnostic(inputPosition, message); + } + + /** + * Sync to position in the input. If we've skipped outputting any tokens that were present in the + * input tokens, output them here and optionally complain. + * + * @param inputPosition the {@code 0}-based input position + */ + public final void sync(int inputPosition) { + if (inputPosition > this.inputPosition) { + ImmutableList tokens = input.getTokens(); + int tokensN = tokens.size(); + this.inputPosition = inputPosition; + if (tokenI < tokensN && inputPosition > tokens.get(tokenI).getTok().getPosition()) { + // Found a missing input token. Insert it and mark it missing (usually not good). + Input.Token token = tokens.get(tokenI++); + throw new FormattingError( + diagnostic(String.format("did not generate token \"%s\"", token.getTok().getText()))); + } } + } - /** Return the text of an upcoming {@link Input.Token}, or absent if there is none. */ - public final Optional peekToken(int skip) { - ImmutableList tokens = input.getTokens(); - int idx = tokenI + skip; - return idx < tokens.size() - ? Optional.of(tokens.get(idx).getTok().getOriginalText()) - : Optional.empty(); - } - - /** - * Returns the {@link Input.Tok}s starting at the current source position, which are satisfied - * by the given predicate. - */ - public ImmutableList peekTokens(int startPosition, Predicate predicate) { - ImmutableList tokens = input.getTokens(); - Preconditions.checkState( - tokens.get(tokenI).getTok().getPosition() == startPosition, - "Expected the current token to be at position %s, found: %s", - startPosition, - tokens.get(tokenI)); - ImmutableList.Builder result = ImmutableList.builder(); - for (int idx = tokenI; idx < tokens.size(); idx++) { - Tok tok = tokens.get(idx).getTok(); - if (!predicate.apply(tok)) { - break; - } - result.add(tok); - } - return result.build(); - } - - /** - * Emit an optional token iff it exists on the input. This is used to emit tokens whose - * existence has been lost in the AST. - * - * @param token the optional token - */ - public final void guessToken(String token) { - token( + /** Output any remaining tokens from the input stream (e.g. terminal whitespace). */ + public final void drain() { + int inputPosition = input.getText().length() + 1; + if (inputPosition > this.inputPosition) { + ImmutableList tokens = input.getTokens(); + int tokensN = tokens.size(); + while (tokenI < tokensN && inputPosition > tokens.get(tokenI).getTok().getPosition()) { + Input.Token token = tokens.get(tokenI++); + add( + Doc.Token.make( token, Doc.Token.RealOrImaginary.IMAGINARY, ZERO, - /* breakAndIndentTrailingComment= */ Optional.empty()); + /* breakAndIndentTrailingComment= */ Optional.empty())); + } } + this.inputPosition = inputPosition; + checkClosed(0); + } - public final void token( - String token, - Doc.Token.RealOrImaginary realOrImaginary, - Indent plusIndentCommentsBefore, - Optional breakAndIndentTrailingComment) { - ImmutableList tokens = input.getTokens(); - if (token.equals(peekToken().orElse(null))) { // Found the input token. Output it. - add( - Doc.Token.make( - tokens.get(tokenI++), - Doc.Token.RealOrImaginary.REAL, - plusIndentCommentsBefore, - breakAndIndentTrailingComment)); - } else { - /* - * Generated a "bad" token, which doesn't exist on the input. Drop it, and complain unless - * (for example) we're guessing at an optional token. - */ - if (realOrImaginary.isReal()) { - throw new FormattingError( - diagnostic( - String.format( - "expected token: '%s'; generated %s instead", - peekToken().orElse(null), token))); - } - } + /** + * Open a new level by emitting an {@link OpenOp}. + * + * @param plusIndent the extra indent for the new level + */ + public final void open(Indent plusIndent) { + add(OpenOp.make(plusIndent)); + } + + /** Close the current level, by emitting a {@link CloseOp}. */ + public final void close() { + add(CloseOp.make()); + } + + /** Return the text of the next {@link Input.Token}, or absent if there is none. */ + public final Optional peekToken() { + return peekToken(0); + } + + /** Return the text of an upcoming {@link Input.Token}, or absent if there is none. */ + public final Optional peekToken(int skip) { + ImmutableList tokens = input.getTokens(); + int idx = tokenI + skip; + return idx < tokens.size() + ? Optional.of(tokens.get(idx).getTok().getOriginalText()) + : Optional.empty(); + } + + /** + * Returns the {@link Input.Tok}s starting at the current source position, which are satisfied by + * the given predicate. + */ + public ImmutableList peekTokens(int startPosition, Predicate predicate) { + ImmutableList tokens = input.getTokens(); + Preconditions.checkState( + tokens.get(tokenI).getTok().getPosition() == startPosition, + "Expected the current token to be at position %s, found: %s", + startPosition, + tokens.get(tokenI)); + ImmutableList.Builder result = ImmutableList.builder(); + for (int idx = tokenI; idx < tokens.size(); idx++) { + Tok tok = tokens.get(idx).getTok(); + if (!predicate.apply(tok)) { + break; + } + result.add(tok); } + return result.build(); + } - /** - * Emit a single- or multi-character op by breaking it into single-character {@link Doc.Token}s. - * - * @param op the operator to emit - */ - public final void op(String op) { - int opN = op.length(); - for (int i = 0; i < opN; i++) { - token( - op.substring(i, i + 1), - Doc.Token.RealOrImaginary.REAL, - ZERO, - /* breakAndIndentTrailingComment= */ Optional.empty()); - } + /** + * Emit an optional token iff it exists on the input. This is used to emit tokens whose existence + * has been lost in the AST. + * + * @param token the optional token + */ + public final void guessToken(String token) { + token( + token, + Doc.Token.RealOrImaginary.IMAGINARY, + ZERO, + /* breakAndIndentTrailingComment= */ Optional.empty()); + } + + public final void token( + String token, + Doc.Token.RealOrImaginary realOrImaginary, + Indent plusIndentCommentsBefore, + Optional breakAndIndentTrailingComment) { + ImmutableList tokens = input.getTokens(); + if (token.equals(peekToken().orElse(null))) { // Found the input token. Output it. + add( + Doc.Token.make( + tokens.get(tokenI++), + Doc.Token.RealOrImaginary.REAL, + plusIndentCommentsBefore, + breakAndIndentTrailingComment)); + } else { + /* + * Generated a "bad" token, which doesn't exist on the input. Drop it, and complain unless + * (for example) we're guessing at an optional token. + */ + if (realOrImaginary.isReal()) { + throw new FormattingError( + diagnostic( + String.format( + "expected token: '%s'; generated %s instead", + peekToken().orElse(null), token))); + } } + } - /** Emit a {@link Doc.Space}. */ - public final void space() { - add(Doc.Space.make()); + /** + * Emit a single- or multi-character op by breaking it into single-character {@link Doc.Token}s. + * + * @param op the operator to emit + */ + public final void op(String op) { + int opN = op.length(); + for (int i = 0; i < opN; i++) { + token( + op.substring(i, i + 1), + Doc.Token.RealOrImaginary.REAL, + ZERO, + /* breakAndIndentTrailingComment= */ Optional.empty()); } + } - /** Emit a {@link Doc.Break}. */ - public final void breakOp() { - breakOp(Doc.FillMode.UNIFIED, "", ZERO); + /** Emit a {@link Doc.Space}. */ + public final void space() { + add(Doc.Space.make()); + } + + /** Emit a {@link Doc.Break}. */ + public final void breakOp() { + breakOp(Doc.FillMode.UNIFIED, "", ZERO); + } + + /** + * Emit a {@link Doc.Break}. + * + * @param plusIndent extra indent if taken + */ + public final void breakOp(Indent plusIndent) { + breakOp(Doc.FillMode.UNIFIED, "", plusIndent); + } + + /** Emit a filled {@link Doc.Break}. */ + public final void breakToFill() { + breakOp(Doc.FillMode.INDEPENDENT, "", ZERO); + } + + /** Emit a forced {@link Doc.Break}. */ + public final void forcedBreak() { + breakOp(Doc.FillMode.FORCED, "", ZERO); + } + + /** + * Emit a forced {@link Doc.Break}. + * + * @param plusIndent extra indent if taken + */ + public final void forcedBreak(Indent plusIndent) { + breakOp(Doc.FillMode.FORCED, "", plusIndent); + } + + /** + * Emit a {@link Doc.Break}, with a specified {@code flat} value (e.g., {@code " "}). + * + * @param flat the {@link Doc.Break} when not broken + */ + public final void breakOp(String flat) { + breakOp(Doc.FillMode.UNIFIED, flat, ZERO); + } + + /** + * Emit a {@link Doc.Break}, with a specified {@code flat} value (e.g., {@code " "}). + * + * @param flat the {@link Doc.Break} when not broken + */ + public final void breakToFill(String flat) { + breakOp(Doc.FillMode.INDEPENDENT, flat, ZERO); + } + + /** + * Emit a generic {@link Doc.Break}. + * + * @param fillMode the {@link Doc.FillMode} + * @param flat the {@link Doc.Break} when not broken + * @param plusIndent extra indent if taken + */ + public final void breakOp(Doc.FillMode fillMode, String flat, Indent plusIndent) { + breakOp(fillMode, flat, plusIndent, /* optionalTag= */ Optional.empty()); + } + + /** + * Emit a generic {@link Doc.Break}. + * + * @param fillMode the {@link Doc.FillMode} + * @param flat the {@link Doc.Break} when not broken + * @param plusIndent extra indent if taken + * @param optionalTag an optional tag for remembering whether the break was taken + */ + public final void breakOp( + Doc.FillMode fillMode, String flat, Indent plusIndent, Optional optionalTag) { + add(Doc.Break.make(fillMode, flat, plusIndent, optionalTag)); + } + + private int lastPartialFormatBoundary = -1; + + /** + * Make the boundary of a region that can be partially formatted. The boundary will be included in + * the following region, e.g.: [[boundary0, boundary1), [boundary1, boundary2), ...]. + */ + public void markForPartialFormat() { + if (lastPartialFormatBoundary == -1) { + lastPartialFormatBoundary = tokenI; + return; } - - /** - * Emit a {@link Doc.Break}. - * - * @param plusIndent extra indent if taken - */ - public final void breakOp(Indent plusIndent) { - breakOp(Doc.FillMode.UNIFIED, "", plusIndent); + if (tokenI == lastPartialFormatBoundary) { + return; } + Token start = input.getTokens().get(lastPartialFormatBoundary); + Token end = input.getTokens().get(tokenI - 1); + output.markForPartialFormat(start, end); + lastPartialFormatBoundary = tokenI; + } - /** Emit a filled {@link Doc.Break}. */ - public final void breakToFill() { - breakOp(Doc.FillMode.INDEPENDENT, "", ZERO); + /** + * Force or suppress a blank line here in the output. + * + * @param wanted whether to force ({@code true}) or suppress {@code false}) the blank line + */ + public final void blankLineWanted(BlankLineWanted wanted) { + output.blankLine(getI(input.getTokens().get(tokenI)), wanted); + } + + private static int getI(Input.Token token) { + for (Input.Tok tok : token.getToksBefore()) { + if (tok.getIndex() >= 0) { + return tok.getIndex(); + } } + return token.getTok().getIndex(); + } - /** Emit a forced {@link Doc.Break}. */ - public final void forcedBreak() { - breakOp(Doc.FillMode.FORCED, "", ZERO); - } + private static final Doc.Space SPACE = Doc.Space.make(); - /** - * Emit a forced {@link Doc.Break}. - * - * @param plusIndent extra indent if taken - */ - public final void forcedBreak(Indent plusIndent) { - breakOp(Doc.FillMode.FORCED, "", plusIndent); - } - - /** - * Emit a {@link Doc.Break}, with a specified {@code flat} value (e.g., {@code " "}). - * - * @param flat the {@link Doc.Break} when not broken - */ - public final void breakOp(String flat) { - breakOp(Doc.FillMode.UNIFIED, flat, ZERO); - } - - /** - * Emit a {@link Doc.Break}, with a specified {@code flat} value (e.g., {@code " "}). - * - * @param flat the {@link Doc.Break} when not broken - */ - public final void breakToFill(String flat) { - breakOp(Doc.FillMode.INDEPENDENT, flat, ZERO); - } - - /** - * Emit a generic {@link Doc.Break}. - * - * @param fillMode the {@link Doc.FillMode} - * @param flat the {@link Doc.Break} when not broken - * @param plusIndent extra indent if taken - */ - public final void breakOp(Doc.FillMode fillMode, String flat, Indent plusIndent) { - breakOp(fillMode, flat, plusIndent, /* optionalTag= */ Optional.empty()); - } - - /** - * Emit a generic {@link Doc.Break}. - * - * @param fillMode the {@link Doc.FillMode} - * @param flat the {@link Doc.Break} when not broken - * @param plusIndent extra indent if taken - * @param optionalTag an optional tag for remembering whether the break was taken - */ - public final void breakOp( - Doc.FillMode fillMode, String flat, Indent plusIndent, Optional optionalTag) { - add(Doc.Break.make(fillMode, flat, plusIndent, optionalTag)); - } - - private int lastPartialFormatBoundary = -1; - - /** - * Make the boundary of a region that can be partially formatted. The boundary will be included - * in the following region, e.g.: [[boundary0, boundary1), [boundary1, boundary2), ...]. - */ - public void markForPartialFormat() { - if (lastPartialFormatBoundary == -1) { - lastPartialFormatBoundary = tokenI; - return; - } - if (tokenI == lastPartialFormatBoundary) { - return; - } - Token start = input.getTokens().get(lastPartialFormatBoundary); - Token end = input.getTokens().get(tokenI - 1); - output.markForPartialFormat(start, end); - lastPartialFormatBoundary = tokenI; - } - - /** - * Force or suppress a blank line here in the output. - * - * @param wanted whether to force ({@code true}) or suppress {@code false}) the blank line - */ - public final void blankLineWanted(BlankLineWanted wanted) { - output.blankLine(getI(input.getTokens().get(tokenI)), wanted); - } - - private static int getI(Input.Token token) { - for (Input.Tok tok : token.getToksBefore()) { - if (tok.getIndex() >= 0) { - return tok.getIndex(); - } - } - return token.getTok().getIndex(); - } - - private static final Doc.Space SPACE = Doc.Space.make(); - - /** - * Build a list of {@link Op}s from the {@code OpsBuilder}. - * - * @return the list of {@link Op}s - */ - public final ImmutableList build() { - markForPartialFormat(); - // Rewrite the ops to insert comments. - Multimap tokOps = ArrayListMultimap.create(); - int opsN = ops.size(); - for (int i = 0; i < opsN; i++) { - Op op = ops.get(i); - if (op instanceof Doc.Token) { - /* - * Token ops can have associated non-tokens, including comments, which we need to insert. - * They can also cause line breaks, so we insert them before or after the current level, - * when possible. - */ - Doc.Token tokenOp = (Doc.Token) op; - Input.Token token = tokenOp.getToken(); - int j = i; // Where to insert toksBefore before. - while (0 < j && ops.get(j - 1) instanceof OpenOp) { - --j; - } - int k = i; // Where to insert toksAfter after. - while (k + 1 < opsN && ops.get(k + 1) instanceof CloseOp) { - ++k; - } - if (tokenOp.realOrImaginary().isReal()) { - /* - * Regular input token. Copy out toksBefore before token, and toksAfter after it. Insert - * this token's toksBefore at position j. - */ - int newlines = 0; // Count of newlines in a row. - boolean space = - false; // Do we need an extra space after a previous "/*" comment? - boolean lastWasComment = false; // Was the last thing we output a comment? - boolean allowBlankAfterLastComment = false; - for (Input.Tok tokBefore : token.getToksBefore()) { - if (tokBefore.isNewline()) { - newlines++; - } else if (tokBefore.isComment()) { - tokOps.put( - j, - Doc.Break.make( - tokBefore.isSlashSlashComment() - ? Doc.FillMode.FORCED - : Doc.FillMode.UNIFIED, - "", - tokenOp.getPlusIndentCommentsBefore())); - tokOps.putAll(j, makeComment(tokBefore)); - space = tokBefore.isSlashStarComment(); - newlines = 0; - lastWasComment = true; - if (tokBefore.isJavadocComment()) { - tokOps.put(j, Doc.Break.makeForced()); - } - allowBlankAfterLastComment = - tokBefore.isSlashSlashComment() - || (tokBefore.isSlashStarComment() - && !tokBefore.isJavadocComment()); - } - } - if (allowBlankAfterLastComment && newlines > 1) { - // Force a line break after two newlines in a row following a line or block - // comment - output.blankLine(token.getTok().getIndex(), BlankLineWanted.YES); - } - if (lastWasComment && newlines > 0) { - tokOps.put(j, Doc.Break.makeForced()); - } else if (space) { - tokOps.put(j, SPACE); - } - // Now we've seen the Token; output the toksAfter. - for (Input.Tok tokAfter : token.getToksAfter()) { - if (tokAfter.isComment()) { - boolean breakAfter = - tokAfter.isJavadocComment() - || (tokAfter.isSlashStarComment() - && tokenOp.breakAndIndentTrailingComment() - .isPresent()); - if (breakAfter) { - tokOps.put( - k + 1, - Doc.Break.make( - Doc.FillMode.FORCED, - "", - tokenOp.breakAndIndentTrailingComment() - .orElse(Const.ZERO))); - } else { - tokOps.put(k + 1, SPACE); - } - tokOps.putAll(k + 1, makeComment(tokAfter)); - if (breakAfter) { - tokOps.put(k + 1, Doc.Break.make(Doc.FillMode.FORCED, "", ZERO)); - } - } - } - } else { - /* - * This input token was mistakenly not generated for output. As no whitespace or comments - * were generated (presumably), copy all input non-tokens literally, even spaces and - * newlines. - */ - int newlines = 0; - boolean lastWasComment = false; - for (Input.Tok tokBefore : token.getToksBefore()) { - if (tokBefore.isNewline()) { - newlines++; - } else if (tokBefore.isComment()) { - newlines = 0; - lastWasComment = tokBefore.isComment(); - } - if (lastWasComment && newlines > 0) { - tokOps.put(j, Doc.Break.makeForced()); - } - tokOps.put(j, Doc.Tok.make(tokBefore)); - } - for (Input.Tok tokAfter : token.getToksAfter()) { - tokOps.put(k + 1, Doc.Tok.make(tokAfter)); - } - } - } - } + /** + * Build a list of {@link Op}s from the {@code OpsBuilder}. + * + * @return the list of {@link Op}s + */ + public final ImmutableList build() { + markForPartialFormat(); + // Rewrite the ops to insert comments. + Multimap tokOps = ArrayListMultimap.create(); + int opsN = ops.size(); + for (int i = 0; i < opsN; i++) { + Op op = ops.get(i); + if (op instanceof Doc.Token) { /* - * Construct new list of ops, splicing in the comments. If a comment is inserted immediately - * before a space, suppress the space. + * Token ops can have associated non-tokens, including comments, which we need to insert. + * They can also cause line breaks, so we insert them before or after the current level, + * when possible. */ - ImmutableList.Builder newOps = ImmutableList.builder(); - boolean afterForcedBreak = false; // Was the last Op a forced break? If so, suppress spaces. - for (int i = 0; i < opsN; i++) { - for (Op op : tokOps.get(i)) { - if (!(afterForcedBreak && op instanceof Doc.Space)) { - newOps.add(op); - afterForcedBreak = isForcedBreak(op); - } - } - Op op = ops.get(i); - if (afterForcedBreak - && (op instanceof Doc.Space - || (op instanceof Doc.Break - && ((Doc.Break) op).getPlusIndent() == 0 - && " ".equals(((Doc) op).getFlat())))) { - continue; - } - newOps.add(op); - if (!(op instanceof OpenOp)) { - afterForcedBreak = isForcedBreak(op); - } + Doc.Token tokenOp = (Doc.Token) op; + Input.Token token = tokenOp.getToken(); + int j = i; // Where to insert toksBefore before. + while (0 < j && ops.get(j - 1) instanceof OpenOp) { + --j; } - for (Op op : tokOps.get(opsN)) { - if (!(afterForcedBreak && op instanceof Doc.Space)) { - newOps.add(op); - afterForcedBreak = isForcedBreak(op); - } + int k = i; // Where to insert toksAfter after. + while (k + 1 < opsN && ops.get(k + 1) instanceof CloseOp) { + ++k; } - return newOps.build(); + if (tokenOp.realOrImaginary().isReal()) { + /* + * Regular input token. Copy out toksBefore before token, and toksAfter after it. Insert + * this token's toksBefore at position j. + */ + int newlines = 0; // Count of newlines in a row. + boolean space = false; // Do we need an extra space after a previous "/*" comment? + boolean lastWasComment = false; // Was the last thing we output a comment? + boolean allowBlankAfterLastComment = false; + for (Input.Tok tokBefore : token.getToksBefore()) { + if (tokBefore.isNewline()) { + newlines++; + } else if (tokBefore.isComment()) { + tokOps.put( + j, + Doc.Break.make( + tokBefore.isSlashSlashComment() ? Doc.FillMode.FORCED : Doc.FillMode.UNIFIED, + "", + tokenOp.getPlusIndentCommentsBefore())); + tokOps.putAll(j, makeComment(tokBefore)); + space = tokBefore.isSlashStarComment(); + newlines = 0; + lastWasComment = true; + if (tokBefore.isJavadocComment()) { + tokOps.put(j, Doc.Break.makeForced()); + } + allowBlankAfterLastComment = + tokBefore.isSlashSlashComment() + || (tokBefore.isSlashStarComment() && !tokBefore.isJavadocComment()); + } + } + if (allowBlankAfterLastComment && newlines > 1) { + // Force a line break after two newlines in a row following a line or block + // comment + output.blankLine(token.getTok().getIndex(), BlankLineWanted.YES); + } + if (lastWasComment && newlines > 0) { + tokOps.put(j, Doc.Break.makeForced()); + } else if (space) { + tokOps.put(j, SPACE); + } + // Now we've seen the Token; output the toksAfter. + for (Input.Tok tokAfter : token.getToksAfter()) { + if (tokAfter.isComment()) { + boolean breakAfter = + tokAfter.isJavadocComment() + || (tokAfter.isSlashStarComment() + && tokenOp.breakAndIndentTrailingComment().isPresent()); + if (breakAfter) { + tokOps.put( + k + 1, + Doc.Break.make( + Doc.FillMode.FORCED, + "", + tokenOp.breakAndIndentTrailingComment().orElse(Const.ZERO))); + } else { + tokOps.put(k + 1, SPACE); + } + tokOps.putAll(k + 1, makeComment(tokAfter)); + if (breakAfter) { + tokOps.put(k + 1, Doc.Break.make(Doc.FillMode.FORCED, "", ZERO)); + } + } + } + } else { + /* + * This input token was mistakenly not generated for output. As no whitespace or comments + * were generated (presumably), copy all input non-tokens literally, even spaces and + * newlines. + */ + int newlines = 0; + boolean lastWasComment = false; + for (Input.Tok tokBefore : token.getToksBefore()) { + if (tokBefore.isNewline()) { + newlines++; + } else if (tokBefore.isComment()) { + newlines = 0; + lastWasComment = tokBefore.isComment(); + } + if (lastWasComment && newlines > 0) { + tokOps.put(j, Doc.Break.makeForced()); + } + tokOps.put(j, Doc.Tok.make(tokBefore)); + } + for (Input.Tok tokAfter : token.getToksAfter()) { + tokOps.put(k + 1, Doc.Tok.make(tokAfter)); + } + } + } } + /* + * Construct new list of ops, splicing in the comments. If a comment is inserted immediately + * before a space, suppress the space. + */ + ImmutableList.Builder newOps = ImmutableList.builder(); + boolean afterForcedBreak = false; // Was the last Op a forced break? If so, suppress spaces. + for (int i = 0; i < opsN; i++) { + for (Op op : tokOps.get(i)) { + if (!(afterForcedBreak && op instanceof Doc.Space)) { + newOps.add(op); + afterForcedBreak = isForcedBreak(op); + } + } + Op op = ops.get(i); + if (afterForcedBreak + && (op instanceof Doc.Space + || (op instanceof Doc.Break + && ((Doc.Break) op).getPlusIndent() == 0 + && " ".equals(((Doc) op).getFlat())))) { + continue; + } + newOps.add(op); + if (!(op instanceof OpenOp)) { + afterForcedBreak = isForcedBreak(op); + } + } + for (Op op : tokOps.get(opsN)) { + if (!(afterForcedBreak && op instanceof Doc.Space)) { + newOps.add(op); + afterForcedBreak = isForcedBreak(op); + } + } + return newOps.build(); + } - private static boolean isForcedBreak(Op op) { - return op instanceof Doc.Break && ((Doc.Break) op).isForced(); - } + private static boolean isForcedBreak(Op op) { + return op instanceof Doc.Break && ((Doc.Break) op).isForced(); + } - private static List makeComment(Input.Tok comment) { - return comment.isSlashStarComment() - ? ImmutableList.of(Doc.Tok.make(comment)) - : ImmutableList.of(Doc.Tok.make(comment), Doc.Break.makeForced()); - } + private static List makeComment(Input.Tok comment) { + return comment.isSlashStarComment() + ? ImmutableList.of(Doc.Tok.make(comment)) + : ImmutableList.of(Doc.Tok.make(comment), Doc.Break.makeForced()); + } - @Override - public final String toString() { - return MoreObjects.toStringHelper(this) - .add("input", input) - .add("ops", ops) - .add("output", output) - .add("tokenI", tokenI) - .add("inputPosition", inputPosition) - .toString(); - } + @Override + public final String toString() { + return MoreObjects.toStringHelper(this) + .add("input", input) + .add("ops", ops) + .add("output", output) + .add("tokenI", tokenI) + .add("inputPosition", inputPosition) + .toString(); + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/Output.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/Output.java index 3dcdd2d6e..b0c0af7f0 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/Output.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/Output.java @@ -22,60 +22,60 @@ import java.util.Optional; /** An output from the formatter. */ public abstract class Output extends InputOutput { - /** Unique identifier for a break. */ - public static final class BreakTag { + /** Unique identifier for a break. */ + public static final class BreakTag { - Optional taken = Optional.empty(); + Optional taken = Optional.empty(); - public void recordBroken(boolean broken) { - // TODO(cushon): enforce invariants. - // Currently we rely on setting Breaks multiple times, e.g. when deciding - // whether a Level should be flowed. Using separate data structures - // instead of mutation or adding an explicit 'reset' step would allow - // a useful invariant to be enforced here. - taken = Optional.of(broken); - } - - public boolean wasBreakTaken() { - return taken.orElse(false); - } + public void recordBroken(boolean broken) { + // TODO(cushon): enforce invariants. + // Currently we rely on setting Breaks multiple times, e.g. when deciding + // whether a Level should be flowed. Using separate data structures + // instead of mutation or adding an explicit 'reset' step would allow + // a useful invariant to be enforced here. + taken = Optional.of(broken); } - /** - * Indent by outputting {@code indent} spaces. - * - * @param indent the current indent - */ - public abstract void indent(int indent); - - /** - * Output a string. - * - * @param text the string - * @param range the {@link Range} corresponding to the string - */ - public abstract void append(String text, Range range); - - /** - * A blank line is or is not wanted here. - * - * @param k the {@link Input.Tok} index - * @param wanted whether a blank line is wanted here - */ - public abstract void blankLine(int k, BlankLineWanted wanted); - - /** Marks a region that can be partially formatted. */ - public abstract void markForPartialFormat(Input.Token start, Input.Token end); - - /** - * Get the {@link CommentsHelper}. - * - * @return the {@link CommentsHelper} - */ - public abstract CommentsHelper getCommentsHelper(); - - @Override - public String toString() { - return MoreObjects.toStringHelper(this).add("super", super.toString()).toString(); + public boolean wasBreakTaken() { + return taken.orElse(false); } + } + + /** + * Indent by outputting {@code indent} spaces. + * + * @param indent the current indent + */ + public abstract void indent(int indent); + + /** + * Output a string. + * + * @param text the string + * @param range the {@link Range} corresponding to the string + */ + public abstract void append(String text, Range range); + + /** + * A blank line is or is not wanted here. + * + * @param k the {@link Input.Tok} index + * @param wanted whether a blank line is wanted here + */ + public abstract void blankLine(int k, BlankLineWanted wanted); + + /** Marks a region that can be partially formatted. */ + public abstract void markForPartialFormat(Input.Token start, Input.Token end); + + /** + * Get the {@link CommentsHelper}. + * + * @return the {@link CommentsHelper} + */ + public abstract CommentsHelper getCommentsHelper(); + + @Override + public String toString() { + return MoreObjects.toStringHelper(this).add("super", super.toString()).toString(); + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/AutoOneOf_JavaInputAstVisitor_AnnotationOrModifier.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/AutoOneOf_JavaInputAstVisitor_AnnotationOrModifier.java index 573aa9db5..03142ab8a 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/AutoOneOf_JavaInputAstVisitor_AnnotationOrModifier.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/AutoOneOf_JavaInputAstVisitor_AnnotationOrModifier.java @@ -7,114 +7,113 @@ import javax.annotation.processing.Generated; @Generated("com.google.auto.value.processor.AutoOneOfProcessor") final class AutoOneOf_JavaInputAstVisitor_AnnotationOrModifier { - private - AutoOneOf_JavaInputAstVisitor_AnnotationOrModifier() {} // There are no instances of this type. + private + AutoOneOf_JavaInputAstVisitor_AnnotationOrModifier() {} // There are no instances of this type. - static JavaInputAstVisitor.AnnotationOrModifier annotation(AnnotationTree annotation) { - if (annotation == null) { - throw new NullPointerException(); - } - return new Impl_annotation(annotation); + static JavaInputAstVisitor.AnnotationOrModifier annotation(AnnotationTree annotation) { + if (annotation == null) { + throw new NullPointerException(); + } + return new Impl_annotation(annotation); + } + + static JavaInputAstVisitor.AnnotationOrModifier modifier(Input.Tok modifier) { + if (modifier == null) { + throw new NullPointerException(); + } + return new Impl_modifier(modifier); + } + + // Parent class that each implementation will inherit from. + private abstract static class Parent_ extends JavaInputAstVisitor.AnnotationOrModifier { + @Override + AnnotationTree annotation() { + throw new UnsupportedOperationException(getKind().toString()); } - static JavaInputAstVisitor.AnnotationOrModifier modifier(Input.Tok modifier) { - if (modifier == null) { - throw new NullPointerException(); - } - return new Impl_modifier(modifier); + @Override + Input.Tok modifier() { + throw new UnsupportedOperationException(getKind().toString()); + } + } + + // Implementation when the contained property is "annotation". + private static final class Impl_annotation extends Parent_ { + private final AnnotationTree annotation; + + Impl_annotation(AnnotationTree annotation) { + this.annotation = annotation; } - // Parent class that each implementation will inherit from. - private abstract static class Parent_ extends JavaInputAstVisitor.AnnotationOrModifier { - @Override - AnnotationTree annotation() { - throw new UnsupportedOperationException(getKind().toString()); - } - - @Override - Input.Tok modifier() { - throw new UnsupportedOperationException(getKind().toString()); - } + @Override + public AnnotationTree annotation() { + return annotation; } - // Implementation when the contained property is "annotation". - private static final class Impl_annotation extends Parent_ { - private final AnnotationTree annotation; - - Impl_annotation(AnnotationTree annotation) { - this.annotation = annotation; - } - - @Override - public AnnotationTree annotation() { - return annotation; - } - - @Override - public String toString() { - return "AnnotationOrModifier{annotation=" + this.annotation + "}"; - } - - @Override - public boolean equals(Object x) { - if (x instanceof JavaInputAstVisitor.AnnotationOrModifier) { - JavaInputAstVisitor.AnnotationOrModifier that = - (JavaInputAstVisitor.AnnotationOrModifier) x; - return this.getKind() == that.getKind() - && this.annotation.equals(that.annotation()); - } else { - return false; - } - } - - @Override - public int hashCode() { - return annotation.hashCode(); - } - - @Override - public JavaInputAstVisitor.AnnotationOrModifier.Kind getKind() { - return JavaInputAstVisitor.AnnotationOrModifier.Kind.ANNOTATION; - } + @Override + public String toString() { + return "AnnotationOrModifier{annotation=" + this.annotation + "}"; } - // Implementation when the contained property is "modifier". - private static final class Impl_modifier extends Parent_ { - private final Input.Tok modifier; - - Impl_modifier(Input.Tok modifier) { - this.modifier = modifier; - } - - @Override - public Input.Tok modifier() { - return modifier; - } - - @Override - public String toString() { - return "AnnotationOrModifier{modifier=" + this.modifier + "}"; - } - - @Override - public boolean equals(Object x) { - if (x instanceof JavaInputAstVisitor.AnnotationOrModifier) { - JavaInputAstVisitor.AnnotationOrModifier that = - (JavaInputAstVisitor.AnnotationOrModifier) x; - return this.getKind() == that.getKind() && this.modifier.equals(that.modifier()); - } else { - return false; - } - } - - @Override - public int hashCode() { - return modifier.hashCode(); - } - - @Override - public JavaInputAstVisitor.AnnotationOrModifier.Kind getKind() { - return JavaInputAstVisitor.AnnotationOrModifier.Kind.MODIFIER; - } + @Override + public boolean equals(Object x) { + if (x instanceof JavaInputAstVisitor.AnnotationOrModifier) { + JavaInputAstVisitor.AnnotationOrModifier that = + (JavaInputAstVisitor.AnnotationOrModifier) x; + return this.getKind() == that.getKind() && this.annotation.equals(that.annotation()); + } else { + return false; + } } + + @Override + public int hashCode() { + return annotation.hashCode(); + } + + @Override + public JavaInputAstVisitor.AnnotationOrModifier.Kind getKind() { + return JavaInputAstVisitor.AnnotationOrModifier.Kind.ANNOTATION; + } + } + + // Implementation when the contained property is "modifier". + private static final class Impl_modifier extends Parent_ { + private final Input.Tok modifier; + + Impl_modifier(Input.Tok modifier) { + this.modifier = modifier; + } + + @Override + public Input.Tok modifier() { + return modifier; + } + + @Override + public String toString() { + return "AnnotationOrModifier{modifier=" + this.modifier + "}"; + } + + @Override + public boolean equals(Object x) { + if (x instanceof JavaInputAstVisitor.AnnotationOrModifier) { + JavaInputAstVisitor.AnnotationOrModifier that = + (JavaInputAstVisitor.AnnotationOrModifier) x; + return this.getKind() == that.getKind() && this.modifier.equals(that.modifier()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return modifier.hashCode(); + } + + @Override + public JavaInputAstVisitor.AnnotationOrModifier.Kind getKind() { + return JavaInputAstVisitor.AnnotationOrModifier.Kind.MODIFIER; + } + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/AutoValue_JavaInputAstVisitor_DeclarationModifiersAndTypeAnnotations.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/AutoValue_JavaInputAstVisitor_DeclarationModifiersAndTypeAnnotations.java index 612d98ad9..efda29e9b 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/AutoValue_JavaInputAstVisitor_DeclarationModifiersAndTypeAnnotations.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/AutoValue_JavaInputAstVisitor_DeclarationModifiersAndTypeAnnotations.java @@ -7,67 +7,67 @@ import javax.annotation.processing.Generated; @Generated("com.google.auto.value.processor.AutoValueProcessor") final class AutoValue_JavaInputAstVisitor_DeclarationModifiersAndTypeAnnotations - extends JavaInputAstVisitor.DeclarationModifiersAndTypeAnnotations { + extends JavaInputAstVisitor.DeclarationModifiersAndTypeAnnotations { - private final ImmutableList declarationModifiers; + private final ImmutableList declarationModifiers; - private final ImmutableList typeAnnotations; + private final ImmutableList typeAnnotations; - AutoValue_JavaInputAstVisitor_DeclarationModifiersAndTypeAnnotations( - ImmutableList declarationModifiers, - ImmutableList typeAnnotations) { - if (declarationModifiers == null) { - throw new NullPointerException("Null declarationModifiers"); - } - this.declarationModifiers = declarationModifiers; - if (typeAnnotations == null) { - throw new NullPointerException("Null typeAnnotations"); - } - this.typeAnnotations = typeAnnotations; + AutoValue_JavaInputAstVisitor_DeclarationModifiersAndTypeAnnotations( + ImmutableList declarationModifiers, + ImmutableList typeAnnotations) { + if (declarationModifiers == null) { + throw new NullPointerException("Null declarationModifiers"); } - - @Override - ImmutableList declarationModifiers() { - return declarationModifiers; + this.declarationModifiers = declarationModifiers; + if (typeAnnotations == null) { + throw new NullPointerException("Null typeAnnotations"); } + this.typeAnnotations = typeAnnotations; + } - @Override - ImmutableList typeAnnotations() { - return typeAnnotations; - } + @Override + ImmutableList declarationModifiers() { + return declarationModifiers; + } - @Override - public String toString() { - return "DeclarationModifiersAndTypeAnnotations{" - + "declarationModifiers=" - + declarationModifiers - + ", " - + "typeAnnotations=" - + typeAnnotations - + "}"; - } + @Override + ImmutableList typeAnnotations() { + return typeAnnotations; + } - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } - if (o instanceof JavaInputAstVisitor.DeclarationModifiersAndTypeAnnotations) { - JavaInputAstVisitor.DeclarationModifiersAndTypeAnnotations that = - (JavaInputAstVisitor.DeclarationModifiersAndTypeAnnotations) o; - return this.declarationModifiers.equals(that.declarationModifiers()) - && this.typeAnnotations.equals(that.typeAnnotations()); - } - return false; - } + @Override + public String toString() { + return "DeclarationModifiersAndTypeAnnotations{" + + "declarationModifiers=" + + declarationModifiers + + ", " + + "typeAnnotations=" + + typeAnnotations + + "}"; + } - @Override - public int hashCode() { - int h$ = 1; - h$ *= 1000003; - h$ ^= declarationModifiers.hashCode(); - h$ *= 1000003; - h$ ^= typeAnnotations.hashCode(); - return h$; + @Override + public boolean equals(Object o) { + if (o == this) { + return true; } + if (o instanceof JavaInputAstVisitor.DeclarationModifiersAndTypeAnnotations) { + JavaInputAstVisitor.DeclarationModifiersAndTypeAnnotations that = + (JavaInputAstVisitor.DeclarationModifiersAndTypeAnnotations) o; + return this.declarationModifiers.equals(that.declarationModifiers()) + && this.typeAnnotations.equals(that.typeAnnotations()); + } + return false; + } + + @Override + public int hashCode() { + int h$ = 1; + h$ *= 1000003; + h$ ^= declarationModifiers.hashCode(); + h$ *= 1000003; + h$ ^= typeAnnotations.hashCode(); + return h$; + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/CommandLineOptions.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/CommandLineOptions.java index 3cb9101bc..fdefb011e 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/CommandLineOptions.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/CommandLineOptions.java @@ -27,277 +27,277 @@ import java.util.Optional; */ final class CommandLineOptions { - private final ImmutableList files; - private final boolean inPlace; - private final ImmutableRangeSet lines; - private final ImmutableList offsets; - private final ImmutableList lengths; - private final boolean aosp; - private final boolean version; - private final boolean help; - private final boolean stdin; - private final boolean fixImportsOnly; - private final boolean sortImports; - private final boolean removeUnusedImports; - private final boolean dryRun; - private final boolean setExitIfChanged; - private final Optional assumeFilename; - private final boolean reflowLongStrings; - private final boolean formatJavadoc; + private final ImmutableList files; + private final boolean inPlace; + private final ImmutableRangeSet lines; + private final ImmutableList offsets; + private final ImmutableList lengths; + private final boolean aosp; + private final boolean version; + private final boolean help; + private final boolean stdin; + private final boolean fixImportsOnly; + private final boolean sortImports; + private final boolean removeUnusedImports; + private final boolean dryRun; + private final boolean setExitIfChanged; + private final Optional assumeFilename; + private final boolean reflowLongStrings; + private final boolean formatJavadoc; - CommandLineOptions( - ImmutableList files, - boolean inPlace, - ImmutableRangeSet lines, - ImmutableList offsets, - ImmutableList lengths, - boolean aosp, - boolean version, - boolean help, - boolean stdin, - boolean fixImportsOnly, - boolean sortImports, - boolean removeUnusedImports, - boolean dryRun, - boolean setExitIfChanged, - Optional assumeFilename, - boolean reflowLongStrings, - boolean formatJavadoc) { - this.files = files; - this.inPlace = inPlace; - this.lines = lines; - this.offsets = offsets; - this.lengths = lengths; - this.aosp = aosp; - this.version = version; - this.help = help; - this.stdin = stdin; - this.fixImportsOnly = fixImportsOnly; - this.sortImports = sortImports; - this.removeUnusedImports = removeUnusedImports; - this.dryRun = dryRun; - this.setExitIfChanged = setExitIfChanged; - this.assumeFilename = assumeFilename; - this.reflowLongStrings = reflowLongStrings; - this.formatJavadoc = formatJavadoc; + CommandLineOptions( + ImmutableList files, + boolean inPlace, + ImmutableRangeSet lines, + ImmutableList offsets, + ImmutableList lengths, + boolean aosp, + boolean version, + boolean help, + boolean stdin, + boolean fixImportsOnly, + boolean sortImports, + boolean removeUnusedImports, + boolean dryRun, + boolean setExitIfChanged, + Optional assumeFilename, + boolean reflowLongStrings, + boolean formatJavadoc) { + this.files = files; + this.inPlace = inPlace; + this.lines = lines; + this.offsets = offsets; + this.lengths = lengths; + this.aosp = aosp; + this.version = version; + this.help = help; + this.stdin = stdin; + this.fixImportsOnly = fixImportsOnly; + this.sortImports = sortImports; + this.removeUnusedImports = removeUnusedImports; + this.dryRun = dryRun; + this.setExitIfChanged = setExitIfChanged; + this.assumeFilename = assumeFilename; + this.reflowLongStrings = reflowLongStrings; + this.formatJavadoc = formatJavadoc; + } + + /** The files to format. */ + ImmutableList files() { + return files; + } + + /** Format files in place. */ + boolean inPlace() { + return inPlace; + } + + /** Line ranges to format. */ + ImmutableRangeSet lines() { + return lines; + } + + /** Character offsets for partial formatting, paired with {@code lengths}. */ + ImmutableList offsets() { + return offsets; + } + + /** Partial formatting region lengths, paired with {@code offsets}. */ + ImmutableList lengths() { + return lengths; + } + + /** Use AOSP style instead of Google Style (4-space indentation). */ + boolean aosp() { + return aosp; + } + + /** Print the version. */ + boolean version() { + return version; + } + + /** Print usage information. */ + boolean help() { + return help; + } + + /** Format input from stdin. */ + boolean stdin() { + return stdin; + } + + /** Fix imports, but do no formatting. */ + boolean fixImportsOnly() { + return fixImportsOnly; + } + + /** Sort imports. */ + boolean sortImports() { + return sortImports; + } + + /** Remove unused imports. */ + boolean removeUnusedImports() { + return removeUnusedImports; + } + + /** + * Print the paths of the files whose contents would change if the formatter were run normally. + */ + boolean dryRun() { + return dryRun; + } + + /** Return exit code 1 if there are any formatting changes. */ + boolean setExitIfChanged() { + return setExitIfChanged; + } + + /** Return the name to use for diagnostics when formatting standard input. */ + Optional assumeFilename() { + return assumeFilename; + } + + boolean reflowLongStrings() { + return reflowLongStrings; + } + + /** Returns true if partial formatting was selected. */ + boolean isSelection() { + return !lines().isEmpty() || !offsets().isEmpty() || !lengths().isEmpty(); + } + + boolean formatJavadoc() { + return formatJavadoc; + } + + static Builder builder() { + return new Builder(); + } + + static class Builder { + + private final ImmutableList.Builder files = ImmutableList.builder(); + private final ImmutableRangeSet.Builder lines = ImmutableRangeSet.builder(); + private final ImmutableList.Builder offsets = ImmutableList.builder(); + private final ImmutableList.Builder lengths = ImmutableList.builder(); + private boolean inPlace = false; + private boolean aosp = false; + private boolean version = false; + private boolean help = false; + private boolean stdin = false; + private boolean fixImportsOnly = false; + private boolean sortImports = true; + private boolean removeUnusedImports = true; + private boolean dryRun = false; + private boolean setExitIfChanged = false; + private Optional assumeFilename = Optional.empty(); + private boolean reflowLongStrings = true; + private boolean formatJavadoc = true; + + ImmutableList.Builder filesBuilder() { + return files; } - /** The files to format. */ - ImmutableList files() { - return files; + Builder inPlace(boolean inPlace) { + this.inPlace = inPlace; + return this; } - /** Format files in place. */ - boolean inPlace() { - return inPlace; + ImmutableRangeSet.Builder linesBuilder() { + return lines; } - /** Line ranges to format. */ - ImmutableRangeSet lines() { - return lines; + Builder addOffset(Integer offset) { + offsets.add(offset); + return this; } - /** Character offsets for partial formatting, paired with {@code lengths}. */ - ImmutableList offsets() { - return offsets; + Builder addLength(Integer length) { + lengths.add(length); + return this; } - /** Partial formatting region lengths, paired with {@code offsets}. */ - ImmutableList lengths() { - return lengths; + Builder aosp(boolean aosp) { + this.aosp = aosp; + return this; } - /** Use AOSP style instead of Google Style (4-space indentation). */ - boolean aosp() { - return aosp; + Builder version(boolean version) { + this.version = version; + return this; } - /** Print the version. */ - boolean version() { - return version; + Builder help(boolean help) { + this.help = help; + return this; } - /** Print usage information. */ - boolean help() { - return help; + Builder stdin(boolean stdin) { + this.stdin = stdin; + return this; } - /** Format input from stdin. */ - boolean stdin() { - return stdin; + Builder fixImportsOnly(boolean fixImportsOnly) { + this.fixImportsOnly = fixImportsOnly; + return this; } - /** Fix imports, but do no formatting. */ - boolean fixImportsOnly() { - return fixImportsOnly; + Builder sortImports(boolean sortImports) { + this.sortImports = sortImports; + return this; } - /** Sort imports. */ - boolean sortImports() { - return sortImports; + Builder removeUnusedImports(boolean removeUnusedImports) { + this.removeUnusedImports = removeUnusedImports; + return this; } - /** Remove unused imports. */ - boolean removeUnusedImports() { - return removeUnusedImports; + Builder dryRun(boolean dryRun) { + this.dryRun = dryRun; + return this; } - /** - * Print the paths of the files whose contents would change if the formatter were run normally. - */ - boolean dryRun() { - return dryRun; + Builder setExitIfChanged(boolean setExitIfChanged) { + this.setExitIfChanged = setExitIfChanged; + return this; } - /** Return exit code 1 if there are any formatting changes. */ - boolean setExitIfChanged() { - return setExitIfChanged; + Builder assumeFilename(String assumeFilename) { + this.assumeFilename = Optional.of(assumeFilename); + return this; } - /** Return the name to use for diagnostics when formatting standard input. */ - Optional assumeFilename() { - return assumeFilename; + Builder reflowLongStrings(boolean reflowLongStrings) { + this.reflowLongStrings = reflowLongStrings; + return this; } - boolean reflowLongStrings() { - return reflowLongStrings; + Builder formatJavadoc(boolean formatJavadoc) { + this.formatJavadoc = formatJavadoc; + return this; } - /** Returns true if partial formatting was selected. */ - boolean isSelection() { - return !lines().isEmpty() || !offsets().isEmpty() || !lengths().isEmpty(); - } - - boolean formatJavadoc() { - return formatJavadoc; - } - - static Builder builder() { - return new Builder(); - } - - static class Builder { - - private final ImmutableList.Builder files = ImmutableList.builder(); - private final ImmutableRangeSet.Builder lines = ImmutableRangeSet.builder(); - private final ImmutableList.Builder offsets = ImmutableList.builder(); - private final ImmutableList.Builder lengths = ImmutableList.builder(); - private boolean inPlace = false; - private boolean aosp = false; - private boolean version = false; - private boolean help = false; - private boolean stdin = false; - private boolean fixImportsOnly = false; - private boolean sortImports = true; - private boolean removeUnusedImports = true; - private boolean dryRun = false; - private boolean setExitIfChanged = false; - private Optional assumeFilename = Optional.empty(); - private boolean reflowLongStrings = true; - private boolean formatJavadoc = true; - - ImmutableList.Builder filesBuilder() { - return files; - } - - Builder inPlace(boolean inPlace) { - this.inPlace = inPlace; - return this; - } - - ImmutableRangeSet.Builder linesBuilder() { - return lines; - } - - Builder addOffset(Integer offset) { - offsets.add(offset); - return this; - } - - Builder addLength(Integer length) { - lengths.add(length); - return this; - } - - Builder aosp(boolean aosp) { - this.aosp = aosp; - return this; - } - - Builder version(boolean version) { - this.version = version; - return this; - } - - Builder help(boolean help) { - this.help = help; - return this; - } - - Builder stdin(boolean stdin) { - this.stdin = stdin; - return this; - } - - Builder fixImportsOnly(boolean fixImportsOnly) { - this.fixImportsOnly = fixImportsOnly; - return this; - } - - Builder sortImports(boolean sortImports) { - this.sortImports = sortImports; - return this; - } - - Builder removeUnusedImports(boolean removeUnusedImports) { - this.removeUnusedImports = removeUnusedImports; - return this; - } - - Builder dryRun(boolean dryRun) { - this.dryRun = dryRun; - return this; - } - - Builder setExitIfChanged(boolean setExitIfChanged) { - this.setExitIfChanged = setExitIfChanged; - return this; - } - - Builder assumeFilename(String assumeFilename) { - this.assumeFilename = Optional.of(assumeFilename); - return this; - } - - Builder reflowLongStrings(boolean reflowLongStrings) { - this.reflowLongStrings = reflowLongStrings; - return this; - } - - Builder formatJavadoc(boolean formatJavadoc) { - this.formatJavadoc = formatJavadoc; - return this; - } - - CommandLineOptions build() { - return new CommandLineOptions( - files.build(), - inPlace, - lines.build(), - offsets.build(), - lengths.build(), - aosp, - version, - help, - stdin, - fixImportsOnly, - sortImports, - removeUnusedImports, - dryRun, - setExitIfChanged, - assumeFilename, - reflowLongStrings, - formatJavadoc); - } + CommandLineOptions build() { + return new CommandLineOptions( + files.build(), + inPlace, + lines.build(), + offsets.build(), + lengths.build(), + aosp, + version, + help, + stdin, + fixImportsOnly, + sortImports, + removeUnusedImports, + dryRun, + setExitIfChanged, + assumeFilename, + reflowLongStrings, + formatJavadoc); } + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/CommandLineOptionsParser.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/CommandLineOptionsParser.java index 25c9a62a6..27ed8e06e 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/CommandLineOptionsParser.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/CommandLineOptionsParser.java @@ -33,179 +33,178 @@ import java.util.List; /** A parser for {@link CommandLineOptions}. */ final class CommandLineOptionsParser { - private static final Splitter COMMA_SPLITTER = Splitter.on(','); - private static final Splitter COLON_SPLITTER = Splitter.on(':'); - private static final Splitter ARG_SPLITTER = - Splitter.on(CharMatcher.breakingWhitespace()).omitEmptyStrings().trimResults(); + private static final Splitter COMMA_SPLITTER = Splitter.on(','); + private static final Splitter COLON_SPLITTER = Splitter.on(':'); + private static final Splitter ARG_SPLITTER = + Splitter.on(CharMatcher.breakingWhitespace()).omitEmptyStrings().trimResults(); - /** Parses {@link CommandLineOptions}. */ - static CommandLineOptions parse(Iterable options) { - CommandLineOptions.Builder optionsBuilder = CommandLineOptions.builder(); - List expandedOptions = new ArrayList<>(); - expandParamsFiles(options, expandedOptions); - Iterator it = expandedOptions.iterator(); - while (it.hasNext()) { - String option = it.next(); - if (!option.startsWith("-")) { - optionsBuilder.filesBuilder().add(option).addAll(it); - break; - } - String flag; - String value; - int idx = option.indexOf('='); - if (idx >= 0) { - flag = option.substring(0, idx); - value = option.substring(idx + 1); - } else { - flag = option; - value = null; - } - // NOTE: update usage information in UsageException when new flags are added - switch (flag) { - case "-i": - case "-r": - case "-replace": - case "--replace": - optionsBuilder.inPlace(true); - break; - case "--lines": - case "-lines": - case "--line": - case "-line": - parseRangeSet(optionsBuilder.linesBuilder(), getValue(flag, it, value)); - break; - case "--offset": - case "-offset": - optionsBuilder.addOffset(parseInteger(it, flag, value)); - break; - case "--length": - case "-length": - optionsBuilder.addLength(parseInteger(it, flag, value)); - break; - case "--aosp": - case "-aosp": - case "-a": - optionsBuilder.aosp(true); - break; - case "--version": - case "-version": - case "-v": - optionsBuilder.version(true); - break; - case "--help": - case "-help": - case "-h": - optionsBuilder.help(true); - break; - case "--fix-imports-only": - optionsBuilder.fixImportsOnly(true); - break; - case "--skip-sorting-imports": - optionsBuilder.sortImports(false); - break; - case "--skip-removing-unused-imports": - optionsBuilder.removeUnusedImports(false); - break; - case "--skip-reflowing-long-strings": - optionsBuilder.reflowLongStrings(false); - break; - case "--skip-javadoc-formatting": - optionsBuilder.formatJavadoc(false); - break; - case "-": - optionsBuilder.stdin(true); - break; - case "-n": - case "--dry-run": - optionsBuilder.dryRun(true); - break; - case "--set-exit-if-changed": - optionsBuilder.setExitIfChanged(true); - break; - case "-assume-filename": - case "--assume-filename": - optionsBuilder.assumeFilename(getValue(flag, it, value)); - break; - default: - throw new IllegalArgumentException("unexpected flag: " + flag); - } - } - return optionsBuilder.build(); + /** Parses {@link CommandLineOptions}. */ + static CommandLineOptions parse(Iterable options) { + CommandLineOptions.Builder optionsBuilder = CommandLineOptions.builder(); + List expandedOptions = new ArrayList<>(); + expandParamsFiles(options, expandedOptions); + Iterator it = expandedOptions.iterator(); + while (it.hasNext()) { + String option = it.next(); + if (!option.startsWith("-")) { + optionsBuilder.filesBuilder().add(option).addAll(it); + break; + } + String flag; + String value; + int idx = option.indexOf('='); + if (idx >= 0) { + flag = option.substring(0, idx); + value = option.substring(idx + 1); + } else { + flag = option; + value = null; + } + // NOTE: update usage information in UsageException when new flags are added + switch (flag) { + case "-i": + case "-r": + case "-replace": + case "--replace": + optionsBuilder.inPlace(true); + break; + case "--lines": + case "-lines": + case "--line": + case "-line": + parseRangeSet(optionsBuilder.linesBuilder(), getValue(flag, it, value)); + break; + case "--offset": + case "-offset": + optionsBuilder.addOffset(parseInteger(it, flag, value)); + break; + case "--length": + case "-length": + optionsBuilder.addLength(parseInteger(it, flag, value)); + break; + case "--aosp": + case "-aosp": + case "-a": + optionsBuilder.aosp(true); + break; + case "--version": + case "-version": + case "-v": + optionsBuilder.version(true); + break; + case "--help": + case "-help": + case "-h": + optionsBuilder.help(true); + break; + case "--fix-imports-only": + optionsBuilder.fixImportsOnly(true); + break; + case "--skip-sorting-imports": + optionsBuilder.sortImports(false); + break; + case "--skip-removing-unused-imports": + optionsBuilder.removeUnusedImports(false); + break; + case "--skip-reflowing-long-strings": + optionsBuilder.reflowLongStrings(false); + break; + case "--skip-javadoc-formatting": + optionsBuilder.formatJavadoc(false); + break; + case "-": + optionsBuilder.stdin(true); + break; + case "-n": + case "--dry-run": + optionsBuilder.dryRun(true); + break; + case "--set-exit-if-changed": + optionsBuilder.setExitIfChanged(true); + break; + case "-assume-filename": + case "--assume-filename": + optionsBuilder.assumeFilename(getValue(flag, it, value)); + break; + default: + throw new IllegalArgumentException("unexpected flag: " + flag); + } } + return optionsBuilder.build(); + } - private static Integer parseInteger(Iterator it, String flag, String value) { + private static Integer parseInteger(Iterator it, String flag, String value) { + try { + return Integer.valueOf(getValue(flag, it, value)); + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + String.format("invalid integer value for %s: %s", flag, value), e); + } + } + + private static String getValue(String flag, Iterator it, String value) { + if (value != null) { + return value; + } + if (!it.hasNext()) { + throw new IllegalArgumentException("required value was not provided for: " + flag); + } + return it.next(); + } + + /** + * Parse multiple --lines flags, like {"1:12,14,20:36", "40:45,50"}. Multiple ranges can be given + * with multiple --lines flags or separated by commas. A single line can be set by a single + * number. Line numbers are {@code 1}-based, but are converted to the {@code 0}-based numbering + * used internally by google-java-format. + */ + private static void parseRangeSet(ImmutableRangeSet.Builder result, String ranges) { + for (String range : COMMA_SPLITTER.split(ranges)) { + result.add(parseRange(range)); + } + } + + /** + * Parse a range, as in "1:12" or "42". Line numbers provided are {@code 1}-based, but are + * converted here to {@code 0}-based. + */ + private static Range parseRange(String arg) { + List args = COLON_SPLITTER.splitToList(arg); + switch (args.size()) { + case 1: + int line = Integer.parseInt(args.get(0)) - 1; + return Range.closedOpen(line, line + 1); + case 2: + int line0 = Integer.parseInt(args.get(0)) - 1; + int line1 = Integer.parseInt(args.get(1)) - 1; + return Range.closedOpen(line0, line1 + 1); + default: + throw new IllegalArgumentException(arg); + } + } + + /** + * Pre-processes an argument list, expanding arguments of the form {@code @filename} by reading + * the content of the file and appending whitespace-delimited options to {@code arguments}. + */ + private static void expandParamsFiles(Iterable args, List expanded) { + for (String arg : args) { + if (arg.isEmpty()) { + continue; + } + if (!arg.startsWith("@")) { + expanded.add(arg); + } else if (arg.startsWith("@@")) { + expanded.add(arg.substring(1)); + } else { + Path path = Paths.get(arg.substring(1)); try { - return Integer.valueOf(getValue(flag, it, value)); - } catch (NumberFormatException e) { - throw new IllegalArgumentException( - String.format("invalid integer value for %s: %s", flag, value), e); - } - } - - private static String getValue(String flag, Iterator it, String value) { - if (value != null) { - return value; - } - if (!it.hasNext()) { - throw new IllegalArgumentException("required value was not provided for: " + flag); - } - return it.next(); - } - - /** - * Parse multiple --lines flags, like {"1:12,14,20:36", "40:45,50"}. Multiple ranges can be - * given with multiple --lines flags or separated by commas. A single line can be set by a - * single number. Line numbers are {@code 1}-based, but are converted to the {@code 0}-based - * numbering used internally by google-java-format. - */ - private static void parseRangeSet(ImmutableRangeSet.Builder result, String ranges) { - for (String range : COMMA_SPLITTER.split(ranges)) { - result.add(parseRange(range)); - } - } - - /** - * Parse a range, as in "1:12" or "42". Line numbers provided are {@code 1}-based, but are - * converted here to {@code 0}-based. - */ - private static Range parseRange(String arg) { - List args = COLON_SPLITTER.splitToList(arg); - switch (args.size()) { - case 1: - int line = Integer.parseInt(args.get(0)) - 1; - return Range.closedOpen(line, line + 1); - case 2: - int line0 = Integer.parseInt(args.get(0)) - 1; - int line1 = Integer.parseInt(args.get(1)) - 1; - return Range.closedOpen(line0, line1 + 1); - default: - throw new IllegalArgumentException(arg); - } - } - - /** - * Pre-processes an argument list, expanding arguments of the form {@code @filename} by reading - * the content of the file and appending whitespace-delimited options to {@code arguments}. - */ - private static void expandParamsFiles(Iterable args, List expanded) { - for (String arg : args) { - if (arg.isEmpty()) { - continue; - } - if (!arg.startsWith("@")) { - expanded.add(arg); - } else if (arg.startsWith("@@")) { - expanded.add(arg.substring(1)); - } else { - Path path = Paths.get(arg.substring(1)); - try { - String sequence = new String(Files.readAllBytes(path), UTF_8); - expandParamsFiles(ARG_SPLITTER.split(sequence), expanded); - } catch (IOException e) { - throw new UncheckedIOException( - path + ": could not read file: " + e.getMessage(), e); - } - } + String sequence = new String(Files.readAllBytes(path), UTF_8); + expandParamsFiles(ARG_SPLITTER.split(sequence), expanded); + } catch (IOException e) { + throw new UncheckedIOException(path + ": could not read file: " + e.getMessage(), e); } + } } + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/DimensionHelpers.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/DimensionHelpers.java index de61f25aa..d3641bd5a 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/DimensionHelpers.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/DimensionHelpers.java @@ -37,89 +37,89 @@ import java.util.List; */ class DimensionHelpers { - /** The array dimension specifiers (including any type annotations) associated with a type. */ - static class TypeWithDims { - final Tree node; - final ImmutableList> dims; + /** The array dimension specifiers (including any type annotations) associated with a type. */ + static class TypeWithDims { + final Tree node; + final ImmutableList> dims; - public TypeWithDims(Tree node, ImmutableList> dims) { - this.node = node; - this.dims = dims; + public TypeWithDims(Tree node, ImmutableList> dims) { + this.node = node; + this.dims = dims; + } + } + + enum SortedDims { + YES, + NO + } + + /** Returns a (possibly re-ordered) {@link TypeWithDims} for the given type. */ + static TypeWithDims extractDims(Tree node, SortedDims sorted) { + Deque> builder = new ArrayDeque<>(); + node = extractDims(builder, node); + Iterable> dims; + if (sorted == SortedDims.YES) { + dims = reorderBySourcePosition(builder); + } else { + dims = builder; + } + return new TypeWithDims(node, ImmutableList.copyOf(dims)); + } + + /** + * Rotate the list of dimension specifiers until all dimensions with type annotations appear in + * source order. + * + *

javac reorders dimension specifiers in method declarations with mixed-array notation, which + * means that any type annotations don't appear in source order. + * + *

For example, the type of {@code int @A [] f() @B [] {}} is parsed as {@code @B [] @A []}. + * + *

This doesn't handle cases with un-annotated dimension specifiers, so the formatting logic + * checks the token stream to figure out which side of the method name they appear on. + */ + private static Iterable> reorderBySourcePosition( + Deque> dims) { + int lastAnnotation = -1; + int lastPos = -1; + int idx = 0; + for (List dim : dims) { + if (!dim.isEmpty()) { + int pos = ((JCTree) dim.get(0)).getStartPosition(); + if (pos < lastPos) { + List> list = new ArrayList<>(dims); + Collections.rotate(list, -(lastAnnotation + 1)); + return list; } + lastPos = pos; + lastAnnotation = idx; + } + idx++; } + return dims; + } - enum SortedDims { - YES, - NO - } - - /** Returns a (possibly re-ordered) {@link TypeWithDims} for the given type. */ - static TypeWithDims extractDims(Tree node, SortedDims sorted) { - Deque> builder = new ArrayDeque<>(); - node = extractDims(builder, node); - Iterable> dims; - if (sorted == SortedDims.YES) { - dims = reorderBySourcePosition(builder); - } else { - dims = builder; - } - return new TypeWithDims(node, ImmutableList.copyOf(dims)); - } - - /** - * Rotate the list of dimension specifiers until all dimensions with type annotations appear in - * source order. - * - *

javac reorders dimension specifiers in method declarations with mixed-array notation, - * which means that any type annotations don't appear in source order. - * - *

For example, the type of {@code int @A [] f() @B [] {}} is parsed as {@code @B [] @A []}. - * - *

This doesn't handle cases with un-annotated dimension specifiers, so the formatting logic - * checks the token stream to figure out which side of the method name they appear on. - */ - private static Iterable> reorderBySourcePosition( - Deque> dims) { - int lastAnnotation = -1; - int lastPos = -1; - int idx = 0; - for (List dim : dims) { - if (!dim.isEmpty()) { - int pos = ((JCTree) dim.get(0)).getStartPosition(); - if (pos < lastPos) { - List> list = new ArrayList<>(dims); - Collections.rotate(list, -(lastAnnotation + 1)); - return list; - } - lastPos = pos; - lastAnnotation = idx; - } - idx++; - } - return dims; - } - - /** - * Accumulates a flattened list of array dimensions specifiers with type annotations, and - * returns the base type. - * - *

Given {@code int @A @B [][] @C []}, adds {@code [[@A, @B], [@C]]} to dims and returns - * {@code int}. - */ - private static Tree extractDims(Deque> dims, Tree node) { - switch (node.getKind()) { - case ARRAY_TYPE: - return extractDims(dims, ((ArrayTypeTree) node).getType()); - case ANNOTATED_TYPE: - AnnotatedTypeTree annotatedTypeTree = (AnnotatedTypeTree) node; - if (annotatedTypeTree.getUnderlyingType().getKind() != Tree.Kind.ARRAY_TYPE) { - return node; - } - node = extractDims(dims, annotatedTypeTree.getUnderlyingType()); - dims.addFirst(ImmutableList.copyOf(annotatedTypeTree.getAnnotations())); - return node; - default: - return node; + /** + * Accumulates a flattened list of array dimensions specifiers with type annotations, and returns + * the base type. + * + *

Given {@code int @A @B [][] @C []}, adds {@code [[@A, @B], [@C]]} to dims and returns {@code + * int}. + */ + private static Tree extractDims(Deque> dims, Tree node) { + switch (node.getKind()) { + case ARRAY_TYPE: + return extractDims(dims, ((ArrayTypeTree) node).getType()); + case ANNOTATED_TYPE: + AnnotatedTypeTree annotatedTypeTree = (AnnotatedTypeTree) node; + if (annotatedTypeTree.getUnderlyingType().getKind() != Tree.Kind.ARRAY_TYPE) { + return node; } + node = extractDims(dims, annotatedTypeTree.getUnderlyingType()); + dims.addFirst(ImmutableList.copyOf(annotatedTypeTree.getAnnotations())); + return node; + default: + return node; } + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/FormatFileCallable.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/FormatFileCallable.java index f7bca19e7..3ad07f69e 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/FormatFileCallable.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/FormatFileCallable.java @@ -25,63 +25,62 @@ import java.util.concurrent.Callable; * format. */ class FormatFileCallable implements Callable { - private final String input; - private final CommandLineOptions parameters; - private final JavaFormatterOptions options; + private final String input; + private final CommandLineOptions parameters; + private final JavaFormatterOptions options; - public FormatFileCallable( - CommandLineOptions parameters, String input, JavaFormatterOptions options) { - this.input = input; - this.parameters = parameters; - this.options = options; + public FormatFileCallable( + CommandLineOptions parameters, String input, JavaFormatterOptions options) { + this.input = input; + this.parameters = parameters; + this.options = options; + } + + @Override + public String call() throws FormatterException { + if (parameters.fixImportsOnly()) { + return fixImports(input); } - @Override - public String call() throws FormatterException { - if (parameters.fixImportsOnly()) { - return fixImports(input); - } + Formatter formatter = new Formatter(options); + String formatted = formatter.formatSource(input, characterRanges(input).asRanges()); + formatted = fixImports(formatted); + if (parameters.reflowLongStrings()) { + formatted = StringWrapper.wrap(Formatter.MAX_LINE_LENGTH, formatted, formatter); + } + return formatted; + } - Formatter formatter = new Formatter(options); - String formatted = formatter.formatSource(input, characterRanges(input).asRanges()); - formatted = fixImports(formatted); - if (parameters.reflowLongStrings()) { - formatted = StringWrapper.wrap(Formatter.MAX_LINE_LENGTH, formatted, formatter); - } - return formatted; + private String fixImports(String input) throws FormatterException { + if (parameters.removeUnusedImports()) { + input = RemoveUnusedImports.removeUnusedImports(input); + } + if (parameters.sortImports()) { + input = ImportOrderer.reorderImports(input, options.style()); + } + return input; + } + + private RangeSet characterRanges(String input) { + final RangeSet characterRanges = TreeRangeSet.create(); + + if (parameters.lines().isEmpty() && parameters.offsets().isEmpty()) { + characterRanges.add(Range.closedOpen(0, input.length())); + return characterRanges; } - private String fixImports(String input) throws FormatterException { - if (parameters.removeUnusedImports()) { - input = RemoveUnusedImports.removeUnusedImports(input); - } - if (parameters.sortImports()) { - input = ImportOrderer.reorderImports(input, options.style()); - } - return input; + characterRanges.addAll(Formatter.lineRangesToCharRanges(input, parameters.lines())); + + for (int i = 0; i < parameters.offsets().size(); i++) { + Integer length = parameters.lengths().get(i); + if (length == 0) { + // 0 stands for "format the line under the cursor" + length = 1; + } + characterRanges.add( + Range.closedOpen(parameters.offsets().get(i), parameters.offsets().get(i) + length)); } - private RangeSet characterRanges(String input) { - final RangeSet characterRanges = TreeRangeSet.create(); - - if (parameters.lines().isEmpty() && parameters.offsets().isEmpty()) { - characterRanges.add(Range.closedOpen(0, input.length())); - return characterRanges; - } - - characterRanges.addAll(Formatter.lineRangesToCharRanges(input, parameters.lines())); - - for (int i = 0; i < parameters.offsets().size(); i++) { - Integer length = parameters.lengths().get(i); - if (length == 0) { - // 0 stands for "format the line under the cursor" - length = 1; - } - characterRanges.add( - Range.closedOpen( - parameters.offsets().get(i), parameters.offsets().get(i) + length)); - } - - return characterRanges; - } + return characterRanges; + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/Formatter.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/Formatter.java index df1013974..bd46d7a6b 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/Formatter.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/Formatter.java @@ -91,223 +91,217 @@ import javax.tools.StandardLocation; @Immutable public final class Formatter { - public static final int MAX_LINE_LENGTH = 100; + public static final int MAX_LINE_LENGTH = 100; - static final Range EMPTY_RANGE = Range.closedOpen(-1, -1); + static final Range EMPTY_RANGE = Range.closedOpen(-1, -1); - private final JavaFormatterOptions options; + private final JavaFormatterOptions options; - /** A new Formatter instance with default options. */ - public Formatter() { - this(JavaFormatterOptions.defaultOptions()); + /** A new Formatter instance with default options. */ + public Formatter() { + this(JavaFormatterOptions.defaultOptions()); + } + + public Formatter(JavaFormatterOptions options) { + this.options = options; + } + + /** + * Construct a {@code Formatter} given a Java compilation unit. Parses the code; builds a {@link + * JavaInput} and the corresponding {@link JavaOutput}. + * + * @param javaInput the input, a Java compilation unit + * @param javaOutput the {@link JavaOutput} + * @param options the {@link JavaFormatterOptions} + */ + static void format(final JavaInput javaInput, JavaOutput javaOutput, JavaFormatterOptions options) + throws FormatterException { + Context context = new Context(); + DiagnosticCollector diagnostics = new DiagnosticCollector<>(); + context.put(DiagnosticListener.class, diagnostics); + Options.instance(context).put("allowStringFolding", "false"); + Options.instance(context).put("--enable-preview", "true"); + JCCompilationUnit unit; + JavacFileManager fileManager = new JavacFileManager(context, true, UTF_8); + try { + fileManager.setLocation(StandardLocation.PLATFORM_CLASS_PATH, ImmutableList.of()); + } catch (IOException e) { + // impossible + throw new IOError(e); } + SimpleJavaFileObject source = + new SimpleJavaFileObject(URI.create("source"), JavaFileObject.Kind.SOURCE) { + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { + return javaInput.getText(); + } + }; + Log.instance(context).useSource(source); + ParserFactory parserFactory = ParserFactory.instance(context); + JavacParser parser = + parserFactory.newParser( + javaInput.getText(), + /*keepDocComments=*/ true, + /*keepEndPos=*/ true, + /*keepLineMap=*/ true); + unit = parser.parseCompilationUnit(); + unit.sourcefile = source; - public Formatter(JavaFormatterOptions options) { - this.options = options; + javaInput.setCompilationUnit(unit); + Iterable> errorDiagnostics = + Iterables.filter(diagnostics.getDiagnostics(), Formatter::errorDiagnostic); + if (!Iterables.isEmpty(errorDiagnostics)) { + throw FormatterException.fromJavacDiagnostics(errorDiagnostics); } + OpsBuilder builder = new OpsBuilder(javaInput, javaOutput); + // Output the compilation unit. + JavaInputAstVisitor visitor = new JavaInputAstVisitor(builder, options.indentationMultiplier()); + visitor.scan(unit, null); + builder.sync(javaInput.getText().length()); + builder.drain(); + Doc doc = new DocBuilder().withOps(builder.build()).build(); + doc.computeBreaks(javaOutput.getCommentsHelper(), MAX_LINE_LENGTH, new Doc.State(+0, 0)); + doc.write(javaOutput); + javaOutput.flush(); + } - /** - * Construct a {@code Formatter} given a Java compilation unit. Parses the code; builds a {@link - * JavaInput} and the corresponding {@link JavaOutput}. - * - * @param javaInput the input, a Java compilation unit - * @param javaOutput the {@link JavaOutput} - * @param options the {@link JavaFormatterOptions} - */ - static void format( - final JavaInput javaInput, JavaOutput javaOutput, JavaFormatterOptions options) - throws FormatterException { - Context context = new Context(); - DiagnosticCollector diagnostics = new DiagnosticCollector<>(); - context.put(DiagnosticListener.class, diagnostics); - Options.instance(context).put("allowStringFolding", "false"); - Options.instance(context).put("--enable-preview", "true"); - JCCompilationUnit unit; - JavacFileManager fileManager = new JavacFileManager(context, true, UTF_8); - try { - fileManager.setLocation(StandardLocation.PLATFORM_CLASS_PATH, ImmutableList.of()); - } catch (IOException e) { - // impossible - throw new IOError(e); - } - SimpleJavaFileObject source = - new SimpleJavaFileObject(URI.create("source"), JavaFileObject.Kind.SOURCE) { - @Override - public CharSequence getCharContent(boolean ignoreEncodingErrors) - throws IOException { - return javaInput.getText(); - } - }; - Log.instance(context).useSource(source); - ParserFactory parserFactory = ParserFactory.instance(context); - JavacParser parser = - parserFactory.newParser( - javaInput.getText(), - /*keepDocComments=*/ true, - /*keepEndPos=*/ true, - /*keepLineMap=*/ true); - unit = parser.parseCompilationUnit(); - unit.sourcefile = source; - - javaInput.setCompilationUnit(unit); - Iterable> errorDiagnostics = - Iterables.filter(diagnostics.getDiagnostics(), Formatter::errorDiagnostic); - if (!Iterables.isEmpty(errorDiagnostics)) { - throw FormatterException.fromJavacDiagnostics(errorDiagnostics); - } - OpsBuilder builder = new OpsBuilder(javaInput, javaOutput); - // Output the compilation unit. - JavaInputAstVisitor visitor = - new JavaInputAstVisitor(builder, options.indentationMultiplier()); - visitor.scan(unit, null); - builder.sync(javaInput.getText().length()); - builder.drain(); - Doc doc = new DocBuilder().withOps(builder.build()).build(); - doc.computeBreaks(javaOutput.getCommentsHelper(), MAX_LINE_LENGTH, new Doc.State(+0, 0)); - doc.write(javaOutput); - javaOutput.flush(); + private static int getJavaMajorVersion() { + try { + Method versionMethod = Runtime.class.getMethod("version"); + Object version = versionMethod.invoke(null); + return (int) version.getClass().getMethod("major").invoke(version); + } catch (Exception e) { + // continue below } - - private static int getJavaMajorVersion() { - try { - Method versionMethod = Runtime.class.getMethod("version"); - Object version = versionMethod.invoke(null); - return (int) version.getClass().getMethod("major").invoke(version); - } catch (Exception e) { - // continue below - } - int version = (int) Double.parseDouble(JAVA_CLASS_VERSION.value()); - if (49 <= version && version <= 52) { - return version - (49 - 5); - } - throw new IllegalStateException( - "Unknown Java version: " + JAVA_SPECIFICATION_VERSION.value()); + int version = (int) Double.parseDouble(JAVA_CLASS_VERSION.value()); + if (49 <= version && version <= 52) { + return version - (49 - 5); } + throw new IllegalStateException("Unknown Java version: " + JAVA_SPECIFICATION_VERSION.value()); + } - static boolean errorDiagnostic(Diagnostic input) { - if (input.getKind() != Diagnostic.Kind.ERROR) { - return false; - } - switch (input.getCode()) { - case "compiler.err.invalid.meth.decl.ret.type.req": - // accept constructor-like method declarations that don't match the name of their - // enclosing class - return false; - default: - break; - } - return true; + static boolean errorDiagnostic(Diagnostic input) { + if (input.getKind() != Diagnostic.Kind.ERROR) { + return false; } - - /** - * Converts zero-indexed, [closed, open) line ranges in the given source file to character - * ranges. - */ - public static RangeSet lineRangesToCharRanges( - String input, RangeSet lineRanges) { - List lines = new ArrayList<>(); - Iterators.addAll(lines, Newlines.lineOffsetIterator(input)); - lines.add(input.length() + 1); - - final RangeSet characterRanges = TreeRangeSet.create(); - for (Range lineRange : - lineRanges.subRangeSet(Range.closedOpen(0, lines.size() - 1)).asRanges()) { - int lineStart = lines.get(lineRange.lowerEndpoint()); - // Exclude the trailing newline. This isn't strictly necessary, but handling blank lines - // as empty ranges is convenient. - int lineEnd = lines.get(lineRange.upperEndpoint()) - 1; - Range range = Range.closedOpen(lineStart, lineEnd); - characterRanges.add(range); - } - return characterRanges; + switch (input.getCode()) { + case "compiler.err.invalid.meth.decl.ret.type.req": + // accept constructor-like method declarations that don't match the name of their + // enclosing class + return false; + default: + break; } + return true; + } - /** - * Format the given input (a Java compilation unit) into the output stream. - * - * @throws FormatterException if the input cannot be parsed - */ - public void formatSource(CharSource input, CharSink output) - throws FormatterException, IOException { - // TODO(cushon): proper support for streaming input/output. Input may - // not be feasible (parsing) but output should be easier. - output.write(formatSource(input.read())); + /** + * Converts zero-indexed, [closed, open) line ranges in the given source file to character ranges. + */ + public static RangeSet lineRangesToCharRanges( + String input, RangeSet lineRanges) { + List lines = new ArrayList<>(); + Iterators.addAll(lines, Newlines.lineOffsetIterator(input)); + lines.add(input.length() + 1); + + final RangeSet characterRanges = TreeRangeSet.create(); + for (Range lineRange : + lineRanges.subRangeSet(Range.closedOpen(0, lines.size() - 1)).asRanges()) { + int lineStart = lines.get(lineRange.lowerEndpoint()); + // Exclude the trailing newline. This isn't strictly necessary, but handling blank lines + // as empty ranges is convenient. + int lineEnd = lines.get(lineRange.upperEndpoint()) - 1; + Range range = Range.closedOpen(lineStart, lineEnd); + characterRanges.add(range); } + return characterRanges; + } - /** - * Format an input string (a Java compilation unit) into an output string. - * - *

Leaves import statements untouched. - * - * @param input the input string - * @return the output string - * @throws FormatterException if the input string cannot be parsed - */ - public String formatSource(String input) throws FormatterException { - return formatSource(input, ImmutableList.of(Range.closedOpen(0, input.length()))); - } - - /** - * Formats an input string (a Java compilation unit) and fixes imports. - * - *

Fixing imports includes ordering, spacing, and removal of unused import statements. - * - * @param input the input string - * @return the output string - * @throws FormatterException if the input string cannot be parsed - * @see - * Google Java Style Guide - 3.3.3 Import ordering and spacing - */ - public String formatSourceAndFixImports(String input) throws FormatterException { - input = ImportOrderer.reorderImports(input, options.style()); - input = RemoveUnusedImports.removeUnusedImports(input); - String formatted = formatSource(input); - formatted = StringWrapper.wrap(formatted, this); - return formatted; - } - - /** - * Format an input string (a Java compilation unit), for only the specified character ranges. - * These ranges are extended as necessary (e.g., to encompass whole lines). - * - * @param input the input string - * @param characterRanges the character ranges to be reformatted - * @return the output string - * @throws FormatterException if the input string cannot be parsed - */ - public String formatSource(String input, Collection> characterRanges) - throws FormatterException { - return JavaOutput.applyReplacements(input, getFormatReplacements(input, characterRanges)); - } - - /** - * Emit a list of {@link Replacement}s to convert from input to output. - * - * @param input the input compilation unit - * @param characterRanges the character ranges to reformat - * @return a list of {@link Replacement}s, sorted from low index to high index, without overlaps - * @throws FormatterException if the input string cannot be parsed - */ - public ImmutableList getFormatReplacements( - String input, Collection> characterRanges) throws FormatterException { - JavaInput javaInput = new JavaInput(input); - - // TODO(cushon): this is only safe because the modifier ordering doesn't affect whitespace, - // and doesn't change the replacements that are output. This is not true in general for - // 'de-linting' changes (e.g. import ordering). - javaInput = ModifierOrderer.reorderModifiers(javaInput, characterRanges); - - String lineSeparator = Newlines.guessLineSeparator(input); - JavaOutput javaOutput = - new JavaOutput( - lineSeparator, javaInput, new JavaCommentsHelper(lineSeparator, options)); - try { - format(javaInput, javaOutput, options); - } catch (FormattingError e) { - throw new FormatterException(e.diagnostics()); - } - RangeSet tokenRangeSet = javaInput.characterRangesToTokenRanges(characterRanges); - return javaOutput.getFormatReplacements(tokenRangeSet); + /** + * Format the given input (a Java compilation unit) into the output stream. + * + * @throws FormatterException if the input cannot be parsed + */ + public void formatSource(CharSource input, CharSink output) + throws FormatterException, IOException { + // TODO(cushon): proper support for streaming input/output. Input may + // not be feasible (parsing) but output should be easier. + output.write(formatSource(input.read())); + } + + /** + * Format an input string (a Java compilation unit) into an output string. + * + *

Leaves import statements untouched. + * + * @param input the input string + * @return the output string + * @throws FormatterException if the input string cannot be parsed + */ + public String formatSource(String input) throws FormatterException { + return formatSource(input, ImmutableList.of(Range.closedOpen(0, input.length()))); + } + + /** + * Formats an input string (a Java compilation unit) and fixes imports. + * + *

Fixing imports includes ordering, spacing, and removal of unused import statements. + * + * @param input the input string + * @return the output string + * @throws FormatterException if the input string cannot be parsed + * @see + * Google Java Style Guide - 3.3.3 Import ordering and spacing + */ + public String formatSourceAndFixImports(String input) throws FormatterException { + input = ImportOrderer.reorderImports(input, options.style()); + input = RemoveUnusedImports.removeUnusedImports(input); + String formatted = formatSource(input); + formatted = StringWrapper.wrap(formatted, this); + return formatted; + } + + /** + * Format an input string (a Java compilation unit), for only the specified character ranges. + * These ranges are extended as necessary (e.g., to encompass whole lines). + * + * @param input the input string + * @param characterRanges the character ranges to be reformatted + * @return the output string + * @throws FormatterException if the input string cannot be parsed + */ + public String formatSource(String input, Collection> characterRanges) + throws FormatterException { + return JavaOutput.applyReplacements(input, getFormatReplacements(input, characterRanges)); + } + + /** + * Emit a list of {@link Replacement}s to convert from input to output. + * + * @param input the input compilation unit + * @param characterRanges the character ranges to reformat + * @return a list of {@link Replacement}s, sorted from low index to high index, without overlaps + * @throws FormatterException if the input string cannot be parsed + */ + public ImmutableList getFormatReplacements( + String input, Collection> characterRanges) throws FormatterException { + JavaInput javaInput = new JavaInput(input); + + // TODO(cushon): this is only safe because the modifier ordering doesn't affect whitespace, + // and doesn't change the replacements that are output. This is not true in general for + // 'de-linting' changes (e.g. import ordering). + javaInput = ModifierOrderer.reorderModifiers(javaInput, characterRanges); + + String lineSeparator = Newlines.guessLineSeparator(input); + JavaOutput javaOutput = + new JavaOutput(lineSeparator, javaInput, new JavaCommentsHelper(lineSeparator, options)); + try { + format(javaInput, javaOutput, options); + } catch (FormattingError e) { + throw new FormatterException(e.diagnostics()); } + RangeSet tokenRangeSet = javaInput.characterRangesToTokenRanges(characterRanges); + return javaOutput.getFormatReplacements(tokenRangeSet); + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/FormatterException.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/FormatterException.java index 236782227..e61b341d7 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/FormatterException.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/FormatterException.java @@ -28,35 +28,33 @@ import javax.tools.JavaFileObject; /** Checked exception class for formatter errors. */ public final class FormatterException extends Exception { - private final ImmutableList diagnostics; + private final ImmutableList diagnostics; - public FormatterException(String message) { - this(FormatterDiagnostic.create(message)); - } + public FormatterException(String message) { + this(FormatterDiagnostic.create(message)); + } - public FormatterException(FormatterDiagnostic diagnostic) { - this(ImmutableList.of(diagnostic)); - } + public FormatterException(FormatterDiagnostic diagnostic) { + this(ImmutableList.of(diagnostic)); + } - public FormatterException(Iterable diagnostics) { - super(diagnostics.iterator().next().toString()); - this.diagnostics = ImmutableList.copyOf(diagnostics); - } + public FormatterException(Iterable diagnostics) { + super(diagnostics.iterator().next().toString()); + this.diagnostics = ImmutableList.copyOf(diagnostics); + } - public List diagnostics() { - return diagnostics; - } + public List diagnostics() { + return diagnostics; + } - public static FormatterException fromJavacDiagnostics( - Iterable> diagnostics) { - return new FormatterException( - Iterables.transform(diagnostics, FormatterException::toFormatterDiagnostic)); - } + public static FormatterException fromJavacDiagnostics( + Iterable> diagnostics) { + return new FormatterException( + Iterables.transform(diagnostics, FormatterException::toFormatterDiagnostic)); + } - private static FormatterDiagnostic toFormatterDiagnostic(Diagnostic input) { - return FormatterDiagnostic.create( - (int) input.getLineNumber(), - (int) input.getColumnNumber(), - input.getMessage(ENGLISH)); - } + private static FormatterDiagnostic toFormatterDiagnostic(Diagnostic input) { + return FormatterDiagnostic.create( + (int) input.getLineNumber(), (int) input.getColumnNumber(), input.getMessage(ENGLISH)); + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/GoogleJavaFormatVersion.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/GoogleJavaFormatVersion.java index 1431af8e1..0b92d4e78 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/GoogleJavaFormatVersion.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/GoogleJavaFormatVersion.java @@ -16,7 +16,7 @@ package com.google.googlejavaformat.java; class GoogleJavaFormatVersion { - static String version() { - return "1.15.0"; - } + static String version() { + return "1.15.0"; + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/ImportOrderer.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/ImportOrderer.java index 0cfccef93..5f9a2c13e 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/ImportOrderer.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/ImportOrderer.java @@ -37,451 +37,450 @@ import java.util.stream.Stream; /** Orders imports in Java source code. */ public class ImportOrderer { - private static final Splitter DOT_SPLITTER = Splitter.on('.'); + private static final Splitter DOT_SPLITTER = Splitter.on('.'); - /** - * Reorder the inputs in {@code text}, a complete Java program. On success, another complete - * Java program is returned, which is the same as the original except the imports are in order. - * - * @throws FormatterException if the input could not be parsed. - */ - public static String reorderImports(String text, Style style) throws FormatterException { - ImmutableList toks = JavaInput.buildToks(text, CLASS_START); - return new ImportOrderer(text, toks, style).reorderImports(); + /** + * Reorder the inputs in {@code text}, a complete Java program. On success, another complete Java + * program is returned, which is the same as the original except the imports are in order. + * + * @throws FormatterException if the input could not be parsed. + */ + public static String reorderImports(String text, Style style) throws FormatterException { + ImmutableList toks = JavaInput.buildToks(text, CLASS_START); + return new ImportOrderer(text, toks, style).reorderImports(); + } + + /** + * Reorder the inputs in {@code text}, a complete Java program, in Google style. On success, + * another complete Java program is returned, which is the same as the original except the imports + * are in order. + * + * @deprecated Use {@link #reorderImports(String, Style)} instead + * @throws FormatterException if the input could not be parsed. + */ + @Deprecated + public static String reorderImports(String text) throws FormatterException { + return reorderImports(text, Style.GOOGLE); + } + + private String reorderImports() throws FormatterException { + int firstImportStart; + Optional maybeFirstImport = findIdentifier(0, IMPORT_OR_CLASS_START); + if (!maybeFirstImport.isPresent() || !tokenAt(maybeFirstImport.get()).equals("import")) { + // No imports, so nothing to do. + return text; + } + firstImportStart = maybeFirstImport.get(); + int unindentedFirstImportStart = unindent(firstImportStart); + + ImportsAndIndex imports = scanImports(firstImportStart); + int afterLastImport = imports.index; + + // Make sure there are no more imports before the next class (etc) definition. + Optional maybeLaterImport = findIdentifier(afterLastImport, IMPORT_OR_CLASS_START); + if (maybeLaterImport.isPresent() && tokenAt(maybeLaterImport.get()).equals("import")) { + throw new FormatterException("Imports not contiguous (perhaps a comment separates them?)"); + } + + StringBuilder result = new StringBuilder(); + String prefix = tokString(0, unindentedFirstImportStart); + result.append(prefix); + if (!prefix.isEmpty() && Newlines.getLineEnding(prefix) == null) { + result.append(lineSeparator).append(lineSeparator); + } + result.append(reorderedImportsString(imports.imports)); + + List tail = new ArrayList<>(); + tail.add(CharMatcher.whitespace().trimLeadingFrom(tokString(afterLastImport, toks.size()))); + if (!toks.isEmpty()) { + Tok lastTok = getLast(toks); + int tailStart = lastTok.getPosition() + lastTok.length(); + tail.add(text.substring(tailStart)); + } + if (tail.stream().anyMatch(s -> !s.isEmpty())) { + result.append(lineSeparator); + tail.forEach(result::append); + } + + return result.toString(); + } + + /** + * {@link TokenKind}s that indicate the start of a type definition. We use this to avoid scanning + * the whole file, since we know that imports must precede any type definition. + */ + private static final ImmutableSet CLASS_START = + ImmutableSet.of(TokenKind.CLASS, TokenKind.INTERFACE, TokenKind.ENUM); + + /** + * We use this set to find the first import, and again to check that there are no imports after + * the place we stopped gathering them. An annotation definition ({@code @interface}) is two + * tokens, the second which is {@code interface}, so we don't need a separate entry for that. + */ + private static final ImmutableSet IMPORT_OR_CLASS_START = + ImmutableSet.of("import", "class", "interface", "enum"); + + /** + * A {@link Comparator} that orders {@link Import}s by Google Style, defined at + * https://google.github.io/styleguide/javaguide.html#s3.3.3-import-ordering-and-spacing. + */ + private static final Comparator GOOGLE_IMPORT_COMPARATOR = + Comparator.comparing(Import::isStatic, trueFirst()).thenComparing(Import::imported); + + /** + * A {@link Comparator} that orders {@link Import}s by AOSP Style, defined at + * https://source.android.com/setup/contribute/code-style#order-import-statements and implemented + * in IntelliJ at + * https://android.googlesource.com/platform/development/+/master/ide/intellij/codestyles/AndroidStyle.xml. + */ + private static final Comparator AOSP_IMPORT_COMPARATOR = + Comparator.comparing(Import::isStatic, trueFirst()) + .thenComparing(Import::isAndroid, trueFirst()) + .thenComparing(Import::isThirdParty, trueFirst()) + .thenComparing(Import::isJava, trueFirst()) + .thenComparing(Import::imported); + + /** + * Determines whether to insert a blank line between the {@code prev} and {@code curr} {@link + * Import}s based on Google style. + */ + private static boolean shouldInsertBlankLineGoogle(Import prev, Import curr) { + return prev.isStatic() && !curr.isStatic(); + } + + /** + * Determines whether to insert a blank line between the {@code prev} and {@code curr} {@link + * Import}s based on AOSP style. + */ + private static boolean shouldInsertBlankLineAosp(Import prev, Import curr) { + if (prev.isStatic() && !curr.isStatic()) { + return true; + } + // insert blank line between "com.android" from "com.anythingelse" + if (prev.isAndroid() && !curr.isAndroid()) { + return true; + } + return !prev.topLevel().equals(curr.topLevel()); + } + + private final String text; + private final ImmutableList toks; + private final String lineSeparator; + private final Comparator importComparator; + private final BiFunction shouldInsertBlankLineFn; + + private ImportOrderer(String text, ImmutableList toks, Style style) { + this.text = text; + this.toks = toks; + this.lineSeparator = Newlines.guessLineSeparator(text); + if (style.equals(Style.GOOGLE)) { + this.importComparator = GOOGLE_IMPORT_COMPARATOR; + this.shouldInsertBlankLineFn = ImportOrderer::shouldInsertBlankLineGoogle; + } else if (style.equals(Style.AOSP)) { + this.importComparator = AOSP_IMPORT_COMPARATOR; + this.shouldInsertBlankLineFn = ImportOrderer::shouldInsertBlankLineAosp; + } else { + throw new IllegalArgumentException("Unsupported code style: " + style); + } + } + + /** An import statement. */ + class Import { + private final String imported; + private final boolean isStatic; + private final String trailing; + + Import(String imported, String trailing, boolean isStatic) { + this.imported = imported; + this.trailing = trailing; + this.isStatic = isStatic; + } + + /** The name being imported, for example {@code java.util.List}. */ + String imported() { + return imported; + } + + /** True if this is {@code import static}. */ + boolean isStatic() { + return isStatic; + } + + /** The top-level package of the import. */ + String topLevel() { + return DOT_SPLITTER.split(imported()).iterator().next(); + } + + /** True if this is an Android import per AOSP style. */ + boolean isAndroid() { + return Stream.of("android.", "androidx.", "dalvik.", "libcore.", "com.android.") + .anyMatch(imported::startsWith); + } + + /** True if this is a Java import per AOSP style. */ + boolean isJava() { + switch (topLevel()) { + case "java": + case "javax": + return true; + default: + return false; + } } /** - * Reorder the inputs in {@code text}, a complete Java program, in Google style. On success, - * another complete Java program is returned, which is the same as the original except the - * imports are in order. - * - * @deprecated Use {@link #reorderImports(String, Style)} instead - * @throws FormatterException if the input could not be parsed. + * The {@code //} comment lines after the final {@code ;}, up to and including the line + * terminator of the last one. Note: In case two imports were separated by a space (which is + * disallowed by the style guide), the trailing whitespace of the first import does not include + * a line terminator. */ - @Deprecated - public static String reorderImports(String text) throws FormatterException { - return reorderImports(text, Style.GOOGLE); + String trailing() { + return trailing; } - private String reorderImports() throws FormatterException { - int firstImportStart; - Optional maybeFirstImport = findIdentifier(0, IMPORT_OR_CLASS_START); - if (!maybeFirstImport.isPresent() || !tokenAt(maybeFirstImport.get()).equals("import")) { - // No imports, so nothing to do. - return text; - } - firstImportStart = maybeFirstImport.get(); - int unindentedFirstImportStart = unindent(firstImportStart); - - ImportsAndIndex imports = scanImports(firstImportStart); - int afterLastImport = imports.index; - - // Make sure there are no more imports before the next class (etc) definition. - Optional maybeLaterImport = findIdentifier(afterLastImport, IMPORT_OR_CLASS_START); - if (maybeLaterImport.isPresent() && tokenAt(maybeLaterImport.get()).equals("import")) { - throw new FormatterException( - "Imports not contiguous (perhaps a comment separates them?)"); - } - - StringBuilder result = new StringBuilder(); - String prefix = tokString(0, unindentedFirstImportStart); - result.append(prefix); - if (!prefix.isEmpty() && Newlines.getLineEnding(prefix) == null) { - result.append(lineSeparator).append(lineSeparator); - } - result.append(reorderedImportsString(imports.imports)); - - List tail = new ArrayList<>(); - tail.add(CharMatcher.whitespace().trimLeadingFrom(tokString(afterLastImport, toks.size()))); - if (!toks.isEmpty()) { - Tok lastTok = getLast(toks); - int tailStart = lastTok.getPosition() + lastTok.length(); - tail.add(text.substring(tailStart)); - } - if (tail.stream().anyMatch(s -> !s.isEmpty())) { - result.append(lineSeparator); - tail.forEach(result::append); - } - - return result.toString(); + /** True if this is a third-party import per AOSP style. */ + public boolean isThirdParty() { + return !(isAndroid() || isJava()); } - /** - * {@link TokenKind}s that indicate the start of a type definition. We use this to avoid - * scanning the whole file, since we know that imports must precede any type definition. - */ - private static final ImmutableSet CLASS_START = - ImmutableSet.of(TokenKind.CLASS, TokenKind.INTERFACE, TokenKind.ENUM); - - /** - * We use this set to find the first import, and again to check that there are no imports after - * the place we stopped gathering them. An annotation definition ({@code @interface}) is two - * tokens, the second which is {@code interface}, so we don't need a separate entry for that. - */ - private static final ImmutableSet IMPORT_OR_CLASS_START = - ImmutableSet.of("import", "class", "interface", "enum"); - - /** - * A {@link Comparator} that orders {@link Import}s by Google Style, defined at - * https://google.github.io/styleguide/javaguide.html#s3.3.3-import-ordering-and-spacing. - */ - private static final Comparator GOOGLE_IMPORT_COMPARATOR = - Comparator.comparing(Import::isStatic, trueFirst()).thenComparing(Import::imported); - - /** - * A {@link Comparator} that orders {@link Import}s by AOSP Style, defined at - * https://source.android.com/setup/contribute/code-style#order-import-statements and - * implemented in IntelliJ at - * https://android.googlesource.com/platform/development/+/master/ide/intellij/codestyles/AndroidStyle.xml. - */ - private static final Comparator AOSP_IMPORT_COMPARATOR = - Comparator.comparing(Import::isStatic, trueFirst()) - .thenComparing(Import::isAndroid, trueFirst()) - .thenComparing(Import::isThirdParty, trueFirst()) - .thenComparing(Import::isJava, trueFirst()) - .thenComparing(Import::imported); - - /** - * Determines whether to insert a blank line between the {@code prev} and {@code curr} {@link - * Import}s based on Google style. - */ - private static boolean shouldInsertBlankLineGoogle(Import prev, Import curr) { - return prev.isStatic() && !curr.isStatic(); + // One or multiple lines, the import itself and following comments, including the line + // terminator. + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("import "); + if (isStatic()) { + sb.append("static "); + } + sb.append(imported()).append(';'); + if (trailing().trim().isEmpty()) { + sb.append(lineSeparator); + } else { + sb.append(trailing()); + } + return sb.toString(); } + } - /** - * Determines whether to insert a blank line between the {@code prev} and {@code curr} {@link - * Import}s based on AOSP style. - */ - private static boolean shouldInsertBlankLineAosp(Import prev, Import curr) { - if (prev.isStatic() && !curr.isStatic()) { - return true; - } - // insert blank line between "com.android" from "com.anythingelse" - if (prev.isAndroid() && !curr.isAndroid()) { - return true; - } - return !prev.topLevel().equals(curr.topLevel()); + private String tokString(int start, int end) { + StringBuilder sb = new StringBuilder(); + for (int i = start; i < end; i++) { + sb.append(toks.get(i).getOriginalText()); } + return sb.toString(); + } - private final String text; - private final ImmutableList toks; - private final String lineSeparator; - private final Comparator importComparator; - private final BiFunction shouldInsertBlankLineFn; + private static class ImportsAndIndex { + final ImmutableSortedSet imports; + final int index; - private ImportOrderer(String text, ImmutableList toks, Style style) { - this.text = text; - this.toks = toks; - this.lineSeparator = Newlines.guessLineSeparator(text); - if (style.equals(Style.GOOGLE)) { - this.importComparator = GOOGLE_IMPORT_COMPARATOR; - this.shouldInsertBlankLineFn = ImportOrderer::shouldInsertBlankLineGoogle; - } else if (style.equals(Style.AOSP)) { - this.importComparator = AOSP_IMPORT_COMPARATOR; - this.shouldInsertBlankLineFn = ImportOrderer::shouldInsertBlankLineAosp; - } else { - throw new IllegalArgumentException("Unsupported code style: " + style); - } + ImportsAndIndex(ImmutableSortedSet imports, int index) { + this.imports = imports; + this.index = index; } + } - /** An import statement. */ - class Import { - private final String imported; - private final boolean isStatic; - private final String trailing; - - Import(String imported, String trailing, boolean isStatic) { - this.imported = imported; - this.trailing = trailing; - this.isStatic = isStatic; + /** + * Scans a sequence of import lines. The parsing uses this approximate grammar: + * + *

{@code
+   *  -> ( | )*
+   *  -> "import"  ("static" )?
+   *     ("." )* ("." "*")? ? ";"
+   *    ? ? ( )*
+   * }
+ * + * @param i the index to start parsing at. + * @return the result of parsing the imports. + * @throws FormatterException if imports could not parsed according to the grammar. + */ + private ImportsAndIndex scanImports(int i) throws FormatterException { + int afterLastImport = i; + ImmutableSortedSet.Builder imports = ImmutableSortedSet.orderedBy(importComparator); + // JavaInput.buildToks appends a zero-width EOF token after all tokens. It won't match any + // of our tests here and protects us from running off the end of the toks list. Since it is + // zero-width it doesn't matter if we include it in our string concatenation at the end. + while (i < toks.size() && tokenAt(i).equals("import")) { + i++; + if (isSpaceToken(i)) { + i++; + } + boolean isStatic = tokenAt(i).equals("static"); + if (isStatic) { + i++; + if (isSpaceToken(i)) { + i++; } - - /** The name being imported, for example {@code java.util.List}. */ - String imported() { - return imported; - } - - /** True if this is {@code import static}. */ - boolean isStatic() { - return isStatic; - } - - /** The top-level package of the import. */ - String topLevel() { - return DOT_SPLITTER.split(imported()).iterator().next(); - } - - /** True if this is an Android import per AOSP style. */ - boolean isAndroid() { - return Stream.of("android.", "androidx.", "dalvik.", "libcore.", "com.android.") - .anyMatch(imported::startsWith); - } - - /** True if this is a Java import per AOSP style. */ - boolean isJava() { - switch (topLevel()) { - case "java": - case "javax": - return true; - default: - return false; - } - } - - /** - * The {@code //} comment lines after the final {@code ;}, up to and including the line - * terminator of the last one. Note: In case two imports were separated by a space (which is - * disallowed by the style guide), the trailing whitespace of the first import does not - * include a line terminator. - */ - String trailing() { - return trailing; - } - - /** True if this is a third-party import per AOSP style. */ - public boolean isThirdParty() { - return !(isAndroid() || isJava()); - } - - // One or multiple lines, the import itself and following comments, including the line - // terminator. - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("import "); - if (isStatic()) { - sb.append("static "); - } - sb.append(imported()).append(';'); - if (trailing().trim().isEmpty()) { - sb.append(lineSeparator); - } else { - sb.append(trailing()); - } - return sb.toString(); + } + if (!isIdentifierToken(i)) { + throw new FormatterException("Unexpected token after import: " + tokenAt(i)); + } + StringAndIndex imported = scanImported(i); + String importedName = imported.string; + i = imported.index; + if (isSpaceToken(i)) { + i++; + } + if (!tokenAt(i).equals(";")) { + throw new FormatterException("Expected ; after import"); + } + while (tokenAt(i).equals(";")) { + // Extra semicolons are not allowed by the JLS but are accepted by javac. + i++; + } + StringBuilder trailing = new StringBuilder(); + if (isSpaceToken(i)) { + trailing.append(tokenAt(i)); + i++; + } + if (isNewlineToken(i)) { + trailing.append(tokenAt(i)); + i++; + } + // Gather (if any) all single line comments and accompanied line terminators following + // this + // import + while (isSlashSlashCommentToken(i)) { + trailing.append(tokenAt(i)); + i++; + if (isNewlineToken(i)) { + trailing.append(tokenAt(i)); + i++; } + } + while (tokenAt(i).equals(";")) { + // Extra semicolons are not allowed by the JLS but are accepted by javac. + i++; + } + imports.add(new Import(importedName, trailing.toString(), isStatic)); + // Remember the position just after the import we just saw, before skipping blank lines. + // If the next thing after the blank lines is not another import then we don't want to + // include those blank lines in the text to be replaced. + afterLastImport = i; + while (isNewlineToken(i) || isSpaceToken(i)) { + i++; + } } + return new ImportsAndIndex(imports.build(), afterLastImport); + } - private String tokString(int start, int end) { - StringBuilder sb = new StringBuilder(); - for (int i = start; i < end; i++) { - sb.append(toks.get(i).getOriginalText()); + // Produces the sorted output based on the imports we have scanned. + private String reorderedImportsString(ImmutableSortedSet imports) { + Preconditions.checkArgument(!imports.isEmpty(), "imports"); + + // Pretend that the first import was preceded by another import of the same kind, so we + // don't + // insert a newline there. + Import prevImport = imports.iterator().next(); + + StringBuilder sb = new StringBuilder(); + for (Import currImport : imports) { + if (shouldInsertBlankLineFn.apply(prevImport, currImport)) { + // Blank line between static and non-static imports. + sb.append(lineSeparator); + } + sb.append(currImport); + prevImport = currImport; + } + return sb.toString(); + } + + private static class StringAndIndex { + private final String string; + private final int index; + + StringAndIndex(String string, int index) { + this.string = string; + this.index = index; + } + } + + /** + * Scans the imported thing, the dot-separated name that comes after import [static] and before + * the semicolon. We don't allow spaces inside the dot-separated name. Wildcard imports are + * supported: if the input is {@code import java.util.*;} then the returned string will be {@code + * java.util.*}. + * + * @param start the index of the start of the identifier. If the import is {@code import + * java.util.List;} then this index points to the token {@code java}. + * @return the parsed import ({@code java.util.List} in the example) and the index of the first + * token after the imported thing ({@code ;} in the example). + * @throws FormatterException if the imported name could not be parsed. + */ + private StringAndIndex scanImported(int start) throws FormatterException { + int i = start; + StringBuilder imported = new StringBuilder(); + // At the start of each iteration of this loop, i points to an identifier. + // On exit from the loop, i points to a token after an identifier or after *. + while (true) { + Preconditions.checkState(isIdentifierToken(i)); + imported.append(tokenAt(i)); + i++; + if (!tokenAt(i).equals(".")) { + return new StringAndIndex(imported.toString(), i); + } + imported.append('.'); + i++; + if (tokenAt(i).equals("*")) { + imported.append('*'); + return new StringAndIndex(imported.toString(), i + 1); + } else if (!isIdentifierToken(i)) { + throw new FormatterException("Could not parse imported name, at: " + tokenAt(i)); + } + } + } + + /** + * Returns the index of the first place where one of the given identifiers occurs, or {@code + * Optional.empty()} if there is none. + * + * @param start the index to start looking at + * @param identifiers the identifiers to look for + */ + private Optional findIdentifier(int start, ImmutableSet identifiers) { + for (int i = start; i < toks.size(); i++) { + if (isIdentifierToken(i)) { + String id = tokenAt(i); + if (identifiers.contains(id)) { + return Optional.of(i); } - return sb.toString(); + } } + return Optional.empty(); + } - private static class ImportsAndIndex { - final ImmutableSortedSet imports; - final int index; - - ImportsAndIndex(ImmutableSortedSet imports, int index) { - this.imports = imports; - this.index = index; - } + /** Returns the given token, or the preceding token if it is a whitespace token. */ + private int unindent(int i) { + if (i > 0 && isSpaceToken(i - 1)) { + return i - 1; + } else { + return i; } + } - /** - * Scans a sequence of import lines. The parsing uses this approximate grammar: - * - *
{@code
-     *  -> ( | )*
-     *  -> "import"  ("static" )?
-     *     ("." )* ("." "*")? ? ";"
-     *    ? ? ( )*
-     * }
- * - * @param i the index to start parsing at. - * @return the result of parsing the imports. - * @throws FormatterException if imports could not parsed according to the grammar. - */ - private ImportsAndIndex scanImports(int i) throws FormatterException { - int afterLastImport = i; - ImmutableSortedSet.Builder imports = ImmutableSortedSet.orderedBy(importComparator); - // JavaInput.buildToks appends a zero-width EOF token after all tokens. It won't match any - // of our tests here and protects us from running off the end of the toks list. Since it is - // zero-width it doesn't matter if we include it in our string concatenation at the end. - while (i < toks.size() && tokenAt(i).equals("import")) { - i++; - if (isSpaceToken(i)) { - i++; - } - boolean isStatic = tokenAt(i).equals("static"); - if (isStatic) { - i++; - if (isSpaceToken(i)) { - i++; - } - } - if (!isIdentifierToken(i)) { - throw new FormatterException("Unexpected token after import: " + tokenAt(i)); - } - StringAndIndex imported = scanImported(i); - String importedName = imported.string; - i = imported.index; - if (isSpaceToken(i)) { - i++; - } - if (!tokenAt(i).equals(";")) { - throw new FormatterException("Expected ; after import"); - } - while (tokenAt(i).equals(";")) { - // Extra semicolons are not allowed by the JLS but are accepted by javac. - i++; - } - StringBuilder trailing = new StringBuilder(); - if (isSpaceToken(i)) { - trailing.append(tokenAt(i)); - i++; - } - if (isNewlineToken(i)) { - trailing.append(tokenAt(i)); - i++; - } - // Gather (if any) all single line comments and accompanied line terminators following - // this - // import - while (isSlashSlashCommentToken(i)) { - trailing.append(tokenAt(i)); - i++; - if (isNewlineToken(i)) { - trailing.append(tokenAt(i)); - i++; - } - } - while (tokenAt(i).equals(";")) { - // Extra semicolons are not allowed by the JLS but are accepted by javac. - i++; - } - imports.add(new Import(importedName, trailing.toString(), isStatic)); - // Remember the position just after the import we just saw, before skipping blank lines. - // If the next thing after the blank lines is not another import then we don't want to - // include those blank lines in the text to be replaced. - afterLastImport = i; - while (isNewlineToken(i) || isSpaceToken(i)) { - i++; - } - } - return new ImportsAndIndex(imports.build(), afterLastImport); + private String tokenAt(int i) { + return toks.get(i).getOriginalText(); + } + + private boolean isIdentifierToken(int i) { + String s = tokenAt(i); + return !s.isEmpty() && Character.isJavaIdentifierStart(s.codePointAt(0)); + } + + private boolean isSpaceToken(int i) { + String s = tokenAt(i); + if (s.isEmpty()) { + return false; + } else { + return " \t\f".indexOf(s.codePointAt(0)) >= 0; } + } - // Produces the sorted output based on the imports we have scanned. - private String reorderedImportsString(ImmutableSortedSet imports) { - Preconditions.checkArgument(!imports.isEmpty(), "imports"); + private boolean isSlashSlashCommentToken(int i) { + return toks.get(i).isSlashSlashComment(); + } - // Pretend that the first import was preceded by another import of the same kind, so we - // don't - // insert a newline there. - Import prevImport = imports.iterator().next(); - - StringBuilder sb = new StringBuilder(); - for (Import currImport : imports) { - if (shouldInsertBlankLineFn.apply(prevImport, currImport)) { - // Blank line between static and non-static imports. - sb.append(lineSeparator); - } - sb.append(currImport); - prevImport = currImport; - } - return sb.toString(); - } - - private static class StringAndIndex { - private final String string; - private final int index; - - StringAndIndex(String string, int index) { - this.string = string; - this.index = index; - } - } - - /** - * Scans the imported thing, the dot-separated name that comes after import [static] and before - * the semicolon. We don't allow spaces inside the dot-separated name. Wildcard imports are - * supported: if the input is {@code import java.util.*;} then the returned string will be - * {@code java.util.*}. - * - * @param start the index of the start of the identifier. If the import is {@code import - * java.util.List;} then this index points to the token {@code java}. - * @return the parsed import ({@code java.util.List} in the example) and the index of the first - * token after the imported thing ({@code ;} in the example). - * @throws FormatterException if the imported name could not be parsed. - */ - private StringAndIndex scanImported(int start) throws FormatterException { - int i = start; - StringBuilder imported = new StringBuilder(); - // At the start of each iteration of this loop, i points to an identifier. - // On exit from the loop, i points to a token after an identifier or after *. - while (true) { - Preconditions.checkState(isIdentifierToken(i)); - imported.append(tokenAt(i)); - i++; - if (!tokenAt(i).equals(".")) { - return new StringAndIndex(imported.toString(), i); - } - imported.append('.'); - i++; - if (tokenAt(i).equals("*")) { - imported.append('*'); - return new StringAndIndex(imported.toString(), i + 1); - } else if (!isIdentifierToken(i)) { - throw new FormatterException("Could not parse imported name, at: " + tokenAt(i)); - } - } - } - - /** - * Returns the index of the first place where one of the given identifiers occurs, or {@code - * Optional.empty()} if there is none. - * - * @param start the index to start looking at - * @param identifiers the identifiers to look for - */ - private Optional findIdentifier(int start, ImmutableSet identifiers) { - for (int i = start; i < toks.size(); i++) { - if (isIdentifierToken(i)) { - String id = tokenAt(i); - if (identifiers.contains(id)) { - return Optional.of(i); - } - } - } - return Optional.empty(); - } - - /** Returns the given token, or the preceding token if it is a whitespace token. */ - private int unindent(int i) { - if (i > 0 && isSpaceToken(i - 1)) { - return i - 1; - } else { - return i; - } - } - - private String tokenAt(int i) { - return toks.get(i).getOriginalText(); - } - - private boolean isIdentifierToken(int i) { - String s = tokenAt(i); - return !s.isEmpty() && Character.isJavaIdentifierStart(s.codePointAt(0)); - } - - private boolean isSpaceToken(int i) { - String s = tokenAt(i); - if (s.isEmpty()) { - return false; - } else { - return " \t\f".indexOf(s.codePointAt(0)) >= 0; - } - } - - private boolean isSlashSlashCommentToken(int i) { - return toks.get(i).isSlashSlashComment(); - } - - private boolean isNewlineToken(int i) { - return toks.get(i).isNewline(); - } + private boolean isNewlineToken(int i) { + return toks.get(i).isNewline(); + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/JavaCommentsHelper.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/JavaCommentsHelper.java index 7147474ed..931ae60d0 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/JavaCommentsHelper.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/JavaCommentsHelper.java @@ -30,153 +30,153 @@ import java.util.regex.Pattern; /** {@code JavaCommentsHelper} extends {@link CommentsHelper} to rewrite Java comments. */ public final class JavaCommentsHelper implements CommentsHelper { - private final String lineSeparator; - private final JavaFormatterOptions options; + private final String lineSeparator; + private final JavaFormatterOptions options; - public JavaCommentsHelper(String lineSeparator, JavaFormatterOptions options) { - this.lineSeparator = lineSeparator; - this.options = options; + public JavaCommentsHelper(String lineSeparator, JavaFormatterOptions options) { + this.lineSeparator = lineSeparator; + this.options = options; + } + + @Override + public String rewrite(Tok tok, int maxWidth, int column0) { + if (!tok.isComment()) { + return tok.getOriginalText(); + } + String text = tok.getOriginalText(); + if (tok.isJavadocComment() && options.formatJavadoc()) { + text = JavadocFormatter.formatJavadoc(text, column0); + } + List lines = new ArrayList<>(); + Iterator it = Newlines.lineIterator(text); + while (it.hasNext()) { + lines.add(CharMatcher.whitespace().trimTrailingFrom(it.next())); + } + if (tok.isSlashSlashComment()) { + return indentLineComments(lines, column0); + } else if (javadocShaped(lines)) { + return indentJavadoc(lines, column0); + } else { + return preserveIndentation(lines, column0); + } + } + + // For non-javadoc-shaped block comments, shift the entire block to the correct + // column, but do not adjust relative indentation. + private String preserveIndentation(List lines, int column0) { + StringBuilder builder = new StringBuilder(); + + // find the leftmost non-whitespace character in all trailing lines + int startCol = -1; + for (int i = 1; i < lines.size(); i++) { + int lineIdx = CharMatcher.whitespace().negate().indexIn(lines.get(i)); + if (lineIdx >= 0 && (startCol == -1 || lineIdx < startCol)) { + startCol = lineIdx; + } } - @Override - public String rewrite(Tok tok, int maxWidth, int column0) { - if (!tok.isComment()) { - return tok.getOriginalText(); - } - String text = tok.getOriginalText(); - if (tok.isJavadocComment() && options.formatJavadoc()) { - text = JavadocFormatter.formatJavadoc(text, column0); - } - List lines = new ArrayList<>(); - Iterator it = Newlines.lineIterator(text); - while (it.hasNext()) { - lines.add(CharMatcher.whitespace().trimTrailingFrom(it.next())); - } - if (tok.isSlashSlashComment()) { - return indentLineComments(lines, column0); - } else if (javadocShaped(lines)) { - return indentJavadoc(lines, column0); - } else { - return preserveIndentation(lines, column0); - } + // output the first line at the current column + builder.append(lines.get(0)); + + // output all trailing lines with plausible indentation + for (int i = 1; i < lines.size(); ++i) { + builder.append(lineSeparator).append(Strings.repeat(" ", column0)); + // check that startCol is valid index, e.g. for blank lines + if (lines.get(i).length() >= startCol) { + builder.append(lines.get(i).substring(startCol)); + } else { + builder.append(lines.get(i)); + } } + return builder.toString(); + } - // For non-javadoc-shaped block comments, shift the entire block to the correct - // column, but do not adjust relative indentation. - private String preserveIndentation(List lines, int column0) { - StringBuilder builder = new StringBuilder(); - - // find the leftmost non-whitespace character in all trailing lines - int startCol = -1; - for (int i = 1; i < lines.size(); i++) { - int lineIdx = CharMatcher.whitespace().negate().indexIn(lines.get(i)); - if (lineIdx >= 0 && (startCol == -1 || lineIdx < startCol)) { - startCol = lineIdx; - } - } - - // output the first line at the current column - builder.append(lines.get(0)); - - // output all trailing lines with plausible indentation - for (int i = 1; i < lines.size(); ++i) { - builder.append(lineSeparator).append(Strings.repeat(" ", column0)); - // check that startCol is valid index, e.g. for blank lines - if (lines.get(i).length() >= startCol) { - builder.append(lines.get(i).substring(startCol)); - } else { - builder.append(lines.get(i)); - } - } - return builder.toString(); + // Wraps and re-indents line comments. + private String indentLineComments(List lines, int column0) { + lines = wrapLineComments(lines, column0); + StringBuilder builder = new StringBuilder(); + builder.append(lines.get(0).trim()); + String indentString = Strings.repeat(" ", column0); + for (int i = 1; i < lines.size(); ++i) { + builder.append(lineSeparator).append(indentString).append(lines.get(i).trim()); } + return builder.toString(); + } - // Wraps and re-indents line comments. - private String indentLineComments(List lines, int column0) { - lines = wrapLineComments(lines, column0); - StringBuilder builder = new StringBuilder(); - builder.append(lines.get(0).trim()); - String indentString = Strings.repeat(" ", column0); - for (int i = 1; i < lines.size(); ++i) { - builder.append(lineSeparator).append(indentString).append(lines.get(i).trim()); + // Preserve special `//noinspection` and `//$NON-NLS-x$` comments used by IDEs, which cannot + // contain leading spaces. + private static final Pattern LINE_COMMENT_MISSING_SPACE_PREFIX = + Pattern.compile("^(//+)(?!noinspection|\\$NON-NLS-\\d+\\$)[^\\s/]"); + + private List wrapLineComments(List lines, int column0) { + List result = new ArrayList<>(); + for (String line : lines) { + // Add missing leading spaces to line comments: `//foo` -> `// foo`. + Matcher matcher = LINE_COMMENT_MISSING_SPACE_PREFIX.matcher(line); + if (matcher.find()) { + int length = matcher.group(1).length(); + line = Strings.repeat("/", length) + " " + line.substring(length); + } + if (line.startsWith("// MOE:")) { + // don't wrap comments for https://github.com/google/MOE + result.add(line); + continue; + } + while (line.length() + column0 > Formatter.MAX_LINE_LENGTH) { + int idx = Formatter.MAX_LINE_LENGTH - column0; + // only break on whitespace characters, and ignore the leading `// ` + while (idx >= 2 && !CharMatcher.whitespace().matches(line.charAt(idx))) { + idx--; } - return builder.toString(); + if (idx <= 2) { + break; + } + result.add(line.substring(0, idx)); + line = "//" + line.substring(idx); + } + result.add(line); } + return result; + } - // Preserve special `//noinspection` and `//$NON-NLS-x$` comments used by IDEs, which cannot - // contain leading spaces. - private static final Pattern LINE_COMMENT_MISSING_SPACE_PREFIX = - Pattern.compile("^(//+)(?!noinspection|\\$NON-NLS-\\d+\\$)[^\\s/]"); - - private List wrapLineComments(List lines, int column0) { - List result = new ArrayList<>(); - for (String line : lines) { - // Add missing leading spaces to line comments: `//foo` -> `// foo`. - Matcher matcher = LINE_COMMENT_MISSING_SPACE_PREFIX.matcher(line); - if (matcher.find()) { - int length = matcher.group(1).length(); - line = Strings.repeat("/", length) + " " + line.substring(length); - } - if (line.startsWith("// MOE:")) { - // don't wrap comments for https://github.com/google/MOE - result.add(line); - continue; - } - while (line.length() + column0 > Formatter.MAX_LINE_LENGTH) { - int idx = Formatter.MAX_LINE_LENGTH - column0; - // only break on whitespace characters, and ignore the leading `// ` - while (idx >= 2 && !CharMatcher.whitespace().matches(line.charAt(idx))) { - idx--; - } - if (idx <= 2) { - break; - } - result.add(line.substring(0, idx)); - line = "//" + line.substring(idx); - } - result.add(line); - } - return result; + // Remove leading whitespace (trailing was already removed), and re-indent. + // Add a +1 indent before '*', and add the '*' if necessary. + private String indentJavadoc(List lines, int column0) { + StringBuilder builder = new StringBuilder(); + builder.append(lines.get(0).trim()); + int indent = column0 + 1; + String indentString = Strings.repeat(" ", indent); + for (int i = 1; i < lines.size(); ++i) { + builder.append(lineSeparator).append(indentString); + String line = lines.get(i).trim(); + if (!line.startsWith("*")) { + builder.append("* "); + } + builder.append(line); } + return builder.toString(); + } - // Remove leading whitespace (trailing was already removed), and re-indent. - // Add a +1 indent before '*', and add the '*' if necessary. - private String indentJavadoc(List lines, int column0) { - StringBuilder builder = new StringBuilder(); - builder.append(lines.get(0).trim()); - int indent = column0 + 1; - String indentString = Strings.repeat(" ", indent); - for (int i = 1; i < lines.size(); ++i) { - builder.append(lineSeparator).append(indentString); - String line = lines.get(i).trim(); - if (!line.startsWith("*")) { - builder.append("* "); - } - builder.append(line); - } - return builder.toString(); + // Returns true if the comment looks like javadoc + private static boolean javadocShaped(List lines) { + Iterator it = lines.iterator(); + if (!it.hasNext()) { + return false; } - - // Returns true if the comment looks like javadoc - private static boolean javadocShaped(List lines) { - Iterator it = lines.iterator(); - if (!it.hasNext()) { - return false; - } - String first = it.next().trim(); - // if it's actually javadoc, we're done - if (first.startsWith("/**")) { - return true; - } - // if it's a block comment, check all trailing lines for '*' - if (!first.startsWith("/*")) { - return false; - } - while (it.hasNext()) { - if (!it.next().trim().startsWith("*")) { - return false; - } - } - return true; + String first = it.next().trim(); + // if it's actually javadoc, we're done + if (first.startsWith("/**")) { + return true; } + // if it's a block comment, check all trailing lines for '*' + if (!first.startsWith("/*")) { + return false; + } + while (it.hasNext()) { + if (!it.next().trim().startsWith("*")) { + return false; + } + } + return true; + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/JavaFormatterOptions.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/JavaFormatterOptions.java index 280a50f6c..fbb6fe7e4 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/JavaFormatterOptions.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/JavaFormatterOptions.java @@ -29,75 +29,75 @@ import com.google.errorprone.annotations.Immutable; @Immutable public class JavaFormatterOptions { - public enum Style { - /** The default Google Java Style configuration. */ - GOOGLE(1), + public enum Style { + /** The default Google Java Style configuration. */ + GOOGLE(1), - /** The AOSP-compliant configuration. */ - AOSP(2); + /** The AOSP-compliant configuration. */ + AOSP(2); - private final int indentationMultiplier; + private final int indentationMultiplier; - Style(int indentationMultiplier) { - this.indentationMultiplier = indentationMultiplier; - } - - int indentationMultiplier() { - return indentationMultiplier; - } + Style(int indentationMultiplier) { + this.indentationMultiplier = indentationMultiplier; } - private final Style style; - private final boolean formatJavadoc; + int indentationMultiplier() { + return indentationMultiplier; + } + } - private JavaFormatterOptions(Style style, boolean formatJavadoc) { - this.style = style; - this.formatJavadoc = formatJavadoc; + private final Style style; + private final boolean formatJavadoc; + + private JavaFormatterOptions(Style style, boolean formatJavadoc) { + this.style = style; + this.formatJavadoc = formatJavadoc; + } + + /** Returns the multiplier for the unit of indent. */ + public int indentationMultiplier() { + return style.indentationMultiplier(); + } + + public boolean formatJavadoc() { + return formatJavadoc; + } + + /** Returns the code style. */ + public Style style() { + return style; + } + + /** Returns the default formatting options. */ + public static JavaFormatterOptions defaultOptions() { + return builder().build(); + } + + /** Returns a builder for {@link JavaFormatterOptions}. */ + public static Builder builder() { + return new Builder(); + } + + /** A builder for {@link JavaFormatterOptions}. */ + public static class Builder { + private Style style = Style.GOOGLE; + private boolean formatJavadoc = true; + + private Builder() {} + + public Builder style(Style style) { + this.style = style; + return this; } - /** Returns the multiplier for the unit of indent. */ - public int indentationMultiplier() { - return style.indentationMultiplier(); + public Builder formatJavadoc(boolean formatJavadoc) { + this.formatJavadoc = formatJavadoc; + return this; } - public boolean formatJavadoc() { - return formatJavadoc; - } - - /** Returns the code style. */ - public Style style() { - return style; - } - - /** Returns the default formatting options. */ - public static JavaFormatterOptions defaultOptions() { - return builder().build(); - } - - /** Returns a builder for {@link JavaFormatterOptions}. */ - public static Builder builder() { - return new Builder(); - } - - /** A builder for {@link JavaFormatterOptions}. */ - public static class Builder { - private Style style = Style.GOOGLE; - private boolean formatJavadoc = true; - - private Builder() {} - - public Builder style(Style style) { - this.style = style; - return this; - } - - public Builder formatJavadoc(boolean formatJavadoc) { - this.formatJavadoc = formatJavadoc; - return this; - } - - public JavaFormatterOptions build() { - return new JavaFormatterOptions(style, formatJavadoc); - } + public JavaFormatterOptions build() { + return new JavaFormatterOptions(style, formatJavadoc); } + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/JavaInput.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/JavaInput.java index 5d4a9b955..ae6d6f6d6 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/JavaInput.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/JavaInput.java @@ -57,641 +57,619 @@ import javax.tools.SimpleJavaFileObject; /** {@code JavaInput} extends {@link Input} to represent a Java input document. */ public final class JavaInput extends Input { + /** + * A {@code JavaInput} is a sequence of {@link Tok}s that cover the Java input. A {@link Tok} is + * either a token (if {@code isToken()}), or a non-token, which is a comment (if {@code + * isComment()}) or a newline (if {@code isNewline()}) or a maximal sequence of other whitespace + * characters (if {@code isSpaces()}). Each {@link Tok} contains a sequence of characters, an + * index (sequential starting at {@code 0} for tokens and comments, else {@code -1}), and a + * ({@code 0}-origin) position in the input. The concatenation of the texts of all the {@link + * Tok}s equals the input. Each Input ends with a token EOF {@link Tok}, with empty text. + * + *

A {@code /*} comment possibly contains newlines; a {@code //} comment does not contain the + * terminating newline character, but is followed by a newline {@link Tok}. + */ + static final class Tok implements Input.Tok { + private final int index; + private final String originalText; + private final String text; + private final int position; + private final int columnI; + private final boolean isToken; + private final TokenKind kind; + /** - * A {@code JavaInput} is a sequence of {@link Tok}s that cover the Java input. A {@link Tok} is - * either a token (if {@code isToken()}), or a non-token, which is a comment (if {@code - * isComment()}) or a newline (if {@code isNewline()}) or a maximal sequence of other whitespace - * characters (if {@code isSpaces()}). Each {@link Tok} contains a sequence of characters, an - * index (sequential starting at {@code 0} for tokens and comments, else {@code -1}), and a - * ({@code 0}-origin) position in the input. The concatenation of the texts of all the {@link - * Tok}s equals the input. Each Input ends with a token EOF {@link Tok}, with empty text. + * The {@code Tok} constructor. * - *

A {@code /*} comment possibly contains newlines; a {@code //} comment does not contain the - * terminating newline character, but is followed by a newline {@link Tok}. + * @param index its index + * @param originalText its original text, before removing Unicode escapes + * @param text its text after removing Unicode escapes + * @param position its {@code 0}-origin position in the input + * @param columnI its {@code 0}-origin column number in the input + * @param isToken whether the {@code Tok} is a token + * @param kind the token kind */ - static final class Tok implements Input.Tok { - private final int index; - private final String originalText; - private final String text; - private final int position; - private final int columnI; - private final boolean isToken; - private final TokenKind kind; - - /** - * The {@code Tok} constructor. - * - * @param index its index - * @param originalText its original text, before removing Unicode escapes - * @param text its text after removing Unicode escapes - * @param position its {@code 0}-origin position in the input - * @param columnI its {@code 0}-origin column number in the input - * @param isToken whether the {@code Tok} is a token - * @param kind the token kind - */ - Tok( - int index, - String originalText, - String text, - int position, - int columnI, - boolean isToken, - TokenKind kind) { - this.index = index; - this.originalText = originalText; - this.text = text; - this.position = position; - this.columnI = columnI; - this.isToken = isToken; - this.kind = kind; - } - - @Override - public int getIndex() { - return index; - } - - @Override - public String getText() { - return text; - } - - @Override - public String getOriginalText() { - return originalText; - } - - @Override - public int length() { - return originalText.length(); - } - - @Override - public int getPosition() { - return position; - } - - @Override - public int getColumn() { - return columnI; - } - - boolean isToken() { - return isToken; - } - - @Override - public boolean isNewline() { - return Newlines.isNewline(text); - } - - @Override - public boolean isSlashSlashComment() { - return text.startsWith("//"); - } - - @Override - public boolean isSlashStarComment() { - return text.startsWith("/*"); - } - - @Override - public boolean isJavadocComment() { - // comments like `/***` are also javadoc, but their formatting probably won't be - // improved - // by the javadoc formatter - return text.startsWith("/**") - && text.charAt("/**".length()) != '*' - && text.length() > 4; - } - - @Override - public boolean isComment() { - return isSlashSlashComment() || isSlashStarComment(); - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("index", index) - .add("text", text) - .add("position", position) - .add("columnI", columnI) - .add("isToken", isToken) - .toString(); - } - - public TokenKind kind() { - return kind; - } + Tok( + int index, + String originalText, + String text, + int position, + int columnI, + boolean isToken, + TokenKind kind) { + this.index = index; + this.originalText = originalText; + this.text = text; + this.position = position; + this.columnI = columnI; + this.isToken = isToken; + this.kind = kind; } - /** - * A {@link Token} contains a token {@link Tok} and its associated non-tokens; each non-token - * {@link Tok} belongs to one {@link Token}. Each {@link Token} has an immutable list of its - * non-tokens that appear before it, and another list of its non-tokens that appear after it. - * The concatenation of the texts of all the {@link Token}s' {@link Tok}s, each preceded by the - * texts of its {@code toksBefore} and followed by the texts of its {@code toksAfter}, equals - * the input. - */ - static final class Token implements Input.Token { - private final Tok tok; - private final ImmutableList toksBefore; - private final ImmutableList toksAfter; - - /** - * Token constructor. - * - * @param toksBefore the earlier non-token {link Tok}s assigned to this {@code Token} - * @param tok this token {@link Tok} - * @param toksAfter the later non-token {link Tok}s assigned to this {@code Token} - */ - Token(List toksBefore, Tok tok, List toksAfter) { - this.toksBefore = ImmutableList.copyOf(toksBefore); - this.tok = tok; - this.toksAfter = ImmutableList.copyOf(toksAfter); - } - - /** - * Get the token's {@link Tok}. - * - * @return the token's {@link Tok} - */ - @Override - public Tok getTok() { - return tok; - } - - /** - * Get the earlier {@link Tok}s assigned to this {@code Token}. - * - * @return the earlier {@link Tok}s assigned to this {@code Token} - */ - @Override - public ImmutableList getToksBefore() { - return toksBefore; - } - - /** - * Get the later {@link Tok}s assigned to this {@code Token}. - * - * @return the later {@link Tok}s assigned to this {@code Token} - */ - @Override - public ImmutableList getToksAfter() { - return toksAfter; - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("tok", tok) - .add("toksBefore", toksBefore) - .add("toksAfter", toksAfter) - .toString(); - } - } - - private final String text; // The input. - private int kN; // The number of numbered toks (tokens or comments), excluding the EOF. - - /* - * The following lists record the sequential indices of the {@code Tok}s on each input line. (Only - * tokens and comments have sequential indices.) Tokens and {@code //} comments lie on just one - * line; {@code /*} comments can lie on multiple lines. These data structures (along with - * equivalent ones for the formatted output) let us compute correspondences between the input and - * output. - */ - - private final ImmutableMap positionToColumnMap; // Map Tok position to column. - private final ImmutableList tokens; // The Tokens for this input. - private final ImmutableRangeMap positionTokenMap; // Map position to Token. - - /** Map from Tok index to the associated Token. */ - private final Token[] kToToken; - - /** - * Input constructor. - * - * @param text the input text - * @throws FormatterException if the input cannot be parsed - */ - public JavaInput(String text) throws FormatterException { - this.text = checkNotNull(text); - setLines(ImmutableList.copyOf(Newlines.lineIterator(text))); - ImmutableList toks = buildToks(text); - positionToColumnMap = makePositionToColumnMap(toks); - tokens = buildTokens(toks); - ImmutableRangeMap.Builder tokenLocations = ImmutableRangeMap.builder(); - for (Token token : tokens) { - Input.Tok end = JavaOutput.endTok(token); - int upper = end.getPosition(); - if (!end.getText().isEmpty()) { - upper += end.length() - 1; - } - tokenLocations.put( - Range.closed(JavaOutput.startTok(token).getPosition(), upper), token); - } - positionTokenMap = tokenLocations.build(); - - // adjust kN for EOF - kToToken = new Token[kN + 1]; - for (Token token : tokens) { - for (Input.Tok tok : token.getToksBefore()) { - if (tok.getIndex() < 0) { - continue; - } - kToToken[tok.getIndex()] = token; - } - kToToken[token.getTok().getIndex()] = token; - for (Input.Tok tok : token.getToksAfter()) { - if (tok.getIndex() < 0) { - continue; - } - kToToken[tok.getIndex()] = token; - } - } - } - - private static ImmutableMap makePositionToColumnMap(List toks) { - ImmutableMap.Builder builder = ImmutableMap.builder(); - for (Tok tok : toks) { - builder.put(tok.getPosition(), tok.getColumn()); - } - return builder.buildOrThrow(); - } - - /** - * Get the input text. - * - * @return the input text - */ @Override - public String getText() { - return text; + public int getIndex() { + return index; } @Override - public ImmutableMap getPositionToColumnMap() { - return positionToColumnMap; + public String getText() { + return text; } - /** Lex the input and build the list of toks. */ - private ImmutableList buildToks(String text) throws FormatterException { - ImmutableList toks = buildToks(text, ImmutableSet.of()); - kN = getLast(toks).getIndex(); - computeRanges(toks); - return toks; - } - - /** - * Lex the input and build the list of toks. - * - * @param text the text to be lexed. - * @param stopTokens a set of tokens which should cause lexing to stop. If one of these is - * found, the returned list will include tokens up to but not including that token. - */ - static ImmutableList buildToks(String text, ImmutableSet stopTokens) - throws FormatterException { - stopTokens = - ImmutableSet.builder().addAll(stopTokens).add(TokenKind.EOF).build(); - Context context = new Context(); - Options.instance(context).put("--enable-preview", "true"); - new JavacFileManager(context, true, UTF_8); - DiagnosticCollector diagnosticCollector = new DiagnosticCollector<>(); - context.put(DiagnosticListener.class, diagnosticCollector); - Log log = Log.instance(context); - log.useSource( - new SimpleJavaFileObject(URI.create("Source.java"), Kind.SOURCE) { - @Override - public CharSequence getCharContent(boolean ignoreEncodingErrors) - throws IOException { - return text; - } - }); - DeferredDiagnosticHandler diagnostics = new DeferredDiagnosticHandler(log); - ImmutableList rawToks = JavacTokens.getTokens(text, context, stopTokens); - if (diagnostics.getDiagnostics().stream() - .anyMatch(d -> d.getKind() == Diagnostic.Kind.ERROR)) { - return ImmutableList.of(new Tok(0, "", "", 0, 0, true, null)); // EOF - } - int kN = 0; - List toks = new ArrayList<>(); - int charI = 0; - int columnI = 0; - for (RawTok t : rawToks) { - if (stopTokens.contains(t.kind())) { - break; - } - int charI0 = t.pos(); - // Get string, possibly with Unicode escapes. - String originalTokText = text.substring(charI0, t.endPos()); - String tokText = - t.kind() == TokenKind.STRINGLITERAL - ? t.stringVal() // Unicode escapes removed. - : originalTokText; - char tokText0 = tokText.charAt(0); // The token's first character. - final boolean isToken; // Is this tok a token? - final boolean isNumbered; // Is this tok numbered? (tokens and comments) - String extraNewline = null; // Extra newline at end? - List strings = new ArrayList<>(); - if (Character.isWhitespace(tokText0)) { - isToken = false; - isNumbered = false; - Iterator it = Newlines.lineIterator(originalTokText); - while (it.hasNext()) { - String line = it.next(); - String newline = Newlines.getLineEnding(line); - if (newline != null) { - String spaces = line.substring(0, line.length() - newline.length()); - if (!spaces.isEmpty()) { - strings.add(spaces); - } - strings.add(newline); - } else if (!line.isEmpty()) { - strings.add(line); - } - } - } else if (tokText.startsWith("'") || tokText.startsWith("\"")) { - isToken = true; - isNumbered = true; - strings.add(originalTokText); - } else if (tokText.startsWith("//") || tokText.startsWith("/*")) { - // For compatibility with an earlier lexer, the newline after a // comment is its - // own tok. - if (tokText.startsWith("//") - && (originalTokText.endsWith("\n") || originalTokText.endsWith("\r"))) { - extraNewline = Newlines.getLineEnding(originalTokText); - tokText = tokText.substring(0, tokText.length() - extraNewline.length()); - originalTokText = - originalTokText.substring( - 0, originalTokText.length() - extraNewline.length()); - } - isToken = false; - isNumbered = true; - strings.add(originalTokText); - } else if (Character.isJavaIdentifierStart(tokText0) - || Character.isDigit(tokText0) - || (tokText0 == '.' - && tokText.length() > 1 - && Character.isDigit(tokText.charAt(1)))) { - // Identifier, keyword, or numeric literal (a dot may begin a number, as in .2D). - isToken = true; - isNumbered = true; - strings.add(tokText); - } else { - // Other tokens ("+" or "++" or ">>" are broken into one-character toks, because - // ">>" - // cannot be lexed without syntactic knowledge. This implementation fails if the - // token - // contains Unicode escapes. - isToken = true; - isNumbered = true; - for (char c : tokText.toCharArray()) { - strings.add(String.valueOf(c)); - } - } - if (strings.size() == 1) { - toks.add( - new Tok( - isNumbered ? kN++ : -1, - originalTokText, - tokText, - charI, - columnI, - isToken, - t.kind())); - charI += originalTokText.length(); - columnI = updateColumn(columnI, originalTokText); - - } else { - if (strings.size() != 1 && !tokText.equals(originalTokText)) { - throw new FormatterException( - "Unicode escapes not allowed in whitespace or multi-character" - + " operators"); - } - for (String str : strings) { - toks.add( - new Tok( - isNumbered ? kN++ : -1, - str, - str, - charI, - columnI, - isToken, - null)); - charI += str.length(); - columnI = updateColumn(columnI, originalTokText); - } - } - if (extraNewline != null) { - toks.add(new Tok(-1, extraNewline, extraNewline, charI, columnI, false, null)); - columnI = 0; - charI += extraNewline.length(); - } - } - toks.add(new Tok(kN, "", "", charI, columnI, true, null)); // EOF tok. - return ImmutableList.copyOf(toks); - } - - private static int updateColumn(int columnI, String originalTokText) { - Integer last = Iterators.getLast(Newlines.lineOffsetIterator(originalTokText)); - if (last > 0) { - columnI = originalTokText.length() - last; - } else { - columnI += originalTokText.length(); - } - return columnI; - } - - private static ImmutableList buildTokens(List toks) { - ImmutableList.Builder tokens = ImmutableList.builder(); - int k = 0; - int kN = toks.size(); - - // Remaining non-tokens before the token go here. - ImmutableList.Builder toksBefore = ImmutableList.builder(); - - OUTERMOST: - while (k < kN) { - while (!toks.get(k).isToken()) { - Tok tok = toks.get(k++); - toksBefore.add(tok); - if (isParamComment(tok)) { - while (toks.get(k).isNewline()) { - // drop newlines after parameter comments - k++; - } - } - } - Tok tok = toks.get(k++); - - // Non-tokens starting on the same line go here too. - ImmutableList.Builder toksAfter = ImmutableList.builder(); - OUTER: - while (k < kN && !toks.get(k).isToken()) { - // Don't attach inline comments to certain leading tokens, e.g. for - // `f(/*flag1=*/true). - // - // Attaching inline comments to the right token is hard, and this barely - // scratches the surface. But it's enough to do a better job with parameter - // name comments. - // - // TODO(cushon): find a better strategy. - if (toks.get(k).isSlashStarComment()) { - switch (tok.getText()) { - case "(": - case "<": - case ".": - break OUTER; - default: - break; - } - } - if (toks.get(k).isJavadocComment()) { - switch (tok.getText()) { - case ";": - break OUTER; - default: - break; - } - } - if (isParamComment(toks.get(k))) { - tokens.add(new Token(toksBefore.build(), tok, toksAfter.build())); - toksBefore = ImmutableList.builder().add(toks.get(k++)); - // drop newlines after parameter comments - while (toks.get(k).isNewline()) { - k++; - } - continue OUTERMOST; - } - Tok nonTokenAfter = toks.get(k++); - toksAfter.add(nonTokenAfter); - if (Newlines.containsBreaks(nonTokenAfter.getText())) { - break; - } - } - tokens.add(new Token(toksBefore.build(), tok, toksAfter.build())); - toksBefore = ImmutableList.builder(); - } - return tokens.build(); - } - - private static boolean isParamComment(Tok tok) { - return tok.isSlashStarComment() - && tok.getText().matches("\\/\\*[A-Za-z0-9\\s_\\-]+=\\s*\\*\\/"); - } - - /** - * Convert from a character range to a token range. - * - * @param characterRange the {@code 0}-based {@link Range} of characters - * @return the {@code 0}-based {@link Range} of tokens - * @throws FormatterException if the upper endpoint of the range is outside the file - */ - Range characterRangeToTokenRange(Range characterRange) - throws FormatterException { - if (characterRange.upperEndpoint() > text.length()) { - throw new FormatterException( - String.format( - "error: invalid length %d, offset + length (%d) is outside the file", - characterRange.upperEndpoint() - characterRange.lowerEndpoint(), - characterRange.upperEndpoint())); - } - // empty range stands for "format the line under the cursor" - Range nonEmptyRange = - characterRange.isEmpty() - ? Range.closedOpen( - characterRange.lowerEndpoint(), characterRange.lowerEndpoint() + 1) - : characterRange; - ImmutableCollection enclosed = - getPositionTokenMap().subRangeMap(nonEmptyRange).asMapOfRanges().values(); - if (enclosed.isEmpty()) { - return EMPTY_RANGE; - } - return Range.closedOpen( - enclosed.iterator().next().getTok().getIndex(), - getLast(enclosed).getTok().getIndex() + 1); - } - - /** - * Get the number of toks. - * - * @return the number of toks, excluding the EOF tok - */ @Override - public int getkN() { - return kN; + public String getOriginalText() { + return originalText; } - /** - * Get the Token by index. - * - * @param k the Tok index - */ @Override - public Token getToken(int k) { - return kToToken[k]; + public int length() { + return originalText.length(); } - /** - * Get the input tokens. - * - * @return the input tokens - */ @Override - public ImmutableList getTokens() { - return tokens; + public int getPosition() { + return position; } - /** - * Get the navigable map from position to {@link Token}. Used to look for tokens following a - * given one, and to implement the --offset and --length flags to reformat a character range in - * the input file. - * - * @return the navigable map from position to {@link Token} - */ @Override - public ImmutableRangeMap getPositionTokenMap() { - return positionTokenMap; + public int getColumn() { + return columnI; + } + + boolean isToken() { + return isToken; + } + + @Override + public boolean isNewline() { + return Newlines.isNewline(text); + } + + @Override + public boolean isSlashSlashComment() { + return text.startsWith("//"); + } + + @Override + public boolean isSlashStarComment() { + return text.startsWith("/*"); + } + + @Override + public boolean isJavadocComment() { + // comments like `/***` are also javadoc, but their formatting probably won't be + // improved + // by the javadoc formatter + return text.startsWith("/**") && text.charAt("/**".length()) != '*' && text.length() > 4; + } + + @Override + public boolean isComment() { + return isSlashSlashComment() || isSlashStarComment(); } @Override public String toString() { - return MoreObjects.toStringHelper(this) - .add("tokens", tokens) - .add("super", super.toString()) - .toString(); + return MoreObjects.toStringHelper(this) + .add("index", index) + .add("text", text) + .add("position", position) + .add("columnI", columnI) + .add("isToken", isToken) + .toString(); } - private JCCompilationUnit unit; + public TokenKind kind() { + return kind; + } + } + /** + * A {@link Token} contains a token {@link Tok} and its associated non-tokens; each non-token + * {@link Tok} belongs to one {@link Token}. Each {@link Token} has an immutable list of its + * non-tokens that appear before it, and another list of its non-tokens that appear after it. The + * concatenation of the texts of all the {@link Token}s' {@link Tok}s, each preceded by the texts + * of its {@code toksBefore} and followed by the texts of its {@code toksAfter}, equals the input. + */ + static final class Token implements Input.Token { + private final Tok tok; + private final ImmutableList toksBefore; + private final ImmutableList toksAfter; + + /** + * Token constructor. + * + * @param toksBefore the earlier non-token {link Tok}s assigned to this {@code Token} + * @param tok this token {@link Tok} + * @param toksAfter the later non-token {link Tok}s assigned to this {@code Token} + */ + Token(List toksBefore, Tok tok, List toksAfter) { + this.toksBefore = ImmutableList.copyOf(toksBefore); + this.tok = tok; + this.toksAfter = ImmutableList.copyOf(toksAfter); + } + + /** + * Get the token's {@link Tok}. + * + * @return the token's {@link Tok} + */ @Override - public int getLineNumber(int inputPosition) { - Verify.verifyNotNull(unit, "Expected compilation unit to be set."); - return unit.getLineMap().getLineNumber(inputPosition); + public Tok getTok() { + return tok; + } + + /** + * Get the earlier {@link Tok}s assigned to this {@code Token}. + * + * @return the earlier {@link Tok}s assigned to this {@code Token} + */ + @Override + public ImmutableList getToksBefore() { + return toksBefore; + } + + /** + * Get the later {@link Tok}s assigned to this {@code Token}. + * + * @return the later {@link Tok}s assigned to this {@code Token} + */ + @Override + public ImmutableList getToksAfter() { + return toksAfter; } @Override - public int getColumnNumber(int inputPosition) { - Verify.verifyNotNull(unit, "Expected compilation unit to be set."); - return unit.getLineMap().getColumnNumber(inputPosition); + public String toString() { + return MoreObjects.toStringHelper(this) + .add("tok", tok) + .add("toksBefore", toksBefore) + .add("toksAfter", toksAfter) + .toString(); } + } - // TODO(cushon): refactor JavaInput so the CompilationUnit can be passed into - // the constructor. - public void setCompilationUnit(JCCompilationUnit unit) { - this.unit = unit; + private final String text; // The input. + private int kN; // The number of numbered toks (tokens or comments), excluding the EOF. + + /* + * The following lists record the sequential indices of the {@code Tok}s on each input line. (Only + * tokens and comments have sequential indices.) Tokens and {@code //} comments lie on just one + * line; {@code /*} comments can lie on multiple lines. These data structures (along with + * equivalent ones for the formatted output) let us compute correspondences between the input and + * output. + */ + + private final ImmutableMap positionToColumnMap; // Map Tok position to column. + private final ImmutableList tokens; // The Tokens for this input. + private final ImmutableRangeMap positionTokenMap; // Map position to Token. + + /** Map from Tok index to the associated Token. */ + private final Token[] kToToken; + + /** + * Input constructor. + * + * @param text the input text + * @throws FormatterException if the input cannot be parsed + */ + public JavaInput(String text) throws FormatterException { + this.text = checkNotNull(text); + setLines(ImmutableList.copyOf(Newlines.lineIterator(text))); + ImmutableList toks = buildToks(text); + positionToColumnMap = makePositionToColumnMap(toks); + tokens = buildTokens(toks); + ImmutableRangeMap.Builder tokenLocations = ImmutableRangeMap.builder(); + for (Token token : tokens) { + Input.Tok end = JavaOutput.endTok(token); + int upper = end.getPosition(); + if (!end.getText().isEmpty()) { + upper += end.length() - 1; + } + tokenLocations.put(Range.closed(JavaOutput.startTok(token).getPosition(), upper), token); } + positionTokenMap = tokenLocations.build(); - public RangeSet characterRangesToTokenRanges( - Collection> characterRanges) throws FormatterException { - RangeSet tokenRangeSet = TreeRangeSet.create(); - for (Range characterRange : characterRanges) { - tokenRangeSet.add( - characterRangeToTokenRange( - characterRange.canonical(DiscreteDomain.integers()))); + // adjust kN for EOF + kToToken = new Token[kN + 1]; + for (Token token : tokens) { + for (Input.Tok tok : token.getToksBefore()) { + if (tok.getIndex() < 0) { + continue; } - return tokenRangeSet; + kToToken[tok.getIndex()] = token; + } + kToToken[token.getTok().getIndex()] = token; + for (Input.Tok tok : token.getToksAfter()) { + if (tok.getIndex() < 0) { + continue; + } + kToToken[tok.getIndex()] = token; + } } + } + + private static ImmutableMap makePositionToColumnMap(List toks) { + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (Tok tok : toks) { + builder.put(tok.getPosition(), tok.getColumn()); + } + return builder.buildOrThrow(); + } + + /** + * Get the input text. + * + * @return the input text + */ + @Override + public String getText() { + return text; + } + + @Override + public ImmutableMap getPositionToColumnMap() { + return positionToColumnMap; + } + + /** Lex the input and build the list of toks. */ + private ImmutableList buildToks(String text) throws FormatterException { + ImmutableList toks = buildToks(text, ImmutableSet.of()); + kN = getLast(toks).getIndex(); + computeRanges(toks); + return toks; + } + + /** + * Lex the input and build the list of toks. + * + * @param text the text to be lexed. + * @param stopTokens a set of tokens which should cause lexing to stop. If one of these is found, + * the returned list will include tokens up to but not including that token. + */ + static ImmutableList buildToks(String text, ImmutableSet stopTokens) + throws FormatterException { + stopTokens = ImmutableSet.builder().addAll(stopTokens).add(TokenKind.EOF).build(); + Context context = new Context(); + Options.instance(context).put("--enable-preview", "true"); + new JavacFileManager(context, true, UTF_8); + DiagnosticCollector diagnosticCollector = new DiagnosticCollector<>(); + context.put(DiagnosticListener.class, diagnosticCollector); + Log log = Log.instance(context); + log.useSource( + new SimpleJavaFileObject(URI.create("Source.java"), Kind.SOURCE) { + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { + return text; + } + }); + DeferredDiagnosticHandler diagnostics = new DeferredDiagnosticHandler(log); + ImmutableList rawToks = JavacTokens.getTokens(text, context, stopTokens); + if (diagnostics.getDiagnostics().stream().anyMatch(d -> d.getKind() == Diagnostic.Kind.ERROR)) { + return ImmutableList.of(new Tok(0, "", "", 0, 0, true, null)); // EOF + } + int kN = 0; + List toks = new ArrayList<>(); + int charI = 0; + int columnI = 0; + for (RawTok t : rawToks) { + if (stopTokens.contains(t.kind())) { + break; + } + int charI0 = t.pos(); + // Get string, possibly with Unicode escapes. + String originalTokText = text.substring(charI0, t.endPos()); + String tokText = + t.kind() == TokenKind.STRINGLITERAL + ? t.stringVal() // Unicode escapes removed. + : originalTokText; + char tokText0 = tokText.charAt(0); // The token's first character. + final boolean isToken; // Is this tok a token? + final boolean isNumbered; // Is this tok numbered? (tokens and comments) + String extraNewline = null; // Extra newline at end? + List strings = new ArrayList<>(); + if (Character.isWhitespace(tokText0)) { + isToken = false; + isNumbered = false; + Iterator it = Newlines.lineIterator(originalTokText); + while (it.hasNext()) { + String line = it.next(); + String newline = Newlines.getLineEnding(line); + if (newline != null) { + String spaces = line.substring(0, line.length() - newline.length()); + if (!spaces.isEmpty()) { + strings.add(spaces); + } + strings.add(newline); + } else if (!line.isEmpty()) { + strings.add(line); + } + } + } else if (tokText.startsWith("'") || tokText.startsWith("\"")) { + isToken = true; + isNumbered = true; + strings.add(originalTokText); + } else if (tokText.startsWith("//") || tokText.startsWith("/*")) { + // For compatibility with an earlier lexer, the newline after a // comment is its + // own tok. + if (tokText.startsWith("//") + && (originalTokText.endsWith("\n") || originalTokText.endsWith("\r"))) { + extraNewline = Newlines.getLineEnding(originalTokText); + tokText = tokText.substring(0, tokText.length() - extraNewline.length()); + originalTokText = + originalTokText.substring(0, originalTokText.length() - extraNewline.length()); + } + isToken = false; + isNumbered = true; + strings.add(originalTokText); + } else if (Character.isJavaIdentifierStart(tokText0) + || Character.isDigit(tokText0) + || (tokText0 == '.' && tokText.length() > 1 && Character.isDigit(tokText.charAt(1)))) { + // Identifier, keyword, or numeric literal (a dot may begin a number, as in .2D). + isToken = true; + isNumbered = true; + strings.add(tokText); + } else { + // Other tokens ("+" or "++" or ">>" are broken into one-character toks, because + // ">>" + // cannot be lexed without syntactic knowledge. This implementation fails if the + // token + // contains Unicode escapes. + isToken = true; + isNumbered = true; + for (char c : tokText.toCharArray()) { + strings.add(String.valueOf(c)); + } + } + if (strings.size() == 1) { + toks.add( + new Tok( + isNumbered ? kN++ : -1, + originalTokText, + tokText, + charI, + columnI, + isToken, + t.kind())); + charI += originalTokText.length(); + columnI = updateColumn(columnI, originalTokText); + + } else { + if (strings.size() != 1 && !tokText.equals(originalTokText)) { + throw new FormatterException( + "Unicode escapes not allowed in whitespace or multi-character" + " operators"); + } + for (String str : strings) { + toks.add(new Tok(isNumbered ? kN++ : -1, str, str, charI, columnI, isToken, null)); + charI += str.length(); + columnI = updateColumn(columnI, originalTokText); + } + } + if (extraNewline != null) { + toks.add(new Tok(-1, extraNewline, extraNewline, charI, columnI, false, null)); + columnI = 0; + charI += extraNewline.length(); + } + } + toks.add(new Tok(kN, "", "", charI, columnI, true, null)); // EOF tok. + return ImmutableList.copyOf(toks); + } + + private static int updateColumn(int columnI, String originalTokText) { + Integer last = Iterators.getLast(Newlines.lineOffsetIterator(originalTokText)); + if (last > 0) { + columnI = originalTokText.length() - last; + } else { + columnI += originalTokText.length(); + } + return columnI; + } + + private static ImmutableList buildTokens(List toks) { + ImmutableList.Builder tokens = ImmutableList.builder(); + int k = 0; + int kN = toks.size(); + + // Remaining non-tokens before the token go here. + ImmutableList.Builder toksBefore = ImmutableList.builder(); + + OUTERMOST: + while (k < kN) { + while (!toks.get(k).isToken()) { + Tok tok = toks.get(k++); + toksBefore.add(tok); + if (isParamComment(tok)) { + while (toks.get(k).isNewline()) { + // drop newlines after parameter comments + k++; + } + } + } + Tok tok = toks.get(k++); + + // Non-tokens starting on the same line go here too. + ImmutableList.Builder toksAfter = ImmutableList.builder(); + OUTER: + while (k < kN && !toks.get(k).isToken()) { + // Don't attach inline comments to certain leading tokens, e.g. for + // `f(/*flag1=*/true). + // + // Attaching inline comments to the right token is hard, and this barely + // scratches the surface. But it's enough to do a better job with parameter + // name comments. + // + // TODO(cushon): find a better strategy. + if (toks.get(k).isSlashStarComment()) { + switch (tok.getText()) { + case "(": + case "<": + case ".": + break OUTER; + default: + break; + } + } + if (toks.get(k).isJavadocComment()) { + switch (tok.getText()) { + case ";": + break OUTER; + default: + break; + } + } + if (isParamComment(toks.get(k))) { + tokens.add(new Token(toksBefore.build(), tok, toksAfter.build())); + toksBefore = ImmutableList.builder().add(toks.get(k++)); + // drop newlines after parameter comments + while (toks.get(k).isNewline()) { + k++; + } + continue OUTERMOST; + } + Tok nonTokenAfter = toks.get(k++); + toksAfter.add(nonTokenAfter); + if (Newlines.containsBreaks(nonTokenAfter.getText())) { + break; + } + } + tokens.add(new Token(toksBefore.build(), tok, toksAfter.build())); + toksBefore = ImmutableList.builder(); + } + return tokens.build(); + } + + private static boolean isParamComment(Tok tok) { + return tok.isSlashStarComment() + && tok.getText().matches("\\/\\*[A-Za-z0-9\\s_\\-]+=\\s*\\*\\/"); + } + + /** + * Convert from a character range to a token range. + * + * @param characterRange the {@code 0}-based {@link Range} of characters + * @return the {@code 0}-based {@link Range} of tokens + * @throws FormatterException if the upper endpoint of the range is outside the file + */ + Range characterRangeToTokenRange(Range characterRange) + throws FormatterException { + if (characterRange.upperEndpoint() > text.length()) { + throw new FormatterException( + String.format( + "error: invalid length %d, offset + length (%d) is outside the file", + characterRange.upperEndpoint() - characterRange.lowerEndpoint(), + characterRange.upperEndpoint())); + } + // empty range stands for "format the line under the cursor" + Range nonEmptyRange = + characterRange.isEmpty() + ? Range.closedOpen(characterRange.lowerEndpoint(), characterRange.lowerEndpoint() + 1) + : characterRange; + ImmutableCollection enclosed = + getPositionTokenMap().subRangeMap(nonEmptyRange).asMapOfRanges().values(); + if (enclosed.isEmpty()) { + return EMPTY_RANGE; + } + return Range.closedOpen( + enclosed.iterator().next().getTok().getIndex(), getLast(enclosed).getTok().getIndex() + 1); + } + + /** + * Get the number of toks. + * + * @return the number of toks, excluding the EOF tok + */ + @Override + public int getkN() { + return kN; + } + + /** + * Get the Token by index. + * + * @param k the Tok index + */ + @Override + public Token getToken(int k) { + return kToToken[k]; + } + + /** + * Get the input tokens. + * + * @return the input tokens + */ + @Override + public ImmutableList getTokens() { + return tokens; + } + + /** + * Get the navigable map from position to {@link Token}. Used to look for tokens following a given + * one, and to implement the --offset and --length flags to reformat a character range in the + * input file. + * + * @return the navigable map from position to {@link Token} + */ + @Override + public ImmutableRangeMap getPositionTokenMap() { + return positionTokenMap; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("tokens", tokens) + .add("super", super.toString()) + .toString(); + } + + private JCCompilationUnit unit; + + @Override + public int getLineNumber(int inputPosition) { + Verify.verifyNotNull(unit, "Expected compilation unit to be set."); + return unit.getLineMap().getLineNumber(inputPosition); + } + + @Override + public int getColumnNumber(int inputPosition) { + Verify.verifyNotNull(unit, "Expected compilation unit to be set."); + return unit.getLineMap().getColumnNumber(inputPosition); + } + + // TODO(cushon): refactor JavaInput so the CompilationUnit can be passed into + // the constructor. + public void setCompilationUnit(JCCompilationUnit unit) { + this.unit = unit; + } + + public RangeSet characterRangesToTokenRanges(Collection> characterRanges) + throws FormatterException { + RangeSet tokenRangeSet = TreeRangeSet.create(); + for (Range characterRange : characterRanges) { + tokenRangeSet.add( + characterRangeToTokenRange(characterRange.canonical(DiscreteDomain.integers()))); + } + return tokenRangeSet; + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/JavaInputAstVisitor.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/JavaInputAstVisitor.java index 4d1d392d3..1ba627864 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/JavaInputAstVisitor.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/JavaInputAstVisitor.java @@ -169,3841 +169,3783 @@ import javax.lang.model.element.Name; */ public class JavaInputAstVisitor extends TreePathScanner { - /** Direction for Annotations (usually VERTICAL). */ - protected enum Direction { - VERTICAL, - HORIZONTAL; + /** Direction for Annotations (usually VERTICAL). */ + protected enum Direction { + VERTICAL, + HORIZONTAL; - boolean isVertical() { - return this == VERTICAL; - } + boolean isVertical() { + return this == VERTICAL; + } + } + + /** Whether to break or not. */ + protected enum BreakOrNot { + YES, + NO; + + boolean isYes() { + return this == YES; + } + } + + /** Whether to collapse empty blocks. */ + protected enum CollapseEmptyOrNot { + YES, + NO; + + static CollapseEmptyOrNot valueOf(boolean b) { + return b ? YES : NO; } - /** Whether to break or not. */ - protected enum BreakOrNot { - YES, - NO; + boolean isYes() { + return this == YES; + } + } - boolean isYes() { - return this == YES; - } + /** Whether to allow leading blank lines in blocks. */ + protected enum AllowLeadingBlankLine { + YES, + NO; + + static AllowLeadingBlankLine valueOf(boolean b) { + return b ? YES : NO; + } + } + + /** Whether to allow trailing blank lines in blocks. */ + protected enum AllowTrailingBlankLine { + YES, + NO; + + static AllowTrailingBlankLine valueOf(boolean b) { + return b ? YES : NO; + } + } + + /** Whether to include braces. */ + protected enum BracesOrNot { + YES, + NO; + + boolean isYes() { + return this == YES; + } + } + + /** Whether or not to include dimensions. */ + enum DimensionsOrNot { + YES, + NO; + + boolean isYes() { + return this == YES; + } + } + + /** Whether or not the declaration is Varargs. */ + enum VarArgsOrNot { + YES, + NO; + + static VarArgsOrNot valueOf(boolean b) { + return b ? YES : NO; } - /** Whether to collapse empty blocks. */ - protected enum CollapseEmptyOrNot { - YES, - NO; - - static CollapseEmptyOrNot valueOf(boolean b) { - return b ? YES : NO; - } - - boolean isYes() { - return this == YES; - } + boolean isYes() { + return this == YES; } - /** Whether to allow leading blank lines in blocks. */ - protected enum AllowLeadingBlankLine { - YES, - NO; - - static AllowLeadingBlankLine valueOf(boolean b) { - return b ? YES : NO; - } + static VarArgsOrNot fromVariable(VariableTree node) { + return valueOf((((JCTree.JCVariableDecl) node).mods.flags & Flags.VARARGS) == Flags.VARARGS); } + } - /** Whether to allow trailing blank lines in blocks. */ - protected enum AllowTrailingBlankLine { - YES, - NO; + /** Whether the formal parameter declaration is a receiver. */ + enum ReceiverParameter { + YES, + NO; - static AllowTrailingBlankLine valueOf(boolean b) { - return b ? YES : NO; - } + boolean isYes() { + return this == YES; } + } - /** Whether to include braces. */ - protected enum BracesOrNot { - YES, - NO; + /** Whether these declarations are the first in the block. */ + protected enum FirstDeclarationsOrNot { + YES, + NO; - boolean isYes() { - return this == YES; - } + boolean isYes() { + return this == YES; } + } - /** Whether or not to include dimensions. */ - enum DimensionsOrNot { - YES, - NO; + // TODO(cushon): generalize this + private static final ImmutableMultimap TYPE_ANNOTATIONS = typeAnnotations(); - boolean isYes() { - return this == YES; - } + private static ImmutableSetMultimap typeAnnotations() { + ImmutableSetMultimap.Builder result = ImmutableSetMultimap.builder(); + for (String annotation : + ImmutableList.of( + "org.jspecify.nullness.Nullable", + "org.checkerframework.checker.nullness.qual.Nullable")) { + String simpleName = annotation.substring(annotation.lastIndexOf('.') + 1); + result.put(simpleName, annotation); } + return result.build(); + } - /** Whether or not the declaration is Varargs. */ - enum VarArgsOrNot { - YES, - NO; + protected final OpsBuilder builder; - static VarArgsOrNot valueOf(boolean b) { - return b ? YES : NO; - } + protected static final Indent.Const ZERO = Indent.Const.ZERO; + protected final int indentMultiplier; + protected final Indent.Const minusTwo; + protected final Indent.Const minusFour; + protected final Indent.Const plusTwo; + protected final Indent.Const plusFour; - boolean isYes() { - return this == YES; - } + private final Set typeAnnotationSimpleNames = new HashSet<>(); - static VarArgsOrNot fromVariable(VariableTree node) { - return valueOf( - (((JCTree.JCVariableDecl) node).mods.flags & Flags.VARARGS) == Flags.VARARGS); - } + private static final ImmutableList breakList(Optional breakTag) { + return ImmutableList.of(Doc.Break.make(Doc.FillMode.UNIFIED, " ", ZERO, breakTag)); + } + + private static final ImmutableList breakFillList(Optional breakTag) { + return ImmutableList.of( + OpenOp.make(ZERO), + Doc.Break.make(Doc.FillMode.INDEPENDENT, " ", ZERO, breakTag), + CloseOp.make()); + } + + private static final ImmutableList forceBreakList(Optional breakTag) { + return ImmutableList.of(Doc.Break.make(FillMode.FORCED, "", Indent.Const.ZERO, breakTag)); + } + + /** + * Allow multi-line filling (of array initializers, argument lists, and boolean expressions) for + * items with length less than or equal to this threshold. + */ + private static final int MAX_ITEM_LENGTH_FOR_FILLING = 10; + + /** + * The {@code Visitor} constructor. + * + * @param builder the {@link OpsBuilder} + */ + public JavaInputAstVisitor(OpsBuilder builder, int indentMultiplier) { + this.builder = builder; + this.indentMultiplier = indentMultiplier; + minusTwo = Indent.Const.make(-2, indentMultiplier); + minusFour = Indent.Const.make(-4, indentMultiplier); + plusTwo = Indent.Const.make(+2, indentMultiplier); + plusFour = Indent.Const.make(+4, indentMultiplier); + } + + /** A record of whether we have visited into an expression. */ + private final Deque inExpression = new ArrayDeque<>(ImmutableList.of(false)); + + private boolean inExpression() { + return inExpression.peekLast(); + } + + @Override + public Void scan(Tree tree, Void unused) { + inExpression.addLast(tree instanceof ExpressionTree || inExpression.peekLast()); + int previous = builder.depth(); + try { + super.scan(tree, null); + } catch (FormattingError e) { + throw e; + } catch (Throwable t) { + throw new FormattingError(builder.diagnostic(Throwables.getStackTraceAsString(t))); + } finally { + inExpression.removeLast(); } + builder.checkClosed(previous); + return null; + } - /** Whether the formal parameter declaration is a receiver. */ - enum ReceiverParameter { - YES, - NO; - - boolean isYes() { - return this == YES; - } + @Override + public Void visitCompilationUnit(CompilationUnitTree node, Void unused) { + boolean first = true; + if (node.getPackageName() != null) { + markForPartialFormat(); + visitPackage(node.getPackageName(), node.getPackageAnnotations()); + builder.forcedBreak(); + first = false; } - - /** Whether these declarations are the first in the block. */ - protected enum FirstDeclarationsOrNot { - YES, - NO; - - boolean isYes() { - return this == YES; - } - } - - // TODO(cushon): generalize this - private static final ImmutableMultimap TYPE_ANNOTATIONS = typeAnnotations(); - - private static ImmutableSetMultimap typeAnnotations() { - ImmutableSetMultimap.Builder result = ImmutableSetMultimap.builder(); - for (String annotation : - ImmutableList.of( - "org.jspecify.nullness.Nullable", - "org.checkerframework.checker.nullness.qual.Nullable")) { - String simpleName = annotation.substring(annotation.lastIndexOf('.') + 1); - result.put(simpleName, annotation); - } - return result.build(); - } - - protected final OpsBuilder builder; - - protected static final Indent.Const ZERO = Indent.Const.ZERO; - protected final int indentMultiplier; - protected final Indent.Const minusTwo; - protected final Indent.Const minusFour; - protected final Indent.Const plusTwo; - protected final Indent.Const plusFour; - - private final Set typeAnnotationSimpleNames = new HashSet<>(); - - private static final ImmutableList breakList(Optional breakTag) { - return ImmutableList.of(Doc.Break.make(Doc.FillMode.UNIFIED, " ", ZERO, breakTag)); - } - - private static final ImmutableList breakFillList(Optional breakTag) { - return ImmutableList.of( - OpenOp.make(ZERO), - Doc.Break.make(Doc.FillMode.INDEPENDENT, " ", ZERO, breakTag), - CloseOp.make()); - } - - private static final ImmutableList forceBreakList(Optional breakTag) { - return ImmutableList.of(Doc.Break.make(FillMode.FORCED, "", Indent.Const.ZERO, breakTag)); - } - - /** - * Allow multi-line filling (of array initializers, argument lists, and boolean expressions) for - * items with length less than or equal to this threshold. - */ - private static final int MAX_ITEM_LENGTH_FOR_FILLING = 10; - - /** - * The {@code Visitor} constructor. - * - * @param builder the {@link OpsBuilder} - */ - public JavaInputAstVisitor(OpsBuilder builder, int indentMultiplier) { - this.builder = builder; - this.indentMultiplier = indentMultiplier; - minusTwo = Indent.Const.make(-2, indentMultiplier); - minusFour = Indent.Const.make(-4, indentMultiplier); - plusTwo = Indent.Const.make(+2, indentMultiplier); - plusFour = Indent.Const.make(+4, indentMultiplier); - } - - /** A record of whether we have visited into an expression. */ - private final Deque inExpression = new ArrayDeque<>(ImmutableList.of(false)); - - private boolean inExpression() { - return inExpression.peekLast(); - } - - @Override - public Void scan(Tree tree, Void unused) { - inExpression.addLast(tree instanceof ExpressionTree || inExpression.peekLast()); - int previous = builder.depth(); - try { - super.scan(tree, null); - } catch (FormattingError e) { - throw e; - } catch (Throwable t) { - throw new FormattingError(builder.diagnostic(Throwables.getStackTraceAsString(t))); - } finally { - inExpression.removeLast(); - } - builder.checkClosed(previous); - return null; - } - - @Override - public Void visitCompilationUnit(CompilationUnitTree node, Void unused) { - boolean first = true; - if (node.getPackageName() != null) { - markForPartialFormat(); - visitPackage(node.getPackageName(), node.getPackageAnnotations()); - builder.forcedBreak(); - first = false; - } - dropEmptyDeclarations(); - if (!node.getImports().isEmpty()) { - if (!first) { - builder.blankLineWanted(BlankLineWanted.YES); - } - for (ImportTree importDeclaration : node.getImports()) { - markForPartialFormat(); - builder.blankLineWanted(PRESERVE); - scan(importDeclaration, null); - builder.forcedBreak(); - } - first = false; - } - dropEmptyDeclarations(); - for (Tree type : node.getTypeDecls()) { - if (type.getKind() == Tree.Kind.IMPORT) { - // javac treats extra semicolons in the import list as type declarations - // TODO(cushon): remove this if https://bugs.openjdk.java.net/browse/JDK-8027682 is - // fixed - continue; - } - if (!first) { - builder.blankLineWanted(BlankLineWanted.YES); - } - markForPartialFormat(); - scan(type, null); - builder.forcedBreak(); - first = false; - dropEmptyDeclarations(); - } - handleModule(first, node); - // set a partial format marker at EOF to make sure we can format the entire file + dropEmptyDeclarations(); + if (!node.getImports().isEmpty()) { + if (!first) { + builder.blankLineWanted(BlankLineWanted.YES); + } + for (ImportTree importDeclaration : node.getImports()) { markForPartialFormat(); - return null; - } - - protected void handleModule(boolean first, CompilationUnitTree node) {} - - /** Skips over extra semi-colons at the top-level, or in a class member declaration lists. */ - protected void dropEmptyDeclarations() { - if (builder.peekToken().equals(Optional.of(";"))) { - while (builder.peekToken().equals(Optional.of(";"))) { - builder.forcedBreak(); - markForPartialFormat(); - token(";"); - } - } - } - - @Override - public Void visitClass(ClassTree tree, Void unused) { - switch (tree.getKind()) { - case ANNOTATION_TYPE: - visitAnnotationType(tree); - break; - case CLASS: - case INTERFACE: - visitClassDeclaration(tree); - break; - case ENUM: - visitEnumDeclaration(tree); - break; - default: - throw new AssertionError(tree.getKind()); - } - return null; - } - - public void visitAnnotationType(ClassTree node) { - sync(node); - builder.open(ZERO); - typeDeclarationModifiers(node.getModifiers()); - builder.open(ZERO); - token("@"); - token("interface"); - builder.breakOp(" "); - visit(node.getSimpleName()); - builder.close(); - builder.close(); - if (node.getMembers() == null) { - builder.open(plusFour); - token(";"); - builder.close(); - } else { - addBodyDeclarations(node.getMembers(), BracesOrNot.YES, FirstDeclarationsOrNot.YES); - } - builder.guessToken(";"); - } - - @Override - public Void visitArrayAccess(ArrayAccessTree node, Void unused) { - sync(node); - visitDot(node); - return null; - } - - @Override - public Void visitNewArray(NewArrayTree node, Void unused) { - if (node.getType() != null) { - builder.open(plusFour); - token("new"); - builder.space(); - - TypeWithDims extractedDims = - DimensionHelpers.extractDims(node.getType(), SortedDims.YES); - Tree base = extractedDims.node; - - Deque dimExpressions = new ArrayDeque<>(node.getDimensions()); - - Deque> annotations = new ArrayDeque<>(); - annotations.add(ImmutableList.copyOf(node.getAnnotations())); - annotations.addAll(node.getDimAnnotations()); - annotations.addAll(extractedDims.dims); - - scan(base, null); - builder.open(ZERO); - maybeAddDims(dimExpressions, annotations); - builder.close(); - builder.close(); - } - if (node.getInitializers() != null) { - if (node.getType() != null) { - builder.space(); - } - visitArrayInitializer(node.getInitializers()); - } - return null; - } - - public boolean visitArrayInitializer(List expressions) { - int cols; - if (expressions.isEmpty()) { - tokenBreakTrailingComment("{", plusTwo); - if (builder.peekToken().equals(Optional.of(","))) { - token(","); - } - token("}", plusTwo); - } else if ((cols = argumentsAreTabular(expressions)) != -1) { - builder.open(plusTwo); - token("{"); - builder.forcedBreak(); - boolean first = true; - for (Iterable row : Iterables.partition(expressions, cols)) { - if (!first) { - builder.forcedBreak(); - } - builder.open( - row.iterator().next().getKind() == NEW_ARRAY || cols == 1 - ? ZERO - : plusFour); - boolean firstInRow = true; - for (ExpressionTree item : row) { - if (!firstInRow) { - token(","); - builder.breakToFill(" "); - } - scan(item, null); - firstInRow = false; - } - builder.guessToken(","); - builder.close(); - first = false; - } - builder.breakOp(minusTwo); - builder.close(); - token("}", plusTwo); - } else { - // Special-case the formatting of array initializers inside annotations - // to more eagerly use a one-per-line layout. - boolean inMemberValuePair = false; - // walk up past the enclosing NewArrayTree (and maybe an enclosing AssignmentTree) - TreePath path = getCurrentPath(); - for (int i = 0; i < 2; i++) { - if (path == null) { - break; - } - if (path.getLeaf().getKind() == ANNOTATION) { - inMemberValuePair = true; - break; - } - path = path.getParentPath(); - } - boolean shortItems = hasOnlyShortItems(expressions); - boolean allowFilledElementsOnOwnLine = shortItems || !inMemberValuePair; - - builder.open(plusTwo); - tokenBreakTrailingComment("{", plusTwo); - boolean hasTrailingComma = hasTrailingToken(builder.getInput(), expressions, ","); - builder.breakOp(hasTrailingComma ? FillMode.FORCED : FillMode.UNIFIED, "", ZERO); - if (allowFilledElementsOnOwnLine) { - builder.open(ZERO); - } - boolean first = true; - FillMode fillMode = shortItems ? FillMode.INDEPENDENT : FillMode.UNIFIED; - for (ExpressionTree expression : expressions) { - if (!first) { - token(","); - builder.breakOp(fillMode, " ", ZERO); - } - scan(expression, null); - first = false; - } - builder.guessToken(","); - if (allowFilledElementsOnOwnLine) { - builder.close(); - } - builder.breakOp(minusTwo); - builder.close(); - token("}", plusTwo); - } - return false; - } - - private boolean hasOnlyShortItems(List expressions) { - for (ExpressionTree expression : expressions) { - int startPosition = getStartPosition(expression); - if (builder.actualSize( - startPosition, - getEndPosition(expression, getCurrentPath()) - startPosition) - >= MAX_ITEM_LENGTH_FOR_FILLING) { - return false; - } - } - return true; - } - - @Override - public Void visitArrayType(ArrayTypeTree node, Void unused) { - sync(node); - visitAnnotatedArrayType(node); - return null; - } - - private void visitAnnotatedArrayType(Tree node) { - TypeWithDims extractedDims = DimensionHelpers.extractDims(node, SortedDims.YES); - builder.open(plusFour); - scan(extractedDims.node, null); - Deque> dims = new ArrayDeque<>(extractedDims.dims); - maybeAddDims(dims); - Verify.verify(dims.isEmpty()); - builder.close(); - } - - @Override - public Void visitAssert(AssertTree node, Void unused) { - sync(node); - builder.open(ZERO); - token("assert"); - builder.space(); - builder.open(node.getDetail() == null ? ZERO : plusFour); - scan(node.getCondition(), null); - if (node.getDetail() != null) { - builder.breakOp(" "); - token(":"); - builder.space(); - scan(node.getDetail(), null); - } - builder.close(); - builder.close(); - token(";"); - return null; - } - - @Override - public Void visitAssignment(AssignmentTree node, Void unused) { - sync(node); - builder.open(plusFour); - scan(node.getVariable(), null); - builder.space(); - splitToken(operatorName(node)); - builder.breakOp(" "); - scan(node.getExpression(), null); - builder.close(); - return null; - } - - @Override - public Void visitBlock(BlockTree node, Void unused) { - visitBlock( - node, CollapseEmptyOrNot.NO, AllowLeadingBlankLine.NO, AllowTrailingBlankLine.NO); - return null; - } - - @Override - public Void visitCompoundAssignment(CompoundAssignmentTree node, Void unused) { - sync(node); - builder.open(plusFour); - scan(node.getVariable(), null); - builder.space(); - splitToken(operatorName(node)); - builder.breakOp(" "); - scan(node.getExpression(), null); - builder.close(); - return null; - } - - @Override - public Void visitBreak(BreakTree node, Void unused) { - sync(node); - builder.open(plusFour); - token("break"); - if (node.getLabel() != null) { - builder.breakOp(" "); - visit(node.getLabel()); - } - builder.close(); - token(";"); - return null; - } - - @Override - public Void visitTypeCast(TypeCastTree node, Void unused) { - sync(node); - builder.open(plusFour); - token("("); - scan(node.getType(), null); - token(")"); - builder.breakOp(" "); - scan(node.getExpression(), null); - builder.close(); - return null; - } - - @Override - public Void visitNewClass(NewClassTree node, Void unused) { - sync(node); - builder.open(ZERO); - if (node.getEnclosingExpression() != null) { - scan(node.getEnclosingExpression(), null); - builder.breakOp(); - token("."); - } - token("new"); - builder.space(); - addTypeArguments(node.getTypeArguments(), plusFour); - if (node.getClassBody() != null) { - List annotations = - visitModifiers( - node.getClassBody().getModifiers(), - Direction.HORIZONTAL, - Optional.empty()); - visitAnnotations(annotations, BreakOrNot.NO, BreakOrNot.YES); - } - scan(node.getIdentifier(), null); - addArguments(node.getArguments(), plusFour); - builder.close(); - if (node.getClassBody() != null) { - addBodyDeclarations( - node.getClassBody().getMembers(), BracesOrNot.YES, FirstDeclarationsOrNot.YES); - } - return null; - } - - @Override - public Void visitConditionalExpression(ConditionalExpressionTree node, Void unused) { - sync(node); - builder.open(plusFour); - scan(node.getCondition(), null); - builder.breakOp(" "); - token("?"); - builder.space(); - scan(node.getTrueExpression(), null); - builder.breakOp(" "); - token(":"); - builder.space(); - scan(node.getFalseExpression(), null); - builder.close(); - return null; - } - - @Override - public Void visitContinue(ContinueTree node, Void unused) { - sync(node); - builder.open(plusFour); - token("continue"); - if (node.getLabel() != null) { - builder.breakOp(" "); - visit(node.getLabel()); - } - token(";"); - builder.close(); - return null; - } - - @Override - public Void visitDoWhileLoop(DoWhileLoopTree node, Void unused) { - sync(node); - token("do"); - visitStatement( - node.getStatement(), - CollapseEmptyOrNot.YES, - AllowLeadingBlankLine.YES, - AllowTrailingBlankLine.YES); - if (node.getStatement().getKind() == BLOCK) { - builder.space(); - } else { - builder.breakOp(" "); - } - token("while"); - builder.space(); - token("("); - scan(skipParen(node.getCondition()), null); - token(")"); - token(";"); - return null; - } - - @Override - public Void visitEmptyStatement(EmptyStatementTree node, Void unused) { - sync(node); - dropEmptyDeclarations(); - return null; - } - - @Override - public Void visitEnhancedForLoop(EnhancedForLoopTree node, Void unused) { - sync(node); - builder.open(ZERO); - token("for"); - builder.space(); - token("("); - builder.open(ZERO); - visitToDeclare( - DeclarationKind.NONE, - Direction.HORIZONTAL, - node.getVariable(), - Optional.of(node.getExpression()), - ":", - /* trailing= */ Optional.empty()); - builder.close(); - token(")"); - builder.close(); - visitStatement( - node.getStatement(), - CollapseEmptyOrNot.YES, - AllowLeadingBlankLine.YES, - AllowTrailingBlankLine.NO); - return null; - } - - private void visitEnumConstantDeclaration(VariableTree enumConstant) { - for (AnnotationTree annotation : enumConstant.getModifiers().getAnnotations()) { - scan(annotation, null); - builder.forcedBreak(); - } - visit(enumConstant.getName()); - NewClassTree init = ((NewClassTree) enumConstant.getInitializer()); - if (init.getArguments().isEmpty()) { - builder.guessToken("("); - builder.guessToken(")"); - } else { - addArguments(init.getArguments(), plusFour); - } - if (init.getClassBody() != null) { - addBodyDeclarations( - init.getClassBody().getMembers(), BracesOrNot.YES, FirstDeclarationsOrNot.YES); - } - } - - public boolean visitEnumDeclaration(ClassTree node) { - sync(node); - builder.open(ZERO); - typeDeclarationModifiers(node.getModifiers()); - builder.open(plusFour); - token("enum"); - builder.breakOp(" "); - visit(node.getSimpleName()); - builder.close(); - builder.close(); - if (!node.getImplementsClause().isEmpty()) { - builder.open(plusFour); - builder.breakOp(" "); - builder.open(plusFour); - token("implements"); - builder.breakOp(" "); - builder.open(ZERO); - boolean first = true; - for (Tree superInterfaceType : node.getImplementsClause()) { - if (!first) { - token(","); - builder.breakToFill(" "); - } - scan(superInterfaceType, null); - first = false; - } - builder.close(); - builder.close(); - builder.close(); - } - builder.space(); - tokenBreakTrailingComment("{", plusTwo); - ArrayList enumConstants = new ArrayList<>(); - ArrayList members = new ArrayList<>(); - for (Tree member : node.getMembers()) { - if (member instanceof JCTree.JCVariableDecl) { - JCTree.JCVariableDecl variableDecl = (JCTree.JCVariableDecl) member; - if ((variableDecl.mods.flags & Flags.ENUM) == Flags.ENUM) { - enumConstants.add(variableDecl); - continue; - } - } - members.add(member); - } - if (enumConstants.isEmpty() && members.isEmpty()) { - if (builder.peekToken().equals(Optional.of(";"))) { - builder.open(plusTwo); - builder.forcedBreak(); - token(";"); - builder.forcedBreak(); - dropEmptyDeclarations(); - builder.close(); - builder.open(ZERO); - builder.forcedBreak(); - builder.blankLineWanted(BlankLineWanted.NO); - token("}", plusTwo); - builder.close(); - } else { - builder.open(ZERO); - builder.blankLineWanted(BlankLineWanted.NO); - token("}"); - builder.close(); - } - } else { - builder.open(plusTwo); - builder.blankLineWanted(BlankLineWanted.NO); - builder.forcedBreak(); - builder.open(ZERO); - boolean first = true; - for (VariableTree enumConstant : enumConstants) { - if (!first) { - token(","); - builder.forcedBreak(); - builder.blankLineWanted(BlankLineWanted.PRESERVE); - } - markForPartialFormat(); - visitEnumConstantDeclaration(enumConstant); - first = false; - } - if (builder.peekToken().orElse("").equals(",")) { - token(","); - builder.forcedBreak(); // The ";" goes on its own line. - } - builder.close(); - builder.close(); - if (builder.peekToken().equals(Optional.of(";"))) { - builder.open(plusTwo); - token(";"); - builder.forcedBreak(); - dropEmptyDeclarations(); - builder.close(); - } - builder.open(ZERO); - addBodyDeclarations(members, BracesOrNot.NO, FirstDeclarationsOrNot.NO); - builder.forcedBreak(); - builder.blankLineWanted(BlankLineWanted.NO); - token("}", plusTwo); - builder.close(); - } - builder.guessToken(";"); - return false; - } - - @Override - public Void visitMemberReference(MemberReferenceTree node, Void unused) { - sync(node); - builder.open(plusFour); - scan(node.getQualifierExpression(), null); - builder.breakOp(); - builder.op("::"); - addTypeArguments(node.getTypeArguments(), plusFour); - switch (node.getMode()) { - case INVOKE: - visit(node.getName()); - break; - case NEW: - token("new"); - break; - default: - throw new AssertionError(node.getMode()); - } - builder.close(); - return null; - } - - @Override - public Void visitExpressionStatement(ExpressionStatementTree node, Void unused) { - sync(node); - scan(node.getExpression(), null); - token(";"); - return null; - } - - @Override - public Void visitVariable(VariableTree node, Void unused) { - sync(node); - visitVariables( - ImmutableList.of(node), - DeclarationKind.NONE, - fieldAnnotationDirection(node.getModifiers())); - return null; - } - - void visitVariables( - List fragments, - DeclarationKind declarationKind, - Direction annotationDirection) { - if (fragments.size() == 1) { - VariableTree fragment = fragments.get(0); - declareOne( - declarationKind, - annotationDirection, - Optional.of(fragment.getModifiers()), - fragment.getType(), - /* name= */ fragment.getName(), - "", - "=", - Optional.ofNullable(fragment.getInitializer()), - Optional.of(";"), - /* receiverExpression= */ Optional.empty(), - Optional.ofNullable(variableFragmentDims(true, 0, fragment.getType()))); - - } else { - declareMany(fragments, annotationDirection); - } - } - - private static TypeWithDims variableFragmentDims(boolean first, int leadingDims, Tree type) { - if (type == null) { - return null; - } - if (first) { - return DimensionHelpers.extractDims(type, SortedDims.YES); - } - TypeWithDims dims = DimensionHelpers.extractDims(type, SortedDims.NO); - return new TypeWithDims( - null, - leadingDims > 0 ? dims.dims.subList(0, dims.dims.size() - leadingDims) : dims.dims); - } - - @Override - public Void visitForLoop(ForLoopTree node, Void unused) { - sync(node); - token("for"); - builder.space(); - token("("); - builder.open(plusFour); - builder.open( - node.getInitializer().size() > 1 - && node.getInitializer().get(0).getKind() - == Tree.Kind.EXPRESSION_STATEMENT - ? plusFour - : ZERO); - if (!node.getInitializer().isEmpty()) { - if (node.getInitializer().get(0).getKind() == VARIABLE) { - PeekingIterator it = - Iterators.peekingIterator(node.getInitializer().iterator()); - visitVariables( - variableFragments(it, it.next()), - DeclarationKind.NONE, - Direction.HORIZONTAL); - } else { - boolean first = true; - builder.open(ZERO); - for (StatementTree t : node.getInitializer()) { - if (!first) { - token(","); - builder.breakOp(" "); - } - scan(((ExpressionStatementTree) t).getExpression(), null); - first = false; - } - token(";"); - builder.close(); - } - } else { - token(";"); - } - builder.close(); - builder.breakOp(" "); - if (node.getCondition() != null) { - scan(node.getCondition(), null); - } - token(";"); - if (!node.getUpdate().isEmpty()) { - builder.breakOp(" "); - builder.open(node.getUpdate().size() <= 1 ? ZERO : plusFour); - boolean firstUpdater = true; - for (ExpressionStatementTree updater : node.getUpdate()) { - if (!firstUpdater) { - token(","); - builder.breakToFill(" "); - } - scan(updater.getExpression(), null); - firstUpdater = false; - } - builder.guessToken(";"); - builder.close(); - } else { - builder.space(); - } - builder.close(); - token(")"); - visitStatement( - node.getStatement(), - CollapseEmptyOrNot.YES, - AllowLeadingBlankLine.YES, - AllowTrailingBlankLine.NO); - return null; - } - - @Override - public Void visitIf(IfTree node, Void unused) { - sync(node); - // Collapse chains of else-ifs. - List expressions = new ArrayList<>(); - List statements = new ArrayList<>(); - while (true) { - expressions.add(node.getCondition()); - statements.add(node.getThenStatement()); - if (node.getElseStatement() != null && node.getElseStatement().getKind() == IF) { - node = (IfTree) node.getElseStatement(); - } else { - break; - } - } - builder.open(ZERO); - boolean first = true; - boolean followingBlock = false; - int expressionsN = expressions.size(); - for (int i = 0; i < expressionsN; i++) { - if (!first) { - if (followingBlock) { - builder.space(); - } else { - builder.forcedBreak(); - } - token("else"); - builder.space(); - } - token("if"); - builder.space(); - token("("); - scan(skipParen(expressions.get(i)), null); - token(")"); - // An empty block can collapse to "{}" if there are no if/else or else clauses - boolean onlyClause = expressionsN == 1 && node.getElseStatement() == null; - // Trailing blank lines are permitted if this isn't the last clause - boolean trailingClauses = i < expressionsN - 1 || node.getElseStatement() != null; - visitStatement( - statements.get(i), - CollapseEmptyOrNot.valueOf(onlyClause), - AllowLeadingBlankLine.YES, - AllowTrailingBlankLine.valueOf(trailingClauses)); - followingBlock = statements.get(i).getKind() == BLOCK; - first = false; - } - if (node.getElseStatement() != null) { - if (followingBlock) { - builder.space(); - } else { - builder.forcedBreak(); - } - token("else"); - visitStatement( - node.getElseStatement(), - CollapseEmptyOrNot.NO, - AllowLeadingBlankLine.YES, - AllowTrailingBlankLine.NO); - } - builder.close(); - return null; - } - - @Override - public Void visitImport(ImportTree node, Void unused) { - checkForTypeAnnotation(node); - sync(node); - token("import"); - builder.space(); - if (node.isStatic()) { - token("static"); - builder.space(); - } - visitName(node.getQualifiedIdentifier()); - token(";"); - // TODO(cushon): remove this if https://bugs.openjdk.java.net/browse/JDK-8027682 is fixed - dropEmptyDeclarations(); - return null; - } - - private void checkForTypeAnnotation(ImportTree node) { - Name simpleName = getSimpleName(node); - Collection wellKnownAnnotations = TYPE_ANNOTATIONS.get(simpleName.toString()); - if (!wellKnownAnnotations.isEmpty() - && wellKnownAnnotations.contains(node.getQualifiedIdentifier().toString())) { - typeAnnotationSimpleNames.add(simpleName); - } - } - - private static Name getSimpleName(ImportTree importTree) { - return importTree.getQualifiedIdentifier() instanceof IdentifierTree - ? ((IdentifierTree) importTree.getQualifiedIdentifier()).getName() - : ((MemberSelectTree) importTree.getQualifiedIdentifier()).getIdentifier(); - } - - @Override - public Void visitBinary(BinaryTree node, Void unused) { - sync(node); - /* - * Collect together all operators with same precedence to clean up indentation. - */ - List operands = new ArrayList<>(); - List operators = new ArrayList<>(); - walkInfix(precedence(node), node, operands, operators); - FillMode fillMode = hasOnlyShortItems(operands) ? INDEPENDENT : UNIFIED; - builder.open(plusFour); - scan(operands.get(0), null); - int operatorsN = operators.size(); - for (int i = 0; i < operatorsN; i++) { - builder.breakOp(fillMode, " ", ZERO); - builder.op(operators.get(i)); - builder.space(); - scan(operands.get(i + 1), null); - } - builder.close(); - return null; - } - - @Override - public Void visitInstanceOf(InstanceOfTree node, Void unused) { - sync(node); - builder.open(plusFour); - scan(node.getExpression(), null); - builder.breakOp(" "); - builder.open(ZERO); - token("instanceof"); - builder.breakOp(" "); - scan(node.getType(), null); - builder.close(); - builder.close(); - return null; - } - - @Override - public Void visitIntersectionType(IntersectionTypeTree node, Void unused) { - sync(node); - builder.open(plusFour); - boolean first = true; - for (Tree type : node.getBounds()) { - if (!first) { - builder.breakToFill(" "); - token("&"); - builder.space(); - } - scan(type, null); - first = false; - } - builder.close(); - return null; - } - - @Override - public Void visitLabeledStatement(LabeledStatementTree node, Void unused) { - sync(node); - builder.open(ZERO); - visit(node.getLabel()); - token(":"); + builder.blankLineWanted(PRESERVE); + scan(importDeclaration, null); builder.forcedBreak(); - builder.close(); - scan(node.getStatement(), null); - return null; + } + first = false; } + dropEmptyDeclarations(); + for (Tree type : node.getTypeDecls()) { + if (type.getKind() == Tree.Kind.IMPORT) { + // javac treats extra semicolons in the import list as type declarations + // TODO(cushon): remove this if https://bugs.openjdk.java.net/browse/JDK-8027682 is + // fixed + continue; + } + if (!first) { + builder.blankLineWanted(BlankLineWanted.YES); + } + markForPartialFormat(); + scan(type, null); + builder.forcedBreak(); + first = false; + dropEmptyDeclarations(); + } + handleModule(first, node); + // set a partial format marker at EOF to make sure we can format the entire file + markForPartialFormat(); + return null; + } - @Override - public Void visitLambdaExpression(LambdaExpressionTree node, Void unused) { - sync(node); - boolean statementBody = node.getBodyKind() == LambdaExpressionTree.BodyKind.STATEMENT; - boolean parens = builder.peekToken().equals(Optional.of("(")); - builder.open(parens ? plusFour : ZERO); - if (parens) { - token("("); - } - boolean first = true; - for (VariableTree parameter : node.getParameters()) { - if (!first) { - token(","); - builder.breakOp(" "); - } - scan(parameter, null); - first = false; - } - if (parens) { - token(")"); - } - builder.close(); + protected void handleModule(boolean first, CompilationUnitTree node) {} + + /** Skips over extra semi-colons at the top-level, or in a class member declaration lists. */ + protected void dropEmptyDeclarations() { + if (builder.peekToken().equals(Optional.of(";"))) { + while (builder.peekToken().equals(Optional.of(";"))) { + builder.forcedBreak(); + markForPartialFormat(); + token(";"); + } + } + } + + @Override + public Void visitClass(ClassTree tree, Void unused) { + switch (tree.getKind()) { + case ANNOTATION_TYPE: + visitAnnotationType(tree); + break; + case CLASS: + case INTERFACE: + visitClassDeclaration(tree); + break; + case ENUM: + visitEnumDeclaration(tree); + break; + default: + throw new AssertionError(tree.getKind()); + } + return null; + } + + public void visitAnnotationType(ClassTree node) { + sync(node); + builder.open(ZERO); + typeDeclarationModifiers(node.getModifiers()); + builder.open(ZERO); + token("@"); + token("interface"); + builder.breakOp(" "); + visit(node.getSimpleName()); + builder.close(); + builder.close(); + if (node.getMembers() == null) { + builder.open(plusFour); + token(";"); + builder.close(); + } else { + addBodyDeclarations(node.getMembers(), BracesOrNot.YES, FirstDeclarationsOrNot.YES); + } + builder.guessToken(";"); + } + + @Override + public Void visitArrayAccess(ArrayAccessTree node, Void unused) { + sync(node); + visitDot(node); + return null; + } + + @Override + public Void visitNewArray(NewArrayTree node, Void unused) { + if (node.getType() != null) { + builder.open(plusFour); + token("new"); + builder.space(); + + TypeWithDims extractedDims = DimensionHelpers.extractDims(node.getType(), SortedDims.YES); + Tree base = extractedDims.node; + + Deque dimExpressions = new ArrayDeque<>(node.getDimensions()); + + Deque> annotations = new ArrayDeque<>(); + annotations.add(ImmutableList.copyOf(node.getAnnotations())); + annotations.addAll(node.getDimAnnotations()); + annotations.addAll(extractedDims.dims); + + scan(base, null); + builder.open(ZERO); + maybeAddDims(dimExpressions, annotations); + builder.close(); + builder.close(); + } + if (node.getInitializers() != null) { + if (node.getType() != null) { builder.space(); - builder.op("->"); - builder.open(statementBody ? ZERO : plusFour); - if (statementBody) { - builder.space(); - } else { - builder.breakOp(" "); - } - if (node.getBody().getKind() == Tree.Kind.BLOCK) { - visitBlock( - (BlockTree) node.getBody(), - CollapseEmptyOrNot.YES, - AllowLeadingBlankLine.NO, - AllowTrailingBlankLine.NO); - } else { - scan(node.getBody(), null); - } - builder.close(); - return null; + } + visitArrayInitializer(node.getInitializers()); } + return null; + } - @Override - public Void visitAnnotation(AnnotationTree node, Void unused) { - sync(node); - - if (visitSingleMemberAnnotation(node)) { - return null; + public boolean visitArrayInitializer(List expressions) { + int cols; + if (expressions.isEmpty()) { + tokenBreakTrailingComment("{", plusTwo); + if (builder.peekToken().equals(Optional.of(","))) { + token(","); + } + token("}", plusTwo); + } else if ((cols = argumentsAreTabular(expressions)) != -1) { + builder.open(plusTwo); + token("{"); + builder.forcedBreak(); + boolean first = true; + for (Iterable row : Iterables.partition(expressions, cols)) { + if (!first) { + builder.forcedBreak(); } - - builder.open(ZERO); - token("@"); - scan(node.getAnnotationType(), null); - if (!node.getArguments().isEmpty()) { - builder.open(plusFour); - token("("); - builder.breakOp(); - boolean first = true; - - // Format the member value pairs one-per-line if any of them are - // initialized with arrays. - boolean hasArrayInitializer = - Iterables.any(node.getArguments(), JavaInputAstVisitor::isArrayValue); - for (ExpressionTree argument : node.getArguments()) { - if (!first) { - token(","); - if (hasArrayInitializer) { - builder.forcedBreak(); - } else { - builder.breakOp(" "); - } - } - if (argument instanceof AssignmentTree) { - visitAnnotationArgument((AssignmentTree) argument); - } else { - scan(argument, null); - } - first = false; - } - token(")"); - builder.close(); - builder.close(); - return null; - - } else if (builder.peekToken().equals(Optional.of("("))) { - token("("); - token(")"); - } - builder.close(); - return null; - } - - private static boolean isArrayValue(ExpressionTree argument) { - if (!(argument instanceof AssignmentTree)) { - return false; - } - ExpressionTree expression = ((AssignmentTree) argument).getExpression(); - return expression instanceof NewArrayTree && ((NewArrayTree) expression).getType() == null; - } - - public void visitAnnotationArgument(AssignmentTree node) { - boolean isArrayInitializer = node.getExpression().getKind() == NEW_ARRAY; - sync(node); - builder.open(isArrayInitializer ? ZERO : plusFour); - scan(node.getVariable(), null); - builder.space(); - token("="); - if (isArrayInitializer) { - builder.space(); - } else { - builder.breakOp(" "); - } - scan(node.getExpression(), null); - builder.close(); - } - - @Override - public Void visitAnnotatedType(AnnotatedTypeTree node, Void unused) { - sync(node); - ExpressionTree base = node.getUnderlyingType(); - if (base instanceof MemberSelectTree) { - MemberSelectTree selectTree = (MemberSelectTree) base; - scan(selectTree.getExpression(), null); - token("."); - visitAnnotations(node.getAnnotations(), BreakOrNot.NO, BreakOrNot.NO); + builder.open(row.iterator().next().getKind() == NEW_ARRAY || cols == 1 ? ZERO : plusFour); + boolean firstInRow = true; + for (ExpressionTree item : row) { + if (!firstInRow) { + token(","); builder.breakToFill(" "); - visit(selectTree.getIdentifier()); - } else if (base instanceof ArrayTypeTree) { - visitAnnotatedArrayType(node); - } else { - visitAnnotations(node.getAnnotations(), BreakOrNot.NO, BreakOrNot.NO); - builder.breakToFill(" "); - scan(base, null); + } + scan(item, null); + firstInRow = false; } - return null; - } - - // TODO(cushon): Use Flags if/when we drop support for Java 11 - - protected static final long COMPACT_RECORD_CONSTRUCTOR = 1L << 51; - - protected static final long RECORD = 1L << 61; - - @Override - public Void visitMethod(MethodTree node, Void unused) { - sync(node); - List annotations = node.getModifiers().getAnnotations(); - List returnTypeAnnotations = ImmutableList.of(); - - boolean isRecordConstructor = - (((JCMethodDecl) node).mods.flags & COMPACT_RECORD_CONSTRUCTOR) - == COMPACT_RECORD_CONSTRUCTOR; - - if (!node.getTypeParameters().isEmpty() && !annotations.isEmpty()) { - int typeParameterStart = getStartPosition(node.getTypeParameters().get(0)); - for (int i = 0; i < annotations.size(); i++) { - if (getStartPosition(annotations.get(i)) > typeParameterStart) { - returnTypeAnnotations = annotations.subList(i, annotations.size()); - annotations = annotations.subList(0, i); - break; - } - } + builder.guessToken(","); + builder.close(); + first = false; + } + builder.breakOp(minusTwo); + builder.close(); + token("}", plusTwo); + } else { + // Special-case the formatting of array initializers inside annotations + // to more eagerly use a one-per-line layout. + boolean inMemberValuePair = false; + // walk up past the enclosing NewArrayTree (and maybe an enclosing AssignmentTree) + TreePath path = getCurrentPath(); + for (int i = 0; i < 2; i++) { + if (path == null) { + break; } - List typeAnnotations = - visitModifiers( - node.getModifiers(), - annotations, - Direction.VERTICAL, - /* declarationAnnotationBreak= */ Optional.empty()); - - Tree baseReturnType = null; - Deque> dims = null; - if (node.getReturnType() != null) { - TypeWithDims extractedDims = - DimensionHelpers.extractDims(node.getReturnType(), SortedDims.YES); - baseReturnType = extractedDims.node; - dims = new ArrayDeque<>(extractedDims.dims); - } else { - verticalAnnotations(typeAnnotations); - typeAnnotations = ImmutableList.of(); + if (path.getLeaf().getKind() == ANNOTATION) { + inMemberValuePair = true; + break; } + path = path.getParentPath(); + } + boolean shortItems = hasOnlyShortItems(expressions); + boolean allowFilledElementsOnOwnLine = shortItems || !inMemberValuePair; - builder.open(plusFour); - BreakTag breakBeforeName = genSym(); - BreakTag breakBeforeType = genSym(); + builder.open(plusTwo); + tokenBreakTrailingComment("{", plusTwo); + boolean hasTrailingComma = hasTrailingToken(builder.getInput(), expressions, ","); + builder.breakOp(hasTrailingComma ? FillMode.FORCED : FillMode.UNIFIED, "", ZERO); + if (allowFilledElementsOnOwnLine) { builder.open(ZERO); - { - boolean first = true; - if (!typeAnnotations.isEmpty()) { - visitAnnotations(typeAnnotations, BreakOrNot.NO, BreakOrNot.NO); - first = false; - } - if (!node.getTypeParameters().isEmpty()) { - if (!first) { - builder.breakToFill(" "); - } - token("<"); - typeParametersRest(node.getTypeParameters(), plusFour); - if (!returnTypeAnnotations.isEmpty()) { - builder.breakToFill(" "); - visitAnnotations(returnTypeAnnotations, BreakOrNot.NO, BreakOrNot.NO); - } - first = false; - } - - boolean openedNameAndTypeScope = false; - // constructor-like declarations that don't match the name of the enclosing class are - // parsed as method declarations with a null return type - if (baseReturnType != null) { - if (!first) { - builder.breakOp(INDEPENDENT, " ", ZERO, Optional.of(breakBeforeType)); - } else { - first = false; - } - if (!openedNameAndTypeScope) { - builder.open(make(breakBeforeType, plusFour, ZERO)); - openedNameAndTypeScope = true; - } - scan(baseReturnType, null); - maybeAddDims(dims); - } - if (!first) { - builder.breakOp(Doc.FillMode.INDEPENDENT, " ", ZERO, Optional.of(breakBeforeName)); - } else { - first = false; - } - if (!openedNameAndTypeScope) { - builder.open(ZERO); - openedNameAndTypeScope = true; - } - String name = node.getName().toString(); - if (name.equals("")) { - name = builder.peekToken().get(); - } - token(name); - if (!isRecordConstructor) { - token("("); - } - // end of name and type scope - builder.close(); + } + boolean first = true; + FillMode fillMode = shortItems ? FillMode.INDEPENDENT : FillMode.UNIFIED; + for (ExpressionTree expression : expressions) { + if (!first) { + token(","); + builder.breakOp(fillMode, " ", ZERO); } + scan(expression, null); + first = false; + } + builder.guessToken(","); + if (allowFilledElementsOnOwnLine) { builder.close(); - - builder.open(Indent.If.make(breakBeforeName, plusFour, ZERO)); - builder.open(Indent.If.make(breakBeforeType, plusFour, ZERO)); - builder.open(ZERO); - { - if (!isRecordConstructor) { - if (!node.getParameters().isEmpty() || node.getReceiverParameter() != null) { - // Break before args. - builder.breakToFill(""); - visitFormals( - Optional.ofNullable(node.getReceiverParameter()), node.getParameters()); - } - token(")"); - } - if (dims != null) { - maybeAddDims(dims); - } - if (!node.getThrows().isEmpty()) { - builder.breakToFill(" "); - builder.open(plusFour); - { - visitThrowsClause(node.getThrows()); - } - builder.close(); - } - if (node.getDefaultValue() != null) { - builder.space(); - token("default"); - if (node.getDefaultValue().getKind() == Tree.Kind.NEW_ARRAY) { - builder.open(minusFour); - { - builder.space(); - scan(node.getDefaultValue(), null); - } - builder.close(); - } else { - builder.open(ZERO); - { - builder.breakToFill(" "); - scan(node.getDefaultValue(), null); - } - builder.close(); - } - } - } - builder.close(); - builder.close(); - builder.close(); - if (node.getBody() == null) { - token(";"); - } else { - builder.space(); - builder.token("{", Doc.Token.RealOrImaginary.REAL, plusTwo, Optional.of(plusTwo)); - } - builder.close(); - - if (node.getBody() != null) { - methodBody(node); - } - - return null; + } + builder.breakOp(minusTwo); + builder.close(); + token("}", plusTwo); } + return false; + } - private void methodBody(MethodTree node) { - if (node.getBody().getStatements().isEmpty()) { - builder.blankLineWanted(BlankLineWanted.NO); - } else { - builder.open(plusTwo); - builder.forcedBreak(); - builder.blankLineWanted(BlankLineWanted.PRESERVE); - visitStatements(node.getBody().getStatements()); - builder.close(); - builder.forcedBreak(); - builder.blankLineWanted(BlankLineWanted.NO); - markForPartialFormat(); + private boolean hasOnlyShortItems(List expressions) { + for (ExpressionTree expression : expressions) { + int startPosition = getStartPosition(expression); + if (builder.actualSize( + startPosition, getEndPosition(expression, getCurrentPath()) - startPosition) + >= MAX_ITEM_LENGTH_FOR_FILLING) { + return false; + } + } + return true; + } + + @Override + public Void visitArrayType(ArrayTypeTree node, Void unused) { + sync(node); + visitAnnotatedArrayType(node); + return null; + } + + private void visitAnnotatedArrayType(Tree node) { + TypeWithDims extractedDims = DimensionHelpers.extractDims(node, SortedDims.YES); + builder.open(plusFour); + scan(extractedDims.node, null); + Deque> dims = new ArrayDeque<>(extractedDims.dims); + maybeAddDims(dims); + Verify.verify(dims.isEmpty()); + builder.close(); + } + + @Override + public Void visitAssert(AssertTree node, Void unused) { + sync(node); + builder.open(ZERO); + token("assert"); + builder.space(); + builder.open(node.getDetail() == null ? ZERO : plusFour); + scan(node.getCondition(), null); + if (node.getDetail() != null) { + builder.breakOp(" "); + token(":"); + builder.space(); + scan(node.getDetail(), null); + } + builder.close(); + builder.close(); + token(";"); + return null; + } + + @Override + public Void visitAssignment(AssignmentTree node, Void unused) { + sync(node); + builder.open(plusFour); + scan(node.getVariable(), null); + builder.space(); + splitToken(operatorName(node)); + builder.breakOp(" "); + scan(node.getExpression(), null); + builder.close(); + return null; + } + + @Override + public Void visitBlock(BlockTree node, Void unused) { + visitBlock(node, CollapseEmptyOrNot.NO, AllowLeadingBlankLine.NO, AllowTrailingBlankLine.NO); + return null; + } + + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree node, Void unused) { + sync(node); + builder.open(plusFour); + scan(node.getVariable(), null); + builder.space(); + splitToken(operatorName(node)); + builder.breakOp(" "); + scan(node.getExpression(), null); + builder.close(); + return null; + } + + @Override + public Void visitBreak(BreakTree node, Void unused) { + sync(node); + builder.open(plusFour); + token("break"); + if (node.getLabel() != null) { + builder.breakOp(" "); + visit(node.getLabel()); + } + builder.close(); + token(";"); + return null; + } + + @Override + public Void visitTypeCast(TypeCastTree node, Void unused) { + sync(node); + builder.open(plusFour); + token("("); + scan(node.getType(), null); + token(")"); + builder.breakOp(" "); + scan(node.getExpression(), null); + builder.close(); + return null; + } + + @Override + public Void visitNewClass(NewClassTree node, Void unused) { + sync(node); + builder.open(ZERO); + if (node.getEnclosingExpression() != null) { + scan(node.getEnclosingExpression(), null); + builder.breakOp(); + token("."); + } + token("new"); + builder.space(); + addTypeArguments(node.getTypeArguments(), plusFour); + if (node.getClassBody() != null) { + List annotations = + visitModifiers( + node.getClassBody().getModifiers(), Direction.HORIZONTAL, Optional.empty()); + visitAnnotations(annotations, BreakOrNot.NO, BreakOrNot.YES); + } + scan(node.getIdentifier(), null); + addArguments(node.getArguments(), plusFour); + builder.close(); + if (node.getClassBody() != null) { + addBodyDeclarations( + node.getClassBody().getMembers(), BracesOrNot.YES, FirstDeclarationsOrNot.YES); + } + return null; + } + + @Override + public Void visitConditionalExpression(ConditionalExpressionTree node, Void unused) { + sync(node); + builder.open(plusFour); + scan(node.getCondition(), null); + builder.breakOp(" "); + token("?"); + builder.space(); + scan(node.getTrueExpression(), null); + builder.breakOp(" "); + token(":"); + builder.space(); + scan(node.getFalseExpression(), null); + builder.close(); + return null; + } + + @Override + public Void visitContinue(ContinueTree node, Void unused) { + sync(node); + builder.open(plusFour); + token("continue"); + if (node.getLabel() != null) { + builder.breakOp(" "); + visit(node.getLabel()); + } + token(";"); + builder.close(); + return null; + } + + @Override + public Void visitDoWhileLoop(DoWhileLoopTree node, Void unused) { + sync(node); + token("do"); + visitStatement( + node.getStatement(), + CollapseEmptyOrNot.YES, + AllowLeadingBlankLine.YES, + AllowTrailingBlankLine.YES); + if (node.getStatement().getKind() == BLOCK) { + builder.space(); + } else { + builder.breakOp(" "); + } + token("while"); + builder.space(); + token("("); + scan(skipParen(node.getCondition()), null); + token(")"); + token(";"); + return null; + } + + @Override + public Void visitEmptyStatement(EmptyStatementTree node, Void unused) { + sync(node); + dropEmptyDeclarations(); + return null; + } + + @Override + public Void visitEnhancedForLoop(EnhancedForLoopTree node, Void unused) { + sync(node); + builder.open(ZERO); + token("for"); + builder.space(); + token("("); + builder.open(ZERO); + visitToDeclare( + DeclarationKind.NONE, + Direction.HORIZONTAL, + node.getVariable(), + Optional.of(node.getExpression()), + ":", + /* trailing= */ Optional.empty()); + builder.close(); + token(")"); + builder.close(); + visitStatement( + node.getStatement(), + CollapseEmptyOrNot.YES, + AllowLeadingBlankLine.YES, + AllowTrailingBlankLine.NO); + return null; + } + + private void visitEnumConstantDeclaration(VariableTree enumConstant) { + for (AnnotationTree annotation : enumConstant.getModifiers().getAnnotations()) { + scan(annotation, null); + builder.forcedBreak(); + } + visit(enumConstant.getName()); + NewClassTree init = ((NewClassTree) enumConstant.getInitializer()); + if (init.getArguments().isEmpty()) { + builder.guessToken("("); + builder.guessToken(")"); + } else { + addArguments(init.getArguments(), plusFour); + } + if (init.getClassBody() != null) { + addBodyDeclarations( + init.getClassBody().getMembers(), BracesOrNot.YES, FirstDeclarationsOrNot.YES); + } + } + + public boolean visitEnumDeclaration(ClassTree node) { + sync(node); + builder.open(ZERO); + typeDeclarationModifiers(node.getModifiers()); + builder.open(plusFour); + token("enum"); + builder.breakOp(" "); + visit(node.getSimpleName()); + builder.close(); + builder.close(); + if (!node.getImplementsClause().isEmpty()) { + builder.open(plusFour); + builder.breakOp(" "); + builder.open(plusFour); + token("implements"); + builder.breakOp(" "); + builder.open(ZERO); + boolean first = true; + for (Tree superInterfaceType : node.getImplementsClause()) { + if (!first) { + token(","); + builder.breakToFill(" "); } + scan(superInterfaceType, null); + first = false; + } + builder.close(); + builder.close(); + builder.close(); + } + builder.space(); + tokenBreakTrailingComment("{", plusTwo); + ArrayList enumConstants = new ArrayList<>(); + ArrayList members = new ArrayList<>(); + for (Tree member : node.getMembers()) { + if (member instanceof JCTree.JCVariableDecl) { + JCTree.JCVariableDecl variableDecl = (JCTree.JCVariableDecl) member; + if ((variableDecl.mods.flags & Flags.ENUM) == Flags.ENUM) { + enumConstants.add(variableDecl); + continue; + } + } + members.add(member); + } + if (enumConstants.isEmpty() && members.isEmpty()) { + if (builder.peekToken().equals(Optional.of(";"))) { + builder.open(plusTwo); + builder.forcedBreak(); + token(";"); + builder.forcedBreak(); + dropEmptyDeclarations(); + builder.close(); + builder.open(ZERO); + builder.forcedBreak(); + builder.blankLineWanted(BlankLineWanted.NO); token("}", plusTwo); - } - - @Override - public Void visitMethodInvocation(MethodInvocationTree node, Void unused) { - sync(node); - if (handleLogStatement(node)) { - return null; - } - visitDot(node); - return null; - } - - /** - * Special-cases log statements, to output: - * - *

{@code
-     * logger.atInfo().log(
-     *     "Number of foos: %d, foos.size());
-     * }
- * - *

Instead of: - * - *

{@code
-     * logger
-     *     .atInfo()
-     *     .log(
-     *         "Number of foos: %d, foos.size());
-     * }
- */ - private boolean handleLogStatement(MethodInvocationTree node) { - if (!getMethodName(node).contentEquals("log")) { - return false; - } - Deque parts = new ArrayDeque<>(); - ExpressionTree curr = node; - while (curr instanceof MethodInvocationTree) { - MethodInvocationTree method = (MethodInvocationTree) curr; - parts.addFirst(method); - if (!LOG_METHODS.contains(getMethodName(method).toString())) { - return false; - } - curr = Trees.getMethodReceiver(method); - } - if (!(curr instanceof IdentifierTree)) { - return false; - } - parts.addFirst(curr); - visitDotWithPrefix( - ImmutableList.copyOf(parts), - false, - ImmutableList.of(parts.size() - 1), - INDEPENDENT); - return true; - } - - static final ImmutableSet LOG_METHODS = - ImmutableSet.of( - "at", - "atConfig", - "atDebug", - "atFine", - "atFiner", - "atFinest", - "atInfo", - "atMostEvery", - "atSevere", - "atWarning", - "every", - "log", - "logVarargs", - "perUnique", - "withCause", - "withStackTrace"); - - private static List handleStream(List parts) { - return indexes( - parts.stream(), - p -> { - if (!(p instanceof MethodInvocationTree)) { - return false; - } - Name name = getMethodName((MethodInvocationTree) p); - return Stream.of("stream", "parallelStream", "toBuilder") - .anyMatch(name::contentEquals); - }) - .collect(toList()); - } - - private static Stream indexes(Stream stream, Predicate predicate) { - return Streams.mapWithIndex(stream, (x, i) -> predicate.apply(x) ? i : -1) - .filter(x -> x != -1); - } - - @Override - public Void visitMemberSelect(MemberSelectTree node, Void unused) { - sync(node); - visitDot(node); - return null; - } - - @Override - public Void visitLiteral(LiteralTree node, Void unused) { - sync(node); - String sourceForNode = getSourceForNode(node, getCurrentPath()); - if (isUnaryMinusLiteral(sourceForNode)) { - token("-"); - sourceForNode = sourceForNode.substring(1).trim(); - } - token(sourceForNode); - return null; - } - - // A negative numeric literal -n is usually represented as unary minus on n, - // but that doesn't work for integer or long MIN_VALUE. The parser works - // around that by representing it directly as a signed literal (with no - // unary minus), but the lexer still expects two tokens. - private static boolean isUnaryMinusLiteral(String literalTreeSource) { - return literalTreeSource.startsWith("-"); - } - - private void visitPackage( - ExpressionTree packageName, List packageAnnotations) { - if (!packageAnnotations.isEmpty()) { - for (AnnotationTree annotation : packageAnnotations) { - builder.forcedBreak(); - scan(annotation, null); - } - builder.forcedBreak(); - } - builder.open(plusFour); - token("package"); - builder.space(); - visitName(packageName); builder.close(); - token(";"); - } - - @Override - public Void visitParameterizedType(ParameterizedTypeTree node, Void unused) { - sync(node); - if (node.getTypeArguments().isEmpty()) { - scan(node.getType(), null); - token("<"); - token(">"); - } else { - builder.open(plusFour); - scan(node.getType(), null); - token("<"); - builder.breakOp(); - builder.open(ZERO); - boolean first = true; - for (Tree typeArgument : node.getTypeArguments()) { - if (!first) { - token(","); - builder.breakOp(" "); - } - scan(typeArgument, null); - first = false; - } - builder.close(); - builder.close(); - token(">"); - } - return null; - } - - @Override - public Void visitParenthesized(ParenthesizedTree node, Void unused) { - token("("); - scan(node.getExpression(), null); - token(")"); - return null; - } - - @Override - public Void visitUnary(UnaryTree node, Void unused) { - sync(node); - String operatorName = operatorName(node); - if (((JCTree) node).getTag().isPostUnaryOp()) { - scan(node.getExpression(), null); - splitToken(operatorName); - } else { - splitToken(operatorName); - if (ambiguousUnaryOperator(node, operatorName)) { - builder.space(); - } - scan(node.getExpression(), null); - } - return null; - } - - private void splitToken(String operatorName) { - for (int i = 0; i < operatorName.length(); i++) { - token(String.valueOf(operatorName.charAt(i))); - } - } - - private boolean ambiguousUnaryOperator(UnaryTree node, String operatorName) { - switch (node.getKind()) { - case UNARY_MINUS: - case UNARY_PLUS: - break; - default: - return false; - } - JCTree.Tag tag = unaryTag(node.getExpression()); - if (tag == null) { - return false; - } - if (tag.isPostUnaryOp()) { - return false; - } - if (!operatorName(node).startsWith(operatorName)) { - return false; - } - return true; - } - - private JCTree.Tag unaryTag(ExpressionTree expression) { - if (expression instanceof UnaryTree) { - return ((JCTree) expression).getTag(); - } - if (expression instanceof LiteralTree - && isUnaryMinusLiteral(getSourceForNode(expression, getCurrentPath()))) { - return JCTree.Tag.MINUS; - } - return null; - } - - @Override - public Void visitPrimitiveType(PrimitiveTypeTree node, Void unused) { - sync(node); - switch (node.getPrimitiveTypeKind()) { - case BOOLEAN: - token("boolean"); - break; - case BYTE: - token("byte"); - break; - case SHORT: - token("short"); - break; - case INT: - token("int"); - break; - case LONG: - token("long"); - break; - case CHAR: - token("char"); - break; - case FLOAT: - token("float"); - break; - case DOUBLE: - token("double"); - break; - case VOID: - token("void"); - break; - default: - throw new AssertionError(node.getPrimitiveTypeKind()); - } - return null; - } - - public boolean visit(Name name) { - token(name.toString()); - return false; - } - - @Override - public Void visitReturn(ReturnTree node, Void unused) { - sync(node); - token("return"); - if (node.getExpression() != null) { - builder.space(); - scan(node.getExpression(), null); - } - token(";"); - return null; - } - - // TODO(cushon): is this worth special-casing? - boolean visitSingleMemberAnnotation(AnnotationTree node) { - if (node.getArguments().size() != 1) { - return false; - } - ExpressionTree value = getOnlyElement(node.getArguments()); - if (value.getKind() == ASSIGNMENT) { - return false; - } - boolean isArrayInitializer = value.getKind() == NEW_ARRAY; - builder.open(isArrayInitializer ? ZERO : plusFour); - token("@"); - scan(node.getAnnotationType(), null); - token("("); - if (!isArrayInitializer) { - builder.breakOp(); - } - scan(value, null); - builder.close(); - token(")"); - return true; - } - - @Override - public Void visitCase(CaseTree node, Void unused) { - sync(node); - markForPartialFormat(); - builder.forcedBreak(); - if (node.getExpression() == null) { - token("default", plusTwo); - token(":"); - } else { - token("case", plusTwo); - builder.space(); - scan(node.getExpression(), null); - token(":"); - } - builder.open(plusTwo); - visitStatements(node.getStatements()); - builder.close(); - return null; - } - - @Override - public Void visitSwitch(SwitchTree node, Void unused) { - sync(node); - visitSwitch(node.getExpression(), node.getCases()); - return null; - } - - protected void visitSwitch(ExpressionTree expression, List cases) { - token("switch"); - builder.space(); - token("("); - scan(skipParen(expression), null); - token(")"); - builder.space(); - tokenBreakTrailingComment("{", plusTwo); - builder.blankLineWanted(BlankLineWanted.NO); - builder.open(plusTwo); - boolean first = true; - for (CaseTree caseTree : cases) { - if (!first) { - builder.blankLineWanted(BlankLineWanted.PRESERVE); - } - scan(caseTree, null); - first = false; - } - builder.close(); - builder.forcedBreak(); - builder.blankLineWanted(BlankLineWanted.NO); - token("}", plusFour); - } - - @Override - public Void visitSynchronized(SynchronizedTree node, Void unused) { - sync(node); - token("synchronized"); - builder.space(); - token("("); - builder.open(plusFour); - builder.breakOp(); - scan(skipParen(node.getExpression()), null); - builder.close(); - token(")"); - builder.space(); - scan(node.getBlock(), null); - return null; - } - - @Override - public Void visitThrow(ThrowTree node, Void unused) { - sync(node); - token("throw"); - builder.space(); - scan(node.getExpression(), null); - token(";"); - return null; - } - - @Override - public Void visitTry(TryTree node, Void unused) { - sync(node); + } else { builder.open(ZERO); - token("try"); - builder.space(); - if (!node.getResources().isEmpty()) { - token("("); - builder.open(node.getResources().size() > 1 ? plusFour : ZERO); - boolean first = true; - for (Tree resource : node.getResources()) { - if (!first) { - builder.forcedBreak(); - } - if (resource instanceof VariableTree) { - VariableTree variableTree = (VariableTree) resource; - declareOne( - DeclarationKind.PARAMETER, - fieldAnnotationDirection(variableTree.getModifiers()), - Optional.of(variableTree.getModifiers()), - variableTree.getType(), - /* name= */ variableTree.getName(), - "", - "=", - Optional.ofNullable(variableTree.getInitializer()), - /* trailing= */ Optional.empty(), - /* receiverExpression= */ Optional.empty(), - /* typeWithDims= */ Optional.empty()); - } else { - // TODO(cushon): think harder about what to do with `try (resource1; resource2) - // {}` - scan(resource, null); - } - if (builder.peekToken().equals(Optional.of(";"))) { - token(";"); - builder.space(); - } - first = false; - } - if (builder.peekToken().equals(Optional.of(";"))) { - token(";"); - builder.space(); - } - token(")"); - builder.close(); - builder.space(); - } - // An empty try-with-resources body can collapse to "{}" if there are no trailing catch or - // finally blocks. - boolean trailingClauses = !node.getCatches().isEmpty() || node.getFinallyBlock() != null; - visitBlock( - node.getBlock(), - CollapseEmptyOrNot.valueOf(!trailingClauses), - AllowLeadingBlankLine.YES, - AllowTrailingBlankLine.valueOf(trailingClauses)); - for (int i = 0; i < node.getCatches().size(); i++) { - CatchTree catchClause = node.getCatches().get(i); - trailingClauses = i < node.getCatches().size() - 1 || node.getFinallyBlock() != null; - visitCatchClause(catchClause, AllowTrailingBlankLine.valueOf(trailingClauses)); - } - if (node.getFinallyBlock() != null) { - builder.space(); - token("finally"); - builder.space(); - visitBlock( - node.getFinallyBlock(), - CollapseEmptyOrNot.NO, - AllowLeadingBlankLine.YES, - AllowTrailingBlankLine.NO); - } + builder.blankLineWanted(BlankLineWanted.NO); + token("}"); builder.close(); - return null; + } + } else { + builder.open(plusTwo); + builder.blankLineWanted(BlankLineWanted.NO); + builder.forcedBreak(); + builder.open(ZERO); + boolean first = true; + for (VariableTree enumConstant : enumConstants) { + if (!first) { + token(","); + builder.forcedBreak(); + builder.blankLineWanted(BlankLineWanted.PRESERVE); + } + markForPartialFormat(); + visitEnumConstantDeclaration(enumConstant); + first = false; + } + if (builder.peekToken().orElse("").equals(",")) { + token(","); + builder.forcedBreak(); // The ";" goes on its own line. + } + builder.close(); + builder.close(); + if (builder.peekToken().equals(Optional.of(";"))) { + builder.open(plusTwo); + token(";"); + builder.forcedBreak(); + dropEmptyDeclarations(); + builder.close(); + } + builder.open(ZERO); + addBodyDeclarations(members, BracesOrNot.NO, FirstDeclarationsOrNot.NO); + builder.forcedBreak(); + builder.blankLineWanted(BlankLineWanted.NO); + token("}", plusTwo); + builder.close(); + } + builder.guessToken(";"); + return false; + } + + @Override + public Void visitMemberReference(MemberReferenceTree node, Void unused) { + sync(node); + builder.open(plusFour); + scan(node.getQualifierExpression(), null); + builder.breakOp(); + builder.op("::"); + addTypeArguments(node.getTypeArguments(), plusFour); + switch (node.getMode()) { + case INVOKE: + visit(node.getName()); + break; + case NEW: + token("new"); + break; + default: + throw new AssertionError(node.getMode()); + } + builder.close(); + return null; + } + + @Override + public Void visitExpressionStatement(ExpressionStatementTree node, Void unused) { + sync(node); + scan(node.getExpression(), null); + token(";"); + return null; + } + + @Override + public Void visitVariable(VariableTree node, Void unused) { + sync(node); + visitVariables( + ImmutableList.of(node), + DeclarationKind.NONE, + fieldAnnotationDirection(node.getModifiers())); + return null; + } + + void visitVariables( + List fragments, + DeclarationKind declarationKind, + Direction annotationDirection) { + if (fragments.size() == 1) { + VariableTree fragment = fragments.get(0); + declareOne( + declarationKind, + annotationDirection, + Optional.of(fragment.getModifiers()), + fragment.getType(), + /* name= */ fragment.getName(), + "", + "=", + Optional.ofNullable(fragment.getInitializer()), + Optional.of(";"), + /* receiverExpression= */ Optional.empty(), + Optional.ofNullable(variableFragmentDims(true, 0, fragment.getType()))); + + } else { + declareMany(fragments, annotationDirection); + } + } + + private static TypeWithDims variableFragmentDims(boolean first, int leadingDims, Tree type) { + if (type == null) { + return null; + } + if (first) { + return DimensionHelpers.extractDims(type, SortedDims.YES); + } + TypeWithDims dims = DimensionHelpers.extractDims(type, SortedDims.NO); + return new TypeWithDims( + null, leadingDims > 0 ? dims.dims.subList(0, dims.dims.size() - leadingDims) : dims.dims); + } + + @Override + public Void visitForLoop(ForLoopTree node, Void unused) { + sync(node); + token("for"); + builder.space(); + token("("); + builder.open(plusFour); + builder.open( + node.getInitializer().size() > 1 + && node.getInitializer().get(0).getKind() == Tree.Kind.EXPRESSION_STATEMENT + ? plusFour + : ZERO); + if (!node.getInitializer().isEmpty()) { + if (node.getInitializer().get(0).getKind() == VARIABLE) { + PeekingIterator it = + Iterators.peekingIterator(node.getInitializer().iterator()); + visitVariables( + variableFragments(it, it.next()), DeclarationKind.NONE, Direction.HORIZONTAL); + } else { + boolean first = true; + builder.open(ZERO); + for (StatementTree t : node.getInitializer()) { + if (!first) { + token(","); + builder.breakOp(" "); + } + scan(((ExpressionStatementTree) t).getExpression(), null); + first = false; + } + token(";"); + builder.close(); + } + } else { + token(";"); + } + builder.close(); + builder.breakOp(" "); + if (node.getCondition() != null) { + scan(node.getCondition(), null); + } + token(";"); + if (!node.getUpdate().isEmpty()) { + builder.breakOp(" "); + builder.open(node.getUpdate().size() <= 1 ? ZERO : plusFour); + boolean firstUpdater = true; + for (ExpressionStatementTree updater : node.getUpdate()) { + if (!firstUpdater) { + token(","); + builder.breakToFill(" "); + } + scan(updater.getExpression(), null); + firstUpdater = false; + } + builder.guessToken(";"); + builder.close(); + } else { + builder.space(); + } + builder.close(); + token(")"); + visitStatement( + node.getStatement(), + CollapseEmptyOrNot.YES, + AllowLeadingBlankLine.YES, + AllowTrailingBlankLine.NO); + return null; + } + + @Override + public Void visitIf(IfTree node, Void unused) { + sync(node); + // Collapse chains of else-ifs. + List expressions = new ArrayList<>(); + List statements = new ArrayList<>(); + while (true) { + expressions.add(node.getCondition()); + statements.add(node.getThenStatement()); + if (node.getElseStatement() != null && node.getElseStatement().getKind() == IF) { + node = (IfTree) node.getElseStatement(); + } else { + break; + } + } + builder.open(ZERO); + boolean first = true; + boolean followingBlock = false; + int expressionsN = expressions.size(); + for (int i = 0; i < expressionsN; i++) { + if (!first) { + if (followingBlock) { + builder.space(); + } else { + builder.forcedBreak(); + } + token("else"); + builder.space(); + } + token("if"); + builder.space(); + token("("); + scan(skipParen(expressions.get(i)), null); + token(")"); + // An empty block can collapse to "{}" if there are no if/else or else clauses + boolean onlyClause = expressionsN == 1 && node.getElseStatement() == null; + // Trailing blank lines are permitted if this isn't the last clause + boolean trailingClauses = i < expressionsN - 1 || node.getElseStatement() != null; + visitStatement( + statements.get(i), + CollapseEmptyOrNot.valueOf(onlyClause), + AllowLeadingBlankLine.YES, + AllowTrailingBlankLine.valueOf(trailingClauses)); + followingBlock = statements.get(i).getKind() == BLOCK; + first = false; + } + if (node.getElseStatement() != null) { + if (followingBlock) { + builder.space(); + } else { + builder.forcedBreak(); + } + token("else"); + visitStatement( + node.getElseStatement(), + CollapseEmptyOrNot.NO, + AllowLeadingBlankLine.YES, + AllowTrailingBlankLine.NO); + } + builder.close(); + return null; + } + + @Override + public Void visitImport(ImportTree node, Void unused) { + checkForTypeAnnotation(node); + sync(node); + token("import"); + builder.space(); + if (node.isStatic()) { + token("static"); + builder.space(); + } + visitName(node.getQualifiedIdentifier()); + token(";"); + // TODO(cushon): remove this if https://bugs.openjdk.java.net/browse/JDK-8027682 is fixed + dropEmptyDeclarations(); + return null; + } + + private void checkForTypeAnnotation(ImportTree node) { + Name simpleName = getSimpleName(node); + Collection wellKnownAnnotations = TYPE_ANNOTATIONS.get(simpleName.toString()); + if (!wellKnownAnnotations.isEmpty() + && wellKnownAnnotations.contains(node.getQualifiedIdentifier().toString())) { + typeAnnotationSimpleNames.add(simpleName); + } + } + + private static Name getSimpleName(ImportTree importTree) { + return importTree.getQualifiedIdentifier() instanceof IdentifierTree + ? ((IdentifierTree) importTree.getQualifiedIdentifier()).getName() + : ((MemberSelectTree) importTree.getQualifiedIdentifier()).getIdentifier(); + } + + @Override + public Void visitBinary(BinaryTree node, Void unused) { + sync(node); + /* + * Collect together all operators with same precedence to clean up indentation. + */ + List operands = new ArrayList<>(); + List operators = new ArrayList<>(); + walkInfix(precedence(node), node, operands, operators); + FillMode fillMode = hasOnlyShortItems(operands) ? INDEPENDENT : UNIFIED; + builder.open(plusFour); + scan(operands.get(0), null); + int operatorsN = operators.size(); + for (int i = 0; i < operatorsN; i++) { + builder.breakOp(fillMode, " ", ZERO); + builder.op(operators.get(i)); + builder.space(); + scan(operands.get(i + 1), null); + } + builder.close(); + return null; + } + + @Override + public Void visitInstanceOf(InstanceOfTree node, Void unused) { + sync(node); + builder.open(plusFour); + scan(node.getExpression(), null); + builder.breakOp(" "); + builder.open(ZERO); + token("instanceof"); + builder.breakOp(" "); + scan(node.getType(), null); + builder.close(); + builder.close(); + return null; + } + + @Override + public Void visitIntersectionType(IntersectionTypeTree node, Void unused) { + sync(node); + builder.open(plusFour); + boolean first = true; + for (Tree type : node.getBounds()) { + if (!first) { + builder.breakToFill(" "); + token("&"); + builder.space(); + } + scan(type, null); + first = false; + } + builder.close(); + return null; + } + + @Override + public Void visitLabeledStatement(LabeledStatementTree node, Void unused) { + sync(node); + builder.open(ZERO); + visit(node.getLabel()); + token(":"); + builder.forcedBreak(); + builder.close(); + scan(node.getStatement(), null); + return null; + } + + @Override + public Void visitLambdaExpression(LambdaExpressionTree node, Void unused) { + sync(node); + boolean statementBody = node.getBodyKind() == LambdaExpressionTree.BodyKind.STATEMENT; + boolean parens = builder.peekToken().equals(Optional.of("(")); + builder.open(parens ? plusFour : ZERO); + if (parens) { + token("("); + } + boolean first = true; + for (VariableTree parameter : node.getParameters()) { + if (!first) { + token(","); + builder.breakOp(" "); + } + scan(parameter, null); + first = false; + } + if (parens) { + token(")"); + } + builder.close(); + builder.space(); + builder.op("->"); + builder.open(statementBody ? ZERO : plusFour); + if (statementBody) { + builder.space(); + } else { + builder.breakOp(" "); + } + if (node.getBody().getKind() == Tree.Kind.BLOCK) { + visitBlock( + (BlockTree) node.getBody(), + CollapseEmptyOrNot.YES, + AllowLeadingBlankLine.NO, + AllowTrailingBlankLine.NO); + } else { + scan(node.getBody(), null); + } + builder.close(); + return null; + } + + @Override + public Void visitAnnotation(AnnotationTree node, Void unused) { + sync(node); + + if (visitSingleMemberAnnotation(node)) { + return null; } - public void visitClassDeclaration(ClassTree node) { - sync(node); - typeDeclarationModifiers(node.getModifiers()); - List permitsTypes = getPermitsClause(node); - boolean hasSuperclassType = node.getExtendsClause() != null; - boolean hasSuperInterfaceTypes = !node.getImplementsClause().isEmpty(); - boolean hasPermitsTypes = !permitsTypes.isEmpty(); - token(node.getKind() == Tree.Kind.INTERFACE ? "interface" : "class"); - builder.space(); - visit(node.getSimpleName()); - if (!node.getTypeParameters().isEmpty()) { - token("<"); + builder.open(ZERO); + token("@"); + scan(node.getAnnotationType(), null); + if (!node.getArguments().isEmpty()) { + builder.open(plusFour); + token("("); + builder.breakOp(); + boolean first = true; + + // Format the member value pairs one-per-line if any of them are + // initialized with arrays. + boolean hasArrayInitializer = + Iterables.any(node.getArguments(), JavaInputAstVisitor::isArrayValue); + for (ExpressionTree argument : node.getArguments()) { + if (!first) { + token(","); + if (hasArrayInitializer) { + builder.forcedBreak(); + } else { + builder.breakOp(" "); + } } + if (argument instanceof AssignmentTree) { + visitAnnotationArgument((AssignmentTree) argument); + } else { + scan(argument, null); + } + first = false; + } + token(")"); + builder.close(); + builder.close(); + return null; + + } else if (builder.peekToken().equals(Optional.of("("))) { + token("("); + token(")"); + } + builder.close(); + return null; + } + + private static boolean isArrayValue(ExpressionTree argument) { + if (!(argument instanceof AssignmentTree)) { + return false; + } + ExpressionTree expression = ((AssignmentTree) argument).getExpression(); + return expression instanceof NewArrayTree && ((NewArrayTree) expression).getType() == null; + } + + public void visitAnnotationArgument(AssignmentTree node) { + boolean isArrayInitializer = node.getExpression().getKind() == NEW_ARRAY; + sync(node); + builder.open(isArrayInitializer ? ZERO : plusFour); + scan(node.getVariable(), null); + builder.space(); + token("="); + if (isArrayInitializer) { + builder.space(); + } else { + builder.breakOp(" "); + } + scan(node.getExpression(), null); + builder.close(); + } + + @Override + public Void visitAnnotatedType(AnnotatedTypeTree node, Void unused) { + sync(node); + ExpressionTree base = node.getUnderlyingType(); + if (base instanceof MemberSelectTree) { + MemberSelectTree selectTree = (MemberSelectTree) base; + scan(selectTree.getExpression(), null); + token("."); + visitAnnotations(node.getAnnotations(), BreakOrNot.NO, BreakOrNot.NO); + builder.breakToFill(" "); + visit(selectTree.getIdentifier()); + } else if (base instanceof ArrayTypeTree) { + visitAnnotatedArrayType(node); + } else { + visitAnnotations(node.getAnnotations(), BreakOrNot.NO, BreakOrNot.NO); + builder.breakToFill(" "); + scan(base, null); + } + return null; + } + + // TODO(cushon): Use Flags if/when we drop support for Java 11 + + protected static final long COMPACT_RECORD_CONSTRUCTOR = 1L << 51; + + protected static final long RECORD = 1L << 61; + + @Override + public Void visitMethod(MethodTree node, Void unused) { + sync(node); + List annotations = node.getModifiers().getAnnotations(); + List returnTypeAnnotations = ImmutableList.of(); + + boolean isRecordConstructor = + (((JCMethodDecl) node).mods.flags & COMPACT_RECORD_CONSTRUCTOR) + == COMPACT_RECORD_CONSTRUCTOR; + + if (!node.getTypeParameters().isEmpty() && !annotations.isEmpty()) { + int typeParameterStart = getStartPosition(node.getTypeParameters().get(0)); + for (int i = 0; i < annotations.size(); i++) { + if (getStartPosition(annotations.get(i)) > typeParameterStart) { + returnTypeAnnotations = annotations.subList(i, annotations.size()); + annotations = annotations.subList(0, i); + break; + } + } + } + List typeAnnotations = + visitModifiers( + node.getModifiers(), + annotations, + Direction.VERTICAL, + /* declarationAnnotationBreak= */ Optional.empty()); + + Tree baseReturnType = null; + Deque> dims = null; + if (node.getReturnType() != null) { + TypeWithDims extractedDims = + DimensionHelpers.extractDims(node.getReturnType(), SortedDims.YES); + baseReturnType = extractedDims.node; + dims = new ArrayDeque<>(extractedDims.dims); + } else { + verticalAnnotations(typeAnnotations); + typeAnnotations = ImmutableList.of(); + } + + builder.open(plusFour); + BreakTag breakBeforeName = genSym(); + BreakTag breakBeforeType = genSym(); + builder.open(ZERO); + { + boolean first = true; + if (!typeAnnotations.isEmpty()) { + visitAnnotations(typeAnnotations, BreakOrNot.NO, BreakOrNot.NO); + first = false; + } + if (!node.getTypeParameters().isEmpty()) { + if (!first) { + builder.breakToFill(" "); + } + token("<"); + typeParametersRest(node.getTypeParameters(), plusFour); + if (!returnTypeAnnotations.isEmpty()) { + builder.breakToFill(" "); + visitAnnotations(returnTypeAnnotations, BreakOrNot.NO, BreakOrNot.NO); + } + first = false; + } + + boolean openedNameAndTypeScope = false; + // constructor-like declarations that don't match the name of the enclosing class are + // parsed as method declarations with a null return type + if (baseReturnType != null) { + if (!first) { + builder.breakOp(INDEPENDENT, " ", ZERO, Optional.of(breakBeforeType)); + } else { + first = false; + } + if (!openedNameAndTypeScope) { + builder.open(make(breakBeforeType, plusFour, ZERO)); + openedNameAndTypeScope = true; + } + scan(baseReturnType, null); + maybeAddDims(dims); + } + if (!first) { + builder.breakOp(Doc.FillMode.INDEPENDENT, " ", ZERO, Optional.of(breakBeforeName)); + } else { + first = false; + } + if (!openedNameAndTypeScope) { + builder.open(ZERO); + openedNameAndTypeScope = true; + } + String name = node.getName().toString(); + if (name.equals("")) { + name = builder.peekToken().get(); + } + token(name); + if (!isRecordConstructor) { + token("("); + } + // end of name and type scope + builder.close(); + } + builder.close(); + + builder.open(Indent.If.make(breakBeforeName, plusFour, ZERO)); + builder.open(Indent.If.make(breakBeforeType, plusFour, ZERO)); + builder.open(ZERO); + { + if (!isRecordConstructor) { + if (!node.getParameters().isEmpty() || node.getReceiverParameter() != null) { + // Break before args. + builder.breakToFill(""); + visitFormals(Optional.ofNullable(node.getReceiverParameter()), node.getParameters()); + } + token(")"); + } + if (dims != null) { + maybeAddDims(dims); + } + if (!node.getThrows().isEmpty()) { + builder.breakToFill(" "); builder.open(plusFour); { - if (!node.getTypeParameters().isEmpty()) { - typeParametersRest( - node.getTypeParameters(), - hasSuperclassType || hasSuperInterfaceTypes || hasPermitsTypes - ? plusFour - : ZERO); - } - if (hasSuperclassType) { - builder.breakToFill(" "); - token("extends"); - builder.space(); - scan(node.getExtendsClause(), null); - } - classDeclarationTypeList( - node.getKind() == Tree.Kind.INTERFACE ? "extends" : "implements", - node.getImplementsClause()); - classDeclarationTypeList("permits", permitsTypes); + visitThrowsClause(node.getThrows()); } builder.close(); - if (node.getMembers() == null) { - token(";"); - } else { - addBodyDeclarations(node.getMembers(), BracesOrNot.YES, FirstDeclarationsOrNot.YES); - } - dropEmptyDeclarations(); - } - - @Override - public Void visitTypeParameter(TypeParameterTree node, Void unused) { - sync(node); - builder.open(ZERO); - visitAnnotations(node.getAnnotations(), BreakOrNot.NO, BreakOrNot.YES); - visit(node.getName()); - if (!node.getBounds().isEmpty()) { - builder.space(); - token("extends"); - builder.open(plusFour); - builder.breakOp(" "); - builder.open(plusFour); - boolean first = true; - for (Tree typeBound : node.getBounds()) { - if (!first) { - builder.breakToFill(" "); - token("&"); - builder.space(); - } - scan(typeBound, null); - first = false; - } - builder.close(); - builder.close(); - } - builder.close(); - return null; - } - - @Override - public Void visitUnionType(UnionTypeTree node, Void unused) { - throw new IllegalStateException("expected manual descent into union types"); - } - - @Override - public Void visitWhileLoop(WhileLoopTree node, Void unused) { - sync(node); - token("while"); + } + if (node.getDefaultValue() != null) { builder.space(); - token("("); - scan(skipParen(node.getCondition()), null); - token(")"); - visitStatement( - node.getStatement(), - CollapseEmptyOrNot.YES, - AllowLeadingBlankLine.YES, - AllowTrailingBlankLine.NO); - return null; - } - - @Override - public Void visitWildcard(WildcardTree node, Void unused) { - sync(node); - builder.open(ZERO); - token("?"); - if (node.getBound() != null) { - builder.open(plusFour); + token("default"); + if (node.getDefaultValue().getKind() == Tree.Kind.NEW_ARRAY) { + builder.open(minusFour); + { builder.space(); - token(node.getKind() == EXTENDS_WILDCARD ? "extends" : "super"); - builder.breakOp(" "); - scan(node.getBound(), null); - builder.close(); - } - builder.close(); - return null; - } - - // Helper methods. - - /** Helper method for annotations. */ - protected void visitAnnotations( - List annotations, - BreakOrNot breakBefore, - BreakOrNot breakAfter) { - if (!annotations.isEmpty()) { - if (breakBefore.isYes()) { - builder.breakToFill(" "); - } - boolean first = true; - for (AnnotationTree annotation : annotations) { - if (!first) { - builder.breakToFill(" "); - } - scan(annotation, null); - first = false; - } - if (breakAfter.isYes()) { - builder.breakToFill(" "); - } - } - } - - void verticalAnnotations(List annotations) { - for (AnnotationTree annotation : annotations) { - builder.forcedBreak(); - scan(annotation, null); - builder.forcedBreak(); - } - } - - /** Helper method for blocks. */ - protected void visitBlock( - BlockTree node, - CollapseEmptyOrNot collapseEmptyOrNot, - AllowLeadingBlankLine allowLeadingBlankLine, - AllowTrailingBlankLine allowTrailingBlankLine) { - sync(node); - if (node.isStatic()) { - token("static"); - builder.space(); - } - if (collapseEmptyOrNot.isYes() && node.getStatements().isEmpty()) { - if (builder.peekToken().equals(Optional.of(";"))) { - // TODO(cushon): is this needed? - token(";"); - } else { - tokenBreakTrailingComment("{", plusTwo); - builder.blankLineWanted(BlankLineWanted.NO); - token("}", plusTwo); - } + scan(node.getDefaultValue(), null); + } + builder.close(); } else { - builder.open(ZERO); - builder.open(plusTwo); - tokenBreakTrailingComment("{", plusTwo); - if (allowLeadingBlankLine == AllowLeadingBlankLine.NO) { - builder.blankLineWanted(BlankLineWanted.NO); - } else { - builder.blankLineWanted(BlankLineWanted.PRESERVE); - } - visitStatements(node.getStatements()); - builder.close(); - builder.forcedBreak(); - builder.close(); - if (allowTrailingBlankLine == AllowTrailingBlankLine.NO) { - builder.blankLineWanted(BlankLineWanted.NO); - } else { - builder.blankLineWanted(BlankLineWanted.PRESERVE); - } - markForPartialFormat(); - token("}", plusTwo); + builder.open(ZERO); + { + builder.breakToFill(" "); + scan(node.getDefaultValue(), null); + } + builder.close(); } + } + } + builder.close(); + builder.close(); + builder.close(); + if (node.getBody() == null) { + token(";"); + } else { + builder.space(); + builder.token("{", Doc.Token.RealOrImaginary.REAL, plusTwo, Optional.of(plusTwo)); + } + builder.close(); + + if (node.getBody() != null) { + methodBody(node); } - /** Helper method for statements. */ - private void visitStatement( - StatementTree node, - CollapseEmptyOrNot collapseEmptyOrNot, - AllowLeadingBlankLine allowLeadingBlank, - AllowTrailingBlankLine allowTrailingBlank) { - sync(node); - switch (node.getKind()) { - case BLOCK: - builder.space(); - visitBlock( - (BlockTree) node, - collapseEmptyOrNot, - allowLeadingBlank, - allowTrailingBlank); - break; - default: - builder.open(plusTwo); - builder.breakOp(" "); - scan(node, null); - builder.close(); - } + return null; + } + + private void methodBody(MethodTree node) { + if (node.getBody().getStatements().isEmpty()) { + builder.blankLineWanted(BlankLineWanted.NO); + } else { + builder.open(plusTwo); + builder.forcedBreak(); + builder.blankLineWanted(BlankLineWanted.PRESERVE); + visitStatements(node.getBody().getStatements()); + builder.close(); + builder.forcedBreak(); + builder.blankLineWanted(BlankLineWanted.NO); + markForPartialFormat(); } + token("}", plusTwo); + } - protected void visitStatements(List statements) { - boolean first = true; - PeekingIterator it = Iterators.peekingIterator(statements.iterator()); - dropEmptyDeclarations(); - while (it.hasNext()) { - StatementTree tree = it.next(); - builder.forcedBreak(); - if (!first) { - builder.blankLineWanted(BlankLineWanted.PRESERVE); - } - markForPartialFormat(); - first = false; - List fragments = variableFragments(it, tree); - if (!fragments.isEmpty()) { - visitVariables( - fragments, - DeclarationKind.NONE, - canLocalHaveHorizontalAnnotations(fragments.get(0).getModifiers())); - } else { - scan(tree, null); - } - } + @Override + public Void visitMethodInvocation(MethodInvocationTree node, Void unused) { + sync(node); + if (handleLogStatement(node)) { + return null; } + visitDot(node); + return null; + } - protected void typeDeclarationModifiers(ModifiersTree modifiers) { - List typeAnnotations = - visitModifiers( - modifiers, - Direction.VERTICAL, - /* declarationAnnotationBreak= */ Optional.empty()); - verticalAnnotations(typeAnnotations); + /** + * Special-cases log statements, to output: + * + *
{@code
+   * logger.atInfo().log(
+   *     "Number of foos: %d, foos.size());
+   * }
+ * + *

Instead of: + * + *

{@code
+   * logger
+   *     .atInfo()
+   *     .log(
+   *         "Number of foos: %d, foos.size());
+   * }
+ */ + private boolean handleLogStatement(MethodInvocationTree node) { + if (!getMethodName(node).contentEquals("log")) { + return false; } - - /** Output combined modifiers and annotations and the trailing break. */ - void visitAndBreakModifiers( - ModifiersTree modifiers, - Direction annotationDirection, - Optional declarationAnnotationBreak) { - List typeAnnotations = - visitModifiers(modifiers, annotationDirection, declarationAnnotationBreak); - visitAnnotations(typeAnnotations, BreakOrNot.NO, BreakOrNot.YES); + Deque parts = new ArrayDeque<>(); + ExpressionTree curr = node; + while (curr instanceof MethodInvocationTree) { + MethodInvocationTree method = (MethodInvocationTree) curr; + parts.addFirst(method); + if (!LOG_METHODS.contains(getMethodName(method).toString())) { + return false; + } + curr = Trees.getMethodReceiver(method); } - - @Override - public Void visitModifiers(ModifiersTree node, Void unused) { - throw new IllegalStateException("expected manual descent into modifiers"); + if (!(curr instanceof IdentifierTree)) { + return false; } + parts.addFirst(curr); + visitDotWithPrefix( + ImmutableList.copyOf(parts), false, ImmutableList.of(parts.size() - 1), INDEPENDENT); + return true; + } - /** Output combined modifiers and annotations and returns the trailing break. */ - @CheckReturnValue - protected ImmutableList visitModifiers( - ModifiersTree modifiersTree, - Direction annotationsDirection, - Optional declarationAnnotationBreak) { - return visitModifiers( - modifiersTree, - modifiersTree.getAnnotations(), - annotationsDirection, - declarationAnnotationBreak); - } + static final ImmutableSet LOG_METHODS = + ImmutableSet.of( + "at", + "atConfig", + "atDebug", + "atFine", + "atFiner", + "atFinest", + "atInfo", + "atMostEvery", + "atSevere", + "atWarning", + "every", + "log", + "logVarargs", + "perUnique", + "withCause", + "withStackTrace"); - @CheckReturnValue - protected ImmutableList visitModifiers( - ModifiersTree modifiersTree, - List annotationTrees, - Direction annotationsDirection, - Optional declarationAnnotationBreak) { - DeclarationModifiersAndTypeAnnotations splitModifiers = - splitModifiers(modifiersTree, annotationTrees); - return visitModifiers(splitModifiers, annotationsDirection, declarationAnnotationBreak); - } - - @CheckReturnValue - private ImmutableList visitModifiers( - DeclarationModifiersAndTypeAnnotations splitModifiers, - Direction annotationsDirection, - Optional declarationAnnotationBreak) { - if (splitModifiers.declarationModifiers().isEmpty()) { - return splitModifiers.typeAnnotations(); - } - Deque declarationModifiers = - new ArrayDeque<>(splitModifiers.declarationModifiers()); - builder.open(ZERO); - boolean first = true; - boolean lastWasAnnotation = false; - while (!declarationModifiers.isEmpty() && !declarationModifiers.peekFirst().isModifier()) { - if (!first) { - builder.addAll( - annotationsDirection.isVertical() - ? forceBreakList(declarationAnnotationBreak) - : breakList(declarationAnnotationBreak)); - } - formatAnnotationOrModifier(declarationModifiers); - first = false; - lastWasAnnotation = true; - } - builder.close(); - ImmutableList trailingBreak = - annotationsDirection.isVertical() - ? forceBreakList(declarationAnnotationBreak) - : breakList(declarationAnnotationBreak); - if (declarationModifiers.isEmpty()) { - builder.addAll(trailingBreak); - return splitModifiers.typeAnnotations(); - } - if (lastWasAnnotation) { - builder.addAll(trailingBreak); - } - - builder.open(ZERO); - first = true; - while (!declarationModifiers.isEmpty()) { - if (!first) { - builder.addAll(breakFillList(Optional.empty())); - } - formatAnnotationOrModifier(declarationModifiers); - first = false; - } - builder.close(); - builder.addAll(breakFillList(Optional.empty())); - return splitModifiers.typeAnnotations(); - } - - /** Represents an annotation or a modifier in a {@link ModifiersTree}. */ - @AutoOneOf(AnnotationOrModifier.Kind.class) - abstract static class AnnotationOrModifier implements Comparable { - enum Kind { - MODIFIER, - ANNOTATION - } - - abstract Kind getKind(); - - abstract AnnotationTree annotation(); - - abstract Input.Tok modifier(); - - static AnnotationOrModifier ofModifier(Input.Tok m) { - return AutoOneOf_JavaInputAstVisitor_AnnotationOrModifier.modifier(m); - } - - static AnnotationOrModifier ofAnnotation(AnnotationTree a) { - return AutoOneOf_JavaInputAstVisitor_AnnotationOrModifier.annotation(a); - } - - boolean isModifier() { - return getKind().equals(Kind.MODIFIER); - } - - boolean isAnnotation() { - return getKind().equals(Kind.ANNOTATION); - } - - int position() { - switch (getKind()) { - case MODIFIER: - return modifier().getPosition(); - case ANNOTATION: - return getStartPosition(annotation()); - } - throw new AssertionError(); - } - - private static final Comparator COMPARATOR = - Comparator.comparingInt(AnnotationOrModifier::position); - - @Override - public int compareTo(AnnotationOrModifier o) { - return COMPARATOR.compare(this, o); - } - } - - /** - * The modifiers annotations for a declaration, grouped in to a prefix that contains all of the - * declaration annotations and modifiers, and a suffix of type annotations. - * - *

For examples like {@code @Deprecated public @Nullable Foo foo();}, this allows us to - * format {@code @Deprecated public} as declaration modifiers, and {@code @Nullable} as a type - * annotation on the return type. - */ - @AutoValue - abstract static class DeclarationModifiersAndTypeAnnotations { - abstract ImmutableList declarationModifiers(); - - abstract ImmutableList typeAnnotations(); - - static DeclarationModifiersAndTypeAnnotations create( - ImmutableList declarationModifiers, - ImmutableList typeAnnotations) { - return new AutoValue_JavaInputAstVisitor_DeclarationModifiersAndTypeAnnotations( - declarationModifiers, typeAnnotations); - } - - static DeclarationModifiersAndTypeAnnotations empty() { - return create(ImmutableList.of(), ImmutableList.of()); - } - - boolean hasDeclarationAnnotation() { - return declarationModifiers().stream().anyMatch(AnnotationOrModifier::isAnnotation); - } - } - - /** - * Examines the token stream to convert the modifiers for a declaration into a {@link - * DeclarationModifiersAndTypeAnnotations}. - */ - DeclarationModifiersAndTypeAnnotations splitModifiers( - ModifiersTree modifiersTree, List annotations) { - if (annotations.isEmpty() && !isModifier(builder.peekToken().get())) { - return DeclarationModifiersAndTypeAnnotations.empty(); - } - RangeSet annotationRanges = TreeRangeSet.create(); - for (AnnotationTree annotationTree : annotations) { - annotationRanges.add( - Range.closedOpen( - getStartPosition(annotationTree), - getEndPosition(annotationTree, getCurrentPath()))); - } - ImmutableList toks = - builder.peekTokens( - getStartPosition(modifiersTree), - (Input.Tok tok) -> - // ModifiersTree end position information isn't reliable, so scan - // tokens as long as - // we're seeing annotations or modifiers - annotationRanges.contains(tok.getPosition()) - || isModifier(tok.getText())); - ImmutableList modifiers = - ImmutableList.copyOf( - Streams.concat( - toks.stream() - // reject tokens from inside AnnotationTrees, we - // only want modifiers - .filter( - t -> - !annotationRanges.contains( - t.getPosition())) - .map(AnnotationOrModifier::ofModifier), - annotations.stream() - .map(AnnotationOrModifier::ofAnnotation)) - .sorted() - .collect(toList())); - // Take a suffix of annotations that are well-known type annotations, and which appear after - // any - // declaration annotations or modifiers - ImmutableList.Builder typeAnnotations = ImmutableList.builder(); - int idx = modifiers.size() - 1; - while (idx >= 0) { - AnnotationOrModifier modifier = modifiers.get(idx); - if (!modifier.isAnnotation() || !isTypeAnnotation(modifier.annotation())) { - break; - } - typeAnnotations.add(modifier.annotation()); - idx--; - } - return DeclarationModifiersAndTypeAnnotations.create( - modifiers.subList(0, idx + 1), typeAnnotations.build().reverse()); - } - - private void formatAnnotationOrModifier(Deque modifiers) { - AnnotationOrModifier modifier = modifiers.removeFirst(); - switch (modifier.getKind()) { - case MODIFIER: - token(modifier.modifier().getText()); - if (modifier.modifier().getText().equals("non")) { - token(modifiers.removeFirst().modifier().getText()); - token(modifiers.removeFirst().modifier().getText()); - } - break; - case ANNOTATION: - scan(modifier.annotation(), null); - break; - } - } - - boolean isTypeAnnotation(AnnotationTree annotationTree) { - Tree annotationType = annotationTree.getAnnotationType(); - if (!(annotationType instanceof IdentifierTree)) { - return false; - } - return typeAnnotationSimpleNames.contains(((IdentifierTree) annotationType).getName()); - } - - private static boolean isModifier(String token) { - switch (token) { - case "public": - case "protected": - case "private": - case "abstract": - case "static": - case "final": - case "transient": - case "volatile": - case "synchronized": - case "native": - case "strictfp": - case "default": - case "sealed": - case "non": - case "-": - return true; - default: + private static List handleStream(List parts) { + return indexes( + parts.stream(), + p -> { + if (!(p instanceof MethodInvocationTree)) { return false; - } + } + Name name = getMethodName((MethodInvocationTree) p); + return Stream.of("stream", "parallelStream", "toBuilder") + .anyMatch(name::contentEquals); + }) + .collect(toList()); + } + + private static Stream indexes(Stream stream, Predicate predicate) { + return Streams.mapWithIndex(stream, (x, i) -> predicate.apply(x) ? i : -1).filter(x -> x != -1); + } + + @Override + public Void visitMemberSelect(MemberSelectTree node, Void unused) { + sync(node); + visitDot(node); + return null; + } + + @Override + public Void visitLiteral(LiteralTree node, Void unused) { + sync(node); + String sourceForNode = getSourceForNode(node, getCurrentPath()); + if (isUnaryMinusLiteral(sourceForNode)) { + token("-"); + sourceForNode = sourceForNode.substring(1).trim(); } + token(sourceForNode); + return null; + } + + // A negative numeric literal -n is usually represented as unary minus on n, + // but that doesn't work for integer or long MIN_VALUE. The parser works + // around that by representing it directly as a signed literal (with no + // unary minus), but the lexer still expects two tokens. + private static boolean isUnaryMinusLiteral(String literalTreeSource) { + return literalTreeSource.startsWith("-"); + } + + private void visitPackage( + ExpressionTree packageName, List packageAnnotations) { + if (!packageAnnotations.isEmpty()) { + for (AnnotationTree annotation : packageAnnotations) { + builder.forcedBreak(); + scan(annotation, null); + } + builder.forcedBreak(); + } + builder.open(plusFour); + token("package"); + builder.space(); + visitName(packageName); + builder.close(); + token(";"); + } + + @Override + public Void visitParameterizedType(ParameterizedTypeTree node, Void unused) { + sync(node); + if (node.getTypeArguments().isEmpty()) { + scan(node.getType(), null); + token("<"); + token(">"); + } else { + builder.open(plusFour); + scan(node.getType(), null); + token("<"); + builder.breakOp(); + builder.open(ZERO); + boolean first = true; + for (Tree typeArgument : node.getTypeArguments()) { + if (!first) { + token(","); + builder.breakOp(" "); + } + scan(typeArgument, null); + first = false; + } + builder.close(); + builder.close(); + token(">"); + } + return null; + } + + @Override + public Void visitParenthesized(ParenthesizedTree node, Void unused) { + token("("); + scan(node.getExpression(), null); + token(")"); + return null; + } + + @Override + public Void visitUnary(UnaryTree node, Void unused) { + sync(node); + String operatorName = operatorName(node); + if (((JCTree) node).getTag().isPostUnaryOp()) { + scan(node.getExpression(), null); + splitToken(operatorName); + } else { + splitToken(operatorName); + if (ambiguousUnaryOperator(node, operatorName)) { + builder.space(); + } + scan(node.getExpression(), null); + } + return null; + } + + private void splitToken(String operatorName) { + for (int i = 0; i < operatorName.length(); i++) { + token(String.valueOf(operatorName.charAt(i))); + } + } + + private boolean ambiguousUnaryOperator(UnaryTree node, String operatorName) { + switch (node.getKind()) { + case UNARY_MINUS: + case UNARY_PLUS: + break; + default: + return false; + } + JCTree.Tag tag = unaryTag(node.getExpression()); + if (tag == null) { + return false; + } + if (tag.isPostUnaryOp()) { + return false; + } + if (!operatorName(node).startsWith(operatorName)) { + return false; + } + return true; + } + + private JCTree.Tag unaryTag(ExpressionTree expression) { + if (expression instanceof UnaryTree) { + return ((JCTree) expression).getTag(); + } + if (expression instanceof LiteralTree + && isUnaryMinusLiteral(getSourceForNode(expression, getCurrentPath()))) { + return JCTree.Tag.MINUS; + } + return null; + } + + @Override + public Void visitPrimitiveType(PrimitiveTypeTree node, Void unused) { + sync(node); + switch (node.getPrimitiveTypeKind()) { + case BOOLEAN: + token("boolean"); + break; + case BYTE: + token("byte"); + break; + case SHORT: + token("short"); + break; + case INT: + token("int"); + break; + case LONG: + token("long"); + break; + case CHAR: + token("char"); + break; + case FLOAT: + token("float"); + break; + case DOUBLE: + token("double"); + break; + case VOID: + token("void"); + break; + default: + throw new AssertionError(node.getPrimitiveTypeKind()); + } + return null; + } + + public boolean visit(Name name) { + token(name.toString()); + return false; + } + + @Override + public Void visitReturn(ReturnTree node, Void unused) { + sync(node); + token("return"); + if (node.getExpression() != null) { + builder.space(); + scan(node.getExpression(), null); + } + token(";"); + return null; + } + + // TODO(cushon): is this worth special-casing? + boolean visitSingleMemberAnnotation(AnnotationTree node) { + if (node.getArguments().size() != 1) { + return false; + } + ExpressionTree value = getOnlyElement(node.getArguments()); + if (value.getKind() == ASSIGNMENT) { + return false; + } + boolean isArrayInitializer = value.getKind() == NEW_ARRAY; + builder.open(isArrayInitializer ? ZERO : plusFour); + token("@"); + scan(node.getAnnotationType(), null); + token("("); + if (!isArrayInitializer) { + builder.breakOp(); + } + scan(value, null); + builder.close(); + token(")"); + return true; + } + + @Override + public Void visitCase(CaseTree node, Void unused) { + sync(node); + markForPartialFormat(); + builder.forcedBreak(); + if (node.getExpression() == null) { + token("default", plusTwo); + token(":"); + } else { + token("case", plusTwo); + builder.space(); + scan(node.getExpression(), null); + token(":"); + } + builder.open(plusTwo); + visitStatements(node.getStatements()); + builder.close(); + return null; + } + + @Override + public Void visitSwitch(SwitchTree node, Void unused) { + sync(node); + visitSwitch(node.getExpression(), node.getCases()); + return null; + } + + protected void visitSwitch(ExpressionTree expression, List cases) { + token("switch"); + builder.space(); + token("("); + scan(skipParen(expression), null); + token(")"); + builder.space(); + tokenBreakTrailingComment("{", plusTwo); + builder.blankLineWanted(BlankLineWanted.NO); + builder.open(plusTwo); + boolean first = true; + for (CaseTree caseTree : cases) { + if (!first) { + builder.blankLineWanted(BlankLineWanted.PRESERVE); + } + scan(caseTree, null); + first = false; + } + builder.close(); + builder.forcedBreak(); + builder.blankLineWanted(BlankLineWanted.NO); + token("}", plusFour); + } + + @Override + public Void visitSynchronized(SynchronizedTree node, Void unused) { + sync(node); + token("synchronized"); + builder.space(); + token("("); + builder.open(plusFour); + builder.breakOp(); + scan(skipParen(node.getExpression()), null); + builder.close(); + token(")"); + builder.space(); + scan(node.getBlock(), null); + return null; + } + + @Override + public Void visitThrow(ThrowTree node, Void unused) { + sync(node); + token("throw"); + builder.space(); + scan(node.getExpression(), null); + token(";"); + return null; + } + + @Override + public Void visitTry(TryTree node, Void unused) { + sync(node); + builder.open(ZERO); + token("try"); + builder.space(); + if (!node.getResources().isEmpty()) { + token("("); + builder.open(node.getResources().size() > 1 ? plusFour : ZERO); + boolean first = true; + for (Tree resource : node.getResources()) { + if (!first) { + builder.forcedBreak(); + } + if (resource instanceof VariableTree) { + VariableTree variableTree = (VariableTree) resource; + declareOne( + DeclarationKind.PARAMETER, + fieldAnnotationDirection(variableTree.getModifiers()), + Optional.of(variableTree.getModifiers()), + variableTree.getType(), + /* name= */ variableTree.getName(), + "", + "=", + Optional.ofNullable(variableTree.getInitializer()), + /* trailing= */ Optional.empty(), + /* receiverExpression= */ Optional.empty(), + /* typeWithDims= */ Optional.empty()); + } else { + // TODO(cushon): think harder about what to do with `try (resource1; resource2) + // {}` + scan(resource, null); + } + if (builder.peekToken().equals(Optional.of(";"))) { + token(";"); + builder.space(); + } + first = false; + } + if (builder.peekToken().equals(Optional.of(";"))) { + token(";"); + builder.space(); + } + token(")"); + builder.close(); + builder.space(); + } + // An empty try-with-resources body can collapse to "{}" if there are no trailing catch or + // finally blocks. + boolean trailingClauses = !node.getCatches().isEmpty() || node.getFinallyBlock() != null; + visitBlock( + node.getBlock(), + CollapseEmptyOrNot.valueOf(!trailingClauses), + AllowLeadingBlankLine.YES, + AllowTrailingBlankLine.valueOf(trailingClauses)); + for (int i = 0; i < node.getCatches().size(); i++) { + CatchTree catchClause = node.getCatches().get(i); + trailingClauses = i < node.getCatches().size() - 1 || node.getFinallyBlock() != null; + visitCatchClause(catchClause, AllowTrailingBlankLine.valueOf(trailingClauses)); + } + if (node.getFinallyBlock() != null) { + builder.space(); + token("finally"); + builder.space(); + visitBlock( + node.getFinallyBlock(), + CollapseEmptyOrNot.NO, + AllowLeadingBlankLine.YES, + AllowTrailingBlankLine.NO); + } + builder.close(); + return null; + } + + public void visitClassDeclaration(ClassTree node) { + sync(node); + typeDeclarationModifiers(node.getModifiers()); + List permitsTypes = getPermitsClause(node); + boolean hasSuperclassType = node.getExtendsClause() != null; + boolean hasSuperInterfaceTypes = !node.getImplementsClause().isEmpty(); + boolean hasPermitsTypes = !permitsTypes.isEmpty(); + token(node.getKind() == Tree.Kind.INTERFACE ? "interface" : "class"); + builder.space(); + visit(node.getSimpleName()); + if (!node.getTypeParameters().isEmpty()) { + token("<"); + } + builder.open(plusFour); + { + if (!node.getTypeParameters().isEmpty()) { + typeParametersRest( + node.getTypeParameters(), + hasSuperclassType || hasSuperInterfaceTypes || hasPermitsTypes ? plusFour : ZERO); + } + if (hasSuperclassType) { + builder.breakToFill(" "); + token("extends"); + builder.space(); + scan(node.getExtendsClause(), null); + } + classDeclarationTypeList( + node.getKind() == Tree.Kind.INTERFACE ? "extends" : "implements", + node.getImplementsClause()); + classDeclarationTypeList("permits", permitsTypes); + } + builder.close(); + if (node.getMembers() == null) { + token(";"); + } else { + addBodyDeclarations(node.getMembers(), BracesOrNot.YES, FirstDeclarationsOrNot.YES); + } + dropEmptyDeclarations(); + } + + @Override + public Void visitTypeParameter(TypeParameterTree node, Void unused) { + sync(node); + builder.open(ZERO); + visitAnnotations(node.getAnnotations(), BreakOrNot.NO, BreakOrNot.YES); + visit(node.getName()); + if (!node.getBounds().isEmpty()) { + builder.space(); + token("extends"); + builder.open(plusFour); + builder.breakOp(" "); + builder.open(plusFour); + boolean first = true; + for (Tree typeBound : node.getBounds()) { + if (!first) { + builder.breakToFill(" "); + token("&"); + builder.space(); + } + scan(typeBound, null); + first = false; + } + builder.close(); + builder.close(); + } + builder.close(); + return null; + } + + @Override + public Void visitUnionType(UnionTypeTree node, Void unused) { + throw new IllegalStateException("expected manual descent into union types"); + } + + @Override + public Void visitWhileLoop(WhileLoopTree node, Void unused) { + sync(node); + token("while"); + builder.space(); + token("("); + scan(skipParen(node.getCondition()), null); + token(")"); + visitStatement( + node.getStatement(), + CollapseEmptyOrNot.YES, + AllowLeadingBlankLine.YES, + AllowTrailingBlankLine.NO); + return null; + } + + @Override + public Void visitWildcard(WildcardTree node, Void unused) { + sync(node); + builder.open(ZERO); + token("?"); + if (node.getBound() != null) { + builder.open(plusFour); + builder.space(); + token(node.getKind() == EXTENDS_WILDCARD ? "extends" : "super"); + builder.breakOp(" "); + scan(node.getBound(), null); + builder.close(); + } + builder.close(); + return null; + } + + // Helper methods. + + /** Helper method for annotations. */ + protected void visitAnnotations( + List annotations, BreakOrNot breakBefore, BreakOrNot breakAfter) { + if (!annotations.isEmpty()) { + if (breakBefore.isYes()) { + builder.breakToFill(" "); + } + boolean first = true; + for (AnnotationTree annotation : annotations) { + if (!first) { + builder.breakToFill(" "); + } + scan(annotation, null); + first = false; + } + if (breakAfter.isYes()) { + builder.breakToFill(" "); + } + } + } + + void verticalAnnotations(List annotations) { + for (AnnotationTree annotation : annotations) { + builder.forcedBreak(); + scan(annotation, null); + builder.forcedBreak(); + } + } + + /** Helper method for blocks. */ + protected void visitBlock( + BlockTree node, + CollapseEmptyOrNot collapseEmptyOrNot, + AllowLeadingBlankLine allowLeadingBlankLine, + AllowTrailingBlankLine allowTrailingBlankLine) { + sync(node); + if (node.isStatic()) { + token("static"); + builder.space(); + } + if (collapseEmptyOrNot.isYes() && node.getStatements().isEmpty()) { + if (builder.peekToken().equals(Optional.of(";"))) { + // TODO(cushon): is this needed? + token(";"); + } else { + tokenBreakTrailingComment("{", plusTwo); + builder.blankLineWanted(BlankLineWanted.NO); + token("}", plusTwo); + } + } else { + builder.open(ZERO); + builder.open(plusTwo); + tokenBreakTrailingComment("{", plusTwo); + if (allowLeadingBlankLine == AllowLeadingBlankLine.NO) { + builder.blankLineWanted(BlankLineWanted.NO); + } else { + builder.blankLineWanted(BlankLineWanted.PRESERVE); + } + visitStatements(node.getStatements()); + builder.close(); + builder.forcedBreak(); + builder.close(); + if (allowTrailingBlankLine == AllowTrailingBlankLine.NO) { + builder.blankLineWanted(BlankLineWanted.NO); + } else { + builder.blankLineWanted(BlankLineWanted.PRESERVE); + } + markForPartialFormat(); + token("}", plusTwo); + } + } + + /** Helper method for statements. */ + private void visitStatement( + StatementTree node, + CollapseEmptyOrNot collapseEmptyOrNot, + AllowLeadingBlankLine allowLeadingBlank, + AllowTrailingBlankLine allowTrailingBlank) { + sync(node); + switch (node.getKind()) { + case BLOCK: + builder.space(); + visitBlock((BlockTree) node, collapseEmptyOrNot, allowLeadingBlank, allowTrailingBlank); + break; + default: + builder.open(plusTwo); + builder.breakOp(" "); + scan(node, null); + builder.close(); + } + } + + protected void visitStatements(List statements) { + boolean first = true; + PeekingIterator it = Iterators.peekingIterator(statements.iterator()); + dropEmptyDeclarations(); + while (it.hasNext()) { + StatementTree tree = it.next(); + builder.forcedBreak(); + if (!first) { + builder.blankLineWanted(BlankLineWanted.PRESERVE); + } + markForPartialFormat(); + first = false; + List fragments = variableFragments(it, tree); + if (!fragments.isEmpty()) { + visitVariables( + fragments, + DeclarationKind.NONE, + canLocalHaveHorizontalAnnotations(fragments.get(0).getModifiers())); + } else { + scan(tree, null); + } + } + } + + protected void typeDeclarationModifiers(ModifiersTree modifiers) { + List typeAnnotations = + visitModifiers( + modifiers, Direction.VERTICAL, /* declarationAnnotationBreak= */ Optional.empty()); + verticalAnnotations(typeAnnotations); + } + + /** Output combined modifiers and annotations and the trailing break. */ + void visitAndBreakModifiers( + ModifiersTree modifiers, + Direction annotationDirection, + Optional declarationAnnotationBreak) { + List typeAnnotations = + visitModifiers(modifiers, annotationDirection, declarationAnnotationBreak); + visitAnnotations(typeAnnotations, BreakOrNot.NO, BreakOrNot.YES); + } + + @Override + public Void visitModifiers(ModifiersTree node, Void unused) { + throw new IllegalStateException("expected manual descent into modifiers"); + } + + /** Output combined modifiers and annotations and returns the trailing break. */ + @CheckReturnValue + protected ImmutableList visitModifiers( + ModifiersTree modifiersTree, + Direction annotationsDirection, + Optional declarationAnnotationBreak) { + return visitModifiers( + modifiersTree, + modifiersTree.getAnnotations(), + annotationsDirection, + declarationAnnotationBreak); + } + + @CheckReturnValue + protected ImmutableList visitModifiers( + ModifiersTree modifiersTree, + List annotationTrees, + Direction annotationsDirection, + Optional declarationAnnotationBreak) { + DeclarationModifiersAndTypeAnnotations splitModifiers = + splitModifiers(modifiersTree, annotationTrees); + return visitModifiers(splitModifiers, annotationsDirection, declarationAnnotationBreak); + } + + @CheckReturnValue + private ImmutableList visitModifiers( + DeclarationModifiersAndTypeAnnotations splitModifiers, + Direction annotationsDirection, + Optional declarationAnnotationBreak) { + if (splitModifiers.declarationModifiers().isEmpty()) { + return splitModifiers.typeAnnotations(); + } + Deque declarationModifiers = + new ArrayDeque<>(splitModifiers.declarationModifiers()); + builder.open(ZERO); + boolean first = true; + boolean lastWasAnnotation = false; + while (!declarationModifiers.isEmpty() && !declarationModifiers.peekFirst().isModifier()) { + if (!first) { + builder.addAll( + annotationsDirection.isVertical() + ? forceBreakList(declarationAnnotationBreak) + : breakList(declarationAnnotationBreak)); + } + formatAnnotationOrModifier(declarationModifiers); + first = false; + lastWasAnnotation = true; + } + builder.close(); + ImmutableList trailingBreak = + annotationsDirection.isVertical() + ? forceBreakList(declarationAnnotationBreak) + : breakList(declarationAnnotationBreak); + if (declarationModifiers.isEmpty()) { + builder.addAll(trailingBreak); + return splitModifiers.typeAnnotations(); + } + if (lastWasAnnotation) { + builder.addAll(trailingBreak); + } + + builder.open(ZERO); + first = true; + while (!declarationModifiers.isEmpty()) { + if (!first) { + builder.addAll(breakFillList(Optional.empty())); + } + formatAnnotationOrModifier(declarationModifiers); + first = false; + } + builder.close(); + builder.addAll(breakFillList(Optional.empty())); + return splitModifiers.typeAnnotations(); + } + + /** Represents an annotation or a modifier in a {@link ModifiersTree}. */ + @AutoOneOf(AnnotationOrModifier.Kind.class) + abstract static class AnnotationOrModifier implements Comparable { + enum Kind { + MODIFIER, + ANNOTATION + } + + abstract Kind getKind(); + + abstract AnnotationTree annotation(); + + abstract Input.Tok modifier(); + + static AnnotationOrModifier ofModifier(Input.Tok m) { + return AutoOneOf_JavaInputAstVisitor_AnnotationOrModifier.modifier(m); + } + + static AnnotationOrModifier ofAnnotation(AnnotationTree a) { + return AutoOneOf_JavaInputAstVisitor_AnnotationOrModifier.annotation(a); + } + + boolean isModifier() { + return getKind().equals(Kind.MODIFIER); + } + + boolean isAnnotation() { + return getKind().equals(Kind.ANNOTATION); + } + + int position() { + switch (getKind()) { + case MODIFIER: + return modifier().getPosition(); + case ANNOTATION: + return getStartPosition(annotation()); + } + throw new AssertionError(); + } + + private static final Comparator COMPARATOR = + Comparator.comparingInt(AnnotationOrModifier::position); @Override - public Void visitCatch(CatchTree node, Void unused) { - throw new IllegalStateException("expected manual descent into catch trees"); + public int compareTo(AnnotationOrModifier o) { + return COMPARATOR.compare(this, o); + } + } + + /** + * The modifiers annotations for a declaration, grouped in to a prefix that contains all of the + * declaration annotations and modifiers, and a suffix of type annotations. + * + *

For examples like {@code @Deprecated public @Nullable Foo foo();}, this allows us to format + * {@code @Deprecated public} as declaration modifiers, and {@code @Nullable} as a type annotation + * on the return type. + */ + @AutoValue + abstract static class DeclarationModifiersAndTypeAnnotations { + abstract ImmutableList declarationModifiers(); + + abstract ImmutableList typeAnnotations(); + + static DeclarationModifiersAndTypeAnnotations create( + ImmutableList declarationModifiers, + ImmutableList typeAnnotations) { + return new AutoValue_JavaInputAstVisitor_DeclarationModifiersAndTypeAnnotations( + declarationModifiers, typeAnnotations); } - /** Helper method for {@link CatchTree}s. */ - private void visitCatchClause(CatchTree node, AllowTrailingBlankLine allowTrailingBlankLine) { - sync(node); - builder.space(); - token("catch"); - builder.space(); - token("("); - builder.open(plusFour); - VariableTree ex = node.getParameter(); - if (ex.getType().getKind() == UNION_TYPE) { - builder.open(ZERO); - visitUnionType(ex); - builder.close(); - } else { - // TODO(cushon): don't break after here for consistency with for, while, etc. - builder.breakToFill(); - builder.open(ZERO); - scan(ex, null); - builder.close(); - } - builder.close(); - token(")"); - builder.space(); - visitBlock( - node.getBlock(), - CollapseEmptyOrNot.NO, - AllowLeadingBlankLine.YES, - allowTrailingBlankLine); + static DeclarationModifiersAndTypeAnnotations empty() { + return create(ImmutableList.of(), ImmutableList.of()); } - /** Formats a union type declaration in a catch clause. */ - private void visitUnionType(VariableTree declaration) { - UnionTypeTree type = (UnionTypeTree) declaration.getType(); - builder.open(ZERO); - sync(declaration); - visitAndBreakModifiers( - declaration.getModifiers(), - Direction.HORIZONTAL, - /* declarationAnnotationBreak= */ Optional.empty()); - List union = type.getTypeAlternatives(); - boolean first = true; - for (int i = 0; i < union.size() - 1; i++) { - if (!first) { - builder.breakOp(" "); - token("|"); - builder.space(); - } else { - first = false; - } - scan(union.get(i), null); + boolean hasDeclarationAnnotation() { + return declarationModifiers().stream().anyMatch(AnnotationOrModifier::isAnnotation); + } + } + + /** + * Examines the token stream to convert the modifiers for a declaration into a {@link + * DeclarationModifiersAndTypeAnnotations}. + */ + DeclarationModifiersAndTypeAnnotations splitModifiers( + ModifiersTree modifiersTree, List annotations) { + if (annotations.isEmpty() && !isModifier(builder.peekToken().get())) { + return DeclarationModifiersAndTypeAnnotations.empty(); + } + RangeSet annotationRanges = TreeRangeSet.create(); + for (AnnotationTree annotationTree : annotations) { + annotationRanges.add( + Range.closedOpen( + getStartPosition(annotationTree), getEndPosition(annotationTree, getCurrentPath()))); + } + ImmutableList toks = + builder.peekTokens( + getStartPosition(modifiersTree), + (Input.Tok tok) -> + // ModifiersTree end position information isn't reliable, so scan + // tokens as long as + // we're seeing annotations or modifiers + annotationRanges.contains(tok.getPosition()) || isModifier(tok.getText())); + ImmutableList modifiers = + ImmutableList.copyOf( + Streams.concat( + toks.stream() + // reject tokens from inside AnnotationTrees, we + // only want modifiers + .filter(t -> !annotationRanges.contains(t.getPosition())) + .map(AnnotationOrModifier::ofModifier), + annotations.stream().map(AnnotationOrModifier::ofAnnotation)) + .sorted() + .collect(toList())); + // Take a suffix of annotations that are well-known type annotations, and which appear after + // any + // declaration annotations or modifiers + ImmutableList.Builder typeAnnotations = ImmutableList.builder(); + int idx = modifiers.size() - 1; + while (idx >= 0) { + AnnotationOrModifier modifier = modifiers.get(idx); + if (!modifier.isAnnotation() || !isTypeAnnotation(modifier.annotation())) { + break; + } + typeAnnotations.add(modifier.annotation()); + idx--; + } + return DeclarationModifiersAndTypeAnnotations.create( + modifiers.subList(0, idx + 1), typeAnnotations.build().reverse()); + } + + private void formatAnnotationOrModifier(Deque modifiers) { + AnnotationOrModifier modifier = modifiers.removeFirst(); + switch (modifier.getKind()) { + case MODIFIER: + token(modifier.modifier().getText()); + if (modifier.modifier().getText().equals("non")) { + token(modifiers.removeFirst().modifier().getText()); + token(modifiers.removeFirst().modifier().getText()); } + break; + case ANNOTATION: + scan(modifier.annotation(), null); + break; + } + } + + boolean isTypeAnnotation(AnnotationTree annotationTree) { + Tree annotationType = annotationTree.getAnnotationType(); + if (!(annotationType instanceof IdentifierTree)) { + return false; + } + return typeAnnotationSimpleNames.contains(((IdentifierTree) annotationType).getName()); + } + + private static boolean isModifier(String token) { + switch (token) { + case "public": + case "protected": + case "private": + case "abstract": + case "static": + case "final": + case "transient": + case "volatile": + case "synchronized": + case "native": + case "strictfp": + case "default": + case "sealed": + case "non": + case "-": + return true; + default: + return false; + } + } + + @Override + public Void visitCatch(CatchTree node, Void unused) { + throw new IllegalStateException("expected manual descent into catch trees"); + } + + /** Helper method for {@link CatchTree}s. */ + private void visitCatchClause(CatchTree node, AllowTrailingBlankLine allowTrailingBlankLine) { + sync(node); + builder.space(); + token("catch"); + builder.space(); + token("("); + builder.open(plusFour); + VariableTree ex = node.getParameter(); + if (ex.getType().getKind() == UNION_TYPE) { + builder.open(ZERO); + visitUnionType(ex); + builder.close(); + } else { + // TODO(cushon): don't break after here for consistency with for, while, etc. + builder.breakToFill(); + builder.open(ZERO); + scan(ex, null); + builder.close(); + } + builder.close(); + token(")"); + builder.space(); + visitBlock( + node.getBlock(), CollapseEmptyOrNot.NO, AllowLeadingBlankLine.YES, allowTrailingBlankLine); + } + + /** Formats a union type declaration in a catch clause. */ + private void visitUnionType(VariableTree declaration) { + UnionTypeTree type = (UnionTypeTree) declaration.getType(); + builder.open(ZERO); + sync(declaration); + visitAndBreakModifiers( + declaration.getModifiers(), + Direction.HORIZONTAL, + /* declarationAnnotationBreak= */ Optional.empty()); + List union = type.getTypeAlternatives(); + boolean first = true; + for (int i = 0; i < union.size() - 1; i++) { + if (!first) { builder.breakOp(" "); token("|"); builder.space(); - Tree last = union.get(union.size() - 1); - declareOne( - DeclarationKind.NONE, - Direction.HORIZONTAL, - /* modifiers= */ Optional.empty(), - last, - /* name= */ declaration.getName(), - /* op= */ "", - "=", - Optional.ofNullable(declaration.getInitializer()), - /* trailing= */ Optional.empty(), - /* receiverExpression= */ Optional.empty(), - /* typeWithDims= */ Optional.empty()); + } else { + first = false; + } + scan(union.get(i), null); + } + builder.breakOp(" "); + token("|"); + builder.space(); + Tree last = union.get(union.size() - 1); + declareOne( + DeclarationKind.NONE, + Direction.HORIZONTAL, + /* modifiers= */ Optional.empty(), + last, + /* name= */ declaration.getName(), + /* op= */ "", + "=", + Optional.ofNullable(declaration.getInitializer()), + /* trailing= */ Optional.empty(), + /* receiverExpression= */ Optional.empty(), + /* typeWithDims= */ Optional.empty()); + builder.close(); + } + + /** Accumulate the operands and operators. */ + private static void walkInfix( + int precedence, + ExpressionTree expression, + List operands, + List operators) { + if (expression instanceof BinaryTree) { + BinaryTree binaryTree = (BinaryTree) expression; + if (precedence(binaryTree) == precedence) { + walkInfix(precedence, binaryTree.getLeftOperand(), operands, operators); + operators.add(operatorName(expression)); + walkInfix(precedence, binaryTree.getRightOperand(), operands, operators); + } else { + operands.add(expression); + } + } else { + operands.add(expression); + } + } + + protected void visitFormals( + Optional receiver, List parameters) { + if (!receiver.isPresent() && parameters.isEmpty()) { + return; + } + builder.open(ZERO); + boolean first = true; + if (receiver.isPresent()) { + // TODO(user): Use builders. + declareOne( + DeclarationKind.PARAMETER, + Direction.HORIZONTAL, + Optional.of(receiver.get().getModifiers()), + receiver.get().getType(), + /* name= */ receiver.get().getName(), + "", + "", + /* initializer= */ Optional.empty(), + !parameters.isEmpty() ? Optional.of(",") : Optional.empty(), + Optional.of(receiver.get().getNameExpression()), + /* typeWithDims= */ Optional.empty()); + first = false; + } + for (int i = 0; i < parameters.size(); i++) { + VariableTree parameter = parameters.get(i); + if (!first) { + builder.breakOp(" "); + } + visitToDeclare( + DeclarationKind.PARAMETER, + Direction.HORIZONTAL, + parameter, + /* initializer= */ Optional.empty(), + "=", + i < parameters.size() - 1 ? Optional.of(",") : /* a= */ Optional.empty()); + first = false; + } + builder.close(); + } + + // /** Helper method for {@link MethodDeclaration}s. */ + private void visitThrowsClause(List thrownExceptionTypes) { + token("throws"); + builder.breakToFill(" "); + boolean first = true; + for (ExpressionTree thrownExceptionType : thrownExceptionTypes) { + if (!first) { + token(","); + builder.breakToFill(" "); + } + scan(thrownExceptionType, null); + first = false; + } + } + + @Override + public Void visitIdentifier(IdentifierTree node, Void unused) { + sync(node); + token(node.getName().toString()); + return null; + } + + @Override + public Void visitModule(ModuleTree node, Void unused) { + for (AnnotationTree annotation : node.getAnnotations()) { + scan(annotation, null); + builder.forcedBreak(); + } + if (node.getModuleType() == ModuleTree.ModuleKind.OPEN) { + token("open"); + builder.space(); + } + token("module"); + builder.space(); + scan(node.getName(), null); + builder.space(); + if (node.getDirectives().isEmpty()) { + tokenBreakTrailingComment("{", plusTwo); + builder.blankLineWanted(BlankLineWanted.NO); + token("}", plusTwo); + } else { + builder.open(plusTwo); + token("{"); + builder.forcedBreak(); + Optional previousDirective = Optional.empty(); + for (DirectiveTree directiveTree : node.getDirectives()) { + markForPartialFormat(); + builder.blankLineWanted( + previousDirective.map(k -> !k.equals(directiveTree.getKind())).orElse(false) + ? BlankLineWanted.YES + : BlankLineWanted.NO); + builder.forcedBreak(); + scan(directiveTree, null); + previousDirective = Optional.of(directiveTree.getKind()); + } + builder.close(); + builder.forcedBreak(); + token("}"); + } + return null; + } + + private void visitDirective( + String name, + String separator, + ExpressionTree nameExpression, + @Nullable List items) { + token(name); + builder.space(); + scan(nameExpression, null); + if (items != null) { + builder.open(plusFour); + builder.space(); + token(separator); + builder.forcedBreak(); + boolean first = true; + for (ExpressionTree item : items) { + if (!first) { + token(","); + builder.forcedBreak(); + } + scan(item, null); + first = false; + } + token(";"); + builder.close(); + } else { + token(";"); + } + } + + @Override + public Void visitExports(ExportsTree node, Void unused) { + visitDirective("exports", "to", node.getPackageName(), node.getModuleNames()); + return null; + } + + @Override + public Void visitOpens(OpensTree node, Void unused) { + visitDirective("opens", "to", node.getPackageName(), node.getModuleNames()); + return null; + } + + @Override + public Void visitProvides(ProvidesTree node, Void unused) { + visitDirective("provides", "with", node.getServiceName(), node.getImplementationNames()); + return null; + } + + @Override + public Void visitRequires(RequiresTree node, Void unused) { + token("requires"); + builder.space(); + while (true) { + if (builder.peekToken().equals(Optional.of("static"))) { + token("static"); + builder.space(); + } else if (builder.peekToken().equals(Optional.of("transitive"))) { + token("transitive"); + builder.space(); + } else { + break; + } + } + scan(node.getModuleName(), null); + token(";"); + return null; + } + + @Override + public Void visitUses(UsesTree node, Void unused) { + token("uses"); + builder.space(); + scan(node.getServiceName(), null); + token(";"); + return null; + } + + /** Helper method for import declarations, names, and qualified names. */ + private void visitName(Tree node) { + Deque stack = new ArrayDeque<>(); + for (; node instanceof MemberSelectTree; node = ((MemberSelectTree) node).getExpression()) { + stack.addFirst(((MemberSelectTree) node).getIdentifier()); + } + stack.addFirst(((IdentifierTree) node).getName()); + boolean first = true; + for (Name name : stack) { + if (!first) { + token("."); + } + token(name.toString()); + first = false; + } + } + + private void visitToDeclare( + DeclarationKind kind, + Direction annotationsDirection, + VariableTree node, + Optional initializer, + String equals, + Optional trailing) { + sync(node); + Optional typeWithDims; + Tree type; + if (node.getType() != null) { + TypeWithDims extractedDims = DimensionHelpers.extractDims(node.getType(), SortedDims.YES); + typeWithDims = Optional.of(extractedDims); + type = extractedDims.node; + } else { + typeWithDims = Optional.empty(); + type = null; + } + declareOne( + kind, + annotationsDirection, + Optional.of(node.getModifiers()), + type, + node.getName(), + "", + equals, + initializer, + trailing, + /* receiverExpression= */ Optional.empty(), + typeWithDims); + } + + /** Does not omit the leading {@code "<"}, which should be associated with the type name. */ + protected void typeParametersRest( + List typeParameters, Indent plusIndent) { + builder.open(plusIndent); + builder.breakOp(); + builder.open(ZERO); + boolean first = true; + for (TypeParameterTree typeParameter : typeParameters) { + if (!first) { + token(","); + builder.breakOp(" "); + } + scan(typeParameter, null); + first = false; + } + token(">"); + builder.close(); + builder.close(); + } + + /** Collapse chains of {@code .} operators, across multiple {@link ASTNode} types. */ + + /** + * Output a "." node. + * + * @param node0 the "." node + */ + void visitDot(ExpressionTree node0) { + ExpressionTree node = node0; + + // collect a flattened list of "."-separated items + // e.g. ImmutableList.builder().add(1).build() -> [ImmutableList, builder(), add(1), + // build()] + Deque stack = new ArrayDeque<>(); + LOOP: + do { + stack.addFirst(node); + if (node.getKind() == ARRAY_ACCESS) { + node = getArrayBase(node); + } + switch (node.getKind()) { + case MEMBER_SELECT: + node = ((MemberSelectTree) node).getExpression(); + break; + case METHOD_INVOCATION: + node = getMethodReceiver((MethodInvocationTree) node); + break; + case IDENTIFIER: + node = null; + break LOOP; + default: + // If the dot chain starts with a primary expression + // (e.g. a class instance creation, or a conditional expression) + // then remove it from the list and deal with it first. + node = stack.removeFirst(); + break LOOP; + } + } while (node != null); + List items = new ArrayList<>(stack); + + boolean needDot = false; + + // The dot chain started with a primary expression: output it normally, and indent + // the rest of the chain +4. + if (node != null) { + // Exception: if it's an anonymous class declaration, we don't need to + // break and indent after the trailing '}'. + if (node.getKind() == NEW_CLASS && ((NewClassTree) node).getClassBody() != null) { + builder.open(ZERO); + scan(getArrayBase(node), null); + token("."); + } else { + builder.open(plusFour); + scan(getArrayBase(node), null); + builder.breakOp(); + needDot = true; + } + formatArrayIndices(getArrayIndices(node)); + if (stack.isEmpty()) { builder.close(); + return; + } } - /** Accumulate the operands and operators. */ - private static void walkInfix( - int precedence, - ExpressionTree expression, - List operands, - List operators) { - if (expression instanceof BinaryTree) { - BinaryTree binaryTree = (BinaryTree) expression; - if (precedence(binaryTree) == precedence) { - walkInfix(precedence, binaryTree.getLeftOperand(), operands, operators); - operators.add(operatorName(expression)); - walkInfix(precedence, binaryTree.getRightOperand(), operands, operators); - } else { - operands.add(expression); - } + Set prefixes = new LinkedHashSet<>(); + + // Check if the dot chain has a prefix that looks like a type name, so we can + // treat the type name-shaped part as a single syntactic unit. + TypeNameClassifier.typePrefixLength(simpleNames(stack)).ifPresent(prefixes::add); + + int invocationCount = 0; + int firstInvocationIndex = -1; + { + for (int i = 0; i < items.size(); i++) { + ExpressionTree expression = items.get(i); + if (expression.getKind() == METHOD_INVOCATION) { + if (i > 0 || node != null) { + // we only want dereference invocations + invocationCount++; + } + if (firstInvocationIndex < 0) { + firstInvocationIndex = i; + } + } + } + } + + // If there's only one invocation, treat leading field accesses as a single + // unit. In the normal case we want to preserve the alignment of subsequent + // method calls, and would emit e.g.: + // + // myField + // .foo() + // .bar(); + // + // But if there's no 'bar()' to worry about the alignment of we prefer: + // + // myField.foo(); + // + // to: + // + // myField + // .foo(); + // + if (invocationCount == 1 && firstInvocationIndex > 0) { + prefixes.add(firstInvocationIndex); + } + + if (prefixes.isEmpty() && items.get(0) instanceof IdentifierTree) { + switch (((IdentifierTree) items.get(0)).getName().toString()) { + case "this": + case "super": + prefixes.add(1); + break; + default: + break; + } + } + + List streamPrefixes = handleStream(items); + streamPrefixes.forEach(x -> prefixes.add(x.intValue())); + if (!prefixes.isEmpty()) { + visitDotWithPrefix( + items, needDot, prefixes, streamPrefixes.isEmpty() ? INDEPENDENT : UNIFIED); + } else { + visitRegularDot(items, needDot); + } + + if (node != null) { + builder.close(); + } + } + + /** + * Output a "regular" chain of dereferences, possibly in builder-style. Break before every dot. + * + * @param items in the chain + * @param needDot whether a leading dot is needed + */ + private void visitRegularDot(List items, boolean needDot) { + boolean trailingDereferences = items.size() > 1; + boolean needDot0 = needDot; + if (!needDot0) { + builder.open(plusFour); + } + // don't break after the first element if it is every small, unless the + // chain starts with another expression + int minLength = indentMultiplier * 4; + int length = needDot0 ? minLength : 0; + for (ExpressionTree e : items) { + if (needDot) { + if (length > minLength) { + builder.breakOp(FillMode.UNIFIED, "", ZERO); + } + token("."); + length++; + } + if (!fillFirstArgument(e, items, trailingDereferences ? ZERO : minusFour)) { + BreakTag tyargTag = genSym(); + dotExpressionUpToArgs(e, Optional.of(tyargTag)); + Indent tyargIndent = Indent.If.make(tyargTag, plusFour, ZERO); + dotExpressionArgsAndParen( + e, tyargIndent, (trailingDereferences || needDot) ? plusFour : ZERO); + } + length += getLength(e, getCurrentPath()); + needDot = true; + } + if (!needDot0) { + builder.close(); + } + } + + // avoid formattings like: + // + // when( + // something + // .happens()) + // .thenReturn(result); + // + private boolean fillFirstArgument(ExpressionTree e, List items, Indent indent) { + // is there a trailing dereference? + if (items.size() < 2) { + return false; + } + // don't special-case calls nested inside expressions + if (e.getKind() != METHOD_INVOCATION) { + return false; + } + MethodInvocationTree methodInvocation = (MethodInvocationTree) e; + Name name = getMethodName(methodInvocation); + if (!(methodInvocation.getMethodSelect() instanceof IdentifierTree) + || name.length() > 4 + || !methodInvocation.getTypeArguments().isEmpty() + || methodInvocation.getArguments().size() != 1) { + return false; + } + builder.open(ZERO); + builder.open(indent); + visit(name); + token("("); + ExpressionTree arg = getOnlyElement(methodInvocation.getArguments()); + scan(arg, null); + builder.close(); + token(")"); + builder.close(); + return true; + } + + /** + * Output a chain of dereferences where some prefix should be treated as a single syntactic unit, + * either because it looks like a type name or because there is only a single method invocation in + * the chain. + * + * @param items in the chain + * @param needDot whether a leading dot is needed + * @param prefixes the terminal indices of 'prefixes' of the expression that should be treated as + * a syntactic unit + */ + private void visitDotWithPrefix( + List items, + boolean needDot, + Collection prefixes, + FillMode prefixFillMode) { + // Are there method invocations or field accesses after the prefix? + boolean trailingDereferences = !prefixes.isEmpty() && getLast(prefixes) < items.size() - 1; + + builder.open(plusFour); + for (int times = 0; times < prefixes.size(); times++) { + builder.open(ZERO); + } + + Deque unconsumedPrefixes = new ArrayDeque<>(ImmutableSortedSet.copyOf(prefixes)); + BreakTag nameTag = genSym(); + for (int i = 0; i < items.size(); i++) { + ExpressionTree e = items.get(i); + if (needDot) { + FillMode fillMode; + if (!unconsumedPrefixes.isEmpty() && i <= unconsumedPrefixes.peekFirst()) { + fillMode = prefixFillMode; } else { - operands.add(expression); + fillMode = FillMode.UNIFIED; } + + builder.breakOp(fillMode, "", ZERO, Optional.of(nameTag)); + token("."); + } + BreakTag tyargTag = genSym(); + dotExpressionUpToArgs(e, Optional.of(tyargTag)); + if (!unconsumedPrefixes.isEmpty() && i == unconsumedPrefixes.peekFirst()) { + builder.close(); + unconsumedPrefixes.removeFirst(); + } + + Indent tyargIndent = Indent.If.make(tyargTag, plusFour, ZERO); + Indent argsIndent = Indent.If.make(nameTag, plusFour, trailingDereferences ? plusFour : ZERO); + dotExpressionArgsAndParen(e, tyargIndent, argsIndent); + + needDot = true; } - protected void visitFormals( - Optional receiver, List parameters) { - if (!receiver.isPresent() && parameters.isEmpty()) { - return; + builder.close(); + } + + /** Returns the simple names of expressions in a "." chain. */ + private static ImmutableList simpleNames(Deque stack) { + ImmutableList.Builder simpleNames = ImmutableList.builder(); + OUTER: + for (ExpressionTree expression : stack) { + boolean isArray = expression.getKind() == ARRAY_ACCESS; + expression = getArrayBase(expression); + switch (expression.getKind()) { + case MEMBER_SELECT: + simpleNames.add(((MemberSelectTree) expression).getIdentifier().toString()); + break; + case IDENTIFIER: + simpleNames.add(((IdentifierTree) expression).getName().toString()); + break; + case METHOD_INVOCATION: + simpleNames.add(getMethodName((MethodInvocationTree) expression).toString()); + break OUTER; + default: + break OUTER; + } + if (isArray) { + break OUTER; + } + } + return simpleNames.build(); + } + + private void dotExpressionUpToArgs(ExpressionTree expression, Optional tyargTag) { + expression = getArrayBase(expression); + switch (expression.getKind()) { + case MEMBER_SELECT: + MemberSelectTree fieldAccess = (MemberSelectTree) expression; + visit(fieldAccess.getIdentifier()); + break; + case METHOD_INVOCATION: + MethodInvocationTree methodInvocation = (MethodInvocationTree) expression; + if (!methodInvocation.getTypeArguments().isEmpty()) { + builder.open(plusFour); + addTypeArguments(methodInvocation.getTypeArguments(), ZERO); + // TODO(user): Should indent the name -4. + builder.breakOp(Doc.FillMode.UNIFIED, "", ZERO, tyargTag); + builder.close(); } + visit(getMethodName(methodInvocation)); + break; + case IDENTIFIER: + visit(((IdentifierTree) expression).getName()); + break; + default: + scan(expression, null); + break; + } + } + + /** + * Returns the base expression of an erray access, e.g. given {@code foo[0][0]} returns {@code + * foo}. + */ + private static ExpressionTree getArrayBase(ExpressionTree node) { + while (node instanceof ArrayAccessTree) { + node = ((ArrayAccessTree) node).getExpression(); + } + return node; + } + + private static ExpressionTree getMethodReceiver(MethodInvocationTree methodInvocation) { + ExpressionTree select = methodInvocation.getMethodSelect(); + return select instanceof MemberSelectTree ? ((MemberSelectTree) select).getExpression() : null; + } + + private void dotExpressionArgsAndParen( + ExpressionTree expression, Indent tyargIndent, Indent indent) { + Deque indices = getArrayIndices(expression); + expression = getArrayBase(expression); + switch (expression.getKind()) { + case METHOD_INVOCATION: + builder.open(tyargIndent); + MethodInvocationTree methodInvocation = (MethodInvocationTree) expression; + addArguments(methodInvocation.getArguments(), indent); + builder.close(); + break; + default: + break; + } + formatArrayIndices(indices); + } + + /** Lays out one or more array indices. Does not output the expression for the array itself. */ + private void formatArrayIndices(Deque indices) { + if (indices.isEmpty()) { + return; + } + builder.open(ZERO); + do { + token("["); + builder.breakToFill(); + scan(indices.removeLast(), null); + token("]"); + } while (!indices.isEmpty()); + builder.close(); + } + + /** + * Returns all array indices for the given expression, e.g. given {@code foo[0][0]} returns the + * expressions for {@code [0][0]}. + */ + private static Deque getArrayIndices(ExpressionTree expression) { + Deque indices = new ArrayDeque<>(); + while (expression instanceof ArrayAccessTree) { + ArrayAccessTree array = (ArrayAccessTree) expression; + indices.addLast(array.getIndex()); + expression = array.getExpression(); + } + return indices; + } + + /** Helper methods for method invocations. */ + void addTypeArguments(List typeArguments, Indent plusIndent) { + if (typeArguments == null || typeArguments.isEmpty()) { + return; + } + token("<"); + builder.open(plusIndent); + boolean first = true; + for (Tree typeArgument : typeArguments) { + if (!first) { + token(","); + builder.breakToFill(" "); + } + scan(typeArgument, null); + first = false; + } + builder.close(); + token(">"); + } + + /** + * Add arguments to a method invocation, etc. The arguments indented {@code plusFour}, filled, + * from the current indent. The arguments may be output two at a time if they seem to be arguments + * to a map constructor, etc. + * + * @param arguments the arguments + * @param plusIndent the extra indent for the arguments + */ + void addArguments(List arguments, Indent plusIndent) { + builder.open(plusIndent); + token("("); + if (!arguments.isEmpty()) { + if (arguments.size() % 2 == 0 && argumentsAreTabular(arguments) == 2) { + builder.forcedBreak(); builder.open(ZERO); boolean first = true; - if (receiver.isPresent()) { - // TODO(user): Use builders. - declareOne( - DeclarationKind.PARAMETER, - Direction.HORIZONTAL, - Optional.of(receiver.get().getModifiers()), - receiver.get().getType(), - /* name= */ receiver.get().getName(), - "", - "", - /* initializer= */ Optional.empty(), - !parameters.isEmpty() ? Optional.of(",") : Optional.empty(), - Optional.of(receiver.get().getNameExpression()), - /* typeWithDims= */ Optional.empty()); - first = false; - } - for (int i = 0; i < parameters.size(); i++) { - VariableTree parameter = parameters.get(i); - if (!first) { - builder.breakOp(" "); - } - visitToDeclare( - DeclarationKind.PARAMETER, - Direction.HORIZONTAL, - parameter, - /* initializer= */ Optional.empty(), - "=", - i < parameters.size() - 1 ? Optional.of(",") : /* a= */ Optional.empty()); - first = false; + for (int i = 0; i < arguments.size() - 1; i += 2) { + ExpressionTree argument0 = arguments.get(i); + ExpressionTree argument1 = arguments.get(i + 1); + if (!first) { + token(","); + builder.forcedBreak(); + } + builder.open(plusFour); + scan(argument0, null); + token(","); + builder.breakOp(" "); + scan(argument1, null); + builder.close(); + first = false; } builder.close(); - } - - // /** Helper method for {@link MethodDeclaration}s. */ - private void visitThrowsClause(List thrownExceptionTypes) { - token("throws"); - builder.breakToFill(" "); - boolean first = true; - for (ExpressionTree thrownExceptionType : thrownExceptionTypes) { - if (!first) { - token(","); - builder.breakToFill(" "); - } - scan(thrownExceptionType, null); - first = false; - } - } - - @Override - public Void visitIdentifier(IdentifierTree node, Void unused) { - sync(node); - token(node.getName().toString()); - return null; - } - - @Override - public Void visitModule(ModuleTree node, Void unused) { - for (AnnotationTree annotation : node.getAnnotations()) { - scan(annotation, null); - builder.forcedBreak(); - } - if (node.getModuleType() == ModuleTree.ModuleKind.OPEN) { - token("open"); - builder.space(); - } - token("module"); - builder.space(); - scan(node.getName(), null); - builder.space(); - if (node.getDirectives().isEmpty()) { - tokenBreakTrailingComment("{", plusTwo); - builder.blankLineWanted(BlankLineWanted.NO); - token("}", plusTwo); - } else { - builder.open(plusTwo); - token("{"); - builder.forcedBreak(); - Optional previousDirective = Optional.empty(); - for (DirectiveTree directiveTree : node.getDirectives()) { - markForPartialFormat(); - builder.blankLineWanted( - previousDirective.map(k -> !k.equals(directiveTree.getKind())).orElse(false) - ? BlankLineWanted.YES - : BlankLineWanted.NO); - builder.forcedBreak(); - scan(directiveTree, null); - previousDirective = Optional.of(directiveTree.getKind()); - } - builder.close(); - builder.forcedBreak(); - token("}"); - } - return null; - } - - private void visitDirective( - String name, - String separator, - ExpressionTree nameExpression, - @Nullable List items) { - token(name); - builder.space(); - scan(nameExpression, null); - if (items != null) { - builder.open(plusFour); - builder.space(); - token(separator); - builder.forcedBreak(); - boolean first = true; - for (ExpressionTree item : items) { - if (!first) { - token(","); - builder.forcedBreak(); - } - scan(item, null); - first = false; - } - token(";"); - builder.close(); - } else { - token(";"); - } - } - - @Override - public Void visitExports(ExportsTree node, Void unused) { - visitDirective("exports", "to", node.getPackageName(), node.getModuleNames()); - return null; - } - - @Override - public Void visitOpens(OpensTree node, Void unused) { - visitDirective("opens", "to", node.getPackageName(), node.getModuleNames()); - return null; - } - - @Override - public Void visitProvides(ProvidesTree node, Void unused) { - visitDirective("provides", "with", node.getServiceName(), node.getImplementationNames()); - return null; - } - - @Override - public Void visitRequires(RequiresTree node, Void unused) { - token("requires"); - builder.space(); - while (true) { - if (builder.peekToken().equals(Optional.of("static"))) { - token("static"); - builder.space(); - } else if (builder.peekToken().equals(Optional.of("transitive"))) { - token("transitive"); - builder.space(); - } else { - break; - } - } - scan(node.getModuleName(), null); - token(";"); - return null; - } - - @Override - public Void visitUses(UsesTree node, Void unused) { - token("uses"); - builder.space(); - scan(node.getServiceName(), null); - token(";"); - return null; - } - - /** Helper method for import declarations, names, and qualified names. */ - private void visitName(Tree node) { - Deque stack = new ArrayDeque<>(); - for (; node instanceof MemberSelectTree; node = ((MemberSelectTree) node).getExpression()) { - stack.addFirst(((MemberSelectTree) node).getIdentifier()); - } - stack.addFirst(((IdentifierTree) node).getName()); - boolean first = true; - for (Name name : stack) { - if (!first) { - token("."); - } - token(name.toString()); - first = false; - } - } - - private void visitToDeclare( - DeclarationKind kind, - Direction annotationsDirection, - VariableTree node, - Optional initializer, - String equals, - Optional trailing) { - sync(node); - Optional typeWithDims; - Tree type; - if (node.getType() != null) { - TypeWithDims extractedDims = - DimensionHelpers.extractDims(node.getType(), SortedDims.YES); - typeWithDims = Optional.of(extractedDims); - type = extractedDims.node; - } else { - typeWithDims = Optional.empty(); - type = null; - } - declareOne( - kind, - annotationsDirection, - Optional.of(node.getModifiers()), - type, - node.getName(), - "", - equals, - initializer, - trailing, - /* receiverExpression= */ Optional.empty(), - typeWithDims); - } - - /** Does not omit the leading {@code "<"}, which should be associated with the type name. */ - protected void typeParametersRest( - List typeParameters, Indent plusIndent) { - builder.open(plusIndent); + } else if (isFormatMethod(arguments)) { builder.breakOp(); builder.open(ZERO); - boolean first = true; - for (TypeParameterTree typeParameter : typeParameters) { - if (!first) { - token(","); - builder.breakOp(" "); - } - scan(typeParameter, null); - first = false; - } - token(">"); - builder.close(); - builder.close(); - } - - /** Collapse chains of {@code .} operators, across multiple {@link ASTNode} types. */ - - /** - * Output a "." node. - * - * @param node0 the "." node - */ - void visitDot(ExpressionTree node0) { - ExpressionTree node = node0; - - // collect a flattened list of "."-separated items - // e.g. ImmutableList.builder().add(1).build() -> [ImmutableList, builder(), add(1), - // build()] - Deque stack = new ArrayDeque<>(); - LOOP: - do { - stack.addFirst(node); - if (node.getKind() == ARRAY_ACCESS) { - node = getArrayBase(node); - } - switch (node.getKind()) { - case MEMBER_SELECT: - node = ((MemberSelectTree) node).getExpression(); - break; - case METHOD_INVOCATION: - node = getMethodReceiver((MethodInvocationTree) node); - break; - case IDENTIFIER: - node = null; - break LOOP; - default: - // If the dot chain starts with a primary expression - // (e.g. a class instance creation, or a conditional expression) - // then remove it from the list and deal with it first. - node = stack.removeFirst(); - break LOOP; - } - } while (node != null); - List items = new ArrayList<>(stack); - - boolean needDot = false; - - // The dot chain started with a primary expression: output it normally, and indent - // the rest of the chain +4. - if (node != null) { - // Exception: if it's an anonymous class declaration, we don't need to - // break and indent after the trailing '}'. - if (node.getKind() == NEW_CLASS && ((NewClassTree) node).getClassBody() != null) { - builder.open(ZERO); - scan(getArrayBase(node), null); - token("."); - } else { - builder.open(plusFour); - scan(getArrayBase(node), null); - builder.breakOp(); - needDot = true; - } - formatArrayIndices(getArrayIndices(node)); - if (stack.isEmpty()) { - builder.close(); - return; - } - } - - Set prefixes = new LinkedHashSet<>(); - - // Check if the dot chain has a prefix that looks like a type name, so we can - // treat the type name-shaped part as a single syntactic unit. - TypeNameClassifier.typePrefixLength(simpleNames(stack)).ifPresent(prefixes::add); - - int invocationCount = 0; - int firstInvocationIndex = -1; - { - for (int i = 0; i < items.size(); i++) { - ExpressionTree expression = items.get(i); - if (expression.getKind() == METHOD_INVOCATION) { - if (i > 0 || node != null) { - // we only want dereference invocations - invocationCount++; - } - if (firstInvocationIndex < 0) { - firstInvocationIndex = i; - } - } - } - } - - // If there's only one invocation, treat leading field accesses as a single - // unit. In the normal case we want to preserve the alignment of subsequent - // method calls, and would emit e.g.: - // - // myField - // .foo() - // .bar(); - // - // But if there's no 'bar()' to worry about the alignment of we prefer: - // - // myField.foo(); - // - // to: - // - // myField - // .foo(); - // - if (invocationCount == 1 && firstInvocationIndex > 0) { - prefixes.add(firstInvocationIndex); - } - - if (prefixes.isEmpty() && items.get(0) instanceof IdentifierTree) { - switch (((IdentifierTree) items.get(0)).getName().toString()) { - case "this": - case "super": - prefixes.add(1); - break; - default: - break; - } - } - - List streamPrefixes = handleStream(items); - streamPrefixes.forEach(x -> prefixes.add(x.intValue())); - if (!prefixes.isEmpty()) { - visitDotWithPrefix( - items, needDot, prefixes, streamPrefixes.isEmpty() ? INDEPENDENT : UNIFIED); - } else { - visitRegularDot(items, needDot); - } - - if (node != null) { - builder.close(); - } - } - - /** - * Output a "regular" chain of dereferences, possibly in builder-style. Break before every dot. - * - * @param items in the chain - * @param needDot whether a leading dot is needed - */ - private void visitRegularDot(List items, boolean needDot) { - boolean trailingDereferences = items.size() > 1; - boolean needDot0 = needDot; - if (!needDot0) { - builder.open(plusFour); - } - // don't break after the first element if it is every small, unless the - // chain starts with another expression - int minLength = indentMultiplier * 4; - int length = needDot0 ? minLength : 0; - for (ExpressionTree e : items) { - if (needDot) { - if (length > minLength) { - builder.breakOp(FillMode.UNIFIED, "", ZERO); - } - token("."); - length++; - } - if (!fillFirstArgument(e, items, trailingDereferences ? ZERO : minusFour)) { - BreakTag tyargTag = genSym(); - dotExpressionUpToArgs(e, Optional.of(tyargTag)); - Indent tyargIndent = Indent.If.make(tyargTag, plusFour, ZERO); - dotExpressionArgsAndParen( - e, tyargIndent, (trailingDereferences || needDot) ? plusFour : ZERO); - } - length += getLength(e, getCurrentPath()); - needDot = true; - } - if (!needDot0) { - builder.close(); - } - } - - // avoid formattings like: - // - // when( - // something - // .happens()) - // .thenReturn(result); - // - private boolean fillFirstArgument(ExpressionTree e, List items, Indent indent) { - // is there a trailing dereference? - if (items.size() < 2) { - return false; - } - // don't special-case calls nested inside expressions - if (e.getKind() != METHOD_INVOCATION) { - return false; - } - MethodInvocationTree methodInvocation = (MethodInvocationTree) e; - Name name = getMethodName(methodInvocation); - if (!(methodInvocation.getMethodSelect() instanceof IdentifierTree) - || name.length() > 4 - || !methodInvocation.getTypeArguments().isEmpty() - || methodInvocation.getArguments().size() != 1) { - return false; - } + scan(arguments.get(0), null); + token(","); + builder.breakOp(" "); builder.open(ZERO); - builder.open(indent); - visit(name); - token("("); - ExpressionTree arg = getOnlyElement(methodInvocation.getArguments()); - scan(arg, null); + argList(arguments.subList(1, arguments.size())); builder.close(); - token(")"); builder.close(); - return true; + } else { + builder.breakOp(); + argList(arguments); + } } + token(")"); + builder.close(); + } - /** - * Output a chain of dereferences where some prefix should be treated as a single syntactic - * unit, either because it looks like a type name or because there is only a single method - * invocation in the chain. - * - * @param items in the chain - * @param needDot whether a leading dot is needed - * @param prefixes the terminal indices of 'prefixes' of the expression that should be treated - * as a syntactic unit - */ - private void visitDotWithPrefix( - List items, - boolean needDot, - Collection prefixes, - FillMode prefixFillMode) { - // Are there method invocations or field accesses after the prefix? - boolean trailingDereferences = !prefixes.isEmpty() && getLast(prefixes) < items.size() - 1; - - builder.open(plusFour); - for (int times = 0; times < prefixes.size(); times++) { - builder.open(ZERO); - } - - Deque unconsumedPrefixes = new ArrayDeque<>(ImmutableSortedSet.copyOf(prefixes)); - BreakTag nameTag = genSym(); - for (int i = 0; i < items.size(); i++) { - ExpressionTree e = items.get(i); - if (needDot) { - FillMode fillMode; - if (!unconsumedPrefixes.isEmpty() && i <= unconsumedPrefixes.peekFirst()) { - fillMode = prefixFillMode; - } else { - fillMode = FillMode.UNIFIED; - } - - builder.breakOp(fillMode, "", ZERO, Optional.of(nameTag)); - token("."); - } - BreakTag tyargTag = genSym(); - dotExpressionUpToArgs(e, Optional.of(tyargTag)); - if (!unconsumedPrefixes.isEmpty() && i == unconsumedPrefixes.peekFirst()) { - builder.close(); - unconsumedPrefixes.removeFirst(); - } - - Indent tyargIndent = Indent.If.make(tyargTag, plusFour, ZERO); - Indent argsIndent = - Indent.If.make(nameTag, plusFour, trailingDereferences ? plusFour : ZERO); - dotExpressionArgsAndParen(e, tyargIndent, argsIndent); - - needDot = true; - } - - builder.close(); + private void argList(List arguments) { + builder.open(ZERO); + boolean first = true; + FillMode fillMode = hasOnlyShortItems(arguments) ? FillMode.INDEPENDENT : FillMode.UNIFIED; + for (ExpressionTree argument : arguments) { + if (!first) { + token(","); + builder.breakOp(fillMode, " ", ZERO); + } + scan(argument, null); + first = false; } + builder.close(); + } - /** Returns the simple names of expressions in a "." chain. */ - private static ImmutableList simpleNames(Deque stack) { - ImmutableList.Builder simpleNames = ImmutableList.builder(); - OUTER: - for (ExpressionTree expression : stack) { - boolean isArray = expression.getKind() == ARRAY_ACCESS; - expression = getArrayBase(expression); - switch (expression.getKind()) { - case MEMBER_SELECT: - simpleNames.add(((MemberSelectTree) expression).getIdentifier().toString()); - break; - case IDENTIFIER: - simpleNames.add(((IdentifierTree) expression).getName().toString()); - break; - case METHOD_INVOCATION: - simpleNames.add(getMethodName((MethodInvocationTree) expression).toString()); - break OUTER; - default: - break OUTER; - } - if (isArray) { - break OUTER; - } - } - return simpleNames.build(); + /** + * Identifies String formatting methods like {@link String#format} which we prefer to format as: + * + *

{@code
+   * String.format(
+   *     "the format string: %s %s %s",
+   *     arg, arg, arg);
+   * }
+ * + *

And not: + * + *

{@code
+   * String.format(
+   *     "the format string: %s %s %s",
+   *     arg,
+   *     arg,
+   *     arg);
+   * }
+ */ + private boolean isFormatMethod(List arguments) { + if (arguments.size() < 2) { + return false; } + return isStringConcat(arguments.get(0)); + } - private void dotExpressionUpToArgs(ExpressionTree expression, Optional tyargTag) { - expression = getArrayBase(expression); - switch (expression.getKind()) { - case MEMBER_SELECT: - MemberSelectTree fieldAccess = (MemberSelectTree) expression; - visit(fieldAccess.getIdentifier()); - break; - case METHOD_INVOCATION: - MethodInvocationTree methodInvocation = (MethodInvocationTree) expression; - if (!methodInvocation.getTypeArguments().isEmpty()) { - builder.open(plusFour); - addTypeArguments(methodInvocation.getTypeArguments(), ZERO); - // TODO(user): Should indent the name -4. - builder.breakOp(Doc.FillMode.UNIFIED, "", ZERO, tyargTag); - builder.close(); - } - visit(getMethodName(methodInvocation)); - break; - case IDENTIFIER: - visit(((IdentifierTree) expression).getName()); - break; - default: - scan(expression, null); - break; + private static final Pattern FORMAT_SPECIFIER = Pattern.compile("%|\\{[0-9]\\}"); + + private boolean isStringConcat(ExpressionTree first) { + final boolean[] stringLiteral = {true}; + final boolean[] formatString = {false}; + new TreeScanner() { + @Override + public void scan(JCTree tree) { + if (tree == null) { + return; } + switch (tree.getKind()) { + case STRING_LITERAL: + break; + case PLUS: + super.scan(tree); + break; + default: + stringLiteral[0] = false; + break; + } + if (tree.getKind() == STRING_LITERAL) { + Object value = ((LiteralTree) tree).getValue(); + if (value instanceof String && FORMAT_SPECIFIER.matcher(value.toString()).find()) { + formatString[0] = true; + } + } + } + }.scan((JCTree) first); + return stringLiteral[0] && formatString[0]; + } + + /** Returns the number of columns if the arguments arg laid out in a grid, or else {@code -1}. */ + private int argumentsAreTabular(List arguments) { + if (arguments.isEmpty()) { + return -1; } - - /** - * Returns the base expression of an erray access, e.g. given {@code foo[0][0]} returns {@code - * foo}. - */ - private static ExpressionTree getArrayBase(ExpressionTree node) { - while (node instanceof ArrayAccessTree) { - node = ((ArrayAccessTree) node).getExpression(); - } - return node; + List> rows = new ArrayList<>(); + PeekingIterator it = Iterators.peekingIterator(arguments.iterator()); + int start0 = actualColumn(it.peek()); + { + List row = new ArrayList<>(); + row.add(it.next()); + while (it.hasNext() && actualColumn(it.peek()) > start0) { + row.add(it.next()); + } + if (!it.hasNext()) { + return -1; + } + if (rowLength(row) <= 1) { + return -1; + } + rows.add(row); } - - private static ExpressionTree getMethodReceiver(MethodInvocationTree methodInvocation) { - ExpressionTree select = methodInvocation.getMethodSelect(); - return select instanceof MemberSelectTree - ? ((MemberSelectTree) select).getExpression() - : null; + while (it.hasNext()) { + List row = new ArrayList<>(); + int start = actualColumn(it.peek()); + if (start != start0) { + return -1; + } + row.add(it.next()); + while (it.hasNext() && actualColumn(it.peek()) > start0) { + row.add(it.next()); + } + rows.add(row); } - - private void dotExpressionArgsAndParen( - ExpressionTree expression, Indent tyargIndent, Indent indent) { - Deque indices = getArrayIndices(expression); - expression = getArrayBase(expression); - switch (expression.getKind()) { - case METHOD_INVOCATION: - builder.open(tyargIndent); - MethodInvocationTree methodInvocation = (MethodInvocationTree) expression; - addArguments(methodInvocation.getArguments(), indent); - builder.close(); - break; - default: - break; - } - formatArrayIndices(indices); + int size0 = rows.get(0).size(); + if (!expressionsAreParallel(rows, 0, rows.size())) { + return -1; } - - /** Lays out one or more array indices. Does not output the expression for the array itself. */ - private void formatArrayIndices(Deque indices) { - if (indices.isEmpty()) { - return; - } - builder.open(ZERO); - do { - token("["); - builder.breakToFill(); - scan(indices.removeLast(), null); - token("]"); - } while (!indices.isEmpty()); - builder.close(); + for (int i = 1; i < size0; i++) { + if (!expressionsAreParallel(rows, i, rows.size() / 2 + 1)) { + return -1; + } } - - /** - * Returns all array indices for the given expression, e.g. given {@code foo[0][0]} returns the - * expressions for {@code [0][0]}. - */ - private static Deque getArrayIndices(ExpressionTree expression) { - Deque indices = new ArrayDeque<>(); - while (expression instanceof ArrayAccessTree) { - ArrayAccessTree array = (ArrayAccessTree) expression; - indices.addLast(array.getIndex()); - expression = array.getExpression(); - } - return indices; - } - - /** Helper methods for method invocations. */ - void addTypeArguments(List typeArguments, Indent plusIndent) { - if (typeArguments == null || typeArguments.isEmpty()) { - return; - } - token("<"); - builder.open(plusIndent); - boolean first = true; - for (Tree typeArgument : typeArguments) { - if (!first) { - token(","); - builder.breakToFill(" "); - } - scan(typeArgument, null); - first = false; - } - builder.close(); - token(">"); - } - - /** - * Add arguments to a method invocation, etc. The arguments indented {@code plusFour}, filled, - * from the current indent. The arguments may be output two at a time if they seem to be - * arguments to a map constructor, etc. - * - * @param arguments the arguments - * @param plusIndent the extra indent for the arguments - */ - void addArguments(List arguments, Indent plusIndent) { - builder.open(plusIndent); - token("("); - if (!arguments.isEmpty()) { - if (arguments.size() % 2 == 0 && argumentsAreTabular(arguments) == 2) { - builder.forcedBreak(); - builder.open(ZERO); - boolean first = true; - for (int i = 0; i < arguments.size() - 1; i += 2) { - ExpressionTree argument0 = arguments.get(i); - ExpressionTree argument1 = arguments.get(i + 1); - if (!first) { - token(","); - builder.forcedBreak(); - } - builder.open(plusFour); - scan(argument0, null); - token(","); - builder.breakOp(" "); - scan(argument1, null); - builder.close(); - first = false; - } - builder.close(); - } else if (isFormatMethod(arguments)) { - builder.breakOp(); - builder.open(ZERO); - scan(arguments.get(0), null); - token(","); - builder.breakOp(" "); - builder.open(ZERO); - argList(arguments.subList(1, arguments.size())); - builder.close(); - builder.close(); - } else { - builder.breakOp(); - argList(arguments); - } - } - token(")"); - builder.close(); - } - - private void argList(List arguments) { - builder.open(ZERO); - boolean first = true; - FillMode fillMode = hasOnlyShortItems(arguments) ? FillMode.INDEPENDENT : FillMode.UNIFIED; - for (ExpressionTree argument : arguments) { - if (!first) { - token(","); - builder.breakOp(fillMode, " ", ZERO); - } - scan(argument, null); - first = false; - } - builder.close(); - } - - /** - * Identifies String formatting methods like {@link String#format} which we prefer to format as: - * - *
{@code
-     * String.format(
-     *     "the format string: %s %s %s",
-     *     arg, arg, arg);
-     * }
- * - *

And not: - * - *

{@code
-     * String.format(
-     *     "the format string: %s %s %s",
-     *     arg,
-     *     arg,
-     *     arg);
-     * }
- */ - private boolean isFormatMethod(List arguments) { - if (arguments.size() < 2) { - return false; - } - return isStringConcat(arguments.get(0)); - } - - private static final Pattern FORMAT_SPECIFIER = Pattern.compile("%|\\{[0-9]\\}"); - - private boolean isStringConcat(ExpressionTree first) { - final boolean[] stringLiteral = {true}; - final boolean[] formatString = {false}; - new TreeScanner() { - @Override - public void scan(JCTree tree) { - if (tree == null) { - return; - } - switch (tree.getKind()) { - case STRING_LITERAL: - break; - case PLUS: - super.scan(tree); - break; - default: - stringLiteral[0] = false; - break; - } - if (tree.getKind() == STRING_LITERAL) { - Object value = ((LiteralTree) tree).getValue(); - if (value instanceof String - && FORMAT_SPECIFIER.matcher(value.toString()).find()) { - formatString[0] = true; - } - } - } - }.scan((JCTree) first); - return stringLiteral[0] && formatString[0]; - } - - /** - * Returns the number of columns if the arguments arg laid out in a grid, or else {@code -1}. - */ - private int argumentsAreTabular(List arguments) { - if (arguments.isEmpty()) { - return -1; - } - List> rows = new ArrayList<>(); - PeekingIterator it = Iterators.peekingIterator(arguments.iterator()); - int start0 = actualColumn(it.peek()); - { - List row = new ArrayList<>(); - row.add(it.next()); - while (it.hasNext() && actualColumn(it.peek()) > start0) { - row.add(it.next()); - } - if (!it.hasNext()) { - return -1; - } - if (rowLength(row) <= 1) { - return -1; - } - rows.add(row); - } - while (it.hasNext()) { - List row = new ArrayList<>(); - int start = actualColumn(it.peek()); - if (start != start0) { - return -1; - } - row.add(it.next()); - while (it.hasNext() && actualColumn(it.peek()) > start0) { - row.add(it.next()); - } - rows.add(row); - } - int size0 = rows.get(0).size(); - if (!expressionsAreParallel(rows, 0, rows.size())) { - return -1; - } - for (int i = 1; i < size0; i++) { - if (!expressionsAreParallel(rows, i, rows.size() / 2 + 1)) { - return -1; - } - } - // if there are only two rows, they must be the same length - if (rows.size() == 2) { - if (size0 == rows.get(1).size()) { - return size0; - } - return -1; - } - // allow a ragged trailing row for >= 3 columns - for (int i = 1; i < rows.size() - 1; i++) { - if (size0 != rows.get(i).size()) { - return -1; - } - } - if (size0 < getLast(rows).size()) { - return -1; - } + // if there are only two rows, they must be the same length + if (rows.size() == 2) { + if (size0 == rows.get(1).size()) { return size0; + } + return -1; + } + // allow a ragged trailing row for >= 3 columns + for (int i = 1; i < rows.size() - 1; i++) { + if (size0 != rows.get(i).size()) { + return -1; + } + } + if (size0 < getLast(rows).size()) { + return -1; + } + return size0; + } + + static int rowLength(List row) { + int size = 0; + for (ExpressionTree tree : row) { + if (tree.getKind() != NEW_ARRAY) { + size++; + continue; + } + NewArrayTree array = (NewArrayTree) tree; + if (array.getInitializers() == null) { + size++; + continue; + } + size += rowLength(array.getInitializers()); + } + return size; + } + + private Integer actualColumn(ExpressionTree expression) { + Map positionToColumnMap = builder.getInput().getPositionToColumnMap(); + return positionToColumnMap.get(builder.actualStartColumn(getStartPosition(expression))); + } + + /** Returns true if {@code atLeastM} of the expressions in the given column are the same kind. */ + private static boolean expressionsAreParallel( + List> rows, int column, int atLeastM) { + Multiset nodeTypes = HashMultiset.create(); + for (List row : rows) { + if (column >= row.size()) { + continue; + } + // Treat UnaryTree expressions as their underlying type for the comparison (so, for + // example + // -ve and +ve numeric literals are considered the same). + if (row.get(column) instanceof UnaryTree) { + nodeTypes.add(((UnaryTree) row.get(column)).getExpression().getKind()); + } else { + nodeTypes.add(row.get(column).getKind()); + } + } + for (Multiset.Entry nodeType : nodeTypes.entrySet()) { + if (nodeType.getCount() >= atLeastM) { + return true; + } + } + return false; + } + + // General helper functions. + + enum DeclarationKind { + NONE, + FIELD, + PARAMETER + } + + /** Declare one variable or variable-like thing. */ + int declareOne( + DeclarationKind kind, + Direction annotationsDirection, + Optional modifiers, + Tree type, + Name name, + String op, + String equals, + Optional initializer, + Optional trailing, + Optional receiverExpression, + Optional typeWithDims) { + + BreakTag typeBreak = genSym(); + BreakTag verticalAnnotationBreak = genSym(); + + // If the node is a field declaration, try to output any declaration + // annotations in-line. If the entire declaration doesn't fit on a single + // line, fall back to one-per-line. + boolean isField = kind == DeclarationKind.FIELD; + + if (isField) { + builder.blankLineWanted(BlankLineWanted.conditional(verticalAnnotationBreak)); } - static int rowLength(List row) { - int size = 0; - for (ExpressionTree tree : row) { - if (tree.getKind() != NEW_ARRAY) { - size++; - continue; - } - NewArrayTree array = (NewArrayTree) tree; - if (array.getInitializers() == null) { - size++; - continue; - } - size += rowLength(array.getInitializers()); - } - return size; - } + Deque> dims = + new ArrayDeque<>(typeWithDims.isPresent() ? typeWithDims.get().dims : ImmutableList.of()); + int baseDims = 0; - private Integer actualColumn(ExpressionTree expression) { - Map positionToColumnMap = builder.getInput().getPositionToColumnMap(); - return positionToColumnMap.get(builder.actualStartColumn(getStartPosition(expression))); - } + // preprocess to separate declaration annotations + modifiers, type annotations - /** - * Returns true if {@code atLeastM} of the expressions in the given column are the same kind. - */ - private static boolean expressionsAreParallel( - List> rows, int column, int atLeastM) { - Multiset nodeTypes = HashMultiset.create(); - for (List row : rows) { - if (column >= row.size()) { - continue; - } - // Treat UnaryTree expressions as their underlying type for the comparison (so, for - // example - // -ve and +ve numeric literals are considered the same). - if (row.get(column) instanceof UnaryTree) { - nodeTypes.add(((UnaryTree) row.get(column)).getExpression().getKind()); - } else { - nodeTypes.add(row.get(column).getKind()); - } - } - for (Multiset.Entry nodeType : nodeTypes.entrySet()) { - if (nodeType.getCount() >= atLeastM) { - return true; - } - } - return false; - } - - // General helper functions. - - enum DeclarationKind { - NONE, - FIELD, - PARAMETER - } - - /** Declare one variable or variable-like thing. */ - int declareOne( - DeclarationKind kind, - Direction annotationsDirection, - Optional modifiers, - Tree type, - Name name, - String op, - String equals, - Optional initializer, - Optional trailing, - Optional receiverExpression, - Optional typeWithDims) { - - BreakTag typeBreak = genSym(); - BreakTag verticalAnnotationBreak = genSym(); - - // If the node is a field declaration, try to output any declaration - // annotations in-line. If the entire declaration doesn't fit on a single - // line, fall back to one-per-line. - boolean isField = kind == DeclarationKind.FIELD; - - if (isField) { - builder.blankLineWanted(BlankLineWanted.conditional(verticalAnnotationBreak)); - } - - Deque> dims = - new ArrayDeque<>( - typeWithDims.isPresent() ? typeWithDims.get().dims : ImmutableList.of()); - int baseDims = 0; - - // preprocess to separate declaration annotations + modifiers, type annotations - - DeclarationModifiersAndTypeAnnotations declarationAndTypeModifiers = - modifiers - .map(m -> splitModifiers(m, m.getAnnotations())) - .orElse(DeclarationModifiersAndTypeAnnotations.empty()); - builder.open( - kind == DeclarationKind.PARAMETER - && declarationAndTypeModifiers.hasDeclarationAnnotation() - ? plusFour - : ZERO); + DeclarationModifiersAndTypeAnnotations declarationAndTypeModifiers = + modifiers + .map(m -> splitModifiers(m, m.getAnnotations())) + .orElse(DeclarationModifiersAndTypeAnnotations.empty()); + builder.open( + kind == DeclarationKind.PARAMETER && declarationAndTypeModifiers.hasDeclarationAnnotation() + ? plusFour + : ZERO); + { + List annotations = + visitModifiers( + declarationAndTypeModifiers, + annotationsDirection, + Optional.of(verticalAnnotationBreak)); + boolean isVar = + builder.peekToken().get().equals("var") + && (!name.contentEquals("var") || builder.peekToken(1).get().equals("var")); + boolean hasType = type != null || isVar; + builder.open(hasType ? plusFour : ZERO); + { + builder.open(ZERO); { - List annotations = - visitModifiers( - declarationAndTypeModifiers, - annotationsDirection, - Optional.of(verticalAnnotationBreak)); - boolean isVar = - builder.peekToken().get().equals("var") - && (!name.contentEquals("var") - || builder.peekToken(1).get().equals("var")); - boolean hasType = type != null || isVar; - builder.open(hasType ? plusFour : ZERO); - { - builder.open(ZERO); - { - builder.open(ZERO); - { - visitAnnotations(annotations, BreakOrNot.NO, BreakOrNot.YES); - if (typeWithDims.isPresent() && typeWithDims.get().node != null) { - scan(typeWithDims.get().node, null); - int totalDims = dims.size(); - builder.open(plusFour); - maybeAddDims(dims); - builder.close(); - baseDims = totalDims - dims.size(); - } else if (isVar) { - token("var"); - } else { - scan(type, null); - } - } - builder.close(); - - if (hasType) { - builder.breakOp( - Doc.FillMode.INDEPENDENT, " ", ZERO, Optional.of(typeBreak)); - } - - // conditionally ident the name and initializer +4 if the type spans - // multiple lines - builder.open(Indent.If.make(typeBreak, plusFour, ZERO)); - if (receiverExpression.isPresent()) { - scan(receiverExpression.get(), null); - } else { - visit(name); - } - builder.op(op); - } - maybeAddDims(dims); - builder.close(); + builder.open(ZERO); + { + visitAnnotations(annotations, BreakOrNot.NO, BreakOrNot.YES); + if (typeWithDims.isPresent() && typeWithDims.get().node != null) { + scan(typeWithDims.get().node, null); + int totalDims = dims.size(); + builder.open(plusFour); + maybeAddDims(dims); + builder.close(); + baseDims = totalDims - dims.size(); + } else if (isVar) { + token("var"); + } else { + scan(type, null); } - builder.close(); + } + builder.close(); - if (initializer.isPresent()) { - builder.space(); - token(equals); - if (initializer.get().getKind() == Tree.Kind.NEW_ARRAY - && ((NewArrayTree) initializer.get()).getType() == null) { - builder.open(minusFour); - builder.space(); - initializer.get().accept(this, null); - builder.close(); - } else { - builder.open(Indent.If.make(typeBreak, plusFour, ZERO)); - { - builder.breakToFill(" "); - scan(initializer.get(), null); - } - builder.close(); - } - } - if (trailing.isPresent() && builder.peekToken().equals(trailing)) { - builder.guessToken(trailing.get()); - } + if (hasType) { + builder.breakOp(Doc.FillMode.INDEPENDENT, " ", ZERO, Optional.of(typeBreak)); + } - // end of conditional name and initializer indent - builder.close(); + // conditionally ident the name and initializer +4 if the type spans + // multiple lines + builder.open(Indent.If.make(typeBreak, plusFour, ZERO)); + if (receiverExpression.isPresent()) { + scan(receiverExpression.get(), null); + } else { + visit(name); + } + builder.op(op); } - builder.close(); - - if (isField) { - builder.blankLineWanted(BlankLineWanted.conditional(verticalAnnotationBreak)); - } - - return baseDims; - } - - private void maybeAddDims(Deque> annotations) { - maybeAddDims(new ArrayDeque<>(), annotations); - } - - /** - * The compiler does not always preserve the concrete syntax of annotated array dimensions, and - * mixed-notation array dimensions. Use look-ahead to preserve the original syntax. - * - *

It is assumed that any number of regular dimension specifiers ({@code []} with no - * annotations) may be present in the input. - * - * @param dimExpressions an ordered list of dimension expressions (e.g. the {@code 0} in {@code - * new int[0]} - * @param annotations an ordered list of type annotations grouped by dimension (e.g. {@code - * [[@A, @B], [@C]]} for {@code int @A [] @B @C []} - */ - private void maybeAddDims( - Deque dimExpressions, - Deque> annotations) { - boolean lastWasAnnotation = false; - while (builder.peekToken().isPresent()) { - switch (builder.peekToken().get()) { - case "@": - if (annotations.isEmpty()) { - return; - } - List dimAnnotations = annotations.removeFirst(); - if (dimAnnotations.isEmpty()) { - continue; - } - builder.breakToFill(" "); - visitAnnotations(dimAnnotations, BreakOrNot.NO, BreakOrNot.NO); - lastWasAnnotation = true; - break; - case "[": - if (lastWasAnnotation) { - builder.breakToFill(" "); - } else { - builder.breakToFill(); - } - token("["); - if (!builder.peekToken().get().equals("]")) { - scan(dimExpressions.removeFirst(), null); - } - token("]"); - lastWasAnnotation = false; - break; - case ".": - if (!builder.peekToken().get().equals(".") - || !builder.peekToken(1).get().equals(".")) { - return; - } - if (lastWasAnnotation) { - builder.breakToFill(" "); - } else { - builder.breakToFill(); - } - builder.op("..."); - lastWasAnnotation = false; - break; - default: - return; - } - } - } - - private void declareMany(List fragments, Direction annotationDirection) { - builder.open(ZERO); - - ModifiersTree modifiers = fragments.get(0).getModifiers(); - Tree type = fragments.get(0).getType(); - - visitAndBreakModifiers( - modifiers, annotationDirection, /* declarationAnnotationBreak= */ Optional.empty()); - builder.open(plusFour); - builder.open(ZERO); - TypeWithDims extractedDims = DimensionHelpers.extractDims(type, SortedDims.YES); - Deque> dims = new ArrayDeque<>(extractedDims.dims); - scan(extractedDims.node, null); - int baseDims = dims.size(); maybeAddDims(dims); - baseDims = baseDims - dims.size(); - boolean first = true; - for (VariableTree fragment : fragments) { - if (!first) { - token(","); - } - TypeWithDims fragmentDims = variableFragmentDims(first, baseDims, fragment.getType()); - dims = new ArrayDeque<>(fragmentDims.dims); - builder.breakOp(" "); - builder.open(ZERO); - maybeAddDims(dims); - visit(fragment.getName()); - maybeAddDims(dims); - ExpressionTree initializer = fragment.getInitializer(); - if (initializer != null) { - builder.space(); - token("="); - builder.open(plusFour); - builder.breakOp(" "); - scan(initializer, null); - builder.close(); - } - builder.close(); - if (first) { - builder.close(); - } - first = false; - } builder.close(); - token(";"); - builder.close(); - } + } + builder.close(); - /** Add a list of declarations. */ - protected void addBodyDeclarations( - List bodyDeclarations, - BracesOrNot braces, - FirstDeclarationsOrNot first0) { - if (bodyDeclarations.isEmpty()) { - if (braces.isYes()) { - builder.space(); - tokenBreakTrailingComment("{", plusTwo); - builder.blankLineWanted(BlankLineWanted.NO); - builder.open(ZERO); - token("}", plusTwo); - builder.close(); - } - } else { - if (braces.isYes()) { - builder.space(); - tokenBreakTrailingComment("{", plusTwo); - builder.open(ZERO); - } - builder.open(plusTwo); - boolean first = first0.isYes(); - boolean lastOneGotBlankLineBefore = false; - PeekingIterator it = Iterators.peekingIterator(bodyDeclarations.iterator()); - while (it.hasNext()) { - Tree bodyDeclaration = it.next(); - dropEmptyDeclarations(); - builder.forcedBreak(); - boolean thisOneGetsBlankLineBefore = - bodyDeclaration.getKind() != VARIABLE || hasJavaDoc(bodyDeclaration); - if (first) { - builder.blankLineWanted(PRESERVE); - } else if (!first && (thisOneGetsBlankLineBefore || lastOneGotBlankLineBefore)) { - builder.blankLineWanted(YES); - } - markForPartialFormat(); - - if (bodyDeclaration.getKind() == VARIABLE) { - visitVariables( - variableFragments(it, bodyDeclaration), - DeclarationKind.FIELD, - fieldAnnotationDirection( - ((VariableTree) bodyDeclaration).getModifiers())); - } else { - scan(bodyDeclaration, null); - } - first = false; - lastOneGotBlankLineBefore = thisOneGetsBlankLineBefore; - } - dropEmptyDeclarations(); - builder.forcedBreak(); - builder.close(); - builder.forcedBreak(); - markForPartialFormat(); - if (braces.isYes()) { - builder.blankLineWanted(BlankLineWanted.NO); - token("}", plusTwo); - builder.close(); - } - } - } - - /** Gets the permits clause for the given node. This is only available in Java 15 and later. */ - protected List getPermitsClause(ClassTree node) { - return ImmutableList.of(); - } - - private void classDeclarationTypeList(String token, List types) { - if (types.isEmpty()) { - return; - } - builder.breakToFill(" "); - builder.open(types.size() > 1 ? plusFour : ZERO); - token(token); + if (initializer.isPresent()) { builder.space(); - boolean first = true; - for (Tree type : types) { - if (!first) { - token(","); - builder.breakOp(" "); - } - scan(type, null); - first = false; + token(equals); + if (initializer.get().getKind() == Tree.Kind.NEW_ARRAY + && ((NewArrayTree) initializer.get()).getType() == null) { + builder.open(minusFour); + builder.space(); + initializer.get().accept(this, null); + builder.close(); + } else { + builder.open(Indent.If.make(typeBreak, plusFour, ZERO)); + { + builder.breakToFill(" "); + scan(initializer.get(), null); + } + builder.close(); } + } + if (trailing.isPresent() && builder.peekToken().equals(trailing)) { + builder.guessToken(trailing.get()); + } + + // end of conditional name and initializer indent + builder.close(); + } + builder.close(); + + if (isField) { + builder.blankLineWanted(BlankLineWanted.conditional(verticalAnnotationBreak)); + } + + return baseDims; + } + + private void maybeAddDims(Deque> annotations) { + maybeAddDims(new ArrayDeque<>(), annotations); + } + + /** + * The compiler does not always preserve the concrete syntax of annotated array dimensions, and + * mixed-notation array dimensions. Use look-ahead to preserve the original syntax. + * + *

It is assumed that any number of regular dimension specifiers ({@code []} with no + * annotations) may be present in the input. + * + * @param dimExpressions an ordered list of dimension expressions (e.g. the {@code 0} in {@code + * new int[0]} + * @param annotations an ordered list of type annotations grouped by dimension (e.g. {@code + * [[@A, @B], [@C]]} for {@code int @A [] @B @C []} + */ + private void maybeAddDims( + Deque dimExpressions, Deque> annotations) { + boolean lastWasAnnotation = false; + while (builder.peekToken().isPresent()) { + switch (builder.peekToken().get()) { + case "@": + if (annotations.isEmpty()) { + return; + } + List dimAnnotations = annotations.removeFirst(); + if (dimAnnotations.isEmpty()) { + continue; + } + builder.breakToFill(" "); + visitAnnotations(dimAnnotations, BreakOrNot.NO, BreakOrNot.NO); + lastWasAnnotation = true; + break; + case "[": + if (lastWasAnnotation) { + builder.breakToFill(" "); + } else { + builder.breakToFill(); + } + token("["); + if (!builder.peekToken().get().equals("]")) { + scan(dimExpressions.removeFirst(), null); + } + token("]"); + lastWasAnnotation = false; + break; + case ".": + if (!builder.peekToken().get().equals(".") || !builder.peekToken(1).get().equals(".")) { + return; + } + if (lastWasAnnotation) { + builder.breakToFill(" "); + } else { + builder.breakToFill(); + } + builder.op("..."); + lastWasAnnotation = false; + break; + default: + return; + } + } + } + + private void declareMany(List fragments, Direction annotationDirection) { + builder.open(ZERO); + + ModifiersTree modifiers = fragments.get(0).getModifiers(); + Tree type = fragments.get(0).getType(); + + visitAndBreakModifiers( + modifiers, annotationDirection, /* declarationAnnotationBreak= */ Optional.empty()); + builder.open(plusFour); + builder.open(ZERO); + TypeWithDims extractedDims = DimensionHelpers.extractDims(type, SortedDims.YES); + Deque> dims = new ArrayDeque<>(extractedDims.dims); + scan(extractedDims.node, null); + int baseDims = dims.size(); + maybeAddDims(dims); + baseDims = baseDims - dims.size(); + boolean first = true; + for (VariableTree fragment : fragments) { + if (!first) { + token(","); + } + TypeWithDims fragmentDims = variableFragmentDims(first, baseDims, fragment.getType()); + dims = new ArrayDeque<>(fragmentDims.dims); + builder.breakOp(" "); + builder.open(ZERO); + maybeAddDims(dims); + visit(fragment.getName()); + maybeAddDims(dims); + ExpressionTree initializer = fragment.getInitializer(); + if (initializer != null) { + builder.space(); + token("="); + builder.open(plusFour); + builder.breakOp(" "); + scan(initializer, null); builder.close(); + } + builder.close(); + if (first) { + builder.close(); + } + first = false; } + builder.close(); + token(";"); + builder.close(); + } - /** - * The parser expands multi-variable declarations into separate single-variable declarations. - * All of the fragments in the original declaration have the same start position, so we use that - * as a signal to collect them and preserve the multi-variable declaration in the output. - * - *

e.g. {@code int x, y;} is parsed as {@code int x; int y;}. - */ - private static List variableFragments( - PeekingIterator it, Tree first) { - List fragments = new ArrayList<>(); - if (first.getKind() == VARIABLE) { - int start = getStartPosition(first); - fragments.add((VariableTree) first); - while (it.hasNext() - && it.peek().getKind() == VARIABLE - && getStartPosition(it.peek()) == start) { - fragments.add((VariableTree) it.next()); - } + /** Add a list of declarations. */ + protected void addBodyDeclarations( + List bodyDeclarations, BracesOrNot braces, FirstDeclarationsOrNot first0) { + if (bodyDeclarations.isEmpty()) { + if (braces.isYes()) { + builder.space(); + tokenBreakTrailingComment("{", plusTwo); + builder.blankLineWanted(BlankLineWanted.NO); + builder.open(ZERO); + token("}", plusTwo); + builder.close(); + } + } else { + if (braces.isYes()) { + builder.space(); + tokenBreakTrailingComment("{", plusTwo); + builder.open(ZERO); + } + builder.open(plusTwo); + boolean first = first0.isYes(); + boolean lastOneGotBlankLineBefore = false; + PeekingIterator it = Iterators.peekingIterator(bodyDeclarations.iterator()); + while (it.hasNext()) { + Tree bodyDeclaration = it.next(); + dropEmptyDeclarations(); + builder.forcedBreak(); + boolean thisOneGetsBlankLineBefore = + bodyDeclaration.getKind() != VARIABLE || hasJavaDoc(bodyDeclaration); + if (first) { + builder.blankLineWanted(PRESERVE); + } else if (!first && (thisOneGetsBlankLineBefore || lastOneGotBlankLineBefore)) { + builder.blankLineWanted(YES); } - return fragments; - } + markForPartialFormat(); - /** Does this declaration have javadoc preceding it? */ - private boolean hasJavaDoc(Tree bodyDeclaration) { - int position = ((JCTree) bodyDeclaration).getStartPosition(); - Input.Token token = builder.getInput().getPositionTokenMap().get(position); - if (token != null) { - for (Input.Tok tok : token.getToksBefore()) { - if (tok.getText().startsWith("/**")) { - return true; - } - } + if (bodyDeclaration.getKind() == VARIABLE) { + visitVariables( + variableFragments(it, bodyDeclaration), + DeclarationKind.FIELD, + fieldAnnotationDirection(((VariableTree) bodyDeclaration).getModifiers())); + } else { + scan(bodyDeclaration, null); } - return false; + first = false; + lastOneGotBlankLineBefore = thisOneGetsBlankLineBefore; + } + dropEmptyDeclarations(); + builder.forcedBreak(); + builder.close(); + builder.forcedBreak(); + markForPartialFormat(); + if (braces.isYes()) { + builder.blankLineWanted(BlankLineWanted.NO); + token("}", plusTwo); + builder.close(); + } } + } - private static Optional getNextToken(Input input, int position) { - return Optional.ofNullable(input.getPositionTokenMap().get(position)); + /** Gets the permits clause for the given node. This is only available in Java 15 and later. */ + protected List getPermitsClause(ClassTree node) { + return ImmutableList.of(); + } + + private void classDeclarationTypeList(String token, List types) { + if (types.isEmpty()) { + return; } + builder.breakToFill(" "); + builder.open(types.size() > 1 ? plusFour : ZERO); + token(token); + builder.space(); + boolean first = true; + for (Tree type : types) { + if (!first) { + token(","); + builder.breakOp(" "); + } + scan(type, null); + first = false; + } + builder.close(); + } - /** Does this list of trees end with the specified token? */ - private boolean hasTrailingToken(Input input, List nodes, String token) { - if (nodes.isEmpty()) { - return false; + /** + * The parser expands multi-variable declarations into separate single-variable declarations. All + * of the fragments in the original declaration have the same start position, so we use that as a + * signal to collect them and preserve the multi-variable declaration in the output. + * + *

e.g. {@code int x, y;} is parsed as {@code int x; int y;}. + */ + private static List variableFragments( + PeekingIterator it, Tree first) { + List fragments = new ArrayList<>(); + if (first.getKind() == VARIABLE) { + int start = getStartPosition(first); + fragments.add((VariableTree) first); + while (it.hasNext() + && it.peek().getKind() == VARIABLE + && getStartPosition(it.peek()) == start) { + fragments.add((VariableTree) it.next()); + } + } + return fragments; + } + + /** Does this declaration have javadoc preceding it? */ + private boolean hasJavaDoc(Tree bodyDeclaration) { + int position = ((JCTree) bodyDeclaration).getStartPosition(); + Input.Token token = builder.getInput().getPositionTokenMap().get(position); + if (token != null) { + for (Input.Tok tok : token.getToksBefore()) { + if (tok.getText().startsWith("/**")) { + return true; } - Tree lastNode = getLast(nodes); - Optional nextToken = - getNextToken(input, getEndPosition(lastNode, getCurrentPath())); - return nextToken.isPresent() && nextToken.get().getTok().getText().equals(token); + } } + return false; + } - /** - * Can a local with a set of modifiers be declared with horizontal annotations? This is - * currently true if there is at most one parameterless annotation, and no others. - * - * @param modifiers the list of {@link ModifiersTree}s - * @return whether the local can be declared with horizontal annotations - */ - private static Direction canLocalHaveHorizontalAnnotations(ModifiersTree modifiers) { - int parameterlessAnnotations = 0; - for (AnnotationTree annotation : modifiers.getAnnotations()) { - if (annotation.getArguments().isEmpty()) { - parameterlessAnnotations++; - } - } - return parameterlessAnnotations <= 1 - && parameterlessAnnotations == modifiers.getAnnotations().size() - ? Direction.HORIZONTAL - : Direction.VERTICAL; - } + private static Optional getNextToken(Input input, int position) { + return Optional.ofNullable(input.getPositionTokenMap().get(position)); + } - /** - * Should a field with a set of modifiers be declared with horizontal annotations? This is - * currently true if all annotations are parameterless annotations. - */ - private static Direction fieldAnnotationDirection(ModifiersTree modifiers) { - for (AnnotationTree annotation : modifiers.getAnnotations()) { - if (!annotation.getArguments().isEmpty()) { - return Direction.VERTICAL; - } - } - return Direction.HORIZONTAL; + /** Does this list of trees end with the specified token? */ + private boolean hasTrailingToken(Input input, List nodes, String token) { + if (nodes.isEmpty()) { + return false; } + Tree lastNode = getLast(nodes); + Optional nextToken = + getNextToken(input, getEndPosition(lastNode, getCurrentPath())); + return nextToken.isPresent() && nextToken.get().getTok().getText().equals(token); + } - /** - * Emit a {@link Doc.Token}. - * - * @param token the {@link String} to wrap in a {@link Doc.Token} - */ - protected final void token(String token) { - builder.token( - token, - Doc.Token.RealOrImaginary.REAL, - ZERO, - /* breakAndIndentTrailingComment= */ Optional.empty()); + /** + * Can a local with a set of modifiers be declared with horizontal annotations? This is currently + * true if there is at most one parameterless annotation, and no others. + * + * @param modifiers the list of {@link ModifiersTree}s + * @return whether the local can be declared with horizontal annotations + */ + private static Direction canLocalHaveHorizontalAnnotations(ModifiersTree modifiers) { + int parameterlessAnnotations = 0; + for (AnnotationTree annotation : modifiers.getAnnotations()) { + if (annotation.getArguments().isEmpty()) { + parameterlessAnnotations++; + } } + return parameterlessAnnotations <= 1 + && parameterlessAnnotations == modifiers.getAnnotations().size() + ? Direction.HORIZONTAL + : Direction.VERTICAL; + } - /** - * Emit a {@link Doc.Token}. - * - * @param token the {@link String} to wrap in a {@link Doc.Token} - * @param plusIndentCommentsBefore extra indent for comments before this token - */ - protected final void token(String token, Indent plusIndentCommentsBefore) { - builder.token( - token, - Doc.Token.RealOrImaginary.REAL, - plusIndentCommentsBefore, - /* breakAndIndentTrailingComment= */ Optional.empty()); + /** + * Should a field with a set of modifiers be declared with horizontal annotations? This is + * currently true if all annotations are parameterless annotations. + */ + private static Direction fieldAnnotationDirection(ModifiersTree modifiers) { + for (AnnotationTree annotation : modifiers.getAnnotations()) { + if (!annotation.getArguments().isEmpty()) { + return Direction.VERTICAL; + } } + return Direction.HORIZONTAL; + } - /** Emit a {@link Doc.Token}, and breaks and indents trailing javadoc or block comments. */ - final void tokenBreakTrailingComment(String token, Indent breakAndIndentTrailingComment) { - builder.token( - token, - Doc.Token.RealOrImaginary.REAL, - ZERO, - Optional.of(breakAndIndentTrailingComment)); - } + /** + * Emit a {@link Doc.Token}. + * + * @param token the {@link String} to wrap in a {@link Doc.Token} + */ + protected final void token(String token) { + builder.token( + token, + Doc.Token.RealOrImaginary.REAL, + ZERO, + /* breakAndIndentTrailingComment= */ Optional.empty()); + } - protected void markForPartialFormat() { - if (!inExpression()) { - builder.markForPartialFormat(); - } - } + /** + * Emit a {@link Doc.Token}. + * + * @param token the {@link String} to wrap in a {@link Doc.Token} + * @param plusIndentCommentsBefore extra indent for comments before this token + */ + protected final void token(String token, Indent plusIndentCommentsBefore) { + builder.token( + token, + Doc.Token.RealOrImaginary.REAL, + plusIndentCommentsBefore, + /* breakAndIndentTrailingComment= */ Optional.empty()); + } - /** - * Sync to position in the input. If we've skipped outputting any tokens that were present in - * the input tokens, output them here and complain. - * - * @param node the ASTNode holding the input position - */ - protected final void sync(Tree node) { - builder.sync(((JCTree) node).getStartPosition()); - } + /** Emit a {@link Doc.Token}, and breaks and indents trailing javadoc or block comments. */ + final void tokenBreakTrailingComment(String token, Indent breakAndIndentTrailingComment) { + builder.token( + token, Doc.Token.RealOrImaginary.REAL, ZERO, Optional.of(breakAndIndentTrailingComment)); + } - final BreakTag genSym() { - return new BreakTag(); + protected void markForPartialFormat() { + if (!inExpression()) { + builder.markForPartialFormat(); } + } - @Override - public final String toString() { - return MoreObjects.toStringHelper(this).add("builder", builder).toString(); - } + /** + * Sync to position in the input. If we've skipped outputting any tokens that were present in the + * input tokens, output them here and complain. + * + * @param node the ASTNode holding the input position + */ + protected final void sync(Tree node) { + builder.sync(((JCTree) node).getStartPosition()); + } + + final BreakTag genSym() { + return new BreakTag(); + } + + @Override + public final String toString() { + return MoreObjects.toStringHelper(this).add("builder", builder).toString(); + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/JavaOutput.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/JavaOutput.java index 8c83d3e9e..c35d3d55d 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/JavaOutput.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/JavaOutput.java @@ -48,364 +48,357 @@ import java.util.Map; * methods to emit the output document. */ public final class JavaOutput extends Output { - private final String lineSeparator; - private final Input javaInput; // Used to follow along while emitting the output. - private final CommentsHelper commentsHelper; // Used to re-flow comments. - private final Map blankLines = - new HashMap<>(); // Info on blank lines. - private final RangeSet partialFormatRanges = TreeRangeSet.create(); + private final String lineSeparator; + private final Input javaInput; // Used to follow along while emitting the output. + private final CommentsHelper commentsHelper; // Used to re-flow comments. + private final Map blankLines = new HashMap<>(); // Info on blank lines. + private final RangeSet partialFormatRanges = TreeRangeSet.create(); - private final List mutableLines = new ArrayList<>(); - private final int kN; // The number of tokens or comments in the input, excluding the EOF. - private int iLine = 0; // Closest corresponding line number on input. - private int lastK = -1; // Last {@link Tok} index output. - private int newlinesPending = 0; - private StringBuilder lineBuilder = new StringBuilder(); - private StringBuilder spacesPending = new StringBuilder(); + private final List mutableLines = new ArrayList<>(); + private final int kN; // The number of tokens or comments in the input, excluding the EOF. + private int iLine = 0; // Closest corresponding line number on input. + private int lastK = -1; // Last {@link Tok} index output. + private int newlinesPending = 0; + private StringBuilder lineBuilder = new StringBuilder(); + private StringBuilder spacesPending = new StringBuilder(); - /** - * {@code JavaOutput} constructor. - * - * @param javaInput the {@link Input}, used to match up blank lines in the output - * @param commentsHelper the {@link CommentsHelper}, used to rewrite comments - */ - public JavaOutput(String lineSeparator, Input javaInput, CommentsHelper commentsHelper) { - this.lineSeparator = lineSeparator; - this.javaInput = javaInput; - this.commentsHelper = commentsHelper; - kN = javaInput.getkN(); + /** + * {@code JavaOutput} constructor. + * + * @param javaInput the {@link Input}, used to match up blank lines in the output + * @param commentsHelper the {@link CommentsHelper}, used to rewrite comments + */ + public JavaOutput(String lineSeparator, Input javaInput, CommentsHelper commentsHelper) { + this.lineSeparator = lineSeparator; + this.javaInput = javaInput; + this.commentsHelper = commentsHelper; + kN = javaInput.getkN(); + } + + @Override + public void blankLine(int k, BlankLineWanted wanted) { + if (blankLines.containsKey(k)) { + blankLines.put(k, blankLines.get(k).merge(wanted)); + } else { + blankLines.put(k, wanted); } + } - @Override - public void blankLine(int k, BlankLineWanted wanted) { - if (blankLines.containsKey(k)) { - blankLines.put(k, blankLines.get(k).merge(wanted)); - } else { - blankLines.put(k, wanted); + @Override + public void markForPartialFormat(Token start, Token end) { + int lo = JavaOutput.startTok(start).getIndex(); + int hi = JavaOutput.endTok(end).getIndex(); + partialFormatRanges.add(Range.closed(lo, hi)); + } + + // TODO(user): Add invariant. + @Override + public void append(String text, Range range) { + if (!range.isEmpty()) { + boolean sawNewlines = false; + // Skip over input line we've passed. + int iN = javaInput.getLineCount(); + while (iLine < iN + && (javaInput.getRanges(iLine).isEmpty() + || javaInput.getRanges(iLine).upperEndpoint() <= range.lowerEndpoint())) { + if (javaInput.getRanges(iLine).isEmpty()) { + // Skipped over a blank line. + sawNewlines = true; } + ++iLine; + } + /* + * Output blank line if we've called {@link OpsBuilder#blankLine}{@code (true)} here, or if + * there's a blank line here and it's a comment. + */ + BlankLineWanted wanted = blankLines.getOrDefault(lastK, BlankLineWanted.NO); + if (isComment(text) ? sawNewlines : wanted.wanted().orElse(sawNewlines)) { + ++newlinesPending; + } } - - @Override - public void markForPartialFormat(Token start, Token end) { - int lo = JavaOutput.startTok(start).getIndex(); - int hi = JavaOutput.endTok(end).getIndex(); - partialFormatRanges.add(Range.closed(lo, hi)); - } - - // TODO(user): Add invariant. - @Override - public void append(String text, Range range) { - if (!range.isEmpty()) { - boolean sawNewlines = false; - // Skip over input line we've passed. - int iN = javaInput.getLineCount(); - while (iLine < iN - && (javaInput.getRanges(iLine).isEmpty() - || javaInput.getRanges(iLine).upperEndpoint() - <= range.lowerEndpoint())) { - if (javaInput.getRanges(iLine).isEmpty()) { - // Skipped over a blank line. - sawNewlines = true; - } - ++iLine; - } - /* - * Output blank line if we've called {@link OpsBuilder#blankLine}{@code (true)} here, or if - * there's a blank line here and it's a comment. - */ - BlankLineWanted wanted = blankLines.getOrDefault(lastK, BlankLineWanted.NO); - if (isComment(text) ? sawNewlines : wanted.wanted().orElse(sawNewlines)) { - ++newlinesPending; - } - } - if (Newlines.isNewline(text)) { - /* - * Don't update range information, and swallow extra newlines. The case below for '\n' is for - * block comments. - */ - if (newlinesPending == 0) { - ++newlinesPending; + if (Newlines.isNewline(text)) { + /* + * Don't update range information, and swallow extra newlines. The case below for '\n' is for + * block comments. + */ + if (newlinesPending == 0) { + ++newlinesPending; + } + spacesPending = new StringBuilder(); + } else { + boolean rangesSet = false; + int textN = text.length(); + for (int i = 0; i < textN; i++) { + char c = text.charAt(i); + switch (c) { + case ' ': + spacesPending.append(' '); + break; + case '\t': + spacesPending.append('\t'); + break; + case '\r': + if (i + 1 < text.length() && text.charAt(i + 1) == '\n') { + i++; } + // falls through + case '\n': spacesPending = new StringBuilder(); + ++newlinesPending; + break; + default: + while (newlinesPending > 0) { + // drop leading blank lines + if (!mutableLines.isEmpty() || lineBuilder.length() > 0) { + mutableLines.add(lineBuilder.toString()); + } + lineBuilder = new StringBuilder(); + rangesSet = false; + --newlinesPending; + } + if (spacesPending.length() > 0) { + lineBuilder.append(spacesPending); + spacesPending = new StringBuilder(); + } + lineBuilder.append(c); + if (!range.isEmpty()) { + if (!rangesSet) { + while (ranges.size() <= mutableLines.size()) { + ranges.add(Formatter.EMPTY_RANGE); + } + ranges.set(mutableLines.size(), union(ranges.get(mutableLines.size()), range)); + rangesSet = true; + } + } + } + } + } + if (!range.isEmpty()) { + lastK = range.upperEndpoint(); + } + } + + @Override + public void indent(int indent) { + spacesPending.append(Strings.repeat(" ", indent)); + } + + /** Flush any incomplete last line, then add the EOF token into our data structures. */ + public void flush() { + String lastLine = lineBuilder.toString(); + if (!CharMatcher.whitespace().matchesAllOf(lastLine)) { + mutableLines.add(lastLine); + } + int jN = mutableLines.size(); + Range eofRange = Range.closedOpen(kN, kN + 1); + while (ranges.size() < jN) { + ranges.add(Formatter.EMPTY_RANGE); + } + ranges.add(eofRange); + setLines(ImmutableList.copyOf(mutableLines)); + } + + // The following methods can be used after the Output has been built. + + @Override + public CommentsHelper getCommentsHelper() { + return commentsHelper; + } + + /** + * Emit a list of {@link Replacement}s to convert from input to output. + * + * @return a list of {@link Replacement}s, sorted by start index, without overlaps + */ + public ImmutableList getFormatReplacements(RangeSet iRangeSet0) { + ImmutableList.Builder result = ImmutableList.builder(); + Map> kToJ = JavaOutput.makeKToIJ(this); + + // Expand the token ranges to align with re-formattable boundaries. + RangeSet breakableRanges = TreeRangeSet.create(); + RangeSet iRangeSet = iRangeSet0.subRangeSet(Range.closed(0, javaInput.getkN())); + for (Range iRange : iRangeSet.asRanges()) { + Range range = expandToBreakableRegions(iRange.canonical(DiscreteDomain.integers())); + if (range.equals(EMPTY_RANGE)) { + // the range contains only whitespace + continue; + } + breakableRanges.add(range); + } + + // Construct replacements for each reformatted region. + for (Range range : breakableRanges.asRanges()) { + + Input.Tok startTok = startTok(javaInput.getToken(range.lowerEndpoint())); + Input.Tok endTok = endTok(javaInput.getToken(range.upperEndpoint() - 1)); + + // Add all output lines in the given token range to the replacement. + StringBuilder replacement = new StringBuilder(); + + int replaceFrom = startTok.getPosition(); + // Replace leading whitespace in the input with the whitespace from the formatted file + while (replaceFrom > 0) { + char previous = javaInput.getText().charAt(replaceFrom - 1); + if (!CharMatcher.whitespace().matches(previous)) { + break; + } + replaceFrom--; + } + + int i = kToJ.get(startTok.getIndex()).lowerEndpoint(); + // Include leading blank lines from the formatted output, unless the formatted range + // starts at the beginning of the file. + while (i > 0 && getLine(i - 1).isEmpty()) { + i--; + } + // Write out the formatted range. + for (; i < kToJ.get(endTok.getIndex()).upperEndpoint(); i++) { + // It's possible to run out of output lines (e.g. if the input ended with + // multiple trailing newlines). + if (i < getLineCount()) { + if (i > 0) { + replacement.append(lineSeparator); + } + replacement.append(getLine(i)); + } + } + + int replaceTo = min(endTok.getPosition() + endTok.length(), javaInput.getText().length()); + // If the formatted ranged ended in the trailing trivia of the last token before EOF, + // format all the way up to EOF to deal with trailing whitespace correctly. + if (endTok.getIndex() == javaInput.getkN() - 1) { + replaceTo = javaInput.getText().length(); + } + // Replace trailing whitespace in the input with the whitespace from the formatted file. + // If the trailing whitespace in the input includes one or more line breaks, preserve + // the + // whitespace after the last newline to avoid re-indenting the line following the + // formatted + // line. + int newline = -1; + while (replaceTo < javaInput.getText().length()) { + char next = javaInput.getText().charAt(replaceTo); + if (!CharMatcher.whitespace().matches(next)) { + break; + } + int newlineLength = Newlines.hasNewlineAt(javaInput.getText(), replaceTo); + if (newlineLength != -1) { + newline = replaceTo; + // Skip over the entire newline; don't count the second character of \r\n as a + // newline. + replaceTo += newlineLength; } else { - boolean rangesSet = false; - int textN = text.length(); - for (int i = 0; i < textN; i++) { - char c = text.charAt(i); - switch (c) { - case ' ': - spacesPending.append(' '); - break; - case '\t': - spacesPending.append('\t'); - break; - case '\r': - if (i + 1 < text.length() && text.charAt(i + 1) == '\n') { - i++; - } - // falls through - case '\n': - spacesPending = new StringBuilder(); - ++newlinesPending; - break; - default: - while (newlinesPending > 0) { - // drop leading blank lines - if (!mutableLines.isEmpty() || lineBuilder.length() > 0) { - mutableLines.add(lineBuilder.toString()); - } - lineBuilder = new StringBuilder(); - rangesSet = false; - --newlinesPending; - } - if (spacesPending.length() > 0) { - lineBuilder.append(spacesPending); - spacesPending = new StringBuilder(); - } - lineBuilder.append(c); - if (!range.isEmpty()) { - if (!rangesSet) { - while (ranges.size() <= mutableLines.size()) { - ranges.add(Formatter.EMPTY_RANGE); - } - ranges.set( - mutableLines.size(), - union(ranges.get(mutableLines.size()), range)); - rangesSet = true; - } - } - } - } + replaceTo++; } - if (!range.isEmpty()) { - lastK = range.upperEndpoint(); + } + if (newline != -1) { + replaceTo = newline; + } + + if (newline == -1) { + // There wasn't an existing trailing newline; add one. + replacement.append(lineSeparator); + } + for (; i < getLineCount(); i++) { + String after = getLine(i); + int idx = CharMatcher.whitespace().negate().indexIn(after); + if (idx == -1) { + // Write out trailing empty lines from the formatted output. + replacement.append(lineSeparator); + } else { + if (newline == -1) { + // If there wasn't a trailing newline in the input, indent the next line. + replacement.append(after, 0, idx); + } + break; } + } + + result.add(Replacement.create(replaceFrom, replaceTo, replacement.toString())); } + return result.build(); + } - @Override - public void indent(int indent) { - spacesPending.append(Strings.repeat(" ", indent)); + /** + * Expand a token range to start and end on acceptable boundaries for re-formatting. + * + * @param iRange the {@link Range} of tokens + * @return the expanded token range + */ + private Range expandToBreakableRegions(Range iRange) { + // The original line range. + int loTok = iRange.lowerEndpoint(); + int hiTok = iRange.upperEndpoint() - 1; + + // Expand the token indices to formattable boundaries (e.g. edges of statements). + if (!partialFormatRanges.contains(loTok) || !partialFormatRanges.contains(hiTok)) { + return EMPTY_RANGE; } + loTok = partialFormatRanges.rangeContaining(loTok).lowerEndpoint(); + hiTok = partialFormatRanges.rangeContaining(hiTok).upperEndpoint(); + return Range.closedOpen(loTok, hiTok + 1); + } - /** Flush any incomplete last line, then add the EOF token into our data structures. */ - public void flush() { - String lastLine = lineBuilder.toString(); - if (!CharMatcher.whitespace().matchesAllOf(lastLine)) { - mutableLines.add(lastLine); - } - int jN = mutableLines.size(); - Range eofRange = Range.closedOpen(kN, kN + 1); - while (ranges.size() < jN) { - ranges.add(Formatter.EMPTY_RANGE); - } - ranges.add(eofRange); - setLines(ImmutableList.copyOf(mutableLines)); + public static String applyReplacements(String input, List replacements) { + replacements = new ArrayList<>(replacements); + replacements.sort(comparing((Replacement r) -> r.getReplaceRange().lowerEndpoint()).reversed()); + StringBuilder writer = new StringBuilder(input); + for (Replacement replacement : replacements) { + writer.replace( + replacement.getReplaceRange().lowerEndpoint(), + replacement.getReplaceRange().upperEndpoint(), + replacement.getReplacementString()); } + return writer.toString(); + } - // The following methods can be used after the Output has been built. - - @Override - public CommentsHelper getCommentsHelper() { - return commentsHelper; + /** The earliest position of any Tok in the Token, including leading whitespace. */ + public static int startPosition(Token token) { + int min = token.getTok().getPosition(); + for (Input.Tok tok : token.getToksBefore()) { + min = min(min, tok.getPosition()); } + return min; + } - /** - * Emit a list of {@link Replacement}s to convert from input to output. - * - * @return a list of {@link Replacement}s, sorted by start index, without overlaps - */ - public ImmutableList getFormatReplacements(RangeSet iRangeSet0) { - ImmutableList.Builder result = ImmutableList.builder(); - Map> kToJ = JavaOutput.makeKToIJ(this); - - // Expand the token ranges to align with re-formattable boundaries. - RangeSet breakableRanges = TreeRangeSet.create(); - RangeSet iRangeSet = iRangeSet0.subRangeSet(Range.closed(0, javaInput.getkN())); - for (Range iRange : iRangeSet.asRanges()) { - Range range = - expandToBreakableRegions(iRange.canonical(DiscreteDomain.integers())); - if (range.equals(EMPTY_RANGE)) { - // the range contains only whitespace - continue; - } - breakableRanges.add(range); - } - - // Construct replacements for each reformatted region. - for (Range range : breakableRanges.asRanges()) { - - Input.Tok startTok = startTok(javaInput.getToken(range.lowerEndpoint())); - Input.Tok endTok = endTok(javaInput.getToken(range.upperEndpoint() - 1)); - - // Add all output lines in the given token range to the replacement. - StringBuilder replacement = new StringBuilder(); - - int replaceFrom = startTok.getPosition(); - // Replace leading whitespace in the input with the whitespace from the formatted file - while (replaceFrom > 0) { - char previous = javaInput.getText().charAt(replaceFrom - 1); - if (!CharMatcher.whitespace().matches(previous)) { - break; - } - replaceFrom--; - } - - int i = kToJ.get(startTok.getIndex()).lowerEndpoint(); - // Include leading blank lines from the formatted output, unless the formatted range - // starts at the beginning of the file. - while (i > 0 && getLine(i - 1).isEmpty()) { - i--; - } - // Write out the formatted range. - for (; i < kToJ.get(endTok.getIndex()).upperEndpoint(); i++) { - // It's possible to run out of output lines (e.g. if the input ended with - // multiple trailing newlines). - if (i < getLineCount()) { - if (i > 0) { - replacement.append(lineSeparator); - } - replacement.append(getLine(i)); - } - } - - int replaceTo = - min(endTok.getPosition() + endTok.length(), javaInput.getText().length()); - // If the formatted ranged ended in the trailing trivia of the last token before EOF, - // format all the way up to EOF to deal with trailing whitespace correctly. - if (endTok.getIndex() == javaInput.getkN() - 1) { - replaceTo = javaInput.getText().length(); - } - // Replace trailing whitespace in the input with the whitespace from the formatted file. - // If the trailing whitespace in the input includes one or more line breaks, preserve - // the - // whitespace after the last newline to avoid re-indenting the line following the - // formatted - // line. - int newline = -1; - while (replaceTo < javaInput.getText().length()) { - char next = javaInput.getText().charAt(replaceTo); - if (!CharMatcher.whitespace().matches(next)) { - break; - } - int newlineLength = Newlines.hasNewlineAt(javaInput.getText(), replaceTo); - if (newlineLength != -1) { - newline = replaceTo; - // Skip over the entire newline; don't count the second character of \r\n as a - // newline. - replaceTo += newlineLength; - } else { - replaceTo++; - } - } - if (newline != -1) { - replaceTo = newline; - } - - if (newline == -1) { - // There wasn't an existing trailing newline; add one. - replacement.append(lineSeparator); - } - for (; i < getLineCount(); i++) { - String after = getLine(i); - int idx = CharMatcher.whitespace().negate().indexIn(after); - if (idx == -1) { - // Write out trailing empty lines from the formatted output. - replacement.append(lineSeparator); - } else { - if (newline == -1) { - // If there wasn't a trailing newline in the input, indent the next line. - replacement.append(after, 0, idx); - } - break; - } - } - - result.add(Replacement.create(replaceFrom, replaceTo, replacement.toString())); - } - return result.build(); + /** The earliest non-whitespace Tok in the Token. */ + public static Input.Tok startTok(Token token) { + for (Input.Tok tok : token.getToksBefore()) { + if (tok.getIndex() >= 0) { + return tok; + } } + return token.getTok(); + } - /** - * Expand a token range to start and end on acceptable boundaries for re-formatting. - * - * @param iRange the {@link Range} of tokens - * @return the expanded token range - */ - private Range expandToBreakableRegions(Range iRange) { - // The original line range. - int loTok = iRange.lowerEndpoint(); - int hiTok = iRange.upperEndpoint() - 1; - - // Expand the token indices to formattable boundaries (e.g. edges of statements). - if (!partialFormatRanges.contains(loTok) || !partialFormatRanges.contains(hiTok)) { - return EMPTY_RANGE; - } - loTok = partialFormatRanges.rangeContaining(loTok).lowerEndpoint(); - hiTok = partialFormatRanges.rangeContaining(hiTok).upperEndpoint(); - return Range.closedOpen(loTok, hiTok + 1); + /** The last non-whitespace Tok in the Token. */ + public static Input.Tok endTok(Token token) { + for (int i = token.getToksAfter().size() - 1; i >= 0; i--) { + Input.Tok tok = token.getToksAfter().get(i); + if (tok.getIndex() >= 0) { + return tok; + } } + return token.getTok(); + } - public static String applyReplacements(String input, List replacements) { - replacements = new ArrayList<>(replacements); - replacements.sort( - comparing((Replacement r) -> r.getReplaceRange().lowerEndpoint()).reversed()); - StringBuilder writer = new StringBuilder(input); - for (Replacement replacement : replacements) { - writer.replace( - replacement.getReplaceRange().lowerEndpoint(), - replacement.getReplaceRange().upperEndpoint(), - replacement.getReplacementString()); - } - return writer.toString(); - } + private boolean isComment(String text) { + return text.startsWith("//") || text.startsWith("/*"); + } - /** The earliest position of any Tok in the Token, including leading whitespace. */ - public static int startPosition(Token token) { - int min = token.getTok().getPosition(); - for (Input.Tok tok : token.getToksBefore()) { - min = min(min, tok.getPosition()); - } - return min; - } + private static Range union(Range x, Range y) { + return x.isEmpty() ? y : y.isEmpty() ? x : x.span(y).canonical(DiscreteDomain.integers()); + } - /** The earliest non-whitespace Tok in the Token. */ - public static Input.Tok startTok(Token token) { - for (Input.Tok tok : token.getToksBefore()) { - if (tok.getIndex() >= 0) { - return tok; - } - } - return token.getTok(); - } - - /** The last non-whitespace Tok in the Token. */ - public static Input.Tok endTok(Token token) { - for (int i = token.getToksAfter().size() - 1; i >= 0; i--) { - Input.Tok tok = token.getToksAfter().get(i); - if (tok.getIndex() >= 0) { - return tok; - } - } - return token.getTok(); - } - - private boolean isComment(String text) { - return text.startsWith("//") || text.startsWith("/*"); - } - - private static Range union(Range x, Range y) { - return x.isEmpty() ? y : y.isEmpty() ? x : x.span(y).canonical(DiscreteDomain.integers()); - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("iLine", iLine) - .add("lastK", lastK) - .add("spacesPending", spacesPending.toString().replace("\t", "\\t")) - .add("newlinesPending", newlinesPending) - .add("blankLines", blankLines) - .add("super", super.toString()) - .toString(); - } + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("iLine", iLine) + .add("lastK", lastK) + .add("spacesPending", spacesPending.toString().replace("\t", "\\t")) + .add("newlinesPending", newlinesPending) + .add("blankLines", blankLines) + .add("super", super.toString()) + .toString(); + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/JavacTokens.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/JavacTokens.java index d950964e2..d1dfa435c 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/JavacTokens.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/JavacTokens.java @@ -33,205 +33,201 @@ import java.util.Set; /** A wrapper around javac's lexer. */ class JavacTokens { - /** The lexer eats terminal comments, so feed it one we don't care about. */ - // TODO(b/33103797): fix javac and remove the work-around - private static final CharSequence EOF_COMMENT = "\n//EOF"; + /** The lexer eats terminal comments, so feed it one we don't care about. */ + // TODO(b/33103797): fix javac and remove the work-around + private static final CharSequence EOF_COMMENT = "\n//EOF"; - /** An unprocessed input token, including whitespace and comments. */ - static class RawTok { - private final String stringVal; - private final TokenKind kind; - private final int pos; - private final int endPos; + /** An unprocessed input token, including whitespace and comments. */ + static class RawTok { + private final String stringVal; + private final TokenKind kind; + private final int pos; + private final int endPos; - RawTok(String stringVal, TokenKind kind, int pos, int endPos) { - this.stringVal = stringVal; - this.kind = kind; - this.pos = pos; - this.endPos = endPos; - } - - /** The token kind, or {@code null} for whitespace and comments. */ - public TokenKind kind() { - return kind; - } - - /** The start position. */ - public int pos() { - return pos; - } - - /** The end position. */ - public int endPos() { - return endPos; - } - - /** The escaped string value of a literal, or {@code null} for other tokens. */ - public String stringVal() { - return stringVal; - } + RawTok(String stringVal, TokenKind kind, int pos, int endPos) { + this.stringVal = stringVal; + this.kind = kind; + this.pos = pos; + this.endPos = endPos; } - /** Lex the input and return a list of {@link RawTok}s. */ - public static ImmutableList getTokens( - String source, Context context, Set stopTokens) { - if (source == null) { - return ImmutableList.of(); - } - ScannerFactory fac = ScannerFactory.instance(context); - char[] buffer = (source + EOF_COMMENT).toCharArray(); - Scanner scanner = - new AccessibleScanner(fac, new CommentSavingTokenizer(fac, buffer, buffer.length)); - ImmutableList.Builder tokens = ImmutableList.builder(); - int end = source.length(); - int last = 0; - do { - scanner.nextToken(); - Token t = scanner.token(); - if (t.comments != null) { - for (Comment c : Lists.reverse(t.comments)) { - if (last < c.getSourcePos(0)) { - tokens.add(new RawTok(null, null, last, c.getSourcePos(0))); - } - tokens.add( - new RawTok( - null, - null, - c.getSourcePos(0), - c.getSourcePos(0) + c.getText().length())); - last = c.getSourcePos(0) + c.getText().length(); - } - } - if (stopTokens.contains(t.kind)) { - if (t.kind != TokenKind.EOF) { - end = t.pos; - } - break; - } - if (last < t.pos) { - tokens.add(new RawTok(null, null, last, t.pos)); - } - tokens.add( - new RawTok( - t.kind == TokenKind.STRINGLITERAL ? "\"" + t.stringVal() + "\"" : null, - t.kind, - t.pos, - t.endPos)); - last = t.endPos; - } while (scanner.token().kind != TokenKind.EOF); - if (last < end) { - tokens.add(new RawTok(null, null, last, end)); - } - return tokens.build(); + /** The token kind, or {@code null} for whitespace and comments. */ + public TokenKind kind() { + return kind; } - /** A {@link JavaTokenizer} that saves comments. */ - static class CommentSavingTokenizer extends JavaTokenizer { - CommentSavingTokenizer(ScannerFactory fac, char[] buffer, int length) { - super(fac, buffer, length); - } - - @Override - protected Comment processComment(int pos, int endPos, CommentStyle style) { - char[] buf = getRawCharactersReflectively(pos, endPos); - return new CommentWithTextAndPosition( - pos, endPos, new AccessibleReader(fac, buf, buf.length), style); - } - - private char[] getRawCharactersReflectively(int beginIndex, int endIndex) { - Object instance; - try { - instance = JavaTokenizer.class.getDeclaredField("reader").get(this); - } catch (ReflectiveOperationException e) { - instance = this; - } - try { - return (char[]) - instance.getClass() - .getMethod("getRawCharacters", int.class, int.class) - .invoke(instance, beginIndex, endIndex); - } catch (ReflectiveOperationException e) { - throw new LinkageError(e.getMessage(), e); - } - } + /** The start position. */ + public int pos() { + return pos; } - /** A {@link Comment} that saves its text and start position. */ - static class CommentWithTextAndPosition implements Comment { - - private final int pos; - private final int endPos; - private final AccessibleReader reader; - private final CommentStyle style; - - private String text = null; - - public CommentWithTextAndPosition( - int pos, int endPos, AccessibleReader reader, CommentStyle style) { - this.pos = pos; - this.endPos = endPos; - this.reader = reader; - this.style = style; - } - - /** - * Returns the source position of the character at index {@code index} in the comment text. - * - *

The handling of javadoc comments in javac has more logic to skip over leading - * whitespace and '*' characters when indexing into doc comments, but we don't need any of - * that. - */ - @Override - public int getSourcePos(int index) { - checkArgument( - 0 <= index && index < (endPos - pos), - "Expected %s in the range [0, %s)", - index, - endPos - pos); - return pos + index; - } - - @Override - public CommentStyle getStyle() { - return style; - } - - @Override - public String getText() { - String text = this.text; - if (text == null) { - this.text = text = new String(reader.getRawCharacters()); - } - return text; - } - - /** - * We don't care about {@code @deprecated} javadoc tags (see the DepAnn check). - * - * @return false - */ - @Override - public boolean isDeprecated() { - return false; - } - - @Override - public String toString() { - return String.format("Comment: '%s'", getText()); - } + /** The end position. */ + public int endPos() { + return endPos; } - // Scanner(ScannerFactory, JavaTokenizer) is package-private - static class AccessibleScanner extends Scanner { - protected AccessibleScanner(ScannerFactory fac, JavaTokenizer tokenizer) { - super(fac, tokenizer); + /** The escaped string value of a literal, or {@code null} for other tokens. */ + public String stringVal() { + return stringVal; + } + } + + /** Lex the input and return a list of {@link RawTok}s. */ + public static ImmutableList getTokens( + String source, Context context, Set stopTokens) { + if (source == null) { + return ImmutableList.of(); + } + ScannerFactory fac = ScannerFactory.instance(context); + char[] buffer = (source + EOF_COMMENT).toCharArray(); + Scanner scanner = + new AccessibleScanner(fac, new CommentSavingTokenizer(fac, buffer, buffer.length)); + ImmutableList.Builder tokens = ImmutableList.builder(); + int end = source.length(); + int last = 0; + do { + scanner.nextToken(); + Token t = scanner.token(); + if (t.comments != null) { + for (Comment c : Lists.reverse(t.comments)) { + if (last < c.getSourcePos(0)) { + tokens.add(new RawTok(null, null, last, c.getSourcePos(0))); + } + tokens.add( + new RawTok(null, null, c.getSourcePos(0), c.getSourcePos(0) + c.getText().length())); + last = c.getSourcePos(0) + c.getText().length(); } + } + if (stopTokens.contains(t.kind)) { + if (t.kind != TokenKind.EOF) { + end = t.pos; + } + break; + } + if (last < t.pos) { + tokens.add(new RawTok(null, null, last, t.pos)); + } + tokens.add( + new RawTok( + t.kind == TokenKind.STRINGLITERAL ? "\"" + t.stringVal() + "\"" : null, + t.kind, + t.pos, + t.endPos)); + last = t.endPos; + } while (scanner.token().kind != TokenKind.EOF); + if (last < end) { + tokens.add(new RawTok(null, null, last, end)); + } + return tokens.build(); + } + + /** A {@link JavaTokenizer} that saves comments. */ + static class CommentSavingTokenizer extends JavaTokenizer { + CommentSavingTokenizer(ScannerFactory fac, char[] buffer, int length) { + super(fac, buffer, length); } - // UnicodeReader(ScannerFactory, char[], int) is package-private - static class AccessibleReader extends UnicodeReader { - protected AccessibleReader(ScannerFactory fac, char[] buffer, int length) { - super(fac, buffer, length); - } + @Override + protected Comment processComment(int pos, int endPos, CommentStyle style) { + char[] buf = getRawCharactersReflectively(pos, endPos); + return new CommentWithTextAndPosition( + pos, endPos, new AccessibleReader(fac, buf, buf.length), style); } + + private char[] getRawCharactersReflectively(int beginIndex, int endIndex) { + Object instance; + try { + instance = JavaTokenizer.class.getDeclaredField("reader").get(this); + } catch (ReflectiveOperationException e) { + instance = this; + } + try { + return (char[]) + instance + .getClass() + .getMethod("getRawCharacters", int.class, int.class) + .invoke(instance, beginIndex, endIndex); + } catch (ReflectiveOperationException e) { + throw new LinkageError(e.getMessage(), e); + } + } + } + + /** A {@link Comment} that saves its text and start position. */ + static class CommentWithTextAndPosition implements Comment { + + private final int pos; + private final int endPos; + private final AccessibleReader reader; + private final CommentStyle style; + + private String text = null; + + public CommentWithTextAndPosition( + int pos, int endPos, AccessibleReader reader, CommentStyle style) { + this.pos = pos; + this.endPos = endPos; + this.reader = reader; + this.style = style; + } + + /** + * Returns the source position of the character at index {@code index} in the comment text. + * + *

The handling of javadoc comments in javac has more logic to skip over leading whitespace + * and '*' characters when indexing into doc comments, but we don't need any of that. + */ + @Override + public int getSourcePos(int index) { + checkArgument( + 0 <= index && index < (endPos - pos), + "Expected %s in the range [0, %s)", + index, + endPos - pos); + return pos + index; + } + + @Override + public CommentStyle getStyle() { + return style; + } + + @Override + public String getText() { + String text = this.text; + if (text == null) { + this.text = text = new String(reader.getRawCharacters()); + } + return text; + } + + /** + * We don't care about {@code @deprecated} javadoc tags (see the DepAnn check). + * + * @return false + */ + @Override + public boolean isDeprecated() { + return false; + } + + @Override + public String toString() { + return String.format("Comment: '%s'", getText()); + } + } + + // Scanner(ScannerFactory, JavaTokenizer) is package-private + static class AccessibleScanner extends Scanner { + protected AccessibleScanner(ScannerFactory fac, JavaTokenizer tokenizer) { + super(fac, tokenizer); + } + } + + // UnicodeReader(ScannerFactory, char[], int) is package-private + static class AccessibleReader extends UnicodeReader { + protected AccessibleReader(ScannerFactory fac, char[] buffer, int length) { + super(fac, buffer, length); + } + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/Main.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/Main.java index 3be7f28a3..f4ad8f1a3 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/Main.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/Main.java @@ -39,231 +39,227 @@ import java.util.concurrent.Future; /** The main class for the Java formatter CLI. */ public final class Main { - private static final int MAX_THREADS = 20; - private static final String STDIN_FILENAME = ""; + private static final int MAX_THREADS = 20; + private static final String STDIN_FILENAME = ""; - static String versionString() { - return "google-java-format: Version " + GoogleJavaFormatVersion.version(); + static String versionString() { + return "google-java-format: Version " + GoogleJavaFormatVersion.version(); + } + + private final PrintWriter outWriter; + private final PrintWriter errWriter; + private final InputStream inStream; + + public Main(PrintWriter outWriter, PrintWriter errWriter, InputStream inStream) { + this.outWriter = outWriter; + this.errWriter = errWriter; + this.inStream = inStream; + } + + /** + * The main method for the formatter, with some number of file names to format. We process them in + * parallel, but we must be careful; if multiple file names refer to the same file (which is hard + * to determine), we must serialize their updates. + * + * @param args the command-line arguments + */ + public static void main(String[] args) { + PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out, UTF_8)); + PrintWriter err = new PrintWriter(new OutputStreamWriter(System.err, UTF_8)); + int result = main(out, err, args); + System.exit(result); + } + + /** + * Package-private main entry point used this CLI program and the java.util.spi.ToolProvider + * implementation in the same package as this Main class. + */ + static int main(PrintWriter out, PrintWriter err, String... args) { + try { + Main formatter = new Main(out, err, System.in); + return formatter.format(args); + } catch (UsageException e) { + err.print(e.getMessage()); + return 0; + } finally { + err.flush(); + out.flush(); + } + } + + /** + * The main entry point for the formatter, with some number of file names to format. We process + * them in parallel, but we must be careful; if multiple file names refer to the same file (which + * is hard to determine), we must serialize their update. + * + * @param args the command-line arguments + */ + public int format(String... args) throws UsageException { + CommandLineOptions parameters = processArgs(args); + if (parameters.version()) { + errWriter.println(versionString()); + return 0; + } + if (parameters.help()) { + throw new UsageException(); } - private final PrintWriter outWriter; - private final PrintWriter errWriter; - private final InputStream inStream; + JavaFormatterOptions options = + JavaFormatterOptions.builder() + .style(parameters.aosp() ? Style.AOSP : Style.GOOGLE) + .formatJavadoc(parameters.formatJavadoc()) + .build(); - public Main(PrintWriter outWriter, PrintWriter errWriter, InputStream inStream) { - this.outWriter = outWriter; - this.errWriter = errWriter; - this.inStream = inStream; + if (parameters.stdin()) { + return formatStdin(parameters, options); + } else { + return formatFiles(parameters, options); + } + } + + private int formatFiles(CommandLineOptions parameters, JavaFormatterOptions options) { + int numThreads = min(MAX_THREADS, parameters.files().size()); + ExecutorService executorService = Executors.newFixedThreadPool(numThreads); + + Map inputs = new LinkedHashMap<>(); + Map> results = new LinkedHashMap<>(); + boolean allOk = true; + + for (String fileName : parameters.files()) { + if (!fileName.endsWith(".java")) { + errWriter.println("Skipping non-Java file: " + fileName); + continue; + } + Path path = Paths.get(fileName); + String input; + try { + input = new String(Files.readAllBytes(path), UTF_8); + inputs.put(path, input); + results.put( + path, executorService.submit(new FormatFileCallable(parameters, input, options))); + } catch (IOException e) { + errWriter.println(fileName + ": could not read file: " + e.getMessage()); + allOk = false; + } } - /** - * The main method for the formatter, with some number of file names to format. We process them - * in parallel, but we must be careful; if multiple file names refer to the same file (which is - * hard to determine), we must serialize their updates. - * - * @param args the command-line arguments - */ - public static void main(String[] args) { - PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out, UTF_8)); - PrintWriter err = new PrintWriter(new OutputStreamWriter(System.err, UTF_8)); - int result = main(out, err, args); - System.exit(result); - } - - /** - * Package-private main entry point used this CLI program and the java.util.spi.ToolProvider - * implementation in the same package as this Main class. - */ - static int main(PrintWriter out, PrintWriter err, String... args) { - try { - Main formatter = new Main(out, err, System.in); - return formatter.format(args); - } catch (UsageException e) { - err.print(e.getMessage()); - return 0; - } finally { - err.flush(); - out.flush(); - } - } - - /** - * The main entry point for the formatter, with some number of file names to format. We process - * them in parallel, but we must be careful; if multiple file names refer to the same file - * (which is hard to determine), we must serialize their update. - * - * @param args the command-line arguments - */ - public int format(String... args) throws UsageException { - CommandLineOptions parameters = processArgs(args); - if (parameters.version()) { - errWriter.println(versionString()); - return 0; - } - if (parameters.help()) { - throw new UsageException(); - } - - JavaFormatterOptions options = - JavaFormatterOptions.builder() - .style(parameters.aosp() ? Style.AOSP : Style.GOOGLE) - .formatJavadoc(parameters.formatJavadoc()) - .build(); - - if (parameters.stdin()) { - return formatStdin(parameters, options); + for (Map.Entry> result : results.entrySet()) { + Path path = result.getKey(); + String formatted; + try { + formatted = result.getValue().get(); + } catch (InterruptedException e) { + errWriter.println(e.getMessage()); + allOk = false; + continue; + } catch (ExecutionException e) { + if (e.getCause() instanceof FormatterException) { + for (FormatterDiagnostic diagnostic : ((FormatterException) e.getCause()).diagnostics()) { + errWriter.println(path + ":" + diagnostic); + } } else { - return formatFiles(parameters, options); + errWriter.println(path + ": error: " + e.getCause().getMessage()); + e.getCause().printStackTrace(errWriter); } - } - - private int formatFiles(CommandLineOptions parameters, JavaFormatterOptions options) { - int numThreads = min(MAX_THREADS, parameters.files().size()); - ExecutorService executorService = Executors.newFixedThreadPool(numThreads); - - Map inputs = new LinkedHashMap<>(); - Map> results = new LinkedHashMap<>(); - boolean allOk = true; - - for (String fileName : parameters.files()) { - if (!fileName.endsWith(".java")) { - errWriter.println("Skipping non-Java file: " + fileName); - continue; - } - Path path = Paths.get(fileName); - String input; - try { - input = new String(Files.readAllBytes(path), UTF_8); - inputs.put(path, input); - results.put( - path, - executorService.submit(new FormatFileCallable(parameters, input, options))); - } catch (IOException e) { - errWriter.println(fileName + ": could not read file: " + e.getMessage()); - allOk = false; - } + allOk = false; + continue; + } + boolean changed = !formatted.equals(inputs.get(path)); + if (changed && parameters.setExitIfChanged()) { + allOk = false; + } + if (parameters.inPlace()) { + if (!changed) { + continue; // preserve original file } - - for (Map.Entry> result : results.entrySet()) { - Path path = result.getKey(); - String formatted; - try { - formatted = result.getValue().get(); - } catch (InterruptedException e) { - errWriter.println(e.getMessage()); - allOk = false; - continue; - } catch (ExecutionException e) { - if (e.getCause() instanceof FormatterException) { - for (FormatterDiagnostic diagnostic : - ((FormatterException) e.getCause()).diagnostics()) { - errWriter.println(path + ":" + diagnostic); - } - } else { - errWriter.println(path + ": error: " + e.getCause().getMessage()); - e.getCause().printStackTrace(errWriter); - } - allOk = false; - continue; - } - boolean changed = !formatted.equals(inputs.get(path)); - if (changed && parameters.setExitIfChanged()) { - allOk = false; - } - if (parameters.inPlace()) { - if (!changed) { - continue; // preserve original file - } - try { - Files.write(path, formatted.getBytes(UTF_8)); - } catch (IOException e) { - errWriter.println(path + ": could not write file: " + e.getMessage()); - allOk = false; - continue; - } - } else if (parameters.dryRun()) { - if (changed) { - outWriter.println(path); - } - } else { - outWriter.write(formatted); - } - } - return allOk ? 0 : 1; - } - - private int formatStdin(CommandLineOptions parameters, JavaFormatterOptions options) { - String input; try { - input = new String(ByteStreams.toByteArray(inStream), UTF_8); + Files.write(path, formatted.getBytes(UTF_8)); } catch (IOException e) { - throw new IOError(e); + errWriter.println(path + ": could not write file: " + e.getMessage()); + allOk = false; + continue; } - String stdinFilename = parameters.assumeFilename().orElse(STDIN_FILENAME); - boolean ok = true; - try { - String output = new FormatFileCallable(parameters, input, options).call(); - boolean changed = !input.equals(output); - if (changed && parameters.setExitIfChanged()) { - ok = false; - } - if (parameters.dryRun()) { - if (changed) { - outWriter.println(stdinFilename); - } - } else { - outWriter.write(output); - } - } catch (FormatterException e) { - for (FormatterDiagnostic diagnostic : e.diagnostics()) { - errWriter.println(stdinFilename + ":" + diagnostic); - } - ok = false; - // TODO(cpovirk): Catch other types of exception (as we do in the formatFiles case). + } else if (parameters.dryRun()) { + if (changed) { + outWriter.println(path); } - return ok ? 0 : 1; + } else { + outWriter.write(formatted); + } + } + return allOk ? 0 : 1; + } + + private int formatStdin(CommandLineOptions parameters, JavaFormatterOptions options) { + String input; + try { + input = new String(ByteStreams.toByteArray(inStream), UTF_8); + } catch (IOException e) { + throw new IOError(e); + } + String stdinFilename = parameters.assumeFilename().orElse(STDIN_FILENAME); + boolean ok = true; + try { + String output = new FormatFileCallable(parameters, input, options).call(); + boolean changed = !input.equals(output); + if (changed && parameters.setExitIfChanged()) { + ok = false; + } + if (parameters.dryRun()) { + if (changed) { + outWriter.println(stdinFilename); + } + } else { + outWriter.write(output); + } + } catch (FormatterException e) { + for (FormatterDiagnostic diagnostic : e.diagnostics()) { + errWriter.println(stdinFilename + ":" + diagnostic); + } + ok = false; + // TODO(cpovirk): Catch other types of exception (as we do in the formatFiles case). + } + return ok ? 0 : 1; + } + + /** Parses and validates command-line flags. */ + public static CommandLineOptions processArgs(String... args) throws UsageException { + CommandLineOptions parameters; + try { + parameters = CommandLineOptionsParser.parse(Arrays.asList(args)); + } catch (IllegalArgumentException e) { + throw new UsageException(e.getMessage()); + } catch (Throwable t) { + t.printStackTrace(); + throw new UsageException(t.getMessage()); + } + int filesToFormat = parameters.files().size(); + if (parameters.stdin()) { + filesToFormat++; } - /** Parses and validates command-line flags. */ - public static CommandLineOptions processArgs(String... args) throws UsageException { - CommandLineOptions parameters; - try { - parameters = CommandLineOptionsParser.parse(Arrays.asList(args)); - } catch (IllegalArgumentException e) { - throw new UsageException(e.getMessage()); - } catch (Throwable t) { - t.printStackTrace(); - throw new UsageException(t.getMessage()); - } - int filesToFormat = parameters.files().size(); - if (parameters.stdin()) { - filesToFormat++; - } - - if (parameters.inPlace() && parameters.files().isEmpty()) { - throw new UsageException( - "in-place formatting was requested but no files were provided"); - } - if (parameters.isSelection() && filesToFormat != 1) { - throw new UsageException("partial formatting is only support for a single file"); - } - if (parameters.offsets().size() != parameters.lengths().size()) { - throw new UsageException( - "-offsets and -lengths flags must be provided in matching pairs"); - } - if (filesToFormat <= 0 && !parameters.version() && !parameters.help()) { - throw new UsageException("no files were provided"); - } - if (parameters.stdin() && !parameters.files().isEmpty()) { - throw new UsageException("cannot format from standard input and files simultaneously"); - } - if (parameters.assumeFilename().isPresent() && !parameters.stdin()) { - throw new UsageException( - "--assume-filename is only supported when formatting standard input"); - } - if (parameters.dryRun() && parameters.inPlace()) { - throw new UsageException("cannot use --dry-run and --in-place at the same time"); - } - return parameters; + if (parameters.inPlace() && parameters.files().isEmpty()) { + throw new UsageException("in-place formatting was requested but no files were provided"); } + if (parameters.isSelection() && filesToFormat != 1) { + throw new UsageException("partial formatting is only support for a single file"); + } + if (parameters.offsets().size() != parameters.lengths().size()) { + throw new UsageException("-offsets and -lengths flags must be provided in matching pairs"); + } + if (filesToFormat <= 0 && !parameters.version() && !parameters.help()) { + throw new UsageException("no files were provided"); + } + if (parameters.stdin() && !parameters.files().isEmpty()) { + throw new UsageException("cannot format from standard input and files simultaneously"); + } + if (parameters.assumeFilename().isPresent() && !parameters.stdin()) { + throw new UsageException( + "--assume-filename is only supported when formatting standard input"); + } + if (parameters.dryRun() && parameters.inPlace()) { + throw new UsageException("cannot use --dry-run and --in-place at the same time"); + } + return parameters; + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/ModifierOrderer.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/ModifierOrderer.java index a9f4c8390..206ef5f60 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/ModifierOrderer.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/ModifierOrderer.java @@ -38,141 +38,139 @@ import javax.lang.model.element.Modifier; /** Fixes sequences of modifiers to be in JLS order. */ final class ModifierOrderer { - /** Reorders all modifiers in the given text to be in JLS order. */ - static JavaInput reorderModifiers(String text) throws FormatterException { - return reorderModifiers( - new JavaInput(text), ImmutableList.of(Range.closedOpen(0, text.length()))); + /** Reorders all modifiers in the given text to be in JLS order. */ + static JavaInput reorderModifiers(String text) throws FormatterException { + return reorderModifiers( + new JavaInput(text), ImmutableList.of(Range.closedOpen(0, text.length()))); + } + + /** + * Reorders all modifiers in the given text and within the given character ranges to be in JLS + * order. + */ + static JavaInput reorderModifiers(JavaInput javaInput, Collection> characterRanges) + throws FormatterException { + if (javaInput.getTokens().isEmpty()) { + // There weren't any tokens, possible because of a lexing error. + // Errors about invalid input will be reported later after parsing. + return javaInput; } + RangeSet tokenRanges = javaInput.characterRangesToTokenRanges(characterRanges); + Iterator it = javaInput.getTokens().iterator(); + TreeRangeMap replacements = TreeRangeMap.create(); + while (it.hasNext()) { + Token token = it.next(); + if (!tokenRanges.contains(token.getTok().getIndex())) { + continue; + } + Modifier mod = asModifier(token); + if (mod == null) { + continue; + } - /** - * Reorders all modifiers in the given text and within the given character ranges to be in JLS - * order. - */ - static JavaInput reorderModifiers( - JavaInput javaInput, Collection> characterRanges) - throws FormatterException { - if (javaInput.getTokens().isEmpty()) { - // There weren't any tokens, possible because of a lexing error. - // Errors about invalid input will be reported later after parsing. - return javaInput; + List modifierTokens = new ArrayList<>(); + List mods = new ArrayList<>(); + + int begin = token.getTok().getPosition(); + mods.add(mod); + modifierTokens.add(token); + + int end = -1; + while (it.hasNext()) { + token = it.next(); + mod = asModifier(token); + if (mod == null) { + break; } - RangeSet tokenRanges = javaInput.characterRangesToTokenRanges(characterRanges); - Iterator it = javaInput.getTokens().iterator(); - TreeRangeMap replacements = TreeRangeMap.create(); - while (it.hasNext()) { - Token token = it.next(); - if (!tokenRanges.contains(token.getTok().getIndex())) { - continue; - } - Modifier mod = asModifier(token); - if (mod == null) { - continue; - } + mods.add(mod); + modifierTokens.add(token); + end = token.getTok().getPosition() + token.getTok().length(); + } - List modifierTokens = new ArrayList<>(); - List mods = new ArrayList<>(); - - int begin = token.getTok().getPosition(); - mods.add(mod); - modifierTokens.add(token); - - int end = -1; - while (it.hasNext()) { - token = it.next(); - mod = asModifier(token); - if (mod == null) { - break; - } - mods.add(mod); - modifierTokens.add(token); - end = token.getTok().getPosition() + token.getTok().length(); - } - - if (!Ordering.natural().isOrdered(mods)) { - Collections.sort(mods); - StringBuilder replacement = new StringBuilder(); - for (int i = 0; i < mods.size(); i++) { - if (i > 0) { - addTrivia(replacement, modifierTokens.get(i).getToksBefore()); - } - replacement.append(mods.get(i)); - if (i < (modifierTokens.size() - 1)) { - addTrivia(replacement, modifierTokens.get(i).getToksAfter()); - } - } - replacements.put(Range.closedOpen(begin, end), replacement.toString()); - } + if (!Ordering.natural().isOrdered(mods)) { + Collections.sort(mods); + StringBuilder replacement = new StringBuilder(); + for (int i = 0; i < mods.size(); i++) { + if (i > 0) { + addTrivia(replacement, modifierTokens.get(i).getToksBefore()); + } + replacement.append(mods.get(i)); + if (i < (modifierTokens.size() - 1)) { + addTrivia(replacement, modifierTokens.get(i).getToksAfter()); + } } - return applyReplacements(javaInput, replacements); + replacements.put(Range.closedOpen(begin, end), replacement.toString()); + } } + return applyReplacements(javaInput, replacements); + } - private static void addTrivia(StringBuilder replacement, ImmutableList toks) { - for (Tok tok : toks) { - replacement.append(tok.getText()); - } + private static void addTrivia(StringBuilder replacement, ImmutableList toks) { + for (Tok tok : toks) { + replacement.append(tok.getText()); } + } - /** - * Returns the given token as a {@link javax.lang.model.element.Modifier}, or {@code null} if it - * is not a modifier. - */ - private static Modifier asModifier(Token token) { - TokenKind kind = ((JavaInput.Tok) token.getTok()).kind(); - if (kind != null) { - switch (kind) { - case PUBLIC: - return Modifier.PUBLIC; - case PROTECTED: - return Modifier.PROTECTED; - case PRIVATE: - return Modifier.PRIVATE; - case ABSTRACT: - return Modifier.ABSTRACT; - case STATIC: - return Modifier.STATIC; - case DEFAULT: - return Modifier.DEFAULT; - case FINAL: - return Modifier.FINAL; - case TRANSIENT: - return Modifier.TRANSIENT; - case VOLATILE: - return Modifier.VOLATILE; - case SYNCHRONIZED: - return Modifier.SYNCHRONIZED; - case NATIVE: - return Modifier.NATIVE; - case STRICTFP: - return Modifier.STRICTFP; - default: // fall out - } - } - switch (token.getTok().getText()) { - case "non-sealed": - return Modifier.valueOf("NON_SEALED"); - case "sealed": - return Modifier.valueOf("SEALED"); - default: - return null; - } + /** + * Returns the given token as a {@link javax.lang.model.element.Modifier}, or {@code null} if it + * is not a modifier. + */ + private static Modifier asModifier(Token token) { + TokenKind kind = ((JavaInput.Tok) token.getTok()).kind(); + if (kind != null) { + switch (kind) { + case PUBLIC: + return Modifier.PUBLIC; + case PROTECTED: + return Modifier.PROTECTED; + case PRIVATE: + return Modifier.PRIVATE; + case ABSTRACT: + return Modifier.ABSTRACT; + case STATIC: + return Modifier.STATIC; + case DEFAULT: + return Modifier.DEFAULT; + case FINAL: + return Modifier.FINAL; + case TRANSIENT: + return Modifier.TRANSIENT; + case VOLATILE: + return Modifier.VOLATILE; + case SYNCHRONIZED: + return Modifier.SYNCHRONIZED; + case NATIVE: + return Modifier.NATIVE; + case STRICTFP: + return Modifier.STRICTFP; + default: // fall out + } } + switch (token.getTok().getText()) { + case "non-sealed": + return Modifier.valueOf("NON_SEALED"); + case "sealed": + return Modifier.valueOf("SEALED"); + default: + return null; + } + } - /** Applies replacements to the given string. */ - private static JavaInput applyReplacements( - JavaInput javaInput, TreeRangeMap replacementMap) - throws FormatterException { - // process in descending order so the replacement ranges aren't perturbed if any - // replacements - // differ in size from the input - Map, String> ranges = replacementMap.asDescendingMapOfRanges(); - if (ranges.isEmpty()) { - return javaInput; - } - StringBuilder sb = new StringBuilder(javaInput.getText()); - for (Entry, String> entry : ranges.entrySet()) { - Range range = entry.getKey(); - sb.replace(range.lowerEndpoint(), range.upperEndpoint(), entry.getValue()); - } - return new JavaInput(sb.toString()); + /** Applies replacements to the given string. */ + private static JavaInput applyReplacements( + JavaInput javaInput, TreeRangeMap replacementMap) throws FormatterException { + // process in descending order so the replacement ranges aren't perturbed if any + // replacements + // differ in size from the input + Map, String> ranges = replacementMap.asDescendingMapOfRanges(); + if (ranges.isEmpty()) { + return javaInput; } + StringBuilder sb = new StringBuilder(javaInput.getText()); + for (Entry, String> entry : ranges.entrySet()) { + Range range = entry.getKey(); + sb.replace(range.lowerEndpoint(), range.upperEndpoint(), entry.getValue()); + } + return new JavaInput(sb.toString()); + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/RemoveUnusedImports.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/RemoveUnusedImports.java index 719e8bbd2..f7ad435a7 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/RemoveUnusedImports.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/RemoveUnusedImports.java @@ -77,288 +77,279 @@ import javax.tools.StandardLocation; */ public class RemoveUnusedImports { - // Visits an AST, recording all simple names that could refer to imported - // types and also any javadoc references that could refer to imported - // types (`@link`, `@see`, `@throws`, etc.) - // - // No attempt is made to determine whether simple names occur in contexts - // where they are type names, so there will be false positives. For example, - // `List` is not identified as unused import below: - // - // ``` - // import java.util.List; - // class List {} - // ``` - // - // This is still reasonably effective in practice because type names differ - // from other kinds of names in casing convention, and simple name - // clashes between imported and declared types are rare. - private static class UnusedImportScanner extends TreePathScanner { + // Visits an AST, recording all simple names that could refer to imported + // types and also any javadoc references that could refer to imported + // types (`@link`, `@see`, `@throws`, etc.) + // + // No attempt is made to determine whether simple names occur in contexts + // where they are type names, so there will be false positives. For example, + // `List` is not identified as unused import below: + // + // ``` + // import java.util.List; + // class List {} + // ``` + // + // This is still reasonably effective in practice because type names differ + // from other kinds of names in casing convention, and simple name + // clashes between imported and declared types are rare. + private static class UnusedImportScanner extends TreePathScanner { - private final Set usedNames = new LinkedHashSet<>(); - private final Multimap> usedInJavadoc = HashMultimap.create(); - final JavacTrees trees; - final DocTreeScanner docTreeSymbolScanner; + private final Set usedNames = new LinkedHashSet<>(); + private final Multimap> usedInJavadoc = HashMultimap.create(); + final JavacTrees trees; + final DocTreeScanner docTreeSymbolScanner; - private UnusedImportScanner(JavacTrees trees) { - this.trees = trees; - docTreeSymbolScanner = new DocTreeScanner(); - } - - /** Skip the imports themselves when checking for usage. */ - @Override - public Void visitImport(ImportTree importTree, Void usedSymbols) { - return null; - } - - @Override - public Void visitIdentifier(IdentifierTree tree, Void unused) { - if (tree == null) { - return null; - } - usedNames.add(tree.getName().toString()); - return null; - } - - // TODO(cushon): remove this override when pattern matching in switch is no longer a preview - // feature, and TreePathScanner visits CaseTree#getLabels instead of CaseTree#getExpressions - @SuppressWarnings("unchecked") // reflection - @Override - public Void visitCase(CaseTree tree, Void unused) { - if (CASE_TREE_GET_LABELS != null) { - try { - scan((List) CASE_TREE_GET_LABELS.invoke(tree), null); - } catch (ReflectiveOperationException e) { - throw new LinkageError(e.getMessage(), e); - } - } - return super.visitCase(tree, null); - } - - private static final Method CASE_TREE_GET_LABELS = caseTreeGetLabels(); - - private static Method caseTreeGetLabels() { - try { - return CaseTree.class.getMethod("getLabels"); - } catch (NoSuchMethodException e) { - return null; - } - } - - @Override - public Void scan(Tree tree, Void unused) { - if (tree == null) { - return null; - } - scanJavadoc(); - return super.scan(tree, unused); - } - - private void scanJavadoc() { - if (getCurrentPath() == null) { - return; - } - DocCommentTree commentTree = trees.getDocCommentTree(getCurrentPath()); - if (commentTree == null) { - return; - } - docTreeSymbolScanner.scan(new DocTreePath(getCurrentPath(), commentTree), null); - } - - // scan javadoc comments, checking for references to imported types - class DocTreeScanner extends DocTreePathScanner { - @Override - public Void visitIdentifier(com.sun.source.doctree.IdentifierTree node, Void aVoid) { - return null; - } - - @Override - public Void visitReference(ReferenceTree referenceTree, Void unused) { - DCReference reference = (DCReference) referenceTree; - long basePos = - reference - .pos((DCTree.DCDocComment) getCurrentPath().getDocComment()) - .getStartPosition(); - // the position of trees inside the reference node aren't stored, but the - // qualifier's - // start position is the beginning of the reference node - if (reference.qualifierExpression != null) { - new ReferenceScanner(basePos).scan(reference.qualifierExpression, null); - } - // Record uses inside method parameters. The javadoc tool doesn't use these, but - // IntelliJ does. - if (reference.paramTypes != null) { - for (JCTree param : reference.paramTypes) { - // TODO(cushon): get start positions for the parameters - new ReferenceScanner(-1).scan(param, null); - } - } - return null; - } - - // scans the qualifier and parameters of a javadoc reference for possible type names - private class ReferenceScanner extends TreeScanner { - private final long basePos; - - public ReferenceScanner(long basePos) { - this.basePos = basePos; - } - - @Override - public Void visitIdentifier(IdentifierTree node, Void aVoid) { - usedInJavadoc.put( - node.getName().toString(), - basePos != -1 - ? Range.closedOpen( - (int) basePos, (int) basePos + node.getName().length()) - : null); - return super.visitIdentifier(node, aVoid); - } - } - } + private UnusedImportScanner(JavacTrees trees) { + this.trees = trees; + docTreeSymbolScanner = new DocTreeScanner(); } - public static String removeUnusedImports(final String contents) throws FormatterException { - Context context = new Context(); - JCCompilationUnit unit = parse(context, contents); - if (unit == null) { - // error handling is done during formatting - return contents; - } - UnusedImportScanner scanner = new UnusedImportScanner(JavacTrees.instance(context)); - scanner.scan(unit, null); - return applyReplacements( - contents, - buildReplacements(contents, unit, scanner.usedNames, scanner.usedInJavadoc)); + /** Skip the imports themselves when checking for usage. */ + @Override + public Void visitImport(ImportTree importTree, Void usedSymbols) { + return null; } - private static JCCompilationUnit parse(Context context, String javaInput) - throws FormatterException { - DiagnosticCollector diagnostics = new DiagnosticCollector<>(); - context.put(DiagnosticListener.class, diagnostics); - Options.instance(context).put("--enable-preview", "true"); - Options.instance(context).put("allowStringFolding", "false"); - JCCompilationUnit unit; - JavacFileManager fileManager = new JavacFileManager(context, true, UTF_8); + @Override + public Void visitIdentifier(IdentifierTree tree, Void unused) { + if (tree == null) { + return null; + } + usedNames.add(tree.getName().toString()); + return null; + } + + // TODO(cushon): remove this override when pattern matching in switch is no longer a preview + // feature, and TreePathScanner visits CaseTree#getLabels instead of CaseTree#getExpressions + @SuppressWarnings("unchecked") // reflection + @Override + public Void visitCase(CaseTree tree, Void unused) { + if (CASE_TREE_GET_LABELS != null) { try { - fileManager.setLocation(StandardLocation.PLATFORM_CLASS_PATH, ImmutableList.of()); - } catch (IOException e) { - // impossible - throw new IOError(e); + scan((List) CASE_TREE_GET_LABELS.invoke(tree), null); + } catch (ReflectiveOperationException e) { + throw new LinkageError(e.getMessage(), e); } - SimpleJavaFileObject source = - new SimpleJavaFileObject(URI.create("source"), JavaFileObject.Kind.SOURCE) { - @Override - public CharSequence getCharContent(boolean ignoreEncodingErrors) - throws IOException { - return javaInput; - } - }; - Log.instance(context).useSource(source); - ParserFactory parserFactory = ParserFactory.instance(context); - JavacParser parser = - parserFactory.newParser( - javaInput, - /*keepDocComments=*/ true, - /*keepEndPos=*/ true, - /*keepLineMap=*/ true); - unit = parser.parseCompilationUnit(); - unit.sourcefile = source; - Iterable> errorDiagnostics = - Iterables.filter(diagnostics.getDiagnostics(), Formatter::errorDiagnostic); - if (!Iterables.isEmpty(errorDiagnostics)) { - // error handling is done during formatting - throw FormatterException.fromJavacDiagnostics(errorDiagnostics); - } - return unit; + } + return super.visitCase(tree, null); } - /** Construct replacements to fix unused imports. */ - private static RangeMap buildReplacements( - String contents, - JCCompilationUnit unit, - Set usedNames, - Multimap> usedInJavadoc) { - RangeMap replacements = TreeRangeMap.create(); - for (JCImport importTree : unit.getImports()) { - String simpleName = getSimpleName(importTree); - if (!isUnused(unit, usedNames, usedInJavadoc, importTree, simpleName)) { - continue; - } - // delete the import - int endPosition = importTree.getEndPosition(unit.endPositions); - endPosition = max(CharMatcher.isNot(' ').indexIn(contents, endPosition), endPosition); - String sep = Newlines.guessLineSeparator(contents); - if (endPosition + sep.length() < contents.length() - && contents.subSequence(endPosition, endPosition + sep.length()) - .toString() - .equals(sep)) { - endPosition += sep.length(); - } - replacements.put(Range.closedOpen(importTree.getStartPosition(), endPosition), ""); - } - return replacements; + private static final Method CASE_TREE_GET_LABELS = caseTreeGetLabels(); + + private static Method caseTreeGetLabels() { + try { + return CaseTree.class.getMethod("getLabels"); + } catch (NoSuchMethodException e) { + return null; + } } - private static String getSimpleName(JCImport importTree) { - return importTree.getQualifiedIdentifier() instanceof JCIdent - ? ((JCIdent) importTree.getQualifiedIdentifier()).getName().toString() - : ((JCFieldAccess) importTree.getQualifiedIdentifier()).getIdentifier().toString(); + @Override + public Void scan(Tree tree, Void unused) { + if (tree == null) { + return null; + } + scanJavadoc(); + return super.scan(tree, unused); } - private static boolean isUnused( - JCCompilationUnit unit, - Set usedNames, - Multimap> usedInJavadoc, - JCImport importTree, - String simpleName) { - String qualifier = - ((JCFieldAccess) importTree.getQualifiedIdentifier()).getExpression().toString(); - if (qualifier.equals("java.lang")) { - return true; - } - if (unit.getPackageName() != null && unit.getPackageName().toString().equals(qualifier)) { - return true; - } - if (importTree.getQualifiedIdentifier() instanceof JCFieldAccess - && ((JCFieldAccess) importTree.getQualifiedIdentifier()) - .getIdentifier() - .contentEquals("*")) { - return false; - } - - if (usedNames.contains(simpleName)) { - return false; - } - if (usedInJavadoc.containsKey(simpleName)) { - return false; - } - return true; + private void scanJavadoc() { + if (getCurrentPath() == null) { + return; + } + DocCommentTree commentTree = trees.getDocCommentTree(getCurrentPath()); + if (commentTree == null) { + return; + } + docTreeSymbolScanner.scan(new DocTreePath(getCurrentPath(), commentTree), null); } - /** Applies the replacements to the given source, and re-format any edited javadoc. */ - private static String applyReplacements(String source, RangeMap replacements) { - // save non-empty fixed ranges for reformatting after fixes are applied - RangeSet fixedRanges = TreeRangeSet.create(); + // scan javadoc comments, checking for references to imported types + class DocTreeScanner extends DocTreePathScanner { + @Override + public Void visitIdentifier(com.sun.source.doctree.IdentifierTree node, Void aVoid) { + return null; + } - // Apply the fixes in increasing order, adjusting ranges to account for - // earlier fixes that change the length of the source. The output ranges are - // needed so we can reformat fixed regions, otherwise the fixes could just - // be applied in descending order without adjusting offsets. - StringBuilder sb = new StringBuilder(source); - int offset = 0; - for (Map.Entry, String> replacement : - replacements.asMapOfRanges().entrySet()) { - Range range = replacement.getKey(); - String replaceWith = replacement.getValue(); - int start = offset + range.lowerEndpoint(); - int end = offset + range.upperEndpoint(); - sb.replace(start, end, replaceWith); - if (!replaceWith.isEmpty()) { - fixedRanges.add(Range.closedOpen(start, end)); - } - offset += replaceWith.length() - (range.upperEndpoint() - range.lowerEndpoint()); + @Override + public Void visitReference(ReferenceTree referenceTree, Void unused) { + DCReference reference = (DCReference) referenceTree; + long basePos = + reference + .pos((DCTree.DCDocComment) getCurrentPath().getDocComment()) + .getStartPosition(); + // the position of trees inside the reference node aren't stored, but the + // qualifier's + // start position is the beginning of the reference node + if (reference.qualifierExpression != null) { + new ReferenceScanner(basePos).scan(reference.qualifierExpression, null); } - return sb.toString(); + // Record uses inside method parameters. The javadoc tool doesn't use these, but + // IntelliJ does. + if (reference.paramTypes != null) { + for (JCTree param : reference.paramTypes) { + // TODO(cushon): get start positions for the parameters + new ReferenceScanner(-1).scan(param, null); + } + } + return null; + } + + // scans the qualifier and parameters of a javadoc reference for possible type names + private class ReferenceScanner extends TreeScanner { + private final long basePos; + + public ReferenceScanner(long basePos) { + this.basePos = basePos; + } + + @Override + public Void visitIdentifier(IdentifierTree node, Void aVoid) { + usedInJavadoc.put( + node.getName().toString(), + basePos != -1 + ? Range.closedOpen((int) basePos, (int) basePos + node.getName().length()) + : null); + return super.visitIdentifier(node, aVoid); + } + } } + } + + public static String removeUnusedImports(final String contents) throws FormatterException { + Context context = new Context(); + JCCompilationUnit unit = parse(context, contents); + if (unit == null) { + // error handling is done during formatting + return contents; + } + UnusedImportScanner scanner = new UnusedImportScanner(JavacTrees.instance(context)); + scanner.scan(unit, null); + return applyReplacements( + contents, buildReplacements(contents, unit, scanner.usedNames, scanner.usedInJavadoc)); + } + + private static JCCompilationUnit parse(Context context, String javaInput) + throws FormatterException { + DiagnosticCollector diagnostics = new DiagnosticCollector<>(); + context.put(DiagnosticListener.class, diagnostics); + Options.instance(context).put("--enable-preview", "true"); + Options.instance(context).put("allowStringFolding", "false"); + JCCompilationUnit unit; + JavacFileManager fileManager = new JavacFileManager(context, true, UTF_8); + try { + fileManager.setLocation(StandardLocation.PLATFORM_CLASS_PATH, ImmutableList.of()); + } catch (IOException e) { + // impossible + throw new IOError(e); + } + SimpleJavaFileObject source = + new SimpleJavaFileObject(URI.create("source"), JavaFileObject.Kind.SOURCE) { + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { + return javaInput; + } + }; + Log.instance(context).useSource(source); + ParserFactory parserFactory = ParserFactory.instance(context); + JavacParser parser = + parserFactory.newParser( + javaInput, /*keepDocComments=*/ true, /*keepEndPos=*/ true, /*keepLineMap=*/ true); + unit = parser.parseCompilationUnit(); + unit.sourcefile = source; + Iterable> errorDiagnostics = + Iterables.filter(diagnostics.getDiagnostics(), Formatter::errorDiagnostic); + if (!Iterables.isEmpty(errorDiagnostics)) { + // error handling is done during formatting + throw FormatterException.fromJavacDiagnostics(errorDiagnostics); + } + return unit; + } + + /** Construct replacements to fix unused imports. */ + private static RangeMap buildReplacements( + String contents, + JCCompilationUnit unit, + Set usedNames, + Multimap> usedInJavadoc) { + RangeMap replacements = TreeRangeMap.create(); + for (JCImport importTree : unit.getImports()) { + String simpleName = getSimpleName(importTree); + if (!isUnused(unit, usedNames, usedInJavadoc, importTree, simpleName)) { + continue; + } + // delete the import + int endPosition = importTree.getEndPosition(unit.endPositions); + endPosition = max(CharMatcher.isNot(' ').indexIn(contents, endPosition), endPosition); + String sep = Newlines.guessLineSeparator(contents); + if (endPosition + sep.length() < contents.length() + && contents.subSequence(endPosition, endPosition + sep.length()).toString().equals(sep)) { + endPosition += sep.length(); + } + replacements.put(Range.closedOpen(importTree.getStartPosition(), endPosition), ""); + } + return replacements; + } + + private static String getSimpleName(JCImport importTree) { + return importTree.getQualifiedIdentifier() instanceof JCIdent + ? ((JCIdent) importTree.getQualifiedIdentifier()).getName().toString() + : ((JCFieldAccess) importTree.getQualifiedIdentifier()).getIdentifier().toString(); + } + + private static boolean isUnused( + JCCompilationUnit unit, + Set usedNames, + Multimap> usedInJavadoc, + JCImport importTree, + String simpleName) { + String qualifier = + ((JCFieldAccess) importTree.getQualifiedIdentifier()).getExpression().toString(); + if (qualifier.equals("java.lang")) { + return true; + } + if (unit.getPackageName() != null && unit.getPackageName().toString().equals(qualifier)) { + return true; + } + if (importTree.getQualifiedIdentifier() instanceof JCFieldAccess + && ((JCFieldAccess) importTree.getQualifiedIdentifier()) + .getIdentifier() + .contentEquals("*")) { + return false; + } + + if (usedNames.contains(simpleName)) { + return false; + } + if (usedInJavadoc.containsKey(simpleName)) { + return false; + } + return true; + } + + /** Applies the replacements to the given source, and re-format any edited javadoc. */ + private static String applyReplacements(String source, RangeMap replacements) { + // save non-empty fixed ranges for reformatting after fixes are applied + RangeSet fixedRanges = TreeRangeSet.create(); + + // Apply the fixes in increasing order, adjusting ranges to account for + // earlier fixes that change the length of the source. The output ranges are + // needed so we can reformat fixed regions, otherwise the fixes could just + // be applied in descending order without adjusting offsets. + StringBuilder sb = new StringBuilder(source); + int offset = 0; + for (Map.Entry, String> replacement : replacements.asMapOfRanges().entrySet()) { + Range range = replacement.getKey(); + String replaceWith = replacement.getValue(); + int start = offset + range.lowerEndpoint(); + int end = offset + range.upperEndpoint(); + sb.replace(start, end, replaceWith); + if (!replaceWith.isEmpty()) { + fixedRanges.add(Range.closedOpen(start, end)); + } + offset += replaceWith.length() - (range.upperEndpoint() - range.lowerEndpoint()); + } + return sb.toString(); + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/Replacement.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/Replacement.java index 022706452..8a7af99e9 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/Replacement.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/Replacement.java @@ -29,45 +29,45 @@ import java.util.Objects; */ public final class Replacement { - public static Replacement create(int startPosition, int endPosition, String replaceWith) { - checkArgument(startPosition >= 0, "startPosition must be non-negative"); - checkArgument(startPosition <= endPosition, "startPosition cannot be after endPosition"); - return new Replacement(Range.closedOpen(startPosition, endPosition), replaceWith); - } + public static Replacement create(int startPosition, int endPosition, String replaceWith) { + checkArgument(startPosition >= 0, "startPosition must be non-negative"); + checkArgument(startPosition <= endPosition, "startPosition cannot be after endPosition"); + return new Replacement(Range.closedOpen(startPosition, endPosition), replaceWith); + } - private final Range replaceRange; - private final String replacementString; + private final Range replaceRange; + private final String replacementString; - private Replacement(Range replaceRange, String replacementString) { - this.replaceRange = checkNotNull(replaceRange, "Null replaceRange"); - this.replacementString = checkNotNull(replacementString, "Null replacementString"); - } + private Replacement(Range replaceRange, String replacementString) { + this.replaceRange = checkNotNull(replaceRange, "Null replaceRange"); + this.replacementString = checkNotNull(replacementString, "Null replacementString"); + } - /** The range of characters in the original source to replace. */ - public Range getReplaceRange() { - return replaceRange; - } + /** The range of characters in the original source to replace. */ + public Range getReplaceRange() { + return replaceRange; + } - /** The string to replace the range of characters with. */ - public String getReplacementString() { - return replacementString; - } + /** The string to replace the range of characters with. */ + public String getReplacementString() { + return replacementString; + } - @Override - public boolean equals(Object o) { - if (o == this) { - return true; - } - if (o instanceof Replacement) { - Replacement that = (Replacement) o; - return replaceRange.equals(that.getReplaceRange()) - && replacementString.equals(that.getReplacementString()); - } - return false; + @Override + public boolean equals(Object o) { + if (o == this) { + return true; } + if (o instanceof Replacement) { + Replacement that = (Replacement) o; + return replaceRange.equals(that.getReplaceRange()) + && replacementString.equals(that.getReplacementString()); + } + return false; + } - @Override - public int hashCode() { - return Objects.hash(replaceRange, replacementString); - } + @Override + public int hashCode() { + return Objects.hash(replaceRange, replacementString); + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/SnippetFormatter.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/SnippetFormatter.java index 1557d9cb1..2c452bd05 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/SnippetFormatter.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/SnippetFormatter.java @@ -30,188 +30,187 @@ import java.util.List; /** Formats a subset of a compilation unit. */ public class SnippetFormatter { - /** The kind of snippet to format. */ - public enum SnippetKind { - COMPILATION_UNIT, - CLASS_BODY_DECLARATIONS, - STATEMENTS, - EXPRESSION + /** The kind of snippet to format. */ + public enum SnippetKind { + COMPILATION_UNIT, + CLASS_BODY_DECLARATIONS, + STATEMENTS, + EXPRESSION + } + + private class SnippetWrapper { + int offset; + final StringBuilder contents = new StringBuilder(); + + public SnippetWrapper append(String str) { + contents.append(str); + return this; } - private class SnippetWrapper { - int offset; - final StringBuilder contents = new StringBuilder(); - - public SnippetWrapper append(String str) { - contents.append(str); - return this; - } - - public SnippetWrapper appendSource(String source) { - this.offset = contents.length(); - contents.append(source); - return this; - } - - public void closeBraces(int initialIndent) { - for (int i = initialIndent; --i >= 0; ) { - contents.append("\n").append(createIndentationString(i)).append("}"); - } - } + public SnippetWrapper appendSource(String source) { + this.offset = contents.length(); + contents.append(source); + return this; } - private static final int INDENTATION_SIZE = 2; - private final Formatter formatter = new Formatter(); - private static final CharMatcher NOT_WHITESPACE = CharMatcher.whitespace().negate(); - - public String createIndentationString(int indentationLevel) { - Preconditions.checkArgument( - indentationLevel >= 0, - "Indentation level cannot be less than zero. Given: %s", - indentationLevel); - int spaces = indentationLevel * INDENTATION_SIZE; - StringBuilder buf = new StringBuilder(spaces); - for (int i = 0; i < spaces; i++) { - buf.append(' '); - } - return buf.toString(); + public void closeBraces(int initialIndent) { + for (int i = initialIndent; --i >= 0; ) { + contents.append("\n").append(createIndentationString(i)).append("}"); + } } + } - private static Range offsetRange(Range range, int offset) { - range = range.canonical(DiscreteDomain.integers()); - return Range.closedOpen(range.lowerEndpoint() + offset, range.upperEndpoint() + offset); + private static final int INDENTATION_SIZE = 2; + private final Formatter formatter = new Formatter(); + private static final CharMatcher NOT_WHITESPACE = CharMatcher.whitespace().negate(); + + public String createIndentationString(int indentationLevel) { + Preconditions.checkArgument( + indentationLevel >= 0, + "Indentation level cannot be less than zero. Given: %s", + indentationLevel); + int spaces = indentationLevel * INDENTATION_SIZE; + StringBuilder buf = new StringBuilder(spaces); + for (int i = 0; i < spaces; i++) { + buf.append(' '); } + return buf.toString(); + } - private static List> offsetRanges(List> ranges, int offset) { - List> result = new ArrayList<>(); - for (Range range : ranges) { - result.add(offsetRange(range, offset)); - } - return result; + private static Range offsetRange(Range range, int offset) { + range = range.canonical(DiscreteDomain.integers()); + return Range.closedOpen(range.lowerEndpoint() + offset, range.upperEndpoint() + offset); + } + + private static List> offsetRanges(List> ranges, int offset) { + List> result = new ArrayList<>(); + for (Range range : ranges) { + result.add(offsetRange(range, offset)); } + return result; + } - /** Runs the Google Java formatter on the given source, with only the given ranges specified. */ - public ImmutableList format( - SnippetKind kind, - String source, - List> ranges, - int initialIndent, - boolean includeComments) - throws FormatterException { - RangeSet rangeSet = TreeRangeSet.create(); - for (Range range : ranges) { - rangeSet.add(range); - } - if (includeComments) { - if (kind != SnippetKind.COMPILATION_UNIT) { - throw new IllegalArgumentException( - "comment formatting is only supported for compilation units"); - } - return formatter.getFormatReplacements(source, ranges); - } - SnippetWrapper wrapper = snippetWrapper(kind, source, initialIndent); - ranges = offsetRanges(ranges, wrapper.offset); - - String replacement = formatter.formatSource(wrapper.contents.toString(), ranges); - replacement = - replacement.substring( - wrapper.offset, - replacement.length() - - (wrapper.contents.length() - wrapper.offset - source.length())); - - return toReplacements(source, replacement).stream() - .filter(r -> rangeSet.encloses(r.getReplaceRange())) - .collect(toImmutableList()); + /** Runs the Google Java formatter on the given source, with only the given ranges specified. */ + public ImmutableList format( + SnippetKind kind, + String source, + List> ranges, + int initialIndent, + boolean includeComments) + throws FormatterException { + RangeSet rangeSet = TreeRangeSet.create(); + for (Range range : ranges) { + rangeSet.add(range); } + if (includeComments) { + if (kind != SnippetKind.COMPILATION_UNIT) { + throw new IllegalArgumentException( + "comment formatting is only supported for compilation units"); + } + return formatter.getFormatReplacements(source, ranges); + } + SnippetWrapper wrapper = snippetWrapper(kind, source, initialIndent); + ranges = offsetRanges(ranges, wrapper.offset); - /** - * Generates {@code Replacement}s rewriting {@code source} to {@code replacement}, under the - * assumption that they differ in whitespace alone. + String replacement = formatter.formatSource(wrapper.contents.toString(), ranges); + replacement = + replacement.substring( + wrapper.offset, + replacement.length() - (wrapper.contents.length() - wrapper.offset - source.length())); + + return toReplacements(source, replacement).stream() + .filter(r -> rangeSet.encloses(r.getReplaceRange())) + .collect(toImmutableList()); + } + + /** + * Generates {@code Replacement}s rewriting {@code source} to {@code replacement}, under the + * assumption that they differ in whitespace alone. + */ + private static List toReplacements(String source, String replacement) { + if (!NOT_WHITESPACE.retainFrom(source).equals(NOT_WHITESPACE.retainFrom(replacement))) { + throw new IllegalArgumentException( + "source = \"" + source + "\", replacement = \"" + replacement + "\""); + } + /* + * In the past we seemed to have problems touching non-whitespace text in the formatter, even + * just replacing some code with itself. Retrospective attempts to reproduce this have failed, + * but this may be an issue for future changes. */ - private static List toReplacements(String source, String replacement) { - if (!NOT_WHITESPACE.retainFrom(source).equals(NOT_WHITESPACE.retainFrom(replacement))) { - throw new IllegalArgumentException( - "source = \"" + source + "\", replacement = \"" + replacement + "\""); - } - /* - * In the past we seemed to have problems touching non-whitespace text in the formatter, even - * just replacing some code with itself. Retrospective attempts to reproduce this have failed, - * but this may be an issue for future changes. - */ - List replacements = new ArrayList<>(); - int i = NOT_WHITESPACE.indexIn(source); - int j = NOT_WHITESPACE.indexIn(replacement); - if (i != 0 || j != 0) { - replacements.add(Replacement.create(0, i, replacement.substring(0, j))); - } - while (i != -1 && j != -1) { - int i2 = NOT_WHITESPACE.indexIn(source, i + 1); - int j2 = NOT_WHITESPACE.indexIn(replacement, j + 1); - if (i2 == -1 || j2 == -1) { - break; - } - if ((i2 - i) != (j2 - j) - || !source.substring(i + 1, i2).equals(replacement.substring(j + 1, j2))) { - replacements.add(Replacement.create(i + 1, i2, replacement.substring(j + 1, j2))); - } - i = i2; - j = j2; - } - return replacements; + List replacements = new ArrayList<>(); + int i = NOT_WHITESPACE.indexIn(source); + int j = NOT_WHITESPACE.indexIn(replacement); + if (i != 0 || j != 0) { + replacements.add(Replacement.create(0, i, replacement.substring(0, j))); } + while (i != -1 && j != -1) { + int i2 = NOT_WHITESPACE.indexIn(source, i + 1); + int j2 = NOT_WHITESPACE.indexIn(replacement, j + 1); + if (i2 == -1 || j2 == -1) { + break; + } + if ((i2 - i) != (j2 - j) + || !source.substring(i + 1, i2).equals(replacement.substring(j + 1, j2))) { + replacements.add(Replacement.create(i + 1, i2, replacement.substring(j + 1, j2))); + } + i = i2; + j = j2; + } + return replacements; + } - private SnippetWrapper snippetWrapper(SnippetKind kind, String source, int initialIndent) { - /* - * Synthesize a dummy class around the code snippet provided by Eclipse. The dummy class is - * correctly formatted -- the blocks use correct indentation, etc. - */ - switch (kind) { - case COMPILATION_UNIT: - { - SnippetWrapper wrapper = new SnippetWrapper(); - for (int i = 1; i <= initialIndent; i++) { - wrapper.append("class Dummy {\n").append(createIndentationString(i)); - } - wrapper.appendSource(source); - wrapper.closeBraces(initialIndent); - return wrapper; - } - case CLASS_BODY_DECLARATIONS: - { - SnippetWrapper wrapper = new SnippetWrapper(); - for (int i = 1; i <= initialIndent; i++) { - wrapper.append("class Dummy {\n").append(createIndentationString(i)); - } - wrapper.appendSource(source); - wrapper.closeBraces(initialIndent); - return wrapper; - } - case STATEMENTS: - { - SnippetWrapper wrapper = new SnippetWrapper(); - wrapper.append("class Dummy {\n").append(createIndentationString(1)); - for (int i = 2; i <= initialIndent; i++) { - wrapper.append("{\n").append(createIndentationString(i)); - } - wrapper.appendSource(source); - wrapper.closeBraces(initialIndent); - return wrapper; - } - case EXPRESSION: - { - SnippetWrapper wrapper = new SnippetWrapper(); - wrapper.append("class Dummy {\n").append(createIndentationString(1)); - for (int i = 2; i <= initialIndent; i++) { - wrapper.append("{\n").append(createIndentationString(i)); - } - wrapper.append("Object o = "); - wrapper.appendSource(source); - wrapper.append(";"); - wrapper.closeBraces(initialIndent); - return wrapper; - } - default: - throw new IllegalArgumentException("Unknown snippet kind: " + kind); + private SnippetWrapper snippetWrapper(SnippetKind kind, String source, int initialIndent) { + /* + * Synthesize a dummy class around the code snippet provided by Eclipse. The dummy class is + * correctly formatted -- the blocks use correct indentation, etc. + */ + switch (kind) { + case COMPILATION_UNIT: + { + SnippetWrapper wrapper = new SnippetWrapper(); + for (int i = 1; i <= initialIndent; i++) { + wrapper.append("class Dummy {\n").append(createIndentationString(i)); + } + wrapper.appendSource(source); + wrapper.closeBraces(initialIndent); + return wrapper; } + case CLASS_BODY_DECLARATIONS: + { + SnippetWrapper wrapper = new SnippetWrapper(); + for (int i = 1; i <= initialIndent; i++) { + wrapper.append("class Dummy {\n").append(createIndentationString(i)); + } + wrapper.appendSource(source); + wrapper.closeBraces(initialIndent); + return wrapper; + } + case STATEMENTS: + { + SnippetWrapper wrapper = new SnippetWrapper(); + wrapper.append("class Dummy {\n").append(createIndentationString(1)); + for (int i = 2; i <= initialIndent; i++) { + wrapper.append("{\n").append(createIndentationString(i)); + } + wrapper.appendSource(source); + wrapper.closeBraces(initialIndent); + return wrapper; + } + case EXPRESSION: + { + SnippetWrapper wrapper = new SnippetWrapper(); + wrapper.append("class Dummy {\n").append(createIndentationString(1)); + for (int i = 2; i <= initialIndent; i++) { + wrapper.append("{\n").append(createIndentationString(i)); + } + wrapper.append("Object o = "); + wrapper.appendSource(source); + wrapper.append(";"); + wrapper.closeBraces(initialIndent); + return wrapper; + } + default: + throw new IllegalArgumentException("Unknown snippet kind: " + kind); } + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/StringWrapper.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/StringWrapper.java index 77f8959c5..233d17e98 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/StringWrapper.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/StringWrapper.java @@ -63,385 +63,374 @@ import javax.tools.StandardLocation; /** Wraps string literals that exceed the column limit. */ public final class StringWrapper { - /** Reflows long string literals in the given Java source code. */ - public static String wrap(String input, Formatter formatter) throws FormatterException { - return StringWrapper.wrap(Formatter.MAX_LINE_LENGTH, input, formatter); + /** Reflows long string literals in the given Java source code. */ + public static String wrap(String input, Formatter formatter) throws FormatterException { + return StringWrapper.wrap(Formatter.MAX_LINE_LENGTH, input, formatter); + } + + /** + * Reflows string literals in the given Java source code that extend past the given column limit. + */ + static String wrap(final int columnLimit, String input, Formatter formatter) + throws FormatterException { + if (!longLines(columnLimit, input)) { + // fast path + return input; } - /** - * Reflows string literals in the given Java source code that extend past the given column - * limit. - */ - static String wrap(final int columnLimit, String input, Formatter formatter) - throws FormatterException { - if (!longLines(columnLimit, input)) { - // fast path - return input; - } + TreeRangeMap replacements = getReflowReplacements(columnLimit, input); + String firstPass = formatter.formatSource(input, replacements.asMapOfRanges().keySet()); - TreeRangeMap replacements = getReflowReplacements(columnLimit, input); - String firstPass = formatter.formatSource(input, replacements.asMapOfRanges().keySet()); - - if (!firstPass.equals(input)) { - // If formatting the replacement ranges resulted in a change, recalculate the - // replacements on - // the updated input. - input = firstPass; - replacements = getReflowReplacements(columnLimit, input); - } - - String result = applyReplacements(input, replacements); - - { - // We really don't want bugs in this pass to change the behaviour of programs we're - // formatting, so check that the pretty-printed AST is the same before and after - // reformatting. - String expected = parse(input, /* allowStringFolding= */ true).toString(); - String actual = parse(result, /* allowStringFolding= */ true).toString(); - if (!expected.equals(actual)) { - throw new FormatterException( - String.format( - "Something has gone terribly wrong. Please file a bug: " - + "https://github.com/google/google-java-format/issues/new" - + "\n\n=== Actual: ===\n%s\n=== Expected: ===\n%s\n", - actual, expected)); - } - } - - return result; + if (!firstPass.equals(input)) { + // If formatting the replacement ranges resulted in a change, recalculate the + // replacements on + // the updated input. + input = firstPass; + replacements = getReflowReplacements(columnLimit, input); } - private static TreeRangeMap getReflowReplacements( - int columnLimit, final String input) throws FormatterException { - JCTree.JCCompilationUnit unit = parse(input, /* allowStringFolding= */ false); - String separator = Newlines.guessLineSeparator(input); + String result = applyReplacements(input, replacements); - // Paths to string literals that extend past the column limit. - List toFix = new ArrayList<>(); - final Position.LineMap lineMap = unit.getLineMap(); - new TreePathScanner() { - @Override - public Void visitLiteral(LiteralTree literalTree, Void aVoid) { - if (literalTree.getKind() != Kind.STRING_LITERAL) { - return null; - } - Tree parent = getCurrentPath().getParentPath().getLeaf(); - if (parent instanceof MemberSelectTree - && ((MemberSelectTree) parent).getExpression().equals(literalTree)) { - return null; - } - int endPosition = getEndPosition(unit, literalTree); - int lineEnd = endPosition; - while (Newlines.hasNewlineAt(input, lineEnd) == -1) { - lineEnd++; - } - if (lineMap.getColumnNumber(lineEnd) - 1 <= columnLimit) { - return null; - } - toFix.add(getCurrentPath()); - return null; - } - }.scan(new TreePath(unit), null); - - TreeRangeMap replacements = TreeRangeMap.create(); - for (TreePath path : toFix) { - // Find the outermost contiguous enclosing concatenation expression - TreePath enclosing = path; - while (enclosing.getParentPath().getLeaf().getKind() == Tree.Kind.PLUS) { - enclosing = enclosing.getParentPath(); - } - // Is the literal being wrapped the first in a chain of concatenation expressions? - // i.e. `ONE + TWO + THREE` - // We need this information to handle continuation indents. - AtomicBoolean first = new AtomicBoolean(false); - // Finds the set of string literals in the concat expression that includes the one that - // needs - // to be wrapped. - List flat = flatten(input, unit, path, enclosing, first); - // Zero-indexed start column - int startColumn = lineMap.getColumnNumber(getStartPosition(flat.get(0))) - 1; - - // Handling leaving trailing non-string tokens at the end of the literal, - // e.g. the trailing `);` in `foo("...");`. - int end = getEndPosition(unit, getLast(flat)); - int lineEnd = end; - while (Newlines.hasNewlineAt(input, lineEnd) == -1) { - lineEnd++; - } - int trailing = lineEnd - end; - - // Get the original source text of the string literals, excluding `"` and `+`. - ImmutableList components = stringComponents(input, unit, flat); - replacements.put( - Range.closedOpen( - getStartPosition(flat.get(0)), getEndPosition(unit, getLast(flat))), - reflow(separator, columnLimit, startColumn, trailing, components, first.get())); - } - return replacements; + { + // We really don't want bugs in this pass to change the behaviour of programs we're + // formatting, so check that the pretty-printed AST is the same before and after + // reformatting. + String expected = parse(input, /* allowStringFolding= */ true).toString(); + String actual = parse(result, /* allowStringFolding= */ true).toString(); + if (!expected.equals(actual)) { + throw new FormatterException( + String.format( + "Something has gone terribly wrong. Please file a bug: " + + "https://github.com/google/google-java-format/issues/new" + + "\n\n=== Actual: ===\n%s\n=== Expected: ===\n%s\n", + actual, expected)); + } } - /** - * Returns the source text of the given string literal trees, excluding the leading and trailing - * double-quotes and the `+` operator. - */ - private static ImmutableList stringComponents( - String input, JCTree.JCCompilationUnit unit, List flat) { - ImmutableList.Builder result = ImmutableList.builder(); - StringBuilder piece = new StringBuilder(); - for (Tree tree : flat) { - // adjust for leading and trailing double quotes - String text = - input.substring(getStartPosition(tree) + 1, getEndPosition(unit, tree) - 1); - int start = 0; - for (int idx = 0; idx < text.length(); idx++) { - if (CharMatcher.whitespace().matches(text.charAt(idx))) { - // continue below - } else if (hasEscapedWhitespaceAt(text, idx) != -1) { - // continue below - } else if (hasEscapedNewlineAt(text, idx) != -1) { - int length; - while ((length = hasEscapedNewlineAt(text, idx)) != -1) { - idx += length; - } - } else { - continue; - } - piece.append(text, start, idx); - result.add(piece.toString()); - piece = new StringBuilder(); - start = idx; - } - if (piece.length() > 0) { - result.add(piece.toString()); - piece = new StringBuilder(); - } - if (start < text.length()) { - piece.append(text, start, text.length()); - } + return result; + } + + private static TreeRangeMap getReflowReplacements( + int columnLimit, final String input) throws FormatterException { + JCTree.JCCompilationUnit unit = parse(input, /* allowStringFolding= */ false); + String separator = Newlines.guessLineSeparator(input); + + // Paths to string literals that extend past the column limit. + List toFix = new ArrayList<>(); + final Position.LineMap lineMap = unit.getLineMap(); + new TreePathScanner() { + @Override + public Void visitLiteral(LiteralTree literalTree, Void aVoid) { + if (literalTree.getKind() != Kind.STRING_LITERAL) { + return null; } - if (piece.length() > 0) { - result.add(piece.toString()); + Tree parent = getCurrentPath().getParentPath().getLeaf(); + if (parent instanceof MemberSelectTree + && ((MemberSelectTree) parent).getExpression().equals(literalTree)) { + return null; } - return result.build(); + int endPosition = getEndPosition(unit, literalTree); + int lineEnd = endPosition; + while (Newlines.hasNewlineAt(input, lineEnd) == -1) { + lineEnd++; + } + if (lineMap.getColumnNumber(lineEnd) - 1 <= columnLimit) { + return null; + } + toFix.add(getCurrentPath()); + return null; + } + }.scan(new TreePath(unit), null); + + TreeRangeMap replacements = TreeRangeMap.create(); + for (TreePath path : toFix) { + // Find the outermost contiguous enclosing concatenation expression + TreePath enclosing = path; + while (enclosing.getParentPath().getLeaf().getKind() == Tree.Kind.PLUS) { + enclosing = enclosing.getParentPath(); + } + // Is the literal being wrapped the first in a chain of concatenation expressions? + // i.e. `ONE + TWO + THREE` + // We need this information to handle continuation indents. + AtomicBoolean first = new AtomicBoolean(false); + // Finds the set of string literals in the concat expression that includes the one that + // needs + // to be wrapped. + List flat = flatten(input, unit, path, enclosing, first); + // Zero-indexed start column + int startColumn = lineMap.getColumnNumber(getStartPosition(flat.get(0))) - 1; + + // Handling leaving trailing non-string tokens at the end of the literal, + // e.g. the trailing `);` in `foo("...");`. + int end = getEndPosition(unit, getLast(flat)); + int lineEnd = end; + while (Newlines.hasNewlineAt(input, lineEnd) == -1) { + lineEnd++; + } + int trailing = lineEnd - end; + + // Get the original source text of the string literals, excluding `"` and `+`. + ImmutableList components = stringComponents(input, unit, flat); + replacements.put( + Range.closedOpen(getStartPosition(flat.get(0)), getEndPosition(unit, getLast(flat))), + reflow(separator, columnLimit, startColumn, trailing, components, first.get())); + } + return replacements; + } + + /** + * Returns the source text of the given string literal trees, excluding the leading and trailing + * double-quotes and the `+` operator. + */ + private static ImmutableList stringComponents( + String input, JCTree.JCCompilationUnit unit, List flat) { + ImmutableList.Builder result = ImmutableList.builder(); + StringBuilder piece = new StringBuilder(); + for (Tree tree : flat) { + // adjust for leading and trailing double quotes + String text = input.substring(getStartPosition(tree) + 1, getEndPosition(unit, tree) - 1); + int start = 0; + for (int idx = 0; idx < text.length(); idx++) { + if (CharMatcher.whitespace().matches(text.charAt(idx))) { + // continue below + } else if (hasEscapedWhitespaceAt(text, idx) != -1) { + // continue below + } else if (hasEscapedNewlineAt(text, idx) != -1) { + int length; + while ((length = hasEscapedNewlineAt(text, idx)) != -1) { + idx += length; + } + } else { + continue; + } + piece.append(text, start, idx); + result.add(piece.toString()); + piece = new StringBuilder(); + start = idx; + } + if (piece.length() > 0) { + result.add(piece.toString()); + piece = new StringBuilder(); + } + if (start < text.length()) { + piece.append(text, start, text.length()); + } + } + if (piece.length() > 0) { + result.add(piece.toString()); + } + return result.build(); + } + + static int hasEscapedWhitespaceAt(String input, int idx) { + return Stream.of("\\t") + .mapToInt(x -> input.startsWith(x, idx) ? x.length() : -1) + .filter(x -> x != -1) + .findFirst() + .orElse(-1); + } + + static int hasEscapedNewlineAt(String input, int idx) { + return Stream.of("\\r\\n", "\\r", "\\n") + .mapToInt(x -> input.startsWith(x, idx) ? x.length() : -1) + .filter(x -> x != -1) + .findFirst() + .orElse(-1); + } + + /** + * Reflows the given source text, trying to split on word boundaries. + * + * @param separator the line separator + * @param columnLimit the number of columns to wrap at + * @param startColumn the column position of the beginning of the original text + * @param trailing extra space to leave after the last line, to accommodate a ; or ) + * @param components the text to reflow. This is a list of "words" of a single literal. Its first + * and last quotes have been stripped + * @param first0 true if the text includes the beginning of its enclosing concat chain + */ + private static String reflow( + String separator, + int columnLimit, + int startColumn, + int trailing, + ImmutableList components, + boolean first0) { + // We have space between the start column and the limit to output the first line. + // Reserve two spaces for the start and end quotes. + int width = columnLimit - startColumn - 2; + Deque input = new ArrayDeque<>(components); + List lines = new ArrayList<>(); + boolean first = first0; + while (!input.isEmpty()) { + int length = 0; + List line = new ArrayList<>(); + // If we know this is going to be the last line, then remove a bit of width to account + // for the + // trailing characters. + if (input.stream().mapToInt(String::length).sum() <= width) { + // This isn’t quite optimal, but arguably good enough. See b/179561701 + width -= trailing; + } + while (!input.isEmpty() && (length <= 4 || (length + input.peekFirst().length()) <= width)) { + String text = input.removeFirst(); + line.add(text); + length += text.length(); + if (text.endsWith("\\n") || text.endsWith("\\r")) { + break; + } + } + if (line.isEmpty()) { + line.add(input.removeFirst()); + } + // add the split line to the output, and process whatever's left + lines.add(String.join("", line)); + if (first) { + width -= 6; // subsequent lines have a four-space continuation indent and a `+ ` + first = false; + } } - static int hasEscapedWhitespaceAt(String input, int idx) { - return Stream.of("\\t") - .mapToInt(x -> input.startsWith(x, idx) ? x.length() : -1) - .filter(x -> x != -1) - .findFirst() - .orElse(-1); + return lines.stream() + .collect( + joining( + "\"" + separator + Strings.repeat(" ", startColumn + (first0 ? 4 : -2)) + "+ \"", + "\"", + "\"")); + } + + /** + * Flattens the given binary expression tree, and extracts the subset that contains the given path + * and any adjacent nodes that are also string literals. + */ + private static List flatten( + String input, + JCTree.JCCompilationUnit unit, + TreePath path, + TreePath parent, + AtomicBoolean firstInChain) { + List flat = new ArrayList<>(); + + // flatten the expression tree with a pre-order traversal + ArrayDeque todo = new ArrayDeque<>(); + todo.add(parent.getLeaf()); + while (!todo.isEmpty()) { + Tree first = todo.removeFirst(); + if (first.getKind() == Tree.Kind.PLUS) { + BinaryTree bt = (BinaryTree) first; + todo.addFirst(bt.getRightOperand()); + todo.addFirst(bt.getLeftOperand()); + } else { + flat.add(first); + } } - static int hasEscapedNewlineAt(String input, int idx) { - return Stream.of("\\r\\n", "\\r", "\\n") - .mapToInt(x -> input.startsWith(x, idx) ? x.length() : -1) - .filter(x -> x != -1) - .findFirst() - .orElse(-1); + int idx = flat.indexOf(path.getLeaf()); + Verify.verify(idx != -1); + + // walk outwards from the leaf for adjacent string literals to also reflow + int startIdx = idx; + int endIdx = idx + 1; + while (startIdx > 0 + && flat.get(startIdx - 1).getKind() == Tree.Kind.STRING_LITERAL + && noComments(input, unit, flat.get(startIdx - 1), flat.get(startIdx))) { + startIdx--; + } + while (endIdx < flat.size() + && flat.get(endIdx).getKind() == Tree.Kind.STRING_LITERAL + && noComments(input, unit, flat.get(endIdx - 1), flat.get(endIdx))) { + endIdx++; } - /** - * Reflows the given source text, trying to split on word boundaries. - * - * @param separator the line separator - * @param columnLimit the number of columns to wrap at - * @param startColumn the column position of the beginning of the original text - * @param trailing extra space to leave after the last line, to accommodate a ; or ) - * @param components the text to reflow. This is a list of "words" of a single literal. Its - * first and last quotes have been stripped - * @param first0 true if the text includes the beginning of its enclosing concat chain - */ - private static String reflow( - String separator, - int columnLimit, - int startColumn, - int trailing, - ImmutableList components, - boolean first0) { - // We have space between the start column and the limit to output the first line. - // Reserve two spaces for the start and end quotes. - int width = columnLimit - startColumn - 2; - Deque input = new ArrayDeque<>(components); - List lines = new ArrayList<>(); - boolean first = first0; - while (!input.isEmpty()) { - int length = 0; - List line = new ArrayList<>(); - // If we know this is going to be the last line, then remove a bit of width to account - // for the - // trailing characters. - if (input.stream().mapToInt(String::length).sum() <= width) { - // This isn’t quite optimal, but arguably good enough. See b/179561701 - width -= trailing; - } - while (!input.isEmpty() - && (length <= 4 || (length + input.peekFirst().length()) <= width)) { - String text = input.removeFirst(); - line.add(text); - length += text.length(); - if (text.endsWith("\\n") || text.endsWith("\\r")) { - break; - } - } - if (line.isEmpty()) { - line.add(input.removeFirst()); - } - // add the split line to the output, and process whatever's left - lines.add(String.join("", line)); - if (first) { - width -= 6; // subsequent lines have a four-space continuation indent and a `+ ` - first = false; - } - } + firstInChain.set(startIdx == 0); + return ImmutableList.copyOf(flat.subList(startIdx, endIdx)); + } - return lines.stream() - .collect( - joining( - "\"" - + separator - + Strings.repeat(" ", startColumn + (first0 ? 4 : -2)) - + "+ \"", - "\"", - "\"")); + private static boolean noComments( + String input, JCTree.JCCompilationUnit unit, Tree one, Tree two) { + return STRING_CONCAT_DELIMITER.matchesAllOf( + input.subSequence(getEndPosition(unit, one), getStartPosition(two))); + } + + public static final CharMatcher STRING_CONCAT_DELIMITER = + CharMatcher.whitespace().or(CharMatcher.anyOf("\"+")); + + private static int getEndPosition(JCTree.JCCompilationUnit unit, Tree tree) { + return ((JCTree) tree).getEndPosition(unit.endPositions); + } + + private static int getStartPosition(Tree tree) { + return ((JCTree) tree).getStartPosition(); + } + + /** Returns true if any lines in the given Java source exceed the column limit. */ + private static boolean longLines(int columnLimit, String input) { + // TODO(cushon): consider adding Newlines.lineIterable? + Iterator it = Newlines.lineIterator(input); + while (it.hasNext()) { + String line = it.next(); + if (line.length() > columnLimit) { + return true; + } } + return false; + } - /** - * Flattens the given binary expression tree, and extracts the subset that contains the given - * path and any adjacent nodes that are also string literals. - */ - private static List flatten( - String input, - JCTree.JCCompilationUnit unit, - TreePath path, - TreePath parent, - AtomicBoolean firstInChain) { - List flat = new ArrayList<>(); - - // flatten the expression tree with a pre-order traversal - ArrayDeque todo = new ArrayDeque<>(); - todo.add(parent.getLeaf()); - while (!todo.isEmpty()) { - Tree first = todo.removeFirst(); - if (first.getKind() == Tree.Kind.PLUS) { - BinaryTree bt = (BinaryTree) first; - todo.addFirst(bt.getRightOperand()); - todo.addFirst(bt.getLeftOperand()); - } else { - flat.add(first); - } - } - - int idx = flat.indexOf(path.getLeaf()); - Verify.verify(idx != -1); - - // walk outwards from the leaf for adjacent string literals to also reflow - int startIdx = idx; - int endIdx = idx + 1; - while (startIdx > 0 - && flat.get(startIdx - 1).getKind() == Tree.Kind.STRING_LITERAL - && noComments(input, unit, flat.get(startIdx - 1), flat.get(startIdx))) { - startIdx--; - } - while (endIdx < flat.size() - && flat.get(endIdx).getKind() == Tree.Kind.STRING_LITERAL - && noComments(input, unit, flat.get(endIdx - 1), flat.get(endIdx))) { - endIdx++; - } - - firstInChain.set(startIdx == 0); - return ImmutableList.copyOf(flat.subList(startIdx, endIdx)); + /** Parses the given Java source. */ + private static JCTree.JCCompilationUnit parse(String source, boolean allowStringFolding) + throws FormatterException { + DiagnosticCollector diagnostics = new DiagnosticCollector<>(); + Context context = new Context(); + context.put(DiagnosticListener.class, diagnostics); + Options.instance(context).put("--enable-preview", "true"); + Options.instance(context).put("allowStringFolding", Boolean.toString(allowStringFolding)); + JCTree.JCCompilationUnit unit; + JavacFileManager fileManager = new JavacFileManager(context, true, UTF_8); + try { + fileManager.setLocation(StandardLocation.PLATFORM_CLASS_PATH, ImmutableList.of()); + } catch (IOException e) { + throw new UncheckedIOException(e); } - - private static boolean noComments( - String input, JCTree.JCCompilationUnit unit, Tree one, Tree two) { - return STRING_CONCAT_DELIMITER.matchesAllOf( - input.subSequence(getEndPosition(unit, one), getStartPosition(two))); + SimpleJavaFileObject sjfo = + new SimpleJavaFileObject(URI.create("source"), JavaFileObject.Kind.SOURCE) { + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) { + return source; + } + }; + Log.instance(context).useSource(sjfo); + ParserFactory parserFactory = ParserFactory.instance(context); + JavacParser parser = + parserFactory.newParser( + source, /*keepDocComments=*/ true, /*keepEndPos=*/ true, /*keepLineMap=*/ true); + unit = parser.parseCompilationUnit(); + unit.sourcefile = sjfo; + Iterable> errorDiagnostics = + Iterables.filter(diagnostics.getDiagnostics(), Formatter::errorDiagnostic); + if (!Iterables.isEmpty(errorDiagnostics)) { + // error handling is done during formatting + throw FormatterException.fromJavacDiagnostics(errorDiagnostics); } + return unit; + } - public static final CharMatcher STRING_CONCAT_DELIMITER = - CharMatcher.whitespace().or(CharMatcher.anyOf("\"+")); - - private static int getEndPosition(JCTree.JCCompilationUnit unit, Tree tree) { - return ((JCTree) tree).getEndPosition(unit.endPositions); + /** Applies replacements to the given string. */ + private static String applyReplacements( + String javaInput, TreeRangeMap replacementMap) throws FormatterException { + // process in descending order so the replacement ranges aren't perturbed if any + // replacements + // differ in size from the input + Map, String> ranges = replacementMap.asDescendingMapOfRanges(); + if (ranges.isEmpty()) { + return javaInput; } - - private static int getStartPosition(Tree tree) { - return ((JCTree) tree).getStartPosition(); + StringBuilder sb = new StringBuilder(javaInput); + for (Map.Entry, String> entry : ranges.entrySet()) { + Range range = entry.getKey(); + sb.replace(range.lowerEndpoint(), range.upperEndpoint(), entry.getValue()); } + return sb.toString(); + } - /** Returns true if any lines in the given Java source exceed the column limit. */ - private static boolean longLines(int columnLimit, String input) { - // TODO(cushon): consider adding Newlines.lineIterable? - Iterator it = Newlines.lineIterator(input); - while (it.hasNext()) { - String line = it.next(); - if (line.length() > columnLimit) { - return true; - } - } - return false; - } - - /** Parses the given Java source. */ - private static JCTree.JCCompilationUnit parse(String source, boolean allowStringFolding) - throws FormatterException { - DiagnosticCollector diagnostics = new DiagnosticCollector<>(); - Context context = new Context(); - context.put(DiagnosticListener.class, diagnostics); - Options.instance(context).put("--enable-preview", "true"); - Options.instance(context).put("allowStringFolding", Boolean.toString(allowStringFolding)); - JCTree.JCCompilationUnit unit; - JavacFileManager fileManager = new JavacFileManager(context, true, UTF_8); - try { - fileManager.setLocation(StandardLocation.PLATFORM_CLASS_PATH, ImmutableList.of()); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - SimpleJavaFileObject sjfo = - new SimpleJavaFileObject(URI.create("source"), JavaFileObject.Kind.SOURCE) { - @Override - public CharSequence getCharContent(boolean ignoreEncodingErrors) { - return source; - } - }; - Log.instance(context).useSource(sjfo); - ParserFactory parserFactory = ParserFactory.instance(context); - JavacParser parser = - parserFactory.newParser( - source, - /*keepDocComments=*/ true, - /*keepEndPos=*/ true, - /*keepLineMap=*/ true); - unit = parser.parseCompilationUnit(); - unit.sourcefile = sjfo; - Iterable> errorDiagnostics = - Iterables.filter(diagnostics.getDiagnostics(), Formatter::errorDiagnostic); - if (!Iterables.isEmpty(errorDiagnostics)) { - // error handling is done during formatting - throw FormatterException.fromJavacDiagnostics(errorDiagnostics); - } - return unit; - } - - /** Applies replacements to the given string. */ - private static String applyReplacements( - String javaInput, TreeRangeMap replacementMap) - throws FormatterException { - // process in descending order so the replacement ranges aren't perturbed if any - // replacements - // differ in size from the input - Map, String> ranges = replacementMap.asDescendingMapOfRanges(); - if (ranges.isEmpty()) { - return javaInput; - } - StringBuilder sb = new StringBuilder(javaInput); - for (Map.Entry, String> entry : ranges.entrySet()) { - Range range = entry.getKey(); - sb.replace(range.lowerEndpoint(), range.upperEndpoint(), entry.getValue()); - } - return sb.toString(); - } - - private StringWrapper() {} + private StringWrapper() {} } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/Trees.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/Trees.java index ae02c3e0b..1926541a4 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/Trees.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/Trees.java @@ -34,90 +34,87 @@ import javax.lang.model.element.Name; /** Utilities for working with {@link Tree}s. */ class Trees { - /** Returns the length of the source for the node. */ - static int getLength(Tree tree, TreePath path) { - return getEndPosition(tree, path) - getStartPosition(tree); - } + /** Returns the length of the source for the node. */ + static int getLength(Tree tree, TreePath path) { + return getEndPosition(tree, path) - getStartPosition(tree); + } - /** Returns the source start position of the node. */ - static int getStartPosition(Tree expression) { - return ((JCTree) expression).getStartPosition(); - } + /** Returns the source start position of the node. */ + static int getStartPosition(Tree expression) { + return ((JCTree) expression).getStartPosition(); + } - /** Returns the source end position of the node. */ - static int getEndPosition(Tree expression, TreePath path) { - return ((JCTree) expression) - .getEndPosition( - ((JCTree.JCCompilationUnit) path.getCompilationUnit()).endPositions); - } + /** Returns the source end position of the node. */ + static int getEndPosition(Tree expression, TreePath path) { + return ((JCTree) expression) + .getEndPosition(((JCTree.JCCompilationUnit) path.getCompilationUnit()).endPositions); + } - /** Returns the source text for the node. */ - static String getSourceForNode(Tree node, TreePath path) { - CharSequence source; - try { - source = path.getCompilationUnit().getSourceFile().getCharContent(false); - } catch (IOException e) { - throw new IOError(e); - } - return source.subSequence(getStartPosition(node), getEndPosition(node, path)).toString(); + /** Returns the source text for the node. */ + static String getSourceForNode(Tree node, TreePath path) { + CharSequence source; + try { + source = path.getCompilationUnit().getSourceFile().getCharContent(false); + } catch (IOException e) { + throw new IOError(e); } + return source.subSequence(getStartPosition(node), getEndPosition(node, path)).toString(); + } - /** Returns the simple name of a (possibly qualified) method invocation expression. */ - static Name getMethodName(MethodInvocationTree methodInvocation) { - ExpressionTree select = methodInvocation.getMethodSelect(); - return select instanceof MemberSelectTree - ? ((MemberSelectTree) select).getIdentifier() - : ((IdentifierTree) select).getName(); - } + /** Returns the simple name of a (possibly qualified) method invocation expression. */ + static Name getMethodName(MethodInvocationTree methodInvocation) { + ExpressionTree select = methodInvocation.getMethodSelect(); + return select instanceof MemberSelectTree + ? ((MemberSelectTree) select).getIdentifier() + : ((IdentifierTree) select).getName(); + } - /** Returns the receiver of a qualified method invocation expression, or {@code null}. */ - static ExpressionTree getMethodReceiver(MethodInvocationTree methodInvocation) { - ExpressionTree select = methodInvocation.getMethodSelect(); - return select instanceof MemberSelectTree - ? ((MemberSelectTree) select).getExpression() - : null; - } + /** Returns the receiver of a qualified method invocation expression, or {@code null}. */ + static ExpressionTree getMethodReceiver(MethodInvocationTree methodInvocation) { + ExpressionTree select = methodInvocation.getMethodSelect(); + return select instanceof MemberSelectTree ? ((MemberSelectTree) select).getExpression() : null; + } - /** Returns the string name of an operator, including assignment and compound assignment. */ - static String operatorName(ExpressionTree expression) { - JCTree.Tag tag = ((JCTree) expression).getTag(); - if (tag == JCTree.Tag.ASSIGN) { - return "="; - } - boolean assignOp = expression instanceof CompoundAssignmentTree; - if (assignOp) { - tag = tag.noAssignOp(); - } - String name = new Pretty(/*writer*/ null, /*sourceOutput*/ true).operatorName(tag); - return assignOp ? name + "=" : name; + /** Returns the string name of an operator, including assignment and compound assignment. */ + static String operatorName(ExpressionTree expression) { + JCTree.Tag tag = ((JCTree) expression).getTag(); + if (tag == JCTree.Tag.ASSIGN) { + return "="; } + boolean assignOp = expression instanceof CompoundAssignmentTree; + if (assignOp) { + tag = tag.noAssignOp(); + } + String name = new Pretty(/*writer*/ null, /*sourceOutput*/ true).operatorName(tag); + return assignOp ? name + "=" : name; + } - /** Returns the precedence of an expression's operator. */ - static int precedence(ExpressionTree expression) { - return TreeInfo.opPrec(((JCTree) expression).getTag()); - } + /** Returns the precedence of an expression's operator. */ + static int precedence(ExpressionTree expression) { + return TreeInfo.opPrec(((JCTree) expression).getTag()); + } - /** - * Returns the enclosing type declaration (class, enum, interface, or annotation) for the given - * path. - */ - static ClassTree getEnclosingTypeDeclaration(TreePath path) { - for (; path != null; path = path.getParentPath()) { - switch (path.getLeaf().getKind()) { - case CLASS: - case ENUM: - case INTERFACE: - case ANNOTATED_TYPE: - return (ClassTree) path.getLeaf(); - default: - break; - } - } - throw new AssertionError(); + /** + * Returns the enclosing type declaration (class, enum, interface, or annotation) for the given + * path. + */ + static ClassTree getEnclosingTypeDeclaration(TreePath path) { + for (; path != null; path = path.getParentPath()) { + switch (path.getLeaf().getKind()) { + case CLASS: + case ENUM: + case INTERFACE: + case ANNOTATED_TYPE: + return (ClassTree) path.getLeaf(); + default: + break; + } } + throw new AssertionError(); + } - /** Skips a single parenthesized tree. */ - static ExpressionTree skipParen(ExpressionTree node) { - return ((ParenthesizedTree) node).getExpression(); - } + /** Skips a single parenthesized tree. */ + static ExpressionTree skipParen(ExpressionTree node) { + return ((ParenthesizedTree) node).getExpression(); + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/TypeNameClassifier.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/TypeNameClassifier.java index 0267becf2..bb0010a7b 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/TypeNameClassifier.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/TypeNameClassifier.java @@ -22,153 +22,153 @@ import java.util.Optional; /** Heuristics for classifying qualified names as types. */ public final class TypeNameClassifier { - private TypeNameClassifier() {} + private TypeNameClassifier() {} - /** A state machine for classifying qualified names. */ - private enum TyParseState { + /** A state machine for classifying qualified names. */ + private enum TyParseState { - /** The start state. */ - START(false) { - @Override - public TyParseState next(JavaCaseFormat n) { - switch (n) { - case UPPERCASE: - // if we see an UpperCamel later, assume this was a class - // e.g. com.google.FOO.Bar - return TyParseState.AMBIGUOUS; - case LOWER_CAMEL: - return TyParseState.REJECT; - case LOWERCASE: - // could be a package - return TyParseState.START; - case UPPER_CAMEL: - return TyParseState.TYPE; - } - throw new AssertionError(); - } - }, - - /** The current prefix is a type. */ - TYPE(true) { - @Override - public TyParseState next(JavaCaseFormat n) { - switch (n) { - case UPPERCASE: - case LOWER_CAMEL: - case LOWERCASE: - return TyParseState.FIRST_STATIC_MEMBER; - case UPPER_CAMEL: - return TyParseState.TYPE; - } - throw new AssertionError(); - } - }, - - /** The current prefix is a type, followed by a single static member access. */ - FIRST_STATIC_MEMBER(true) { - @Override - public TyParseState next(JavaCaseFormat n) { - return TyParseState.REJECT; - } - }, - - /** Anything not represented by one of the other states. */ - REJECT(false) { - @Override - public TyParseState next(JavaCaseFormat n) { - return TyParseState.REJECT; - } - }, - - /** An ambiguous type prefix. */ - AMBIGUOUS(false) { - @Override - public TyParseState next(JavaCaseFormat n) { - switch (n) { - case UPPERCASE: - return AMBIGUOUS; - case LOWER_CAMEL: - case LOWERCASE: - return TyParseState.REJECT; - case UPPER_CAMEL: - return TyParseState.TYPE; - } - throw new AssertionError(); - } - }; - - private final boolean isSingleUnit; - - TyParseState(boolean isSingleUnit) { - this.isSingleUnit = isSingleUnit; + /** The start state. */ + START(false) { + @Override + public TyParseState next(JavaCaseFormat n) { + switch (n) { + case UPPERCASE: + // if we see an UpperCamel later, assume this was a class + // e.g. com.google.FOO.Bar + return TyParseState.AMBIGUOUS; + case LOWER_CAMEL: + return TyParseState.REJECT; + case LOWERCASE: + // could be a package + return TyParseState.START; + case UPPER_CAMEL: + return TyParseState.TYPE; } + throw new AssertionError(); + } + }, - public boolean isSingleUnit() { - return isSingleUnit; + /** The current prefix is a type. */ + TYPE(true) { + @Override + public TyParseState next(JavaCaseFormat n) { + switch (n) { + case UPPERCASE: + case LOWER_CAMEL: + case LOWERCASE: + return TyParseState.FIRST_STATIC_MEMBER; + case UPPER_CAMEL: + return TyParseState.TYPE; } + throw new AssertionError(); + } + }, - /** Transition function. */ - public abstract TyParseState next(JavaCaseFormat n); + /** The current prefix is a type, followed by a single static member access. */ + FIRST_STATIC_MEMBER(true) { + @Override + public TyParseState next(JavaCaseFormat n) { + return TyParseState.REJECT; + } + }, + + /** Anything not represented by one of the other states. */ + REJECT(false) { + @Override + public TyParseState next(JavaCaseFormat n) { + return TyParseState.REJECT; + } + }, + + /** An ambiguous type prefix. */ + AMBIGUOUS(false) { + @Override + public TyParseState next(JavaCaseFormat n) { + switch (n) { + case UPPERCASE: + return AMBIGUOUS; + case LOWER_CAMEL: + case LOWERCASE: + return TyParseState.REJECT; + case UPPER_CAMEL: + return TyParseState.TYPE; + } + throw new AssertionError(); + } + }; + + private final boolean isSingleUnit; + + TyParseState(boolean isSingleUnit) { + this.isSingleUnit = isSingleUnit; } - /** - * Returns the end index (inclusive) of the longest prefix that matches the naming conventions - * of a type or static field access, or -1 if no such prefix was found. - * - *

Examples: - * - *

    - *
  • ClassName - *
  • ClassName.staticMemberName - *
  • com.google.ClassName.InnerClass.staticMemberName - *
- */ - static Optional typePrefixLength(List nameParts) { - TyParseState state = TyParseState.START; - Optional typeLength = Optional.empty(); - for (int i = 0; i < nameParts.size(); i++) { - state = state.next(JavaCaseFormat.from(nameParts.get(i))); - if (state == TyParseState.REJECT) { - break; - } - if (state.isSingleUnit()) { - typeLength = Optional.of(i); - } - } - return typeLength; + public boolean isSingleUnit() { + return isSingleUnit; } - /** Case formats used in Java identifiers. */ - public enum JavaCaseFormat { - UPPERCASE, - LOWERCASE, - UPPER_CAMEL, - LOWER_CAMEL; + /** Transition function. */ + public abstract TyParseState next(JavaCaseFormat n); + } - /** Classifies an identifier's case format. */ - static JavaCaseFormat from(String name) { - Verify.verify(!name.isEmpty()); - boolean firstUppercase = false; - boolean hasUppercase = false; - boolean hasLowercase = false; - boolean first = true; - for (int i = 0; i < name.length(); i++) { - char c = name.charAt(i); - if (!Character.isAlphabetic(c)) { - continue; - } - if (first) { - firstUppercase = Character.isUpperCase(c); - first = false; - } - hasUppercase |= Character.isUpperCase(c); - hasLowercase |= Character.isLowerCase(c); - } - if (firstUppercase) { - return (hasLowercase || name.length() == 1) ? UPPER_CAMEL : UPPERCASE; - } else { - return hasUppercase ? LOWER_CAMEL : LOWERCASE; - } - } + /** + * Returns the end index (inclusive) of the longest prefix that matches the naming conventions of + * a type or static field access, or -1 if no such prefix was found. + * + *

Examples: + * + *

    + *
  • ClassName + *
  • ClassName.staticMemberName + *
  • com.google.ClassName.InnerClass.staticMemberName + *
+ */ + static Optional typePrefixLength(List nameParts) { + TyParseState state = TyParseState.START; + Optional typeLength = Optional.empty(); + for (int i = 0; i < nameParts.size(); i++) { + state = state.next(JavaCaseFormat.from(nameParts.get(i))); + if (state == TyParseState.REJECT) { + break; + } + if (state.isSingleUnit()) { + typeLength = Optional.of(i); + } } + return typeLength; + } + + /** Case formats used in Java identifiers. */ + public enum JavaCaseFormat { + UPPERCASE, + LOWERCASE, + UPPER_CAMEL, + LOWER_CAMEL; + + /** Classifies an identifier's case format. */ + static JavaCaseFormat from(String name) { + Verify.verify(!name.isEmpty()); + boolean firstUppercase = false; + boolean hasUppercase = false; + boolean hasLowercase = false; + boolean first = true; + for (int i = 0; i < name.length(); i++) { + char c = name.charAt(i); + if (!Character.isAlphabetic(c)) { + continue; + } + if (first) { + firstUppercase = Character.isUpperCase(c); + first = false; + } + hasUppercase |= Character.isUpperCase(c); + hasLowercase |= Character.isLowerCase(c); + } + if (firstUppercase) { + return (hasLowercase || name.length() == 1) ? UPPER_CAMEL : UPPERCASE; + } else { + return hasUppercase ? LOWER_CAMEL : LOWERCASE; + } + } + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/UsageException.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/UsageException.java index 7008e8040..a10f2d076 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/UsageException.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/UsageException.java @@ -21,88 +21,88 @@ import com.google.common.base.Joiner; /** Checked exception class for formatter command-line usage errors. */ final class UsageException extends Exception { - private static final Joiner NEWLINE_JOINER = Joiner.on(System.lineSeparator()); + private static final Joiner NEWLINE_JOINER = Joiner.on(System.lineSeparator()); - private static final String[] DOCS_LINK = { - "https://github.com/google/google-java-format", - }; + private static final String[] DOCS_LINK = { + "https://github.com/google/google-java-format", + }; - private static final String[] USAGE = { - "", - "Usage: google-java-format [options] file(s)", - "", - "Options:", - " -i, -r, -replace, --replace", - " Send formatted output back to files, not stdout.", - " -", - " Format stdin -> stdout", - " --assume-filename, -assume-filename", - " File name to use for diagnostics when formatting standard input (default is ).", - " --aosp, -aosp, -a", - " Use AOSP style instead of Google Style (4-space indentation).", - " --fix-imports-only", - " Fix import order and remove any unused imports, but do no other formatting.", - " --skip-sorting-imports", - " Do not fix the import order. Unused imports will still be removed.", - " --skip-removing-unused-imports", - " Do not remove unused imports. Imports will still be sorted.", - " --skip-reflowing-long-strings", - " Do not reflow string literals that exceed the column limit.", - " --skip-javadoc-formatting", - " Do not reformat javadoc.", - " --dry-run, -n", - " Prints the paths of the files whose contents would change if the formatter were run" - + " normally.", - " --set-exit-if-changed", - " Return exit code 1 if there are any formatting changes.", - " --lines, -lines, --line, -line", - " Line range(s) to format, like 5:10 (1-based; default is all).", - " --offset, -offset", - " Character offset to format (0-based; default is all).", - " --length, -length", - " Character length to format.", - " --help, -help, -h", - " Print this usage statement.", - " --version, -version, -v", - " Print the version.", - " @", - " Read options and filenames from file.", - "", - }; + private static final String[] USAGE = { + "", + "Usage: google-java-format [options] file(s)", + "", + "Options:", + " -i, -r, -replace, --replace", + " Send formatted output back to files, not stdout.", + " -", + " Format stdin -> stdout", + " --assume-filename, -assume-filename", + " File name to use for diagnostics when formatting standard input (default is ).", + " --aosp, -aosp, -a", + " Use AOSP style instead of Google Style (4-space indentation).", + " --fix-imports-only", + " Fix import order and remove any unused imports, but do no other formatting.", + " --skip-sorting-imports", + " Do not fix the import order. Unused imports will still be removed.", + " --skip-removing-unused-imports", + " Do not remove unused imports. Imports will still be sorted.", + " --skip-reflowing-long-strings", + " Do not reflow string literals that exceed the column limit.", + " --skip-javadoc-formatting", + " Do not reformat javadoc.", + " --dry-run, -n", + " Prints the paths of the files whose contents would change if the formatter were run" + + " normally.", + " --set-exit-if-changed", + " Return exit code 1 if there are any formatting changes.", + " --lines, -lines, --line, -line", + " Line range(s) to format, like 5:10 (1-based; default is all).", + " --offset, -offset", + " Character offset to format (0-based; default is all).", + " --length, -length", + " Character length to format.", + " --help, -help, -h", + " Print this usage statement.", + " --version, -version, -v", + " Print the version.", + " @", + " Read options and filenames from file.", + "", + }; - private static final String[] ADDITIONAL_USAGE = { - "If -i is given with -, the result is sent to stdout.", - "The --lines, --offset, and --length flags may be given more than once.", - "The --offset and --length flags must be given an equal number of times.", - "If --lines, --offset, or --length are given, only one file (or -) may be given." - }; + private static final String[] ADDITIONAL_USAGE = { + "If -i is given with -, the result is sent to stdout.", + "The --lines, --offset, and --length flags may be given more than once.", + "The --offset and --length flags must be given an equal number of times.", + "If --lines, --offset, or --length are given, only one file (or -) may be given." + }; - UsageException() { - super(buildMessage(null)); + UsageException() { + super(buildMessage(null)); + } + + UsageException(String message) { + super(buildMessage(checkNotNull(message))); + } + + private static String buildMessage(String message) { + StringBuilder builder = new StringBuilder(); + if (message != null) { + builder.append(message).append('\n'); } + appendLines(builder, USAGE); + appendLines(builder, ADDITIONAL_USAGE); + appendLines(builder, new String[] {""}); + appendLine(builder, Main.versionString()); + appendLines(builder, DOCS_LINK); + return builder.toString(); + } - UsageException(String message) { - super(buildMessage(checkNotNull(message))); - } + private static void appendLine(StringBuilder builder, String line) { + builder.append(line).append(System.lineSeparator()); + } - private static String buildMessage(String message) { - StringBuilder builder = new StringBuilder(); - if (message != null) { - builder.append(message).append('\n'); - } - appendLines(builder, USAGE); - appendLines(builder, ADDITIONAL_USAGE); - appendLines(builder, new String[] {""}); - appendLine(builder, Main.versionString()); - appendLines(builder, DOCS_LINK); - return builder.toString(); - } - - private static void appendLine(StringBuilder builder, String line) { - builder.append(line).append(System.lineSeparator()); - } - - private static void appendLines(StringBuilder builder, String[] lines) { - NEWLINE_JOINER.appendTo(builder, lines).append(System.lineSeparator()); - } + private static void appendLines(StringBuilder builder, String[] lines) { + NEWLINE_JOINER.appendTo(builder, lines).append(System.lineSeparator()); + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/filer/FormattingFiler.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/filer/FormattingFiler.java index 60d54a09e..eaaf349c7 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/filer/FormattingFiler.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/filer/FormattingFiler.java @@ -35,57 +35,57 @@ import javax.tools.JavaFileObject; */ public final class FormattingFiler implements Filer { - private final Filer delegate; - // TODO(ronshapiro): consider allowing users to create their own Formatter instance - private final Formatter formatter = new Formatter(); - private final Messager messager; + private final Filer delegate; + // TODO(ronshapiro): consider allowing users to create their own Formatter instance + private final Formatter formatter = new Formatter(); + private final Messager messager; - /** - * @param delegate filer to decorate - */ - public FormattingFiler(Filer delegate) { - this(delegate, null); - } + /** + * @param delegate filer to decorate + */ + public FormattingFiler(Filer delegate) { + this(delegate, null); + } - /** - * Create a new {@link FormattingFiler}. An optional {@link Messager} may be specified to make - * logs more visible. - * - * @param delegate filer to decorate - * @param messager to log warnings to - */ - public FormattingFiler(Filer delegate, @Nullable Messager messager) { - this.delegate = checkNotNull(delegate); - this.messager = messager; - } + /** + * Create a new {@link FormattingFiler}. An optional {@link Messager} may be specified to make + * logs more visible. + * + * @param delegate filer to decorate + * @param messager to log warnings to + */ + public FormattingFiler(Filer delegate, @Nullable Messager messager) { + this.delegate = checkNotNull(delegate); + this.messager = messager; + } - @Override - public JavaFileObject createSourceFile(CharSequence name, Element... originatingElements) - throws IOException { - return new FormattingJavaFileObject( - delegate.createSourceFile(name, originatingElements), formatter, messager); - } + @Override + public JavaFileObject createSourceFile(CharSequence name, Element... originatingElements) + throws IOException { + return new FormattingJavaFileObject( + delegate.createSourceFile(name, originatingElements), formatter, messager); + } - @Override - public JavaFileObject createClassFile(CharSequence name, Element... originatingElements) - throws IOException { - return delegate.createClassFile(name, originatingElements); - } + @Override + public JavaFileObject createClassFile(CharSequence name, Element... originatingElements) + throws IOException { + return delegate.createClassFile(name, originatingElements); + } - @Override - public FileObject createResource( - JavaFileManager.Location location, - CharSequence pkg, - CharSequence relativeName, - Element... originatingElements) - throws IOException { - return delegate.createResource(location, pkg, relativeName, originatingElements); - } + @Override + public FileObject createResource( + JavaFileManager.Location location, + CharSequence pkg, + CharSequence relativeName, + Element... originatingElements) + throws IOException { + return delegate.createResource(location, pkg, relativeName, originatingElements); + } - @Override - public FileObject getResource( - JavaFileManager.Location location, CharSequence pkg, CharSequence relativeName) - throws IOException { - return delegate.getResource(location, pkg, relativeName); - } + @Override + public FileObject getResource( + JavaFileManager.Location location, CharSequence pkg, CharSequence relativeName) + throws IOException { + return delegate.getResource(location, pkg, relativeName); + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/filer/FormattingJavaFileObject.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/filer/FormattingJavaFileObject.java index 7be1bf5c2..ebd8410cb 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/filer/FormattingJavaFileObject.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/filer/FormattingJavaFileObject.java @@ -33,68 +33,67 @@ import javax.tools.JavaFileObject; /** A {@link JavaFileObject} decorator which {@linkplain Formatter formats} source code. */ final class FormattingJavaFileObject extends ForwardingJavaFileObject { - /** A rough estimate of the average file size: 80 chars per line, 500 lines. */ - private static final int DEFAULT_FILE_SIZE = 80 * 500; + /** A rough estimate of the average file size: 80 chars per line, 500 lines. */ + private static final int DEFAULT_FILE_SIZE = 80 * 500; - private final Formatter formatter; - private final Messager messager; + private final Formatter formatter; + private final Messager messager; - /** - * Create a new {@link FormattingJavaFileObject}. - * - * @param delegate {@link JavaFileObject} to decorate - * @param messager to log messages with. - */ - FormattingJavaFileObject( - JavaFileObject delegate, Formatter formatter, @Nullable Messager messager) { - super(checkNotNull(delegate)); - this.formatter = checkNotNull(formatter); - this.messager = messager; - } + /** + * Create a new {@link FormattingJavaFileObject}. + * + * @param delegate {@link JavaFileObject} to decorate + * @param messager to log messages with. + */ + FormattingJavaFileObject( + JavaFileObject delegate, Formatter formatter, @Nullable Messager messager) { + super(checkNotNull(delegate)); + this.formatter = checkNotNull(formatter); + this.messager = messager; + } - @Override - public Writer openWriter() throws IOException { - final StringBuilder stringBuilder = new StringBuilder(DEFAULT_FILE_SIZE); - return new Writer() { - @Override - public void write(char[] chars, int start, int end) throws IOException { - stringBuilder.append(chars, start, end - start); - } + @Override + public Writer openWriter() throws IOException { + final StringBuilder stringBuilder = new StringBuilder(DEFAULT_FILE_SIZE); + return new Writer() { + @Override + public void write(char[] chars, int start, int end) throws IOException { + stringBuilder.append(chars, start, end - start); + } - @Override - public void write(String string) throws IOException { - stringBuilder.append(string); - } + @Override + public void write(String string) throws IOException { + stringBuilder.append(string); + } - @Override - public void flush() throws IOException {} + @Override + public void flush() throws IOException {} - @Override - public void close() throws IOException { - try { - formatter.formatSource( - CharSource.wrap(stringBuilder), - new CharSink() { - @Override - public Writer openStream() throws IOException { - return fileObject.openWriter(); - } - }); - } catch (FormatterException e) { - // An exception will happen when the code being formatted has an error. It's - // better to - // log the exception and emit unformatted code so the developer can view the - // code which - // caused a problem. - try (Writer writer = fileObject.openWriter()) { - writer.append(stringBuilder.toString()); - } - if (messager != null) { - messager.printMessage( - Diagnostic.Kind.NOTE, "Error formatting " + getName()); - } + @Override + public void close() throws IOException { + try { + formatter.formatSource( + CharSource.wrap(stringBuilder), + new CharSink() { + @Override + public Writer openStream() throws IOException { + return fileObject.openWriter(); } - } - }; - } + }); + } catch (FormatterException e) { + // An exception will happen when the code being formatted has an error. It's + // better to + // log the exception and emit unformatted code so the developer can view the + // code which + // caused a problem. + try (Writer writer = fileObject.openWriter()) { + writer.append(stringBuilder.toString()); + } + if (messager != null) { + messager.printMessage(Diagnostic.Kind.NOTE, "Error formatting " + getName()); + } + } + } + }; + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/javadoc/CharStream.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/javadoc/CharStream.java index 1f42c9f6a..8fbd49f9f 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/javadoc/CharStream.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/javadoc/CharStream.java @@ -27,42 +27,42 @@ import java.util.regex.Pattern; * characters from tryConsume? -- but it is convenient for the lexer. */ final class CharStream { - String remaining; - int toConsume; + String remaining; + int toConsume; - CharStream(String input) { - this.remaining = checkNotNull(input); - } + CharStream(String input) { + this.remaining = checkNotNull(input); + } - boolean tryConsume(String expected) { - if (!remaining.startsWith(expected)) { - return false; - } - toConsume = expected.length(); - return true; + boolean tryConsume(String expected) { + if (!remaining.startsWith(expected)) { + return false; } + toConsume = expected.length(); + return true; + } - /* - * @param pattern the pattern to search for, which must be anchored to match only at position 0 - */ - boolean tryConsumeRegex(Pattern pattern) { - Matcher matcher = pattern.matcher(remaining); - if (!matcher.find()) { - return false; - } - checkArgument(matcher.start() == 0); - toConsume = matcher.end(); - return true; + /* + * @param pattern the pattern to search for, which must be anchored to match only at position 0 + */ + boolean tryConsumeRegex(Pattern pattern) { + Matcher matcher = pattern.matcher(remaining); + if (!matcher.find()) { + return false; } + checkArgument(matcher.start() == 0); + toConsume = matcher.end(); + return true; + } - String readAndResetRecorded() { - String result = remaining.substring(0, toConsume); - remaining = remaining.substring(toConsume); - toConsume = 0; // TODO(cpovirk): Set this to a bogus value here and in the constructor. - return result; - } + String readAndResetRecorded() { + String result = remaining.substring(0, toConsume); + remaining = remaining.substring(toConsume); + toConsume = 0; // TODO(cpovirk): Set this to a bogus value here and in the constructor. + return result; + } - boolean isExhausted() { - return remaining.isEmpty(); - } + boolean isExhausted() { + return remaining.isEmpty(); + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/javadoc/JavadocFormatter.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/javadoc/JavadocFormatter.java index f83230df5..f4593b716 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/javadoc/JavadocFormatter.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/javadoc/JavadocFormatter.java @@ -37,162 +37,160 @@ import java.util.regex.Pattern; */ public final class JavadocFormatter { - static final int MAX_LINE_LENGTH = 100; + static final int MAX_LINE_LENGTH = 100; - /** - * Formats the given Javadoc comment, which must start with ∕✱✱ and end with ✱∕. The output will - * start and end with the same characters. - */ - public static String formatJavadoc(String input, int blockIndent) { - ImmutableList tokens; - try { - tokens = lex(input); - } catch (LexException e) { - return input; - } - String result = render(tokens, blockIndent); - return makeSingleLineIfPossible(blockIndent, result); + /** + * Formats the given Javadoc comment, which must start with ∕✱✱ and end with ✱∕. The output will + * start and end with the same characters. + */ + public static String formatJavadoc(String input, int blockIndent) { + ImmutableList tokens; + try { + tokens = lex(input); + } catch (LexException e) { + return input; } + String result = render(tokens, blockIndent); + return makeSingleLineIfPossible(blockIndent, result); + } - private static String render(List input, int blockIndent) { - JavadocWriter output = new JavadocWriter(blockIndent); - for (Token token : input) { - switch (token.getType()) { - case BEGIN_JAVADOC: - output.writeBeginJavadoc(); - break; - case END_JAVADOC: - output.writeEndJavadoc(); - return output.toString(); - case FOOTER_JAVADOC_TAG_START: - output.writeFooterJavadocTagStart(token); - break; - case LIST_OPEN_TAG: - output.writeListOpen(token); - break; - case LIST_CLOSE_TAG: - output.writeListClose(token); - break; - case LIST_ITEM_OPEN_TAG: - output.writeListItemOpen(token); - break; - case HEADER_OPEN_TAG: - output.writeHeaderOpen(token); - break; - case HEADER_CLOSE_TAG: - output.writeHeaderClose(token); - break; - case PARAGRAPH_OPEN_TAG: - output.writeParagraphOpen(standardizePToken(token)); - break; - case BLOCKQUOTE_OPEN_TAG: - case BLOCKQUOTE_CLOSE_TAG: - output.writeBlockquoteOpenOrClose(token); - break; - case PRE_OPEN_TAG: - output.writePreOpen(token); - break; - case PRE_CLOSE_TAG: - output.writePreClose(token); - break; - case CODE_OPEN_TAG: - output.writeCodeOpen(token); - break; - case CODE_CLOSE_TAG: - output.writeCodeClose(token); - break; - case TABLE_OPEN_TAG: - output.writeTableOpen(token); - break; - case TABLE_CLOSE_TAG: - output.writeTableClose(token); - break; - case MOE_BEGIN_STRIP_COMMENT: - output.requestMoeBeginStripComment(token); - break; - case MOE_END_STRIP_COMMENT: - output.writeMoeEndStripComment(token); - break; - case HTML_COMMENT: - output.writeHtmlComment(token); - break; - case BR_TAG: - output.writeBr(standardizeBrToken(token)); - break; - case WHITESPACE: - output.requestWhitespace(); - break; - case FORCED_NEWLINE: - output.writeLineBreakNoAutoIndent(); - break; - case LITERAL: - output.writeLiteral(token); - break; - case PARAGRAPH_CLOSE_TAG: - case LIST_ITEM_CLOSE_TAG: - case OPTIONAL_LINE_BREAK: - break; - default: - throw new AssertionError(token.getType()); - } - } - throw new AssertionError(); + private static String render(List input, int blockIndent) { + JavadocWriter output = new JavadocWriter(blockIndent); + for (Token token : input) { + switch (token.getType()) { + case BEGIN_JAVADOC: + output.writeBeginJavadoc(); + break; + case END_JAVADOC: + output.writeEndJavadoc(); + return output.toString(); + case FOOTER_JAVADOC_TAG_START: + output.writeFooterJavadocTagStart(token); + break; + case LIST_OPEN_TAG: + output.writeListOpen(token); + break; + case LIST_CLOSE_TAG: + output.writeListClose(token); + break; + case LIST_ITEM_OPEN_TAG: + output.writeListItemOpen(token); + break; + case HEADER_OPEN_TAG: + output.writeHeaderOpen(token); + break; + case HEADER_CLOSE_TAG: + output.writeHeaderClose(token); + break; + case PARAGRAPH_OPEN_TAG: + output.writeParagraphOpen(standardizePToken(token)); + break; + case BLOCKQUOTE_OPEN_TAG: + case BLOCKQUOTE_CLOSE_TAG: + output.writeBlockquoteOpenOrClose(token); + break; + case PRE_OPEN_TAG: + output.writePreOpen(token); + break; + case PRE_CLOSE_TAG: + output.writePreClose(token); + break; + case CODE_OPEN_TAG: + output.writeCodeOpen(token); + break; + case CODE_CLOSE_TAG: + output.writeCodeClose(token); + break; + case TABLE_OPEN_TAG: + output.writeTableOpen(token); + break; + case TABLE_CLOSE_TAG: + output.writeTableClose(token); + break; + case MOE_BEGIN_STRIP_COMMENT: + output.requestMoeBeginStripComment(token); + break; + case MOE_END_STRIP_COMMENT: + output.writeMoeEndStripComment(token); + break; + case HTML_COMMENT: + output.writeHtmlComment(token); + break; + case BR_TAG: + output.writeBr(standardizeBrToken(token)); + break; + case WHITESPACE: + output.requestWhitespace(); + break; + case FORCED_NEWLINE: + output.writeLineBreakNoAutoIndent(); + break; + case LITERAL: + output.writeLiteral(token); + break; + case PARAGRAPH_CLOSE_TAG: + case LIST_ITEM_CLOSE_TAG: + case OPTIONAL_LINE_BREAK: + break; + default: + throw new AssertionError(token.getType()); + } } + throw new AssertionError(); + } - /* - * TODO(cpovirk): Is this really the right location for the standardize* methods? Maybe the lexer - * should include them as part of its own postprocessing? Or even the writer could make sense. - */ + /* + * TODO(cpovirk): Is this really the right location for the standardize* methods? Maybe the lexer + * should include them as part of its own postprocessing? Or even the writer could make sense. + */ - private static Token standardizeBrToken(Token token) { - return standardize(token, STANDARD_BR_TOKEN); + private static Token standardizeBrToken(Token token) { + return standardize(token, STANDARD_BR_TOKEN); + } + + private static Token standardizePToken(Token token) { + return standardize(token, STANDARD_P_TOKEN); + } + + private static Token standardize(Token token, Token standardToken) { + return SIMPLE_TAG_PATTERN.matcher(token.getValue()).matches() ? standardToken : token; + } + + private static final Token STANDARD_BR_TOKEN = new Token(BR_TAG, "
"); + private static final Token STANDARD_P_TOKEN = new Token(PARAGRAPH_OPEN_TAG, "

"); + private static final Pattern SIMPLE_TAG_PATTERN = compile("^<\\w+\\s*/?\\s*>", CASE_INSENSITIVE); + + private static final Pattern ONE_CONTENT_LINE_PATTERN = compile(" */[*][*]\n *[*] (.*)\n *[*]/"); + + /** + * Returns the given string or a one-line version of it (e.g., "∕✱✱ Tests for foos. ✱∕") if it + * fits on one line. + */ + private static String makeSingleLineIfPossible(int blockIndent, String input) { + Matcher matcher = ONE_CONTENT_LINE_PATTERN.matcher(input); + if (matcher.matches()) { + String line = matcher.group(1); + if (line.isEmpty()) { + return "/** */"; + } else if (oneLineJavadoc(line, blockIndent)) { + return "/** " + line + " */"; + } } + return input; + } - private static Token standardizePToken(Token token) { - return standardize(token, STANDARD_P_TOKEN); + private static boolean oneLineJavadoc(String line, int blockIndent) { + int oneLinerContentLength = MAX_LINE_LENGTH - "/** */".length() - blockIndent; + if (line.length() > oneLinerContentLength) { + return false; } - - private static Token standardize(Token token, Token standardToken) { - return SIMPLE_TAG_PATTERN.matcher(token.getValue()).matches() ? standardToken : token; + // If the javadoc contains only a tag, use multiple lines to encourage writing a summary + // fragment, unless it's /* @hide */. + if (line.startsWith("@") && !line.equals("@hide")) { + return false; } + return true; + } - private static final Token STANDARD_BR_TOKEN = new Token(BR_TAG, "
"); - private static final Token STANDARD_P_TOKEN = new Token(PARAGRAPH_OPEN_TAG, "

"); - private static final Pattern SIMPLE_TAG_PATTERN = - compile("^<\\w+\\s*/?\\s*>", CASE_INSENSITIVE); - - private static final Pattern ONE_CONTENT_LINE_PATTERN = - compile(" */[*][*]\n *[*] (.*)\n *[*]/"); - - /** - * Returns the given string or a one-line version of it (e.g., "∕✱✱ Tests for foos. ✱∕") if it - * fits on one line. - */ - private static String makeSingleLineIfPossible(int blockIndent, String input) { - Matcher matcher = ONE_CONTENT_LINE_PATTERN.matcher(input); - if (matcher.matches()) { - String line = matcher.group(1); - if (line.isEmpty()) { - return "/** */"; - } else if (oneLineJavadoc(line, blockIndent)) { - return "/** " + line + " */"; - } - } - return input; - } - - private static boolean oneLineJavadoc(String line, int blockIndent) { - int oneLinerContentLength = MAX_LINE_LENGTH - "/** */".length() - blockIndent; - if (line.length() > oneLinerContentLength) { - return false; - } - // If the javadoc contains only a tag, use multiple lines to encourage writing a summary - // fragment, unless it's /* @hide */. - if (line.startsWith("@") && !line.equals("@hide")) { - return false; - } - return true; - } - - private JavadocFormatter() {} + private JavadocFormatter() {} } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/javadoc/JavadocLexer.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/javadoc/JavadocLexer.java index abaca9e93..368cef7d9 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/javadoc/JavadocLexer.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/javadoc/JavadocLexer.java @@ -63,508 +63,500 @@ import java.util.regex.Pattern; /** Lexer for the Javadoc formatter. */ final class JavadocLexer { - /** Takes a Javadoc comment, including ∕✱✱ and ✱∕, and returns tokens, including ∕✱✱ and ✱∕. */ - static ImmutableList lex(String input) throws LexException { - /* - * TODO(cpovirk): In theory, we should interpret Unicode escapes (yet output them in their - * original form). This would mean mean everything from an encoded ∕✱✱ to an encoded

 tag,
-         * so we'll probably never bother.
-         */
-        input = stripJavadocBeginAndEnd(input);
-        input = normalizeLineEndings(input);
-        return new JavadocLexer(new CharStream(input)).generateTokens();
-    }
-
-    /** The lexer crashes on windows line endings, so for now just normalize to `\n`. */
-    // TODO(cushon): use the platform line separator for output
-    private static String normalizeLineEndings(String input) {
-        return NON_UNIX_LINE_ENDING.matcher(input).replaceAll("\n");
-    }
-
-    private static final Pattern NON_UNIX_LINE_ENDING = Pattern.compile("\r\n?");
-
-    private static String stripJavadocBeginAndEnd(String input) {
-        /*
-         * We do this ahead of time so that the main part of the lexer need not say things like
-         * "(?![*]/)" to avoid accidentally swallowing ✱∕ when consuming a newline.
-         */
-        checkArgument(input.startsWith("/**"), "Missing /**: %s", input);
-        checkArgument(input.endsWith("*/") && input.length() > 4, "Missing */: %s", input);
-        return input.substring("/**".length(), input.length() - "*/".length());
-    }
-
-    private final CharStream input;
-    private final NestingCounter braceDepth = new NestingCounter();
-    private final NestingCounter preDepth = new NestingCounter();
-    private final NestingCounter codeDepth = new NestingCounter();
-    private final NestingCounter tableDepth = new NestingCounter();
-    private boolean somethingSinceNewline;
-
-    private JavadocLexer(CharStream input) {
-        this.input = checkNotNull(input);
-    }
-
-    private ImmutableList generateTokens() throws LexException {
-        ImmutableList.Builder tokens = ImmutableList.builder();
-
-        Token token = new Token(BEGIN_JAVADOC, "/**");
-        tokens.add(token);
-
-        while (!input.isExhausted()) {
-            token = readToken();
-            tokens.add(token);
-        }
-
-        checkMatchingTags();
-
-        token = new Token(END_JAVADOC, "*/");
-        tokens.add(token);
-
-        ImmutableList result = tokens.build();
-        result = joinAdjacentLiteralsAndAdjacentWhitespace(result);
-        result = inferParagraphTags(result);
-        result = optionalizeSpacesAfterLinks(result);
-        result = deindentPreCodeBlocks(result);
-        return result;
-    }
-
-    private Token readToken() throws LexException {
-        Type type = consumeToken();
-        String value = input.readAndResetRecorded();
-        return new Token(type, value);
-    }
-
-    private Type consumeToken() throws LexException {
-        boolean preserveExistingFormatting = preserveExistingFormatting();
-
-        if (input.tryConsumeRegex(NEWLINE_PATTERN)) {
-            somethingSinceNewline = false;
-            return preserveExistingFormatting ? FORCED_NEWLINE : WHITESPACE;
-        } else if (input.tryConsume(" ") || input.tryConsume("\t")) {
-            // TODO(cpovirk): How about weird whitespace chars? Ideally we'd distinguish breaking
-            // vs. not.
-            // Returning LITERAL here prevent us from breaking a 
 line. For more info, see
-            // LITERAL.
-            return preserveExistingFormatting ? LITERAL : WHITESPACE;
-        }
-
-        /*
-         * TODO(cpovirk): Maybe try to detect things like "{@code\n@GwtCompatible}" that aren't intended
-         * as tags. But in the most likely case, in which that happens inside 
{@code, we have no
-         * great options for fixing it.
-         * https://github.com/google/google-java-format/issues/7#issuecomment-197383926
-         */
-        if (!somethingSinceNewline && input.tryConsumeRegex(FOOTER_TAG_PATTERN)) {
-            checkMatchingTags();
-            somethingSinceNewline = true;
-            return FOOTER_JAVADOC_TAG_START;
-        }
-        somethingSinceNewline = true;
-
-        if (input.tryConsumeRegex(INLINE_TAG_OPEN_PATTERN)) {
-            braceDepth.increment();
-            return LITERAL;
-        } else if (input.tryConsume("{")) {
-            braceDepth.incrementIfPositive();
-            return LITERAL;
-        } else if (input.tryConsume("}")) {
-            braceDepth.decrementIfPositive();
-            return LITERAL;
-        }
-
-        // Inside an inline tag, don't do any HTML interpretation.
-        if (braceDepth.isPositive()) {
-            verify(input.tryConsumeRegex(LITERAL_PATTERN));
-            return LITERAL;
-        }
-
-        if (input.tryConsumeRegex(PRE_OPEN_PATTERN)) {
-            preDepth.increment();
-            return preserveExistingFormatting ? LITERAL : PRE_OPEN_TAG;
-        } else if (input.tryConsumeRegex(PRE_CLOSE_PATTERN)) {
-            preDepth.decrementIfPositive();
-            return preserveExistingFormatting() ? LITERAL : PRE_CLOSE_TAG;
-        }
-
-        if (input.tryConsumeRegex(CODE_OPEN_PATTERN)) {
-            codeDepth.increment();
-            return preserveExistingFormatting ? LITERAL : CODE_OPEN_TAG;
-        } else if (input.tryConsumeRegex(CODE_CLOSE_PATTERN)) {
-            codeDepth.decrementIfPositive();
-            return preserveExistingFormatting() ? LITERAL : CODE_CLOSE_TAG;
-        }
-
-        if (input.tryConsumeRegex(TABLE_OPEN_PATTERN)) {
-            tableDepth.increment();
-            return preserveExistingFormatting ? LITERAL : TABLE_OPEN_TAG;
-        } else if (input.tryConsumeRegex(TABLE_CLOSE_PATTERN)) {
-            tableDepth.decrementIfPositive();
-            return preserveExistingFormatting() ? LITERAL : TABLE_CLOSE_TAG;
-        }
-
-        if (preserveExistingFormatting) {
-            verify(input.tryConsumeRegex(LITERAL_PATTERN));
-            return LITERAL;
-        }
-
-        if (input.tryConsumeRegex(PARAGRAPH_OPEN_PATTERN)) {
-            return PARAGRAPH_OPEN_TAG;
-        } else if (input.tryConsumeRegex(PARAGRAPH_CLOSE_PATTERN)) {
-            return PARAGRAPH_CLOSE_TAG;
-        } else if (input.tryConsumeRegex(LIST_OPEN_PATTERN)) {
-            return LIST_OPEN_TAG;
-        } else if (input.tryConsumeRegex(LIST_CLOSE_PATTERN)) {
-            return LIST_CLOSE_TAG;
-        } else if (input.tryConsumeRegex(LIST_ITEM_OPEN_PATTERN)) {
-            return LIST_ITEM_OPEN_TAG;
-        } else if (input.tryConsumeRegex(LIST_ITEM_CLOSE_PATTERN)) {
-            return LIST_ITEM_CLOSE_TAG;
-        } else if (input.tryConsumeRegex(BLOCKQUOTE_OPEN_PATTERN)) {
-            return BLOCKQUOTE_OPEN_TAG;
-        } else if (input.tryConsumeRegex(BLOCKQUOTE_CLOSE_PATTERN)) {
-            return BLOCKQUOTE_CLOSE_TAG;
-        } else if (input.tryConsumeRegex(HEADER_OPEN_PATTERN)) {
-            return HEADER_OPEN_TAG;
-        } else if (input.tryConsumeRegex(HEADER_CLOSE_PATTERN)) {
-            return HEADER_CLOSE_TAG;
-        } else if (input.tryConsumeRegex(BR_PATTERN)) {
-            return BR_TAG;
-        } else if (input.tryConsumeRegex(MOE_BEGIN_STRIP_COMMENT_PATTERN)) {
-            return MOE_BEGIN_STRIP_COMMENT;
-        } else if (input.tryConsumeRegex(MOE_END_STRIP_COMMENT_PATTERN)) {
-            return MOE_END_STRIP_COMMENT;
-        } else if (input.tryConsumeRegex(HTML_COMMENT_PATTERN)) {
-            return HTML_COMMENT;
-        } else if (input.tryConsumeRegex(LITERAL_PATTERN)) {
-            return LITERAL;
-        }
-        throw new AssertionError();
-    }
-
-    private boolean preserveExistingFormatting() {
-        return preDepth.isPositive() || tableDepth.isPositive() || codeDepth.isPositive();
-    }
-
-    private void checkMatchingTags() throws LexException {
-        if (braceDepth.isPositive()
-                || preDepth.isPositive()
-                || tableDepth.isPositive()
-                || codeDepth.isPositive()) {
-            throw new LexException();
-        }
-    }
-
-    /**
-     * Join together adjacent literal tokens, and join together adjacent whitespace tokens.
-     *
-     * 

For literal tokens, this means something like {@code ["", "foo", ""] => - * ["foo"]}. See {@link #LITERAL_PATTERN} for discussion of why those tokens are separate - * to begin with. - * - *

Whitespace tokens are treated analogously. We don't really "want" to join whitespace - * tokens, but in the course of joining literals, we incidentally join whitespace, too. We do - * take advantage of the joining later on: It simplifies {@link #inferParagraphTags}. - * - *

Note that we do not merge a literal token and a whitespace token together. + /** Takes a Javadoc comment, including ∕✱✱ and ✱∕, and returns tokens, including ∕✱✱ and ✱∕. */ + static ImmutableList lex(String input) throws LexException { + /* + * TODO(cpovirk): In theory, we should interpret Unicode escapes (yet output them in their + * original form). This would mean mean everything from an encoded ∕✱✱ to an encoded

 tag,
+     * so we'll probably never bother.
      */
-    private static ImmutableList joinAdjacentLiteralsAndAdjacentWhitespace(
-            List input) {
-        /*
-         * Note: Our final token is always END_JAVADOC. This saves us some trouble:
-         *
-         * - Our inner while() doesn't need a hasNext() check.
-         *
-         * - We don't need to check for leftover accumulated literals after we exit the loop.
-         */
-        ImmutableList.Builder output = ImmutableList.builder();
-        StringBuilder accumulated = new StringBuilder();
+    input = stripJavadocBeginAndEnd(input);
+    input = normalizeLineEndings(input);
+    return new JavadocLexer(new CharStream(input)).generateTokens();
+  }
 
-        for (PeekingIterator tokens = peekingIterator(input.iterator());
-                tokens.hasNext(); ) {
-            if (tokens.peek().getType() == LITERAL) {
-                accumulated.append(tokens.peek().getValue());
-                tokens.next();
-                continue;
-            }
+  /** The lexer crashes on windows line endings, so for now just normalize to `\n`. */
+  // TODO(cushon): use the platform line separator for output
+  private static String normalizeLineEndings(String input) {
+    return NON_UNIX_LINE_ENDING.matcher(input).replaceAll("\n");
+  }
 
-            /*
-             * IF we have accumulated some literals to join together (say, "foobar"), and IF we'll
-             * next see whitespace followed by a "@" literal, we need to join that together with the
-             * previous literals. That ensures that we won't insert a line break before the "@," turning
-             * it into a tag.
-             */
+  private static final Pattern NON_UNIX_LINE_ENDING = Pattern.compile("\r\n?");
 
-            if (accumulated.length() == 0) {
-                output.add(tokens.peek());
-                tokens.next();
-                continue;
-            }
-
-            StringBuilder seenWhitespace = new StringBuilder();
-            while (tokens.peek().getType() == WHITESPACE) {
-                seenWhitespace.append(tokens.next().getValue());
-            }
-
-            if (tokens.peek().getType() == LITERAL && tokens.peek().getValue().startsWith("@")) {
-                // OK, we're in the case described above.
-                accumulated.append(" ");
-                accumulated.append(tokens.peek().getValue());
-                tokens.next();
-                continue;
-            }
-
-            output.add(new Token(LITERAL, accumulated.toString()));
-            accumulated.setLength(0);
-
-            if (seenWhitespace.length() > 0) {
-                output.add(new Token(WHITESPACE, seenWhitespace.toString()));
-            }
-
-            // We have another token coming, possibly of type OTHER. Leave it for the next
-            // iteration.
-        }
-
-        /*
-         * TODO(cpovirk): Another case where we could try to join tokens is if a line ends with
-         * /[^ -]-/, as in "non-\nblocking."
-         */
-        return output.build();
-    }
-
-    /**
-     * Where the input has two consecutive line breaks between literals, insert a {@code 

} tag - * between the literals. - * - *

This method must be called after {@link #joinAdjacentLiteralsAndAdjacentWhitespace}, as it - * assumes that adjacent whitespace tokens have already been joined. + private static String stripJavadocBeginAndEnd(String input) { + /* + * We do this ahead of time so that the main part of the lexer need not say things like + * "(?![*]/)" to avoid accidentally swallowing ✱∕ when consuming a newline. */ - private static ImmutableList inferParagraphTags(List input) { - ImmutableList.Builder output = ImmutableList.builder(); + checkArgument(input.startsWith("/**"), "Missing /**: %s", input); + checkArgument(input.endsWith("*/") && input.length() > 4, "Missing */: %s", input); + return input.substring("/**".length(), input.length() - "*/".length()); + } - for (PeekingIterator tokens = peekingIterator(input.iterator()); - tokens.hasNext(); ) { - if (tokens.peek().getType() == LITERAL) { - output.add(tokens.next()); + private final CharStream input; + private final NestingCounter braceDepth = new NestingCounter(); + private final NestingCounter preDepth = new NestingCounter(); + private final NestingCounter codeDepth = new NestingCounter(); + private final NestingCounter tableDepth = new NestingCounter(); + private boolean somethingSinceNewline; - if (tokens.peek().getType() == WHITESPACE - && hasMultipleNewlines(tokens.peek().getValue())) { - output.add(tokens.next()); + private JavadocLexer(CharStream input) { + this.input = checkNotNull(input); + } - if (tokens.peek().getType() == LITERAL) { - output.add(new Token(PARAGRAPH_OPEN_TAG, "

")); - } - } - } else { - // TODO(cpovirk): Or just `continue` from the

case and move this out of the - // `else`? - output.add(tokens.next()); - } - } + private ImmutableList generateTokens() throws LexException { + ImmutableList.Builder tokens = ImmutableList.builder(); - return output.build(); + Token token = new Token(BEGIN_JAVADOC, "/**"); + tokens.add(token); - /* - * Note: We do not want to insert

tags inside

. Fortunately, the formatter gets that
-         * right without special effort on our part. The reason: Line breaks inside a 
 section are
-         * of type FORCED_NEWLINE rather than WHITESPACE.
-         */
+    while (!input.isExhausted()) {
+      token = readToken();
+      tokens.add(token);
     }
 
-    /**
-     * Replaces whitespace after a {@code href=...>} token with an "optional link break." This
-     * allows us to output either {@code foo} or {@code \nfoo},
-     * depending on how much space we have left on the line.
-     *
-     * 

This method must be called after {@link #joinAdjacentLiteralsAndAdjacentWhitespace}, as it - * assumes that adjacent whitespace tokens have already been joined. - */ - private static ImmutableList optionalizeSpacesAfterLinks(List input) { - ImmutableList.Builder output = ImmutableList.builder(); + checkMatchingTags(); - for (PeekingIterator tokens = peekingIterator(input.iterator()); - tokens.hasNext(); ) { - if (tokens.peek().getType() == LITERAL - && tokens.peek().getValue().matches("^href=[^>]*>")) { - output.add(tokens.next()); + token = new Token(END_JAVADOC, "*/"); + tokens.add(token); - if (tokens.peek().getType() == WHITESPACE) { - output.add(new Token(OPTIONAL_LINE_BREAK, tokens.next().getValue())); - } - } else { - output.add(tokens.next()); - } - } + ImmutableList result = tokens.build(); + result = joinAdjacentLiteralsAndAdjacentWhitespace(result); + result = inferParagraphTags(result); + result = optionalizeSpacesAfterLinks(result); + result = deindentPreCodeBlocks(result); + return result; + } - return output.build(); + private Token readToken() throws LexException { + Type type = consumeToken(); + String value = input.readAndResetRecorded(); + return new Token(type, value); + } - /* - * Note: We do not want to insert

tags inside

. Fortunately, the formatter gets that
-         * right without special effort on our part. The reason: Line breaks inside a 
 section are
-         * of type FORCED_NEWLINE rather than WHITESPACE.
-         */
-    }
+  private Type consumeToken() throws LexException {
+    boolean preserveExistingFormatting = preserveExistingFormatting();
 
-    /**
-     * Adjust indentation inside `
{@code` blocks.
-     *
-     * 

Also trim leading and trailing blank lines, and move the trailing `}` to its own line. - */ - private static ImmutableList deindentPreCodeBlocks(List input) { - ImmutableList.Builder output = ImmutableList.builder(); - for (PeekingIterator tokens = peekingIterator(input.iterator()); - tokens.hasNext(); ) { - if (tokens.peek().getType() != PRE_OPEN_TAG) { - output.add(tokens.next()); - continue; - } - - output.add(tokens.next()); - List initialNewlines = new ArrayList<>(); - while (tokens.hasNext() && tokens.peek().getType() == FORCED_NEWLINE) { - initialNewlines.add(tokens.next()); - } - if (tokens.peek().getType() != LITERAL - || !tokens.peek().getValue().matches("[ \t]*[{]@code")) { - output.addAll(initialNewlines); - output.add(tokens.next()); - continue; - } - - deindentPreCodeBlock(output, tokens); - } - return output.build(); - } - - private static void deindentPreCodeBlock( - ImmutableList.Builder output, PeekingIterator tokens) { - Deque saved = new ArrayDeque<>(); - output.add(new Token(LITERAL, tokens.next().getValue().trim())); - while (tokens.hasNext() && tokens.peek().getType() != PRE_CLOSE_TAG) { - Token token = tokens.next(); - saved.addLast(token); - } - while (!saved.isEmpty() && saved.peekFirst().getType() == FORCED_NEWLINE) { - saved.removeFirst(); - } - while (!saved.isEmpty() && saved.peekLast().getType() == FORCED_NEWLINE) { - saved.removeLast(); - } - if (saved.isEmpty()) { - return; - } - - // move the trailing `}` to its own line - Token last = saved.peekLast(); - boolean trailingBrace = false; - if (last.getType() == LITERAL && last.getValue().endsWith("}")) { - saved.removeLast(); - if (last.length() > 1) { - saved.addLast( - new Token( - LITERAL, - last.getValue().substring(0, last.getValue().length() - 1))); - saved.addLast(new Token(FORCED_NEWLINE, null)); - } - trailingBrace = true; - } - - int trim = -1; - for (Token token : saved) { - if (token.getType() == LITERAL) { - int idx = CharMatcher.isNot(' ').indexIn(token.getValue()); - if (idx != -1 && (trim == -1 || idx < trim)) { - trim = idx; - } - } - } - - output.add(new Token(FORCED_NEWLINE, "\n")); - for (Token token : saved) { - if (token.getType() == LITERAL) { - output.add( - new Token( - LITERAL, - trim > 0 && token.length() > trim - ? token.getValue().substring(trim) - : token.getValue())); - } else { - output.add(token); - } - } - - if (trailingBrace) { - output.add(new Token(LITERAL, "}")); - } else { - output.add(new Token(FORCED_NEWLINE, "\n")); - } - } - - private static final CharMatcher NEWLINE = CharMatcher.is('\n'); - - private static boolean hasMultipleNewlines(String s) { - return NEWLINE.countIn(s) > 1; + if (input.tryConsumeRegex(NEWLINE_PATTERN)) { + somethingSinceNewline = false; + return preserveExistingFormatting ? FORCED_NEWLINE : WHITESPACE; + } else if (input.tryConsume(" ") || input.tryConsume("\t")) { + // TODO(cpovirk): How about weird whitespace chars? Ideally we'd distinguish breaking + // vs. not. + // Returning LITERAL here prevent us from breaking a

 line. For more info, see
+      // LITERAL.
+      return preserveExistingFormatting ? LITERAL : WHITESPACE;
     }
 
     /*
-     * This also eats any trailing whitespace. We would be smart enough to ignore that, anyway --
-     * except in the case of 
/, inside which we otherwise leave whitespace intact.
-     *
-     * We'd remove the trailing whitespace later on (in JavaCommentsHelper.rewrite), but I feel safer
-     * stripping it now: It otherwise might confuse our line-length count, which we use for wrapping.
+     * TODO(cpovirk): Maybe try to detect things like "{@code\n@GwtCompatible}" that aren't intended
+     * as tags. But in the most likely case, in which that happens inside 
{@code, we have no
+     * great options for fixing it.
+     * https://github.com/google/google-java-format/issues/7#issuecomment-197383926
      */
-    private static final Pattern NEWLINE_PATTERN = compile("^[ \t]*\n[ \t]*[*]?[ \t]?");
+    if (!somethingSinceNewline && input.tryConsumeRegex(FOOTER_TAG_PATTERN)) {
+      checkMatchingTags();
+      somethingSinceNewline = true;
+      return FOOTER_JAVADOC_TAG_START;
+    }
+    somethingSinceNewline = true;
 
-    // We ensure elsewhere that we match this only at the beginning of a line.
-    // Only match tags that start with a lowercase letter, to avoid false matches on unescaped
-    // annotations inside code blocks.
-    // Match "@param " specially in case the  is a 

or other HTML tag we treat specially. - private static final Pattern FOOTER_TAG_PATTERN = compile("^@(param\\s+<\\w+>|[a-z]\\w*)"); - private static final Pattern MOE_BEGIN_STRIP_COMMENT_PATTERN = - compile("^"); - private static final Pattern MOE_END_STRIP_COMMENT_PATTERN = - compile("^"); - private static final Pattern HTML_COMMENT_PATTERN = fullCommentPattern(); - private static final Pattern PRE_OPEN_PATTERN = openTagPattern("pre"); - private static final Pattern PRE_CLOSE_PATTERN = closeTagPattern("pre"); - private static final Pattern CODE_OPEN_PATTERN = openTagPattern("code"); - private static final Pattern CODE_CLOSE_PATTERN = closeTagPattern("code"); - private static final Pattern TABLE_OPEN_PATTERN = openTagPattern("table"); - private static final Pattern TABLE_CLOSE_PATTERN = closeTagPattern("table"); - private static final Pattern LIST_OPEN_PATTERN = openTagPattern("ul|ol|dl"); - private static final Pattern LIST_CLOSE_PATTERN = closeTagPattern("ul|ol|dl"); - private static final Pattern LIST_ITEM_OPEN_PATTERN = openTagPattern("li|dt|dd"); - private static final Pattern LIST_ITEM_CLOSE_PATTERN = closeTagPattern("li|dt|dd"); - private static final Pattern HEADER_OPEN_PATTERN = openTagPattern("h[1-6]"); - private static final Pattern HEADER_CLOSE_PATTERN = closeTagPattern("h[1-6]"); - private static final Pattern PARAGRAPH_OPEN_PATTERN = openTagPattern("p"); - private static final Pattern PARAGRAPH_CLOSE_PATTERN = closeTagPattern("p"); - private static final Pattern BLOCKQUOTE_OPEN_PATTERN = openTagPattern("blockquote"); - private static final Pattern BLOCKQUOTE_CLOSE_PATTERN = closeTagPattern("blockquote"); - private static final Pattern BR_PATTERN = openTagPattern("br"); - private static final Pattern INLINE_TAG_OPEN_PATTERN = compile("^[{]@\\w*"); + if (input.tryConsumeRegex(INLINE_TAG_OPEN_PATTERN)) { + braceDepth.increment(); + return LITERAL; + } else if (input.tryConsume("{")) { + braceDepth.incrementIfPositive(); + return LITERAL; + } else if (input.tryConsume("}")) { + braceDepth.decrementIfPositive(); + return LITERAL; + } + + // Inside an inline tag, don't do any HTML interpretation. + if (braceDepth.isPositive()) { + verify(input.tryConsumeRegex(LITERAL_PATTERN)); + return LITERAL; + } + + if (input.tryConsumeRegex(PRE_OPEN_PATTERN)) { + preDepth.increment(); + return preserveExistingFormatting ? LITERAL : PRE_OPEN_TAG; + } else if (input.tryConsumeRegex(PRE_CLOSE_PATTERN)) { + preDepth.decrementIfPositive(); + return preserveExistingFormatting() ? LITERAL : PRE_CLOSE_TAG; + } + + if (input.tryConsumeRegex(CODE_OPEN_PATTERN)) { + codeDepth.increment(); + return preserveExistingFormatting ? LITERAL : CODE_OPEN_TAG; + } else if (input.tryConsumeRegex(CODE_CLOSE_PATTERN)) { + codeDepth.decrementIfPositive(); + return preserveExistingFormatting() ? LITERAL : CODE_CLOSE_TAG; + } + + if (input.tryConsumeRegex(TABLE_OPEN_PATTERN)) { + tableDepth.increment(); + return preserveExistingFormatting ? LITERAL : TABLE_OPEN_TAG; + } else if (input.tryConsumeRegex(TABLE_CLOSE_PATTERN)) { + tableDepth.decrementIfPositive(); + return preserveExistingFormatting() ? LITERAL : TABLE_CLOSE_TAG; + } + + if (preserveExistingFormatting) { + verify(input.tryConsumeRegex(LITERAL_PATTERN)); + return LITERAL; + } + + if (input.tryConsumeRegex(PARAGRAPH_OPEN_PATTERN)) { + return PARAGRAPH_OPEN_TAG; + } else if (input.tryConsumeRegex(PARAGRAPH_CLOSE_PATTERN)) { + return PARAGRAPH_CLOSE_TAG; + } else if (input.tryConsumeRegex(LIST_OPEN_PATTERN)) { + return LIST_OPEN_TAG; + } else if (input.tryConsumeRegex(LIST_CLOSE_PATTERN)) { + return LIST_CLOSE_TAG; + } else if (input.tryConsumeRegex(LIST_ITEM_OPEN_PATTERN)) { + return LIST_ITEM_OPEN_TAG; + } else if (input.tryConsumeRegex(LIST_ITEM_CLOSE_PATTERN)) { + return LIST_ITEM_CLOSE_TAG; + } else if (input.tryConsumeRegex(BLOCKQUOTE_OPEN_PATTERN)) { + return BLOCKQUOTE_OPEN_TAG; + } else if (input.tryConsumeRegex(BLOCKQUOTE_CLOSE_PATTERN)) { + return BLOCKQUOTE_CLOSE_TAG; + } else if (input.tryConsumeRegex(HEADER_OPEN_PATTERN)) { + return HEADER_OPEN_TAG; + } else if (input.tryConsumeRegex(HEADER_CLOSE_PATTERN)) { + return HEADER_CLOSE_TAG; + } else if (input.tryConsumeRegex(BR_PATTERN)) { + return BR_TAG; + } else if (input.tryConsumeRegex(MOE_BEGIN_STRIP_COMMENT_PATTERN)) { + return MOE_BEGIN_STRIP_COMMENT; + } else if (input.tryConsumeRegex(MOE_END_STRIP_COMMENT_PATTERN)) { + return MOE_END_STRIP_COMMENT; + } else if (input.tryConsumeRegex(HTML_COMMENT_PATTERN)) { + return HTML_COMMENT; + } else if (input.tryConsumeRegex(LITERAL_PATTERN)) { + return LITERAL; + } + throw new AssertionError(); + } + + private boolean preserveExistingFormatting() { + return preDepth.isPositive() || tableDepth.isPositive() || codeDepth.isPositive(); + } + + private void checkMatchingTags() throws LexException { + if (braceDepth.isPositive() + || preDepth.isPositive() + || tableDepth.isPositive() + || codeDepth.isPositive()) { + throw new LexException(); + } + } + + /** + * Join together adjacent literal tokens, and join together adjacent whitespace tokens. + * + *

For literal tokens, this means something like {@code ["", "foo", ""] => + * ["foo"]}. See {@link #LITERAL_PATTERN} for discussion of why those tokens are separate + * to begin with. + * + *

Whitespace tokens are treated analogously. We don't really "want" to join whitespace tokens, + * but in the course of joining literals, we incidentally join whitespace, too. We do take + * advantage of the joining later on: It simplifies {@link #inferParagraphTags}. + * + *

Note that we do not merge a literal token and a whitespace token together. + */ + private static ImmutableList joinAdjacentLiteralsAndAdjacentWhitespace(List input) { /* - * We exclude < so that we don't swallow following HTML tags. This lets us fix up "foo

" (~400 - * hits in Google-internal code). We will join unnecessarily split "words" (like "foobar") - * in a later step. There's a similar story for braces. I'm not sure I actually need to exclude @ - * or *. TODO(cpovirk): Try removing them. + * Note: Our final token is always END_JAVADOC. This saves us some trouble: * - * Thanks to the "rejoin" step in joinAdjacentLiteralsAndAdjacentWhitespace(), we could get away - * with matching only one character here. That would eliminate the need for the regex entirely. - * That might be faster or slower than what we do now. + * - Our inner while() doesn't need a hasNext() check. + * + * - We don't need to check for leftover accumulated literals after we exit the loop. */ - private static final Pattern LITERAL_PATTERN = compile("^.[^ \t\n@<{}*]*", DOTALL); + ImmutableList.Builder output = ImmutableList.builder(); + StringBuilder accumulated = new StringBuilder(); - private static Pattern fullCommentPattern() { - return compile("^", DOTALL); + for (PeekingIterator tokens = peekingIterator(input.iterator()); tokens.hasNext(); ) { + if (tokens.peek().getType() == LITERAL) { + accumulated.append(tokens.peek().getValue()); + tokens.next(); + continue; + } + + /* + * IF we have accumulated some literals to join together (say, "foobar"), and IF we'll + * next see whitespace followed by a "@" literal, we need to join that together with the + * previous literals. That ensures that we won't insert a line break before the "@," turning + * it into a tag. + */ + + if (accumulated.length() == 0) { + output.add(tokens.peek()); + tokens.next(); + continue; + } + + StringBuilder seenWhitespace = new StringBuilder(); + while (tokens.peek().getType() == WHITESPACE) { + seenWhitespace.append(tokens.next().getValue()); + } + + if (tokens.peek().getType() == LITERAL && tokens.peek().getValue().startsWith("@")) { + // OK, we're in the case described above. + accumulated.append(" "); + accumulated.append(tokens.peek().getValue()); + tokens.next(); + continue; + } + + output.add(new Token(LITERAL, accumulated.toString())); + accumulated.setLength(0); + + if (seenWhitespace.length() > 0) { + output.add(new Token(WHITESPACE, seenWhitespace.toString())); + } + + // We have another token coming, possibly of type OTHER. Leave it for the next + // iteration. } - private static Pattern openTagPattern(String namePattern) { - return compile(format("^<(?:%s)\\b[^>]*>", namePattern), CASE_INSENSITIVE); + /* + * TODO(cpovirk): Another case where we could try to join tokens is if a line ends with + * /[^ -]-/, as in "non-\nblocking." + */ + return output.build(); + } + + /** + * Where the input has two consecutive line breaks between literals, insert a {@code

} tag + * between the literals. + * + *

This method must be called after {@link #joinAdjacentLiteralsAndAdjacentWhitespace}, as it + * assumes that adjacent whitespace tokens have already been joined. + */ + private static ImmutableList inferParagraphTags(List input) { + ImmutableList.Builder output = ImmutableList.builder(); + + for (PeekingIterator tokens = peekingIterator(input.iterator()); tokens.hasNext(); ) { + if (tokens.peek().getType() == LITERAL) { + output.add(tokens.next()); + + if (tokens.peek().getType() == WHITESPACE + && hasMultipleNewlines(tokens.peek().getValue())) { + output.add(tokens.next()); + + if (tokens.peek().getType() == LITERAL) { + output.add(new Token(PARAGRAPH_OPEN_TAG, "

")); + } + } + } else { + // TODO(cpovirk): Or just `continue` from the

case and move this out of the + // `else`? + output.add(tokens.next()); + } } - private static Pattern closeTagPattern(String namePattern) { - return compile(format("^]*>", namePattern), CASE_INSENSITIVE); + return output.build(); + + /* + * Note: We do not want to insert

tags inside

. Fortunately, the formatter gets that
+     * right without special effort on our part. The reason: Line breaks inside a 
 section are
+     * of type FORCED_NEWLINE rather than WHITESPACE.
+     */
+  }
+
+  /**
+   * Replaces whitespace after a {@code href=...>} token with an "optional link break." This allows
+   * us to output either {@code foo} or {@code \nfoo}, depending on
+   * how much space we have left on the line.
+   *
+   * 

This method must be called after {@link #joinAdjacentLiteralsAndAdjacentWhitespace}, as it + * assumes that adjacent whitespace tokens have already been joined. + */ + private static ImmutableList optionalizeSpacesAfterLinks(List input) { + ImmutableList.Builder output = ImmutableList.builder(); + + for (PeekingIterator tokens = peekingIterator(input.iterator()); tokens.hasNext(); ) { + if (tokens.peek().getType() == LITERAL && tokens.peek().getValue().matches("^href=[^>]*>")) { + output.add(tokens.next()); + + if (tokens.peek().getType() == WHITESPACE) { + output.add(new Token(OPTIONAL_LINE_BREAK, tokens.next().getValue())); + } + } else { + output.add(tokens.next()); + } } - static class LexException extends Exception {} + return output.build(); + + /* + * Note: We do not want to insert

tags inside

. Fortunately, the formatter gets that
+     * right without special effort on our part. The reason: Line breaks inside a 
 section are
+     * of type FORCED_NEWLINE rather than WHITESPACE.
+     */
+  }
+
+  /**
+   * Adjust indentation inside `
{@code` blocks.
+   *
+   * 

Also trim leading and trailing blank lines, and move the trailing `}` to its own line. + */ + private static ImmutableList deindentPreCodeBlocks(List input) { + ImmutableList.Builder output = ImmutableList.builder(); + for (PeekingIterator tokens = peekingIterator(input.iterator()); tokens.hasNext(); ) { + if (tokens.peek().getType() != PRE_OPEN_TAG) { + output.add(tokens.next()); + continue; + } + + output.add(tokens.next()); + List initialNewlines = new ArrayList<>(); + while (tokens.hasNext() && tokens.peek().getType() == FORCED_NEWLINE) { + initialNewlines.add(tokens.next()); + } + if (tokens.peek().getType() != LITERAL + || !tokens.peek().getValue().matches("[ \t]*[{]@code")) { + output.addAll(initialNewlines); + output.add(tokens.next()); + continue; + } + + deindentPreCodeBlock(output, tokens); + } + return output.build(); + } + + private static void deindentPreCodeBlock( + ImmutableList.Builder output, PeekingIterator tokens) { + Deque saved = new ArrayDeque<>(); + output.add(new Token(LITERAL, tokens.next().getValue().trim())); + while (tokens.hasNext() && tokens.peek().getType() != PRE_CLOSE_TAG) { + Token token = tokens.next(); + saved.addLast(token); + } + while (!saved.isEmpty() && saved.peekFirst().getType() == FORCED_NEWLINE) { + saved.removeFirst(); + } + while (!saved.isEmpty() && saved.peekLast().getType() == FORCED_NEWLINE) { + saved.removeLast(); + } + if (saved.isEmpty()) { + return; + } + + // move the trailing `}` to its own line + Token last = saved.peekLast(); + boolean trailingBrace = false; + if (last.getType() == LITERAL && last.getValue().endsWith("}")) { + saved.removeLast(); + if (last.length() > 1) { + saved.addLast( + new Token(LITERAL, last.getValue().substring(0, last.getValue().length() - 1))); + saved.addLast(new Token(FORCED_NEWLINE, null)); + } + trailingBrace = true; + } + + int trim = -1; + for (Token token : saved) { + if (token.getType() == LITERAL) { + int idx = CharMatcher.isNot(' ').indexIn(token.getValue()); + if (idx != -1 && (trim == -1 || idx < trim)) { + trim = idx; + } + } + } + + output.add(new Token(FORCED_NEWLINE, "\n")); + for (Token token : saved) { + if (token.getType() == LITERAL) { + output.add( + new Token( + LITERAL, + trim > 0 && token.length() > trim + ? token.getValue().substring(trim) + : token.getValue())); + } else { + output.add(token); + } + } + + if (trailingBrace) { + output.add(new Token(LITERAL, "}")); + } else { + output.add(new Token(FORCED_NEWLINE, "\n")); + } + } + + private static final CharMatcher NEWLINE = CharMatcher.is('\n'); + + private static boolean hasMultipleNewlines(String s) { + return NEWLINE.countIn(s) > 1; + } + + /* + * This also eats any trailing whitespace. We would be smart enough to ignore that, anyway -- + * except in the case of

/
, inside which we otherwise leave whitespace intact. + * + * We'd remove the trailing whitespace later on (in JavaCommentsHelper.rewrite), but I feel safer + * stripping it now: It otherwise might confuse our line-length count, which we use for wrapping. + */ + private static final Pattern NEWLINE_PATTERN = compile("^[ \t]*\n[ \t]*[*]?[ \t]?"); + + // We ensure elsewhere that we match this only at the beginning of a line. + // Only match tags that start with a lowercase letter, to avoid false matches on unescaped + // annotations inside code blocks. + // Match "@param " specially in case the is a

or other HTML tag we treat specially. + private static final Pattern FOOTER_TAG_PATTERN = compile("^@(param\\s+<\\w+>|[a-z]\\w*)"); + private static final Pattern MOE_BEGIN_STRIP_COMMENT_PATTERN = + compile("^"); + private static final Pattern MOE_END_STRIP_COMMENT_PATTERN = + compile("^"); + private static final Pattern HTML_COMMENT_PATTERN = fullCommentPattern(); + private static final Pattern PRE_OPEN_PATTERN = openTagPattern("pre"); + private static final Pattern PRE_CLOSE_PATTERN = closeTagPattern("pre"); + private static final Pattern CODE_OPEN_PATTERN = openTagPattern("code"); + private static final Pattern CODE_CLOSE_PATTERN = closeTagPattern("code"); + private static final Pattern TABLE_OPEN_PATTERN = openTagPattern("table"); + private static final Pattern TABLE_CLOSE_PATTERN = closeTagPattern("table"); + private static final Pattern LIST_OPEN_PATTERN = openTagPattern("ul|ol|dl"); + private static final Pattern LIST_CLOSE_PATTERN = closeTagPattern("ul|ol|dl"); + private static final Pattern LIST_ITEM_OPEN_PATTERN = openTagPattern("li|dt|dd"); + private static final Pattern LIST_ITEM_CLOSE_PATTERN = closeTagPattern("li|dt|dd"); + private static final Pattern HEADER_OPEN_PATTERN = openTagPattern("h[1-6]"); + private static final Pattern HEADER_CLOSE_PATTERN = closeTagPattern("h[1-6]"); + private static final Pattern PARAGRAPH_OPEN_PATTERN = openTagPattern("p"); + private static final Pattern PARAGRAPH_CLOSE_PATTERN = closeTagPattern("p"); + private static final Pattern BLOCKQUOTE_OPEN_PATTERN = openTagPattern("blockquote"); + private static final Pattern BLOCKQUOTE_CLOSE_PATTERN = closeTagPattern("blockquote"); + private static final Pattern BR_PATTERN = openTagPattern("br"); + private static final Pattern INLINE_TAG_OPEN_PATTERN = compile("^[{]@\\w*"); + /* + * We exclude < so that we don't swallow following HTML tags. This lets us fix up "foo

" (~400 + * hits in Google-internal code). We will join unnecessarily split "words" (like "foobar") + * in a later step. There's a similar story for braces. I'm not sure I actually need to exclude @ + * or *. TODO(cpovirk): Try removing them. + * + * Thanks to the "rejoin" step in joinAdjacentLiteralsAndAdjacentWhitespace(), we could get away + * with matching only one character here. That would eliminate the need for the regex entirely. + * That might be faster or slower than what we do now. + */ + private static final Pattern LITERAL_PATTERN = compile("^.[^ \t\n@<{}*]*", DOTALL); + + private static Pattern fullCommentPattern() { + return compile("^", DOTALL); + } + + private static Pattern openTagPattern(String namePattern) { + return compile(format("^<(?:%s)\\b[^>]*>", namePattern), CASE_INSENSITIVE); + } + + private static Pattern closeTagPattern(String namePattern) { + return compile(format("^]*>", namePattern), CASE_INSENSITIVE); + } + + static class LexException extends Exception {} } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/javadoc/JavadocWriter.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/javadoc/JavadocWriter.java index 2d309ab1e..9656d6ecd 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/javadoc/JavadocWriter.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/javadoc/JavadocWriter.java @@ -39,375 +39,375 @@ import com.google.googlejavaformat.java.javadoc.Token.Type; * are we inside?" */ final class JavadocWriter { - /** - * Tokens that are always pinned to the following token. For example, {@code

} in {@code - *

Foo bar} (never {@code

Foo bar} or {@code

\nFoo bar}). + /** + * Tokens that are always pinned to the following token. For example, {@code

} in {@code

Foo + * bar} (never {@code

Foo bar} or {@code

\nFoo bar}). + * + *

This is not the only kind of "pinning" that we do: See also the joining of LITERAL tokens + * done by the lexer. The special pinning here is necessary because these tokens are not of type + * LITERAL (because they require other special handling). + */ + private static final ImmutableSet START_OF_LINE_TOKENS = + immutableEnumSet(LIST_ITEM_OPEN_TAG, PARAGRAPH_OPEN_TAG, HEADER_OPEN_TAG); + + private final int blockIndent; + private final StringBuilder output = new StringBuilder(); + private final NestingCounter continuingListItemCount = new NestingCounter(); + private final NestingCounter continuingListCount = new NestingCounter(); + private final NestingCounter postWriteModifiedContinuingListCount = new NestingCounter(); + /** + * Whether we are inside an {@code

  • } element, excluding the case in which the {@code
  • } + * contains a {@code
      } or {@code
        } that we are also inside -- unless of course we're + * inside an {@code
      1. } element in that inner list :) + */ + private boolean continuingListItemOfInnermostList; + + private boolean continuingFooterTag; + private int remainingOnLine; + private boolean atStartOfLine; + private RequestedWhitespace requestedWhitespace = NONE; + private Token requestedMoeBeginStripComment; + private int indentForMoeEndStripComment; + private boolean wroteAnythingSignificant; + + JavadocWriter(int blockIndent) { + this.blockIndent = blockIndent; + } + + /** + * Requests whitespace between the previously written token and the next written token. The + * request may be honored, or it may be overridden by a request for "more significant" whitespace, + * like a newline. + */ + void requestWhitespace() { + requestWhitespace(WHITESPACE); + } + + void requestMoeBeginStripComment(Token token) { + // We queue this up so that we can put it after any requested whitespace. + requestedMoeBeginStripComment = checkNotNull(token); + } + + void writeBeginJavadoc() { + /* + * JavaCommentsHelper will make sure this is indented right. But it seems sensible enough that, + * if our input starts with ∕✱✱, so too does our output. + */ + output.append("/**"); + writeNewline(); + } + + void writeEndJavadoc() { + output.append("\n"); + appendSpaces(blockIndent + 1); + output.append("*/"); + } + + void writeFooterJavadocTagStart(Token token) { + // Close any unclosed lists (e.g.,
      2. without
          ). + // TODO(cpovirk): Actually generate
        , etc.? + /* + * TODO(cpovirk): Also generate and
  • if appropriate. This is necessary for + * idempotency in broken Javadoc. (We don't necessarily need that, but full idempotency may be a + * nice goal, especially if it helps us use a fuzzer to test.) Unfortunately, the writer doesn't + * currently know which of those tags are open. + */ + continuingListItemOfInnermostList = false; + continuingListItemCount.reset(); + continuingListCount.reset(); + /* + * There's probably no need for this, since its only effect is to disable blank lines in some + * cases -- and we're doing that already in the footer. + */ + postWriteModifiedContinuingListCount.reset(); + + if (!wroteAnythingSignificant) { + // Javadoc consists solely of tags. This is frowned upon in general but OK for + // @Overrides. + } else if (!continuingFooterTag) { + // First footer tag after a body tag. + requestBlankLine(); + } else { + // Subsequent footer tag. + continuingFooterTag = false; + requestNewline(); + } + writeToken(token); + continuingFooterTag = true; + } + + void writeListOpen(Token token) { + requestBlankLine(); + + writeToken(token); + continuingListItemOfInnermostList = false; + continuingListCount.increment(); + postWriteModifiedContinuingListCount.increment(); + + requestNewline(); + } + + void writeListClose(Token token) { + requestNewline(); + + continuingListItemCount.decrementIfPositive(); + continuingListCount.decrementIfPositive(); + writeToken(token); + postWriteModifiedContinuingListCount.decrementIfPositive(); + + requestBlankLine(); + } + + void writeListItemOpen(Token token) { + requestNewline(); + + if (continuingListItemOfInnermostList) { + continuingListItemOfInnermostList = false; + continuingListItemCount.decrementIfPositive(); + } + writeToken(token); + continuingListItemOfInnermostList = true; + continuingListItemCount.increment(); + } + + void writeHeaderOpen(Token token) { + requestBlankLine(); + + writeToken(token); + } + + void writeHeaderClose(Token token) { + writeToken(token); + + requestBlankLine(); + } + + void writeParagraphOpen(Token token) { + if (!wroteAnythingSignificant) { + /* + * The user included an initial

    tag. Ignore it, and don't request a blank line before the + * next token. + */ + return; + } + + requestBlankLine(); + + writeToken(token); + } + + void writeBlockquoteOpenOrClose(Token token) { + requestBlankLine(); + + writeToken(token); + + requestBlankLine(); + } + + void writePreOpen(Token token) { + requestBlankLine(); + + writeToken(token); + } + + void writePreClose(Token token) { + writeToken(token); + + requestBlankLine(); + } + + void writeCodeOpen(Token token) { + writeToken(token); + } + + void writeCodeClose(Token token) { + writeToken(token); + } + + void writeTableOpen(Token token) { + requestBlankLine(); + + writeToken(token); + } + + void writeTableClose(Token token) { + writeToken(token); + + requestBlankLine(); + } + + void writeMoeEndStripComment(Token token) { + writeLineBreakNoAutoIndent(); + appendSpaces(indentForMoeEndStripComment); + + // Or maybe just "output.append(token.getValue())?" I'm kind of surprised this is so easy. + writeToken(token); + + requestNewline(); + } + + void writeHtmlComment(Token token) { + requestNewline(); + + writeToken(token); + + requestNewline(); + } + + void writeBr(Token token) { + writeToken(token); + + requestNewline(); + } + + void writeLineBreakNoAutoIndent() { + writeNewline(NO_AUTO_INDENT); + } + + void writeLiteral(Token token) { + writeToken(token); + } + + @Override + public String toString() { + return output.toString(); + } + + private void requestBlankLine() { + requestWhitespace(BLANK_LINE); + } + + private void requestNewline() { + requestWhitespace(NEWLINE); + } + + private void requestWhitespace(RequestedWhitespace requestedWhitespace) { + this.requestedWhitespace = max(requestedWhitespace, this.requestedWhitespace); + } + + private void writeToken(Token token) { + if (requestedMoeBeginStripComment != null) { + requestNewline(); + } + + if (requestedWhitespace == BLANK_LINE + && (postWriteModifiedContinuingListCount.isPositive() || continuingFooterTag)) { + /* + * We don't write blank lines inside lists or footer tags, even in cases where we otherwise + * would (e.g., before a

    tag). Justification: We don't write blank lines _between_ list + * items or footer tags, so it would be strange to write blank lines _within_ one. Of course, + * an alternative approach would be to go ahead and write blank lines between items/tags, + * either always or only in the case that an item contains a blank line. + */ + requestedWhitespace = NEWLINE; + } + + if (requestedWhitespace == BLANK_LINE) { + writeBlankLine(); + requestedWhitespace = NONE; + } else if (requestedWhitespace == NEWLINE) { + writeNewline(); + requestedWhitespace = NONE; + } + boolean needWhitespace = (requestedWhitespace == WHITESPACE); + + /* + * Write a newline if necessary to respect the line limit. (But if we're at the beginning of the + * line, a newline won't help. Or it might help but only by separating "

    veryverylongword," + * which goes against our style.) + */ + if (!atStartOfLine && token.length() + (needWhitespace ? 1 : 0) > remainingOnLine) { + writeNewline(); + } + if (!atStartOfLine && needWhitespace) { + output.append(" "); + remainingOnLine--; + } + + if (requestedMoeBeginStripComment != null) { + output.append(requestedMoeBeginStripComment.getValue()); + requestedMoeBeginStripComment = null; + indentForMoeEndStripComment = innerIndent(); + requestNewline(); + writeToken(token); + return; + } + + output.append(token.getValue()); + + if (!START_OF_LINE_TOKENS.contains(token.getType())) { + atStartOfLine = false; + } + + /* + * TODO(cpovirk): We really want the number of "characters," not chars. Figure out what the + * right way of measuring that is (grapheme count (with BreakIterator?)? sum of widths of all + * graphemes? I don't think that our style guide is specific about this.). Moreover, I am + * probably brushing other problems with surrogates, etc. under the table. Hopefully I mostly + * get away with it by joining all non-space, non-tab characters together. * - *

    This is not the only kind of "pinning" that we do: See also the joining of LITERAL tokens - * done by the lexer. The special pinning here is necessary because these tokens are not of type - * LITERAL (because they require other special handling). + * Possibly the "width" question has no right answer: + * http://denisbider.blogspot.com/2015/09/when-monospace-fonts-arent-unicode.html */ - private static final ImmutableSet START_OF_LINE_TOKENS = - immutableEnumSet(LIST_ITEM_OPEN_TAG, PARAGRAPH_OPEN_TAG, HEADER_OPEN_TAG); + remainingOnLine -= token.length(); + requestedWhitespace = NONE; + wroteAnythingSignificant = true; + } - private final int blockIndent; - private final StringBuilder output = new StringBuilder(); - private final NestingCounter continuingListItemCount = new NestingCounter(); - private final NestingCounter continuingListCount = new NestingCounter(); - private final NestingCounter postWriteModifiedContinuingListCount = new NestingCounter(); - /** - * Whether we are inside an {@code

  • } element, excluding the case in which the {@code
  • } - * contains a {@code
      } or {@code
        } that we are also inside -- unless of course we're - * inside an {@code
      1. } element in that inner list :) - */ - private boolean continuingListItemOfInnermostList; + private void writeBlankLine() { + output.append("\n"); + appendSpaces(blockIndent + 1); + output.append("*"); + writeNewline(); + } - private boolean continuingFooterTag; - private int remainingOnLine; - private boolean atStartOfLine; - private RequestedWhitespace requestedWhitespace = NONE; - private Token requestedMoeBeginStripComment; - private int indentForMoeEndStripComment; - private boolean wroteAnythingSignificant; + private void writeNewline() { + writeNewline(AUTO_INDENT); + } - JavadocWriter(int blockIndent) { - this.blockIndent = blockIndent; + private void writeNewline(AutoIndent autoIndent) { + output.append("\n"); + appendSpaces(blockIndent + 1); + output.append("*"); + appendSpaces(1); + remainingOnLine = JavadocFormatter.MAX_LINE_LENGTH - blockIndent - 3; + if (autoIndent == AUTO_INDENT) { + appendSpaces(innerIndent()); + remainingOnLine -= innerIndent(); } + atStartOfLine = true; + } - /** - * Requests whitespace between the previously written token and the next written token. The - * request may be honored, or it may be overridden by a request for "more significant" - * whitespace, like a newline. - */ - void requestWhitespace() { - requestWhitespace(WHITESPACE); + private int innerIndent() { + int innerIndent = continuingListItemCount.value() * 4 + continuingListCount.value() * 2; + if (continuingFooterTag) { + innerIndent += 4; } + return innerIndent; + } - void requestMoeBeginStripComment(Token token) { - // We queue this up so that we can put it after any requested whitespace. - requestedMoeBeginStripComment = checkNotNull(token); - } + // If this is a hotspot, keep a String of many spaces around, and call append(string, start, + // end). + private void appendSpaces(int count) { + output.append(Strings.repeat(" ", count)); + } - void writeBeginJavadoc() { - /* - * JavaCommentsHelper will make sure this is indented right. But it seems sensible enough that, - * if our input starts with ∕✱✱, so too does our output. - */ - output.append("/**"); - writeNewline(); - } + /** + * The kind of whitespace that has been requested between the previous and next tokens. The order + * of the values is significant: It goes from lowest priority to highest. For example, if the + * previous token requests {@link #BLANK_LINE} after it but the next token requests only {@link + * #NEWLINE} before it, we insert {@link #BLANK_LINE}. + */ + enum RequestedWhitespace { + NONE, + WHITESPACE, + NEWLINE, + BLANK_LINE, + ; + } - void writeEndJavadoc() { - output.append("\n"); - appendSpaces(blockIndent + 1); - output.append("*/"); - } - - void writeFooterJavadocTagStart(Token token) { - // Close any unclosed lists (e.g.,
      2. without
          ). - // TODO(cpovirk): Actually generate
        , etc.? - /* - * TODO(cpovirk): Also generate
  • and if appropriate. This is necessary for - * idempotency in broken Javadoc. (We don't necessarily need that, but full idempotency may be a - * nice goal, especially if it helps us use a fuzzer to test.) Unfortunately, the writer doesn't - * currently know which of those tags are open. - */ - continuingListItemOfInnermostList = false; - continuingListItemCount.reset(); - continuingListCount.reset(); - /* - * There's probably no need for this, since its only effect is to disable blank lines in some - * cases -- and we're doing that already in the footer. - */ - postWriteModifiedContinuingListCount.reset(); - - if (!wroteAnythingSignificant) { - // Javadoc consists solely of tags. This is frowned upon in general but OK for - // @Overrides. - } else if (!continuingFooterTag) { - // First footer tag after a body tag. - requestBlankLine(); - } else { - // Subsequent footer tag. - continuingFooterTag = false; - requestNewline(); - } - writeToken(token); - continuingFooterTag = true; - } - - void writeListOpen(Token token) { - requestBlankLine(); - - writeToken(token); - continuingListItemOfInnermostList = false; - continuingListCount.increment(); - postWriteModifiedContinuingListCount.increment(); - - requestNewline(); - } - - void writeListClose(Token token) { - requestNewline(); - - continuingListItemCount.decrementIfPositive(); - continuingListCount.decrementIfPositive(); - writeToken(token); - postWriteModifiedContinuingListCount.decrementIfPositive(); - - requestBlankLine(); - } - - void writeListItemOpen(Token token) { - requestNewline(); - - if (continuingListItemOfInnermostList) { - continuingListItemOfInnermostList = false; - continuingListItemCount.decrementIfPositive(); - } - writeToken(token); - continuingListItemOfInnermostList = true; - continuingListItemCount.increment(); - } - - void writeHeaderOpen(Token token) { - requestBlankLine(); - - writeToken(token); - } - - void writeHeaderClose(Token token) { - writeToken(token); - - requestBlankLine(); - } - - void writeParagraphOpen(Token token) { - if (!wroteAnythingSignificant) { - /* - * The user included an initial

    tag. Ignore it, and don't request a blank line before the - * next token. - */ - return; - } - - requestBlankLine(); - - writeToken(token); - } - - void writeBlockquoteOpenOrClose(Token token) { - requestBlankLine(); - - writeToken(token); - - requestBlankLine(); - } - - void writePreOpen(Token token) { - requestBlankLine(); - - writeToken(token); - } - - void writePreClose(Token token) { - writeToken(token); - - requestBlankLine(); - } - - void writeCodeOpen(Token token) { - writeToken(token); - } - - void writeCodeClose(Token token) { - writeToken(token); - } - - void writeTableOpen(Token token) { - requestBlankLine(); - - writeToken(token); - } - - void writeTableClose(Token token) { - writeToken(token); - - requestBlankLine(); - } - - void writeMoeEndStripComment(Token token) { - writeLineBreakNoAutoIndent(); - appendSpaces(indentForMoeEndStripComment); - - // Or maybe just "output.append(token.getValue())?" I'm kind of surprised this is so easy. - writeToken(token); - - requestNewline(); - } - - void writeHtmlComment(Token token) { - requestNewline(); - - writeToken(token); - - requestNewline(); - } - - void writeBr(Token token) { - writeToken(token); - - requestNewline(); - } - - void writeLineBreakNoAutoIndent() { - writeNewline(NO_AUTO_INDENT); - } - - void writeLiteral(Token token) { - writeToken(token); - } - - @Override - public String toString() { - return output.toString(); - } - - private void requestBlankLine() { - requestWhitespace(BLANK_LINE); - } - - private void requestNewline() { - requestWhitespace(NEWLINE); - } - - private void requestWhitespace(RequestedWhitespace requestedWhitespace) { - this.requestedWhitespace = max(requestedWhitespace, this.requestedWhitespace); - } - - private void writeToken(Token token) { - if (requestedMoeBeginStripComment != null) { - requestNewline(); - } - - if (requestedWhitespace == BLANK_LINE - && (postWriteModifiedContinuingListCount.isPositive() || continuingFooterTag)) { - /* - * We don't write blank lines inside lists or footer tags, even in cases where we otherwise - * would (e.g., before a

    tag). Justification: We don't write blank lines _between_ list - * items or footer tags, so it would be strange to write blank lines _within_ one. Of course, - * an alternative approach would be to go ahead and write blank lines between items/tags, - * either always or only in the case that an item contains a blank line. - */ - requestedWhitespace = NEWLINE; - } - - if (requestedWhitespace == BLANK_LINE) { - writeBlankLine(); - requestedWhitespace = NONE; - } else if (requestedWhitespace == NEWLINE) { - writeNewline(); - requestedWhitespace = NONE; - } - boolean needWhitespace = (requestedWhitespace == WHITESPACE); - - /* - * Write a newline if necessary to respect the line limit. (But if we're at the beginning of the - * line, a newline won't help. Or it might help but only by separating "

    veryverylongword," - * which goes against our style.) - */ - if (!atStartOfLine && token.length() + (needWhitespace ? 1 : 0) > remainingOnLine) { - writeNewline(); - } - if (!atStartOfLine && needWhitespace) { - output.append(" "); - remainingOnLine--; - } - - if (requestedMoeBeginStripComment != null) { - output.append(requestedMoeBeginStripComment.getValue()); - requestedMoeBeginStripComment = null; - indentForMoeEndStripComment = innerIndent(); - requestNewline(); - writeToken(token); - return; - } - - output.append(token.getValue()); - - if (!START_OF_LINE_TOKENS.contains(token.getType())) { - atStartOfLine = false; - } - - /* - * TODO(cpovirk): We really want the number of "characters," not chars. Figure out what the - * right way of measuring that is (grapheme count (with BreakIterator?)? sum of widths of all - * graphemes? I don't think that our style guide is specific about this.). Moreover, I am - * probably brushing other problems with surrogates, etc. under the table. Hopefully I mostly - * get away with it by joining all non-space, non-tab characters together. - * - * Possibly the "width" question has no right answer: - * http://denisbider.blogspot.com/2015/09/when-monospace-fonts-arent-unicode.html - */ - remainingOnLine -= token.length(); - requestedWhitespace = NONE; - wroteAnythingSignificant = true; - } - - private void writeBlankLine() { - output.append("\n"); - appendSpaces(blockIndent + 1); - output.append("*"); - writeNewline(); - } - - private void writeNewline() { - writeNewline(AUTO_INDENT); - } - - private void writeNewline(AutoIndent autoIndent) { - output.append("\n"); - appendSpaces(blockIndent + 1); - output.append("*"); - appendSpaces(1); - remainingOnLine = JavadocFormatter.MAX_LINE_LENGTH - blockIndent - 3; - if (autoIndent == AUTO_INDENT) { - appendSpaces(innerIndent()); - remainingOnLine -= innerIndent(); - } - atStartOfLine = true; - } - - private int innerIndent() { - int innerIndent = continuingListItemCount.value() * 4 + continuingListCount.value() * 2; - if (continuingFooterTag) { - innerIndent += 4; - } - return innerIndent; - } - - // If this is a hotspot, keep a String of many spaces around, and call append(string, start, - // end). - private void appendSpaces(int count) { - output.append(Strings.repeat(" ", count)); - } - - /** - * The kind of whitespace that has been requested between the previous and next tokens. The - * order of the values is significant: It goes from lowest priority to highest. For example, if - * the previous token requests {@link #BLANK_LINE} after it but the next token requests only - * {@link #NEWLINE} before it, we insert {@link #BLANK_LINE}. - */ - enum RequestedWhitespace { - NONE, - WHITESPACE, - NEWLINE, - BLANK_LINE, - ; - } - - enum AutoIndent { - AUTO_INDENT, - NO_AUTO_INDENT - } + enum AutoIndent { + AUTO_INDENT, + NO_AUTO_INDENT + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/javadoc/NestingCounter.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/javadoc/NestingCounter.java index 16599435a..43e7125c9 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/javadoc/NestingCounter.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/javadoc/NestingCounter.java @@ -16,33 +16,33 @@ package com.google.googlejavaformat.java.javadoc; /** Mutable integer for tracking the level of nesting. */ final class NestingCounter { - private int value; + private int value; - int value() { - return value; - } + int value() { + return value; + } - void increment() { - value++; - } + void increment() { + value++; + } - void incrementIfPositive() { - if (value > 0) { - value++; - } + void incrementIfPositive() { + if (value > 0) { + value++; } + } - void decrementIfPositive() { - if (value > 0) { - value--; - } + void decrementIfPositive() { + if (value > 0) { + value--; } + } - boolean isPositive() { - return value > 0; - } + boolean isPositive() { + return value > 0; + } - void reset() { - value = 0; - } + void reset() { + value = 0; + } } diff --git a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/javadoc/Token.java b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/javadoc/Token.java index 8e5fcab53..d617824b0 100644 --- a/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/javadoc/Token.java +++ b/build-tools/google-java-format/src/main/java/com/google/googlejavaformat/java/javadoc/Token.java @@ -20,113 +20,112 @@ package com.google.googlejavaformat.java.javadoc; * are in {@link JavadocLexer}. */ final class Token { + /** + * Javadoc token type. + * + *

    The general idea is that every token that requires special handling (extra line breaks, + * indentation, forcing or forbidding whitespace) from {@link JavadocWriter} gets its own type. + * But I haven't been super careful about it, so I'd imagine that we could merge or remove some of + * these if we wanted. (For example, PARAGRAPH_CLOSE_TAG and LIST_ITEM_CLOSE_TAG could share a + * common IGNORABLE token type. But their corresponding OPEN tags exist, so I've kept the CLOSE + * tags.) + * + *

    Note, though, that tokens of the same type may still have been handled differently by {@link + * JavadocLexer} when it created them. For example, LITERAL is used for both plain text and inline + * tags, even though the two affect the lexer's state differently. + */ + enum Type { + /** ∕✱✱ */ + BEGIN_JAVADOC, + /** ✱∕ */ + END_JAVADOC, + /** The {@code @foo} that begins a block Javadoc tag like {@code @throws}. */ + FOOTER_JAVADOC_TAG_START, + LIST_OPEN_TAG, + LIST_CLOSE_TAG, + LIST_ITEM_OPEN_TAG, + LIST_ITEM_CLOSE_TAG, + HEADER_OPEN_TAG, + HEADER_CLOSE_TAG, + PARAGRAPH_OPEN_TAG, + PARAGRAPH_CLOSE_TAG, + // TODO(cpovirk): Support

    (probably identically to
    ). + BLOCKQUOTE_OPEN_TAG, + BLOCKQUOTE_CLOSE_TAG, + PRE_OPEN_TAG, + PRE_CLOSE_TAG, + CODE_OPEN_TAG, + CODE_CLOSE_TAG, + TABLE_OPEN_TAG, + TABLE_CLOSE_TAG, + /** {@code } */ + MOE_BEGIN_STRIP_COMMENT, + /** {@code } */ + MOE_END_STRIP_COMMENT, + HTML_COMMENT, + // TODO(cpovirk): Support
    (probably a blank line before and after). + BR_TAG, /** - * Javadoc token type. - * - *

    The general idea is that every token that requires special handling (extra line breaks, - * indentation, forcing or forbidding whitespace) from {@link JavadocWriter} gets its own type. - * But I haven't been super careful about it, so I'd imagine that we could merge or remove some - * of these if we wanted. (For example, PARAGRAPH_CLOSE_TAG and LIST_ITEM_CLOSE_TAG could share - * a common IGNORABLE token type. But their corresponding OPEN tags exist, so I've kept the - * CLOSE tags.) - * - *

    Note, though, that tokens of the same type may still have been handled differently by - * {@link JavadocLexer} when it created them. For example, LITERAL is used for both plain text - * and inline tags, even though the two affect the lexer's state differently. + * Whitespace that is not in a {@code

    } or {@code } section. Whitespace includes
    +     * leading newlines, asterisks, and tabs and spaces. In the output, it is translated to newlines
    +     * (with leading spaces and asterisks) or spaces.
          */
    -    enum Type {
    -        /** ∕✱✱ */
    -        BEGIN_JAVADOC,
    -        /** ✱∕ */
    -        END_JAVADOC,
    -        /** The {@code @foo} that begins a block Javadoc tag like {@code @throws}. */
    -        FOOTER_JAVADOC_TAG_START,
    -        LIST_OPEN_TAG,
    -        LIST_CLOSE_TAG,
    -        LIST_ITEM_OPEN_TAG,
    -        LIST_ITEM_CLOSE_TAG,
    -        HEADER_OPEN_TAG,
    -        HEADER_CLOSE_TAG,
    -        PARAGRAPH_OPEN_TAG,
    -        PARAGRAPH_CLOSE_TAG,
    -        // TODO(cpovirk): Support 
    (probably identically to
    ). - BLOCKQUOTE_OPEN_TAG, - BLOCKQUOTE_CLOSE_TAG, - PRE_OPEN_TAG, - PRE_CLOSE_TAG, - CODE_OPEN_TAG, - CODE_CLOSE_TAG, - TABLE_OPEN_TAG, - TABLE_CLOSE_TAG, - /** {@code } */ - MOE_BEGIN_STRIP_COMMENT, - /** {@code } */ - MOE_END_STRIP_COMMENT, - HTML_COMMENT, - // TODO(cpovirk): Support
    (probably a blank line before and after). - BR_TAG, - /** - * Whitespace that is not in a {@code
    } or {@code 
    } section. Whitespace includes - * leading newlines, asterisks, and tabs and spaces. In the output, it is translated to - * newlines (with leading spaces and asterisks) or spaces. - */ - WHITESPACE, - /** - * A newline in a {@code
    } or {@code 
    } section. We preserve user formatting in - * these sections, including newlines. - */ - FORCED_NEWLINE, - /** - * Token that permits but does not force a line break. The way that we accomplish this is - * somewhat indirect: As far as {@link JavadocWriter} is concerned, this token is - * meaningless. But its mere existence prevents {@link JavadocLexer} from joining two {@link - * #LITERAL} tokens that would otherwise be adjacent. Since this token is not real - * whitespace, the writer may end up writing the literals together with no space between, - * just as if they'd been joined. However, if they don't fit together on the line, the - * writer will write the first one, start a new line, and write the second. Hence, the token - * acts as an optional line break. - */ - OPTIONAL_LINE_BREAK, - /** - * Anything else: {@code foo}, {@code }, {@code {@code foo}} etc. {@link JavadocLexer} - * sometimes creates adjacent literal tokens, which it then merges into a single, larger - * literal token before returning its output. - * - *

    This also includes whitespace in a {@code

    } or {@code 
    } section. We - * preserve user formatting in these sections, including arbitrary numbers of spaces. By - * treating such whitespace as a literal, we can merge it with adjacent literals, preventing - * us from autowrapping inside these sections -- and doing so naively, to boot. The wrapped - * line would have no indentation after "* " or, possibly worse, it might begin with an - * arbitrary amount of whitespace that didn't fit on the previous line. Of course, by doing - * this, we're potentially creating lines of more than 100 characters. But it seems fair to - * call in the humans to resolve such problems. - */ - LITERAL, - ; - } + WHITESPACE, + /** + * A newline in a {@code
    } or {@code 
    } section. We preserve user formatting in these + * sections, including newlines. + */ + FORCED_NEWLINE, + /** + * Token that permits but does not force a line break. The way that we accomplish this is + * somewhat indirect: As far as {@link JavadocWriter} is concerned, this token is meaningless. + * But its mere existence prevents {@link JavadocLexer} from joining two {@link #LITERAL} tokens + * that would otherwise be adjacent. Since this token is not real whitespace, the writer may end + * up writing the literals together with no space between, just as if they'd been joined. + * However, if they don't fit together on the line, the writer will write the first one, start a + * new line, and write the second. Hence, the token acts as an optional line break. + */ + OPTIONAL_LINE_BREAK, + /** + * Anything else: {@code foo}, {@code }, {@code {@code foo}} etc. {@link JavadocLexer} + * sometimes creates adjacent literal tokens, which it then merges into a single, larger literal + * token before returning its output. + * + *

    This also includes whitespace in a {@code

    } or {@code 
    } section. We preserve + * user formatting in these sections, including arbitrary numbers of spaces. By treating such + * whitespace as a literal, we can merge it with adjacent literals, preventing us from + * autowrapping inside these sections -- and doing so naively, to boot. The wrapped line would + * have no indentation after "* " or, possibly worse, it might begin with an arbitrary amount of + * whitespace that didn't fit on the previous line. Of course, by doing this, we're potentially + * creating lines of more than 100 characters. But it seems fair to call in the humans to + * resolve such problems. + */ + LITERAL, + ; + } - private final Type type; - private final String value; + private final Type type; + private final String value; - Token(Type type, String value) { - this.type = type; - this.value = value; - } + Token(Type type, String value) { + this.type = type; + this.value = value; + } - Type getType() { - return type; - } + Type getType() { + return type; + } - String getValue() { - return value; - } + String getValue() { + return value; + } - int length() { - return value.length(); - } + int length() { + return value.length(); + } - @Override - public String toString() { - return "\n" + getType() + ": " + getValue(); - } + @Override + public String toString() { + return "\n" + getType() + ": " + getValue(); + } } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/CancelChecker.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/CancelChecker.java index 330276b9b..cd921da9a 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/CancelChecker.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/CancelChecker.java @@ -1,39 +1,31 @@ /****************************************************************************** * Copyright (c) 2016 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc; import java.util.concurrent.CancellationException; -/** - * Used for processing requests with cancellation support. - */ +/** Used for processing requests with cancellation support. */ public interface CancelChecker { - /** - * Throw a {@link CancellationException} if the currently processed request - * has been canceled. - */ - void checkCanceled(); - - /** - * Check for cancellation without throwing an exception. - */ - default boolean isCanceled() { - try { - checkCanceled(); - } catch (CancellationException ce) { - return true; - } - return false; - } + /** Throw a {@link CancellationException} if the currently processed request has been canceled. */ + void checkCanceled(); + /** Check for cancellation without throwing an exception. */ + default boolean isCanceled() { + try { + checkCanceled(); + } catch (CancellationException ce) { + return true; + } + return false; + } } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/CompletableFutures.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/CompletableFutures.java index 44682f9d8..3c5295724 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/CompletableFutures.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/CompletableFutures.java @@ -1,12 +1,12 @@ /****************************************************************************** * Copyright (c) 2016 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc; @@ -17,53 +17,53 @@ import java.util.concurrent.Executor; import java.util.function.Function; public final class CompletableFutures { - private CompletableFutures() {} - - /** - * A utility method to create a {@link CompletableFuture} with cancellation support. - * - * @param code a function that accepts a {@link CancelChecker} and returns the to be computed value - * @return a future - */ - public static CompletableFuture computeAsync(Function code) { - CompletableFuture start = new CompletableFuture<>(); - CompletableFuture result = start.thenApplyAsync(code); - start.complete(new FutureCancelChecker(result)); - return result; - } - - /** - * A utility method to create a {@link CompletableFuture} with cancellation support. - * - * @param code a function that accepts a {@link CancelChecker} and returns the to be computed value - * @return a future - */ - public static CompletableFuture computeAsync(Executor executor, Function code) { - CompletableFuture start = new CompletableFuture<>(); - CompletableFuture result = start.thenApplyAsync(code, executor); - start.complete(new FutureCancelChecker(result)); - return result; - } - - public static class FutureCancelChecker implements CancelChecker { - - private final CompletableFuture future; - - public FutureCancelChecker(CompletableFuture future) { - this.future = future; - } + private CompletableFutures() {} - @Override - public void checkCanceled() { - if (future.isCancelled()) - throw new CancellationException(); - } - - @Override - public boolean isCanceled() { - return future.isCancelled(); - } - - } - + /** + * A utility method to create a {@link CompletableFuture} with cancellation support. + * + * @param code a function that accepts a {@link CancelChecker} and returns the to be computed + * value + * @return a future + */ + public static CompletableFuture computeAsync(Function code) { + CompletableFuture start = new CompletableFuture<>(); + CompletableFuture result = start.thenApplyAsync(code); + start.complete(new FutureCancelChecker(result)); + return result; + } + + /** + * A utility method to create a {@link CompletableFuture} with cancellation support. + * + * @param code a function that accepts a {@link CancelChecker} and returns the to be computed + * value + * @return a future + */ + public static CompletableFuture computeAsync( + Executor executor, Function code) { + CompletableFuture start = new CompletableFuture<>(); + CompletableFuture result = start.thenApplyAsync(code, executor); + start.complete(new FutureCancelChecker(result)); + return result; + } + + public static class FutureCancelChecker implements CancelChecker { + + private final CompletableFuture future; + + public FutureCancelChecker(CompletableFuture future) { + this.future = future; + } + + @Override + public void checkCanceled() { + if (future.isCancelled()) throw new CancellationException(); + } + + @Override + public boolean isCanceled() { + return future.isCancelled(); + } + } } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/Endpoint.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/Endpoint.java index 82cac6c7d..5a95e452f 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/Endpoint.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/Endpoint.java @@ -1,25 +1,22 @@ /****************************************************************************** * Copyright (c) 2016 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc; import java.util.concurrent.CompletableFuture; -/** - * An endpoint is a generic interface that accepts jsonrpc requests and notifications. - */ +/** An endpoint is a generic interface that accepts jsonrpc requests and notifications. */ public interface Endpoint { - CompletableFuture request(String method, Object parameter); - - void notify(String method, Object parameter); - + CompletableFuture request(String method, Object parameter); + + void notify(String method, Object parameter); } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/JsonRpcException.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/JsonRpcException.java index 7cbc786aa..65937577e 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/JsonRpcException.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/JsonRpcException.java @@ -1,12 +1,12 @@ /****************************************************************************** * Copyright (c) 2018 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc; @@ -16,29 +16,24 @@ import java.io.InterruptedIOException; import java.net.SocketException; import java.nio.channels.ClosedChannelException; -/** - * An exception thrown when accessing the JSON-RPC communication channel fails. - */ +/** An exception thrown when accessing the JSON-RPC communication channel fails. */ public class JsonRpcException extends RuntimeException { - - /** - * Whether the given exception indicates that the currently accessed stream has been closed. - */ - public static boolean indicatesStreamClosed(Throwable thr) { - return thr instanceof InterruptedIOException - || thr instanceof ClosedChannelException - || thr instanceof IOException && "Pipe closed".equals(thr.getMessage()) - || thr instanceof SocketException && - ("Connection reset".equals(thr.getMessage()) || - "Socket closed".equals(thr.getMessage()) || - "Broken pipe (Write failed)".equals(thr.getMessage())) - || thr instanceof JsonRpcException && indicatesStreamClosed(thr.getCause()); - } - private static final long serialVersionUID = -7952794305289314670L; - - public JsonRpcException(Throwable cause) { - super(cause); - } + /** Whether the given exception indicates that the currently accessed stream has been closed. */ + public static boolean indicatesStreamClosed(Throwable thr) { + return thr instanceof InterruptedIOException + || thr instanceof ClosedChannelException + || thr instanceof IOException && "Pipe closed".equals(thr.getMessage()) + || thr instanceof SocketException + && ("Connection reset".equals(thr.getMessage()) + || "Socket closed".equals(thr.getMessage()) + || "Broken pipe (Write failed)".equals(thr.getMessage())) + || thr instanceof JsonRpcException && indicatesStreamClosed(thr.getCause()); + } + private static final long serialVersionUID = -7952794305289314670L; + + public JsonRpcException(Throwable cause) { + super(cause); + } } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/Launcher.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/Launcher.java index b543a739e..eaea321fa 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/Launcher.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/Launcher.java @@ -1,12 +1,12 @@ /****************************************************************************** * Copyright (c) 2016-2018 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc; @@ -37,406 +37,438 @@ import org.eclipse.lsp4j.jsonrpc.validation.ReflectiveMessageValidator; import com.google.gson.GsonBuilder; /** - * This is the entry point for applications that use LSP4J. A Launcher does all the wiring that is necessary to connect - * your endpoint via an input stream and an output stream. - * + * This is the entry point for applications that use LSP4J. A Launcher does all the wiring that is + * necessary to connect your endpoint via an input stream and an output stream. + * * @param remote service interface type */ public interface Launcher { - - /** - * Create a new Launcher for a given local service object, a given remote interface and an input and output stream. - * - * @param localService - the object that receives method calls from the remote service - * @param remoteInterface - an interface on which RPC methods are looked up - * @param in - input stream to listen for incoming messages - * @param out - output stream to send outgoing messages - */ - static Launcher createLauncher(Object localService, Class remoteInterface, InputStream in, OutputStream out) { - return new Builder() - .setLocalService(localService) - .setRemoteInterface(remoteInterface) - .setInput(in) - .setOutput(out) - .create(); - } - - /** - * Create a new Launcher for a given local service object, a given remote interface and an input and output stream, - * and set up message validation and tracing. - * - * @param localService - the object that receives method calls from the remote service - * @param remoteInterface - an interface on which RPC methods are looked up - * @param in - input stream to listen for incoming messages - * @param out - output stream to send outgoing messages - * @param validate - whether messages should be validated with the {@link ReflectiveMessageValidator} - * @param trace - a writer to which incoming and outgoing messages are traced, or {@code null} to disable tracing - */ - static Launcher createLauncher(Object localService, Class remoteInterface, InputStream in, OutputStream out, - boolean validate, PrintWriter trace) { - return new Builder() - .setLocalService(localService) - .setRemoteInterface(remoteInterface) - .setInput(in) - .setOutput(out) - .validateMessages(validate) - .traceMessages(trace) - .create(); - } - - /** - * Create a new Launcher for a given local service object, a given remote interface and an input and output stream. - * Threads are started with the given executor service. The wrapper function is applied to the incoming and - * outgoing message streams so additional message handling such as validation and tracing can be included. - * - * @param localService - the object that receives method calls from the remote service - * @param remoteInterface - an interface on which RPC methods are looked up - * @param in - input stream to listen for incoming messages - * @param out - output stream to send outgoing messages - * @param executorService - the executor service used to start threads - * @param wrapper - a function for plugging in additional message consumers - */ - static Launcher createLauncher(Object localService, Class remoteInterface, InputStream in, OutputStream out, - ExecutorService executorService, Function wrapper) { - return createIoLauncher(localService, remoteInterface, in, out, executorService, wrapper); - } - - /** - * Create a new Launcher for a given local service object, a given remote interface and an input and output stream. - * Threads are started with the given executor service. The wrapper function is applied to the incoming and - * outgoing message streams so additional message handling such as validation and tracing can be included. - * - * @param localService - the object that receives method calls from the remote service - * @param remoteInterface - an interface on which RPC methods are looked up - * @param in - input stream to listen for incoming messages - * @param out - output stream to send outgoing messages - * @param executorService - the executor service used to start threads - * @param wrapper - a function for plugging in additional message consumers - */ - static Launcher createIoLauncher(Object localService, Class remoteInterface, InputStream in, OutputStream out, - ExecutorService executorService, Function wrapper) { - return new Builder() - .setLocalService(localService) - .setRemoteInterface(remoteInterface) - .setInput(in) - .setOutput(out) - .setExecutorService(executorService) - .wrapMessages(wrapper) - .create(); - } - - /** - * Create a new Launcher for a given local service object, a given remote interface and an input and output stream. - * Threads are started with the given executor service. The wrapper function is applied to the incoming and - * outgoing message streams so additional message handling such as validation and tracing can be included. - * The {@code configureGson} function can be used to register additional type adapters in the {@link GsonBuilder} - * in order to support protocol classes that cannot be handled by Gson's reflective capabilities. - * - * @param localService - the object that receives method calls from the remote service - * @param remoteInterface - an interface on which RPC methods are looked up - * @param in - input stream to listen for incoming messages - * @param out - output stream to send outgoing messages - * @param executorService - the executor service used to start threads - * @param wrapper - a function for plugging in additional message consumers - * @param configureGson - a function for Gson configuration - */ - static Launcher createIoLauncher(Object localService, Class remoteInterface, InputStream in, OutputStream out, - ExecutorService executorService, Function wrapper, Consumer configureGson) { - return new Builder() - .setLocalService(localService) - .setRemoteInterface(remoteInterface) - .setInput(in) - .setOutput(out) - .setExecutorService(executorService) - .wrapMessages(wrapper) - .configureGson(configureGson) - .create(); - } - - /** - * Create a new Launcher for a given local service object, a given remote interface and an input and output stream. - * Threads are started with the given executor service. The wrapper function is applied to the incoming and - * outgoing message streams so additional message handling such as validation and tracing can be included. - * The {@code configureGson} function can be used to register additional type adapters in the {@link GsonBuilder} - * in order to support protocol classes that cannot be handled by Gson's reflective capabilities. - * - * @param localService - the object that receives method calls from the remote service - * @param remoteInterface - an interface on which RPC methods are looked up - * @param in - input stream to listen for incoming messages - * @param out - output stream to send outgoing messages - * @param validate - whether messages should be validated with the {@link ReflectiveMessageValidator} - * @param executorService - the executor service used to start threads - * @param wrapper - a function for plugging in additional message consumers - * @param configureGson - a function for Gson configuration - */ - static Launcher createIoLauncher(Object localService, Class remoteInterface, InputStream in, OutputStream out, boolean validate, - ExecutorService executorService, Function wrapper, Consumer configureGson) { - return new Builder() - .setLocalService(localService) - .setRemoteInterface(remoteInterface) - .setInput(in) - .setOutput(out) - .validateMessages(validate) - .setExecutorService(executorService) - .wrapMessages(wrapper) - .configureGson(configureGson) - .create(); - } - - /** - * Create a new Launcher for a collection of local service objects, a collection of remote interfaces and an - * input and output stream. Threads are started with the given executor service. The wrapper function is applied - * to the incoming and outgoing message streams so additional message handling such as validation and tracing - * can be included. The {@code configureGson} function can be used to register additional type adapters in - * the {@link GsonBuilder} in order to support protocol classes that cannot be handled by Gson's reflective - * capabilities. - * - * @param localServices - the objects that receive method calls from the remote services - * @param remoteInterfaces - interfaces on which RPC methods are looked up - * @param classLoader - a class loader that is able to resolve all given interfaces - * @param in - input stream to listen for incoming messages - * @param out - output stream to send outgoing messages - * @param executorService - the executor service used to start threads - * @param wrapper - a function for plugging in additional message consumers - * @param configureGson - a function for Gson configuration - */ - static Launcher createIoLauncher(Collection localServices, Collection> remoteInterfaces, ClassLoader classLoader, - InputStream in, OutputStream out, ExecutorService executorService, Function wrapper, - Consumer configureGson) { - return new Builder() - .setLocalServices(localServices) - .setRemoteInterfaces(remoteInterfaces) - .setClassLoader(classLoader) - .setInput(in) - .setOutput(out) - .setExecutorService(executorService) - .wrapMessages(wrapper) - .configureGson(configureGson) - .create(); - } - - - //------------------------------ Builder Class ------------------------------// - - /** - * The launcher builder wires up all components for JSON-RPC communication. - * - * @param remote service interface type - */ - public static class Builder { - - protected Collection localServices; - protected Collection> remoteInterfaces; - protected InputStream input; - protected OutputStream output; - protected ExecutorService executorService; - protected Function messageWrapper; - protected Function exceptionHandler; - protected boolean validateMessages; - protected Consumer configureGson; - protected ClassLoader classLoader; - protected MessageTracer messageTracer; - - public Builder setLocalService(Object localService) { - this.localServices = Collections.singletonList(localService); - return this; - } - public Builder setLocalServices(Collection localServices) { - this.localServices = localServices; - return this; - } + /** + * Create a new Launcher for a given local service object, a given remote interface and an input + * and output stream. + * + * @param localService - the object that receives method calls from the remote service + * @param remoteInterface - an interface on which RPC methods are looked up + * @param in - input stream to listen for incoming messages + * @param out - output stream to send outgoing messages + */ + static Launcher createLauncher( + Object localService, Class remoteInterface, InputStream in, OutputStream out) { + return new Builder() + .setLocalService(localService) + .setRemoteInterface(remoteInterface) + .setInput(in) + .setOutput(out) + .create(); + } - public Builder setRemoteInterface(Class remoteInterface) { - this.remoteInterfaces = Collections.singletonList(remoteInterface); - return this; - } - - public Builder setRemoteInterfaces(Collection> remoteInterfaces) { - this.remoteInterfaces = remoteInterfaces; - return this; - } + /** + * Create a new Launcher for a given local service object, a given remote interface and an input + * and output stream, and set up message validation and tracing. + * + * @param localService - the object that receives method calls from the remote service + * @param remoteInterface - an interface on which RPC methods are looked up + * @param in - input stream to listen for incoming messages + * @param out - output stream to send outgoing messages + * @param validate - whether messages should be validated with the {@link + * ReflectiveMessageValidator} + * @param trace - a writer to which incoming and outgoing messages are traced, or {@code null} to + * disable tracing + */ + static Launcher createLauncher( + Object localService, + Class remoteInterface, + InputStream in, + OutputStream out, + boolean validate, + PrintWriter trace) { + return new Builder() + .setLocalService(localService) + .setRemoteInterface(remoteInterface) + .setInput(in) + .setOutput(out) + .validateMessages(validate) + .traceMessages(trace) + .create(); + } - public Builder setInput(InputStream input) { - this.input = input; - return this; - } + /** + * Create a new Launcher for a given local service object, a given remote interface and an input + * and output stream. Threads are started with the given executor service. The wrapper function is + * applied to the incoming and outgoing message streams so additional message handling such as + * validation and tracing can be included. + * + * @param localService - the object that receives method calls from the remote service + * @param remoteInterface - an interface on which RPC methods are looked up + * @param in - input stream to listen for incoming messages + * @param out - output stream to send outgoing messages + * @param executorService - the executor service used to start threads + * @param wrapper - a function for plugging in additional message consumers + */ + static Launcher createLauncher( + Object localService, + Class remoteInterface, + InputStream in, + OutputStream out, + ExecutorService executorService, + Function wrapper) { + return createIoLauncher(localService, remoteInterface, in, out, executorService, wrapper); + } - public Builder setOutput(OutputStream output) { - this.output = output; - return this; - } + /** + * Create a new Launcher for a given local service object, a given remote interface and an input + * and output stream. Threads are started with the given executor service. The wrapper function is + * applied to the incoming and outgoing message streams so additional message handling such as + * validation and tracing can be included. + * + * @param localService - the object that receives method calls from the remote service + * @param remoteInterface - an interface on which RPC methods are looked up + * @param in - input stream to listen for incoming messages + * @param out - output stream to send outgoing messages + * @param executorService - the executor service used to start threads + * @param wrapper - a function for plugging in additional message consumers + */ + static Launcher createIoLauncher( + Object localService, + Class remoteInterface, + InputStream in, + OutputStream out, + ExecutorService executorService, + Function wrapper) { + return new Builder() + .setLocalService(localService) + .setRemoteInterface(remoteInterface) + .setInput(in) + .setOutput(out) + .setExecutorService(executorService) + .wrapMessages(wrapper) + .create(); + } - public Builder setExecutorService(ExecutorService executorService) { - this.executorService = executorService; - return this; - } + /** + * Create a new Launcher for a given local service object, a given remote interface and an input + * and output stream. Threads are started with the given executor service. The wrapper function is + * applied to the incoming and outgoing message streams so additional message handling such as + * validation and tracing can be included. The {@code configureGson} function can be used to + * register additional type adapters in the {@link GsonBuilder} in order to support protocol + * classes that cannot be handled by Gson's reflective capabilities. + * + * @param localService - the object that receives method calls from the remote service + * @param remoteInterface - an interface on which RPC methods are looked up + * @param in - input stream to listen for incoming messages + * @param out - output stream to send outgoing messages + * @param executorService - the executor service used to start threads + * @param wrapper - a function for plugging in additional message consumers + * @param configureGson - a function for Gson configuration + */ + static Launcher createIoLauncher( + Object localService, + Class remoteInterface, + InputStream in, + OutputStream out, + ExecutorService executorService, + Function wrapper, + Consumer configureGson) { + return new Builder() + .setLocalService(localService) + .setRemoteInterface(remoteInterface) + .setInput(in) + .setOutput(out) + .setExecutorService(executorService) + .wrapMessages(wrapper) + .configureGson(configureGson) + .create(); + } - public Builder setClassLoader(ClassLoader classLoader) { - this.classLoader = classLoader; - return this; - } + /** + * Create a new Launcher for a given local service object, a given remote interface and an input + * and output stream. Threads are started with the given executor service. The wrapper function is + * applied to the incoming and outgoing message streams so additional message handling such as + * validation and tracing can be included. The {@code configureGson} function can be used to + * register additional type adapters in the {@link GsonBuilder} in order to support protocol + * classes that cannot be handled by Gson's reflective capabilities. + * + * @param localService - the object that receives method calls from the remote service + * @param remoteInterface - an interface on which RPC methods are looked up + * @param in - input stream to listen for incoming messages + * @param out - output stream to send outgoing messages + * @param validate - whether messages should be validated with the {@link + * ReflectiveMessageValidator} + * @param executorService - the executor service used to start threads + * @param wrapper - a function for plugging in additional message consumers + * @param configureGson - a function for Gson configuration + */ + static Launcher createIoLauncher( + Object localService, + Class remoteInterface, + InputStream in, + OutputStream out, + boolean validate, + ExecutorService executorService, + Function wrapper, + Consumer configureGson) { + return new Builder() + .setLocalService(localService) + .setRemoteInterface(remoteInterface) + .setInput(in) + .setOutput(out) + .validateMessages(validate) + .setExecutorService(executorService) + .wrapMessages(wrapper) + .configureGson(configureGson) + .create(); + } - public Builder wrapMessages(Function wrapper) { - this.messageWrapper = wrapper; - return this; - } - - public Builder setExceptionHandler(Function exceptionHandler) { - this.exceptionHandler = exceptionHandler; - return this; - } - - public Builder validateMessages(boolean validate) { - this.validateMessages = validate; - return this; - } - - public Builder traceMessages(PrintWriter tracer) { - if (tracer != null) { - this.messageTracer = new MessageTracer(tracer); - } - return this; - } + /** + * Create a new Launcher for a collection of local service objects, a collection of remote + * interfaces and an input and output stream. Threads are started with the given executor service. + * The wrapper function is applied to the incoming and outgoing message streams so additional + * message handling such as validation and tracing can be included. The {@code configureGson} + * function can be used to register additional type adapters in the {@link GsonBuilder} in order + * to support protocol classes that cannot be handled by Gson's reflective capabilities. + * + * @param localServices - the objects that receive method calls from the remote services + * @param remoteInterfaces - interfaces on which RPC methods are looked up + * @param classLoader - a class loader that is able to resolve all given interfaces + * @param in - input stream to listen for incoming messages + * @param out - output stream to send outgoing messages + * @param executorService - the executor service used to start threads + * @param wrapper - a function for plugging in additional message consumers + * @param configureGson - a function for Gson configuration + */ + static Launcher createIoLauncher( + Collection localServices, + Collection> remoteInterfaces, + ClassLoader classLoader, + InputStream in, + OutputStream out, + ExecutorService executorService, + Function wrapper, + Consumer configureGson) { + return new Builder() + .setLocalServices(localServices) + .setRemoteInterfaces(remoteInterfaces) + .setClassLoader(classLoader) + .setInput(in) + .setOutput(out) + .setExecutorService(executorService) + .wrapMessages(wrapper) + .configureGson(configureGson) + .create(); + } - public Builder configureGson(Consumer configureGson) { - this.configureGson = configureGson; - return this; - } + // ------------------------------ Builder Class ------------------------------// - public Launcher create() { - // Validate input - if (input == null) - throw new IllegalStateException("Input stream must be configured."); - if (output == null) - throw new IllegalStateException("Output stream must be configured."); - if (localServices == null) - throw new IllegalStateException("Local service must be configured."); - if (remoteInterfaces == null) - throw new IllegalStateException("Remote interface must be configured."); - - // Create the JSON handler, remote endpoint and remote proxy - MessageJsonHandler jsonHandler = createJsonHandler(); - RemoteEndpoint remoteEndpoint = createRemoteEndpoint(jsonHandler); - T remoteProxy = createProxy(remoteEndpoint); - - // Create the message processor - StreamMessageProducer reader = new StreamMessageProducer(input, jsonHandler, remoteEndpoint); - MessageConsumer messageConsumer = wrapMessageConsumer(remoteEndpoint); - ConcurrentMessageProcessor msgProcessor = createMessageProcessor(reader, messageConsumer, remoteProxy); - ExecutorService execService = executorService != null ? executorService : Executors.newCachedThreadPool(); - return createLauncher(execService, remoteProxy, remoteEndpoint, msgProcessor); - } - - /** - * Create the JSON handler for messages between the local and remote services. - */ - protected MessageJsonHandler createJsonHandler() { - Map supportedMethods = getSupportedMethods(); - if (configureGson != null) - return new MessageJsonHandler(supportedMethods, configureGson); - else - return new MessageJsonHandler(supportedMethods); - } - - /** - * Create the remote endpoint that communicates with the local services. - */ - protected RemoteEndpoint createRemoteEndpoint(MessageJsonHandler jsonHandler) { - MessageConsumer outgoingMessageStream = new StreamMessageConsumer(output, jsonHandler); - outgoingMessageStream = wrapMessageConsumer(outgoingMessageStream); - Endpoint localEndpoint = ServiceEndpoints.toEndpoint(localServices); - RemoteEndpoint remoteEndpoint; - if (exceptionHandler == null) - remoteEndpoint = new RemoteEndpoint(outgoingMessageStream, localEndpoint); - else - remoteEndpoint = new RemoteEndpoint(outgoingMessageStream, localEndpoint, exceptionHandler); - jsonHandler.setMethodProvider(remoteEndpoint); - return remoteEndpoint; - } - - /** - * Create the proxy for calling methods on the remote service. - */ - @SuppressWarnings("unchecked") - protected T createProxy(RemoteEndpoint remoteEndpoint) { - if (localServices.size() == 1 && remoteInterfaces.size() == 1) { - return ServiceEndpoints.toServiceObject(remoteEndpoint, remoteInterfaces.iterator().next()); - } else { - return (T) ServiceEndpoints.toServiceObject(remoteEndpoint, (Collection>) (Object) remoteInterfaces, classLoader); - } - } - - /** - * Create the message processor that listens to the input stream. - */ - protected ConcurrentMessageProcessor createMessageProcessor(MessageProducer reader, - MessageConsumer messageConsumer, T remoteProxy) { - return new ConcurrentMessageProcessor(reader, messageConsumer); - } - - protected Launcher createLauncher(ExecutorService execService, T remoteProxy, RemoteEndpoint remoteEndpoint, - ConcurrentMessageProcessor msgProcessor) { - return new StandardLauncher(execService, remoteProxy, remoteEndpoint, msgProcessor); - } - - protected MessageConsumer wrapMessageConsumer(MessageConsumer consumer) { - MessageConsumer result = consumer; - if (messageTracer != null) { - result = messageTracer.apply(consumer); - } - if (validateMessages) { - result = new ReflectiveMessageValidator(result); - } - if (messageWrapper != null) { - result = messageWrapper.apply(result); - } - return result; - } - - /** - * Gather all JSON-RPC methods from the local and remote services. - */ - protected Map getSupportedMethods() { - Map supportedMethods = new LinkedHashMap<>(); - // Gather the supported methods of remote interfaces - for (Class interface_ : remoteInterfaces) { - supportedMethods.putAll(ServiceEndpoints.getSupportedMethods(interface_)); - } - - // Gather the supported methods of local services - for (Object localService : localServices) { - if (localService instanceof JsonRpcMethodProvider) { - JsonRpcMethodProvider rpcMethodProvider = (JsonRpcMethodProvider) localService; - supportedMethods.putAll(rpcMethodProvider.supportedMethods()); - } else { - supportedMethods.putAll(ServiceEndpoints.getSupportedMethods(localService.getClass())); - } - } - - return supportedMethods; - } - } - - - //---------------------------- Interface Methods ----------------------------// - - /** - * Start a thread that listens to the input stream. The thread terminates when the stream is closed. - * - * @return a future that returns {@code null} when the listener thread is terminated - */ - Future startListening(); - - /** - * Returns the proxy instance that implements the remote service interfaces. - */ - T getRemoteProxy(); - - /** - * Returns the remote endpoint. Use this one to send generic {@code request} or {@code notify} methods - * to the remote services. - */ - RemoteEndpoint getRemoteEndpoint(); - + /** + * The launcher builder wires up all components for JSON-RPC communication. + * + * @param remote service interface type + */ + public static class Builder { + + protected Collection localServices; + protected Collection> remoteInterfaces; + protected InputStream input; + protected OutputStream output; + protected ExecutorService executorService; + protected Function messageWrapper; + protected Function exceptionHandler; + protected boolean validateMessages; + protected Consumer configureGson; + protected ClassLoader classLoader; + protected MessageTracer messageTracer; + + public Builder setLocalService(Object localService) { + this.localServices = Collections.singletonList(localService); + return this; + } + + public Builder setLocalServices(Collection localServices) { + this.localServices = localServices; + return this; + } + + public Builder setRemoteInterface(Class remoteInterface) { + this.remoteInterfaces = Collections.singletonList(remoteInterface); + return this; + } + + public Builder setRemoteInterfaces(Collection> remoteInterfaces) { + this.remoteInterfaces = remoteInterfaces; + return this; + } + + public Builder setInput(InputStream input) { + this.input = input; + return this; + } + + public Builder setOutput(OutputStream output) { + this.output = output; + return this; + } + + public Builder setExecutorService(ExecutorService executorService) { + this.executorService = executorService; + return this; + } + + public Builder setClassLoader(ClassLoader classLoader) { + this.classLoader = classLoader; + return this; + } + + public Builder wrapMessages(Function wrapper) { + this.messageWrapper = wrapper; + return this; + } + + public Builder setExceptionHandler(Function exceptionHandler) { + this.exceptionHandler = exceptionHandler; + return this; + } + + public Builder validateMessages(boolean validate) { + this.validateMessages = validate; + return this; + } + + public Builder traceMessages(PrintWriter tracer) { + if (tracer != null) { + this.messageTracer = new MessageTracer(tracer); + } + return this; + } + + public Builder configureGson(Consumer configureGson) { + this.configureGson = configureGson; + return this; + } + + public Launcher create() { + // Validate input + if (input == null) throw new IllegalStateException("Input stream must be configured."); + if (output == null) throw new IllegalStateException("Output stream must be configured."); + if (localServices == null) + throw new IllegalStateException("Local service must be configured."); + if (remoteInterfaces == null) + throw new IllegalStateException("Remote interface must be configured."); + + // Create the JSON handler, remote endpoint and remote proxy + MessageJsonHandler jsonHandler = createJsonHandler(); + RemoteEndpoint remoteEndpoint = createRemoteEndpoint(jsonHandler); + T remoteProxy = createProxy(remoteEndpoint); + + // Create the message processor + StreamMessageProducer reader = new StreamMessageProducer(input, jsonHandler, remoteEndpoint); + MessageConsumer messageConsumer = wrapMessageConsumer(remoteEndpoint); + ConcurrentMessageProcessor msgProcessor = + createMessageProcessor(reader, messageConsumer, remoteProxy); + ExecutorService execService = + executorService != null ? executorService : Executors.newCachedThreadPool(); + return createLauncher(execService, remoteProxy, remoteEndpoint, msgProcessor); + } + + /** Create the JSON handler for messages between the local and remote services. */ + protected MessageJsonHandler createJsonHandler() { + Map supportedMethods = getSupportedMethods(); + if (configureGson != null) return new MessageJsonHandler(supportedMethods, configureGson); + else return new MessageJsonHandler(supportedMethods); + } + + /** Create the remote endpoint that communicates with the local services. */ + protected RemoteEndpoint createRemoteEndpoint(MessageJsonHandler jsonHandler) { + MessageConsumer outgoingMessageStream = new StreamMessageConsumer(output, jsonHandler); + outgoingMessageStream = wrapMessageConsumer(outgoingMessageStream); + Endpoint localEndpoint = ServiceEndpoints.toEndpoint(localServices); + RemoteEndpoint remoteEndpoint; + if (exceptionHandler == null) + remoteEndpoint = new RemoteEndpoint(outgoingMessageStream, localEndpoint); + else + remoteEndpoint = new RemoteEndpoint(outgoingMessageStream, localEndpoint, exceptionHandler); + jsonHandler.setMethodProvider(remoteEndpoint); + return remoteEndpoint; + } + + /** Create the proxy for calling methods on the remote service. */ + @SuppressWarnings("unchecked") + protected T createProxy(RemoteEndpoint remoteEndpoint) { + if (localServices.size() == 1 && remoteInterfaces.size() == 1) { + return ServiceEndpoints.toServiceObject(remoteEndpoint, remoteInterfaces.iterator().next()); + } else { + return (T) + ServiceEndpoints.toServiceObject( + remoteEndpoint, (Collection>) (Object) remoteInterfaces, classLoader); + } + } + + /** Create the message processor that listens to the input stream. */ + protected ConcurrentMessageProcessor createMessageProcessor( + MessageProducer reader, MessageConsumer messageConsumer, T remoteProxy) { + return new ConcurrentMessageProcessor(reader, messageConsumer); + } + + protected Launcher createLauncher( + ExecutorService execService, + T remoteProxy, + RemoteEndpoint remoteEndpoint, + ConcurrentMessageProcessor msgProcessor) { + return new StandardLauncher(execService, remoteProxy, remoteEndpoint, msgProcessor); + } + + protected MessageConsumer wrapMessageConsumer(MessageConsumer consumer) { + MessageConsumer result = consumer; + if (messageTracer != null) { + result = messageTracer.apply(consumer); + } + if (validateMessages) { + result = new ReflectiveMessageValidator(result); + } + if (messageWrapper != null) { + result = messageWrapper.apply(result); + } + return result; + } + + /** Gather all JSON-RPC methods from the local and remote services. */ + protected Map getSupportedMethods() { + Map supportedMethods = new LinkedHashMap<>(); + // Gather the supported methods of remote interfaces + for (Class interface_ : remoteInterfaces) { + supportedMethods.putAll(ServiceEndpoints.getSupportedMethods(interface_)); + } + + // Gather the supported methods of local services + for (Object localService : localServices) { + if (localService instanceof JsonRpcMethodProvider) { + JsonRpcMethodProvider rpcMethodProvider = (JsonRpcMethodProvider) localService; + supportedMethods.putAll(rpcMethodProvider.supportedMethods()); + } else { + supportedMethods.putAll(ServiceEndpoints.getSupportedMethods(localService.getClass())); + } + } + + return supportedMethods; + } + } + + // ---------------------------- Interface Methods ----------------------------// + + /** + * Start a thread that listens to the input stream. The thread terminates when the stream is + * closed. + * + * @return a future that returns {@code null} when the listener thread is terminated + */ + Future startListening(); + + /** Returns the proxy instance that implements the remote service interfaces. */ + T getRemoteProxy(); + + /** + * Returns the remote endpoint. Use this one to send generic {@code request} or {@code notify} + * methods to the remote services. + */ + RemoteEndpoint getRemoteEndpoint(); } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/MessageConsumer.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/MessageConsumer.java index eb494658b..104052e31 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/MessageConsumer.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/MessageConsumer.java @@ -1,12 +1,12 @@ /****************************************************************************** * Copyright (c) 2016 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc; @@ -14,13 +14,13 @@ package org.eclipse.lsp4j.jsonrpc; import org.eclipse.lsp4j.jsonrpc.messages.Message; public interface MessageConsumer { - - /** - * Consume a single message. - * - * @throws MessageIssueException when an issue is found that prevents further processing of the message - * @throws JsonRpcException when accessing the JSON-RPC communication channel fails - */ - void consume(Message message) throws MessageIssueException, JsonRpcException; - + + /** + * Consume a single message. + * + * @throws MessageIssueException when an issue is found that prevents further processing of the + * message + * @throws JsonRpcException when accessing the JSON-RPC communication channel fails + */ + void consume(Message message) throws MessageIssueException, JsonRpcException; } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/MessageIssueException.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/MessageIssueException.java index 58dec38a4..c3d33d238 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/MessageIssueException.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/MessageIssueException.java @@ -1,12 +1,12 @@ /****************************************************************************** * Copyright (c) 2018 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc; @@ -22,43 +22,42 @@ import org.eclipse.lsp4j.jsonrpc.messages.Message; import org.eclipse.lsp4j.jsonrpc.messages.MessageIssue; /** - * An exception thrown to notify the caller of a {@link MessageConsumer} that one or more issues were - * found while parsing or validating a message. This information can be passed to a {@link MessageIssueHandler} - * in order to construct a proper response. + * An exception thrown to notify the caller of a {@link MessageConsumer} that one or more issues + * were found while parsing or validating a message. This information can be passed to a {@link + * MessageIssueHandler} in order to construct a proper response. */ public class MessageIssueException extends RuntimeException { - - private static final long serialVersionUID = 1118210473728652711L; - - private final Message rpcMessage; - private final List issues; - public MessageIssueException(Message rpcMessage, MessageIssue issue) { - this.rpcMessage = rpcMessage; - this.issues = Collections.singletonList(issue); - } + private static final long serialVersionUID = 1118210473728652711L; - public MessageIssueException(Message rpcMessage, Collection issues) { - this.rpcMessage = rpcMessage; - this.issues = Collections.unmodifiableList(new ArrayList<>(issues)); - } - - public MessageIssueException(Message rpcMessage, Stream issueStream) { - this.rpcMessage = rpcMessage; - this.issues = Collections.unmodifiableList(issueStream.collect(Collectors.toList())); - } + private final Message rpcMessage; + private final List issues; - @Override - public String getMessage() { - return issues.stream().map(issue -> issue.getText()).collect(Collectors.joining("\n")); - } - - public Message getRpcMessage() { - return rpcMessage; - } - - public List getIssues() { - return issues; - } + public MessageIssueException(Message rpcMessage, MessageIssue issue) { + this.rpcMessage = rpcMessage; + this.issues = Collections.singletonList(issue); + } + public MessageIssueException(Message rpcMessage, Collection issues) { + this.rpcMessage = rpcMessage; + this.issues = Collections.unmodifiableList(new ArrayList<>(issues)); + } + + public MessageIssueException(Message rpcMessage, Stream issueStream) { + this.rpcMessage = rpcMessage; + this.issues = Collections.unmodifiableList(issueStream.collect(Collectors.toList())); + } + + @Override + public String getMessage() { + return issues.stream().map(issue -> issue.getText()).collect(Collectors.joining("\n")); + } + + public Message getRpcMessage() { + return rpcMessage; + } + + public List getIssues() { + return issues; + } } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/MessageIssueHandler.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/MessageIssueHandler.java index 768465b65..283be649e 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/MessageIssueHandler.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/MessageIssueHandler.java @@ -1,12 +1,12 @@ /****************************************************************************** * Copyright (c) 2018 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc; @@ -17,10 +17,10 @@ import org.eclipse.lsp4j.jsonrpc.messages.Message; import org.eclipse.lsp4j.jsonrpc.messages.MessageIssue; public interface MessageIssueHandler { - - /** - * Handle issues found while parsing or validating a message. The list of issues must not be empty. - */ - void handle(Message message, List issues); + /** + * Handle issues found while parsing or validating a message. The list of issues must not be + * empty. + */ + void handle(Message message, List issues); } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/MessageProducer.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/MessageProducer.java index 30b2e5146..1431236d1 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/MessageProducer.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/MessageProducer.java @@ -1,24 +1,23 @@ /****************************************************************************** * Copyright (c) 2016 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc; public interface MessageProducer { - - /** - * Listen to a message source and forward all messages to the given consumer. Typically this method - * blocks until the message source is unable to deliver more messages. - * - * @throws JsonRpcException when accessing the JSON-RPC communication channel fails - */ - void listen(MessageConsumer messageConsumer) throws JsonRpcException; - + + /** + * Listen to a message source and forward all messages to the given consumer. Typically this + * method blocks until the message source is unable to deliver more messages. + * + * @throws JsonRpcException when accessing the JSON-RPC communication channel fails + */ + void listen(MessageConsumer messageConsumer) throws JsonRpcException; } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/MessageTracer.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/MessageTracer.java index 8398f6f19..362eadcf3 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/MessageTracer.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/MessageTracer.java @@ -25,17 +25,17 @@ import java.util.function.Function; * https://microsoft.github.io/language-server-protocol/inspector/ */ public class MessageTracer implements Function { - private final PrintWriter printWriter; - private final Map sentRequests = new HashMap<>(); - private final Map receivedRequests = new HashMap<>(); + private final PrintWriter printWriter; + private final Map sentRequests = new HashMap<>(); + private final Map receivedRequests = new HashMap<>(); - MessageTracer(PrintWriter printWriter) { - this.printWriter = Objects.requireNonNull(printWriter); - } + MessageTracer(PrintWriter printWriter) { + this.printWriter = Objects.requireNonNull(printWriter); + } - @Override - public MessageConsumer apply(MessageConsumer messageConsumer) { - return new TracingMessageConsumer( - messageConsumer, sentRequests, receivedRequests, printWriter, Clock.systemDefaultZone()); - } + @Override + public MessageConsumer apply(MessageConsumer messageConsumer) { + return new TracingMessageConsumer( + messageConsumer, sentRequests, receivedRequests, printWriter, Clock.systemDefaultZone()); + } } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/ResponseErrorException.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/ResponseErrorException.java index d700591f2..249617c33 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/ResponseErrorException.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/ResponseErrorException.java @@ -1,36 +1,34 @@ /****************************************************************************** * Copyright (c) 2016 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc; import org.eclipse.lsp4j.jsonrpc.messages.ResponseError; -/** - * An exception thrown in order to send a response with an attached {@code error} object. - */ +/** An exception thrown in order to send a response with an attached {@code error} object. */ public class ResponseErrorException extends RuntimeException { - private static final long serialVersionUID = -5970739895395246885L; - private ResponseError responseError; + private static final long serialVersionUID = -5970739895395246885L; + private ResponseError responseError; - public ResponseErrorException(ResponseError responseError) { - this.responseError = responseError; - } + public ResponseErrorException(ResponseError responseError) { + this.responseError = responseError; + } - @Override - public String getMessage() { - return responseError.getMessage(); - } - - public ResponseError getResponseError() { - return responseError; - } + @Override + public String getMessage() { + return responseError.getMessage(); + } + + public ResponseError getResponseError() { + return responseError; + } } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/StandardLauncher.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/StandardLauncher.java index 0dd6b1492..e609a2ebb 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/StandardLauncher.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/StandardLauncher.java @@ -1,12 +1,12 @@ /****************************************************************************** * Copyright (c) 2018 Red Hat Inc - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc; @@ -18,38 +18,47 @@ import org.eclipse.lsp4j.jsonrpc.json.ConcurrentMessageProcessor; import org.eclipse.lsp4j.jsonrpc.json.StreamMessageProducer; public class StandardLauncher implements Launcher { - private final ExecutorService execService; - private final T remoteProxy; - private final RemoteEndpoint remoteEndpoint; - private final ConcurrentMessageProcessor msgProcessor; + private final ExecutorService execService; + private final T remoteProxy; + private final RemoteEndpoint remoteEndpoint; + private final ConcurrentMessageProcessor msgProcessor; - public StandardLauncher(StreamMessageProducer reader, MessageConsumer messageConsumer, - ExecutorService execService, T remoteProxy, RemoteEndpoint remoteEndpoint) { - this(execService, remoteProxy, remoteEndpoint, - new ConcurrentMessageProcessor(reader, messageConsumer)); - } - - public StandardLauncher( - ExecutorService execService2, T remoteProxy2, RemoteEndpoint remoteEndpoint2, - ConcurrentMessageProcessor msgProcessor) { - this.execService = execService2; - this.remoteProxy = remoteProxy2; - this.remoteEndpoint = remoteEndpoint2; - this.msgProcessor = msgProcessor; - } + public StandardLauncher( + StreamMessageProducer reader, + MessageConsumer messageConsumer, + ExecutorService execService, + T remoteProxy, + RemoteEndpoint remoteEndpoint) { + this( + execService, + remoteProxy, + remoteEndpoint, + new ConcurrentMessageProcessor(reader, messageConsumer)); + } - @Override - public Future startListening() { - return msgProcessor.beginProcessing(execService); - } + public StandardLauncher( + ExecutorService execService2, + T remoteProxy2, + RemoteEndpoint remoteEndpoint2, + ConcurrentMessageProcessor msgProcessor) { + this.execService = execService2; + this.remoteProxy = remoteProxy2; + this.remoteEndpoint = remoteEndpoint2; + this.msgProcessor = msgProcessor; + } - @Override - public T getRemoteProxy() { - return remoteProxy; - } + @Override + public Future startListening() { + return msgProcessor.beginProcessing(execService); + } - @Override - public RemoteEndpoint getRemoteEndpoint() { - return remoteEndpoint; - } -} \ No newline at end of file + @Override + public T getRemoteProxy() { + return remoteProxy; + } + + @Override + public RemoteEndpoint getRemoteEndpoint() { + return remoteEndpoint; + } +} diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/JsonRpcMethod.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/JsonRpcMethod.java index b2dcf13d8..52f424580 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/JsonRpcMethod.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/JsonRpcMethod.java @@ -1,12 +1,12 @@ /****************************************************************************** * Copyright (c) 2016 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc.json; @@ -15,74 +15,75 @@ import java.lang.reflect.Type; import com.google.gson.TypeAdapterFactory; -/** - * A description of a JSON-RPC method. - */ +/** A description of a JSON-RPC method. */ public class JsonRpcMethod { - - private final String methodName; - private final Type[] parameterTypes; - private final Type returnType; - private final TypeAdapterFactory returnTypeAdapterFactory; - private final boolean isNotification; - - private JsonRpcMethod(String methodName, Type[] parameterTypes, Type returnType, TypeAdapterFactory returnTypeAdapterFactory, - boolean isNotification) { - if (methodName == null) - throw new NullPointerException("methodName"); - this.methodName = methodName; - this.parameterTypes = parameterTypes; - this.returnType = returnType; - this.returnTypeAdapterFactory = returnTypeAdapterFactory; - this.isNotification = isNotification; - } - public String getMethodName() { - return methodName; - } - - public Type[] getParameterTypes() { - return parameterTypes; - } - - public Type getReturnType() { - return returnType; - } - - public TypeAdapterFactory getReturnTypeAdapterFactory() { - return returnTypeAdapterFactory; - } - - public boolean isNotification() { - return isNotification; - } - - public static JsonRpcMethod notification(String name, Type... parameterTypes) { - return new JsonRpcMethod(name, parameterTypes, Void.class, null, true); - } - - public static JsonRpcMethod request(String name, Type returnType, Type... parameterTypes) { - return new JsonRpcMethod(name, parameterTypes, returnType, null, false); - } - - public static JsonRpcMethod request(String name, Type returnType, TypeAdapterFactory returnTypeAdapterFactory, Type... parameterTypes) { - return new JsonRpcMethod(name, parameterTypes, returnType, returnTypeAdapterFactory, false); - } - - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - if (isNotification) - builder.append("JsonRpcMethod (notification) {\n"); - else - builder.append("JsonRpcMethod (request) {\n"); - builder.append("\tmethodName: ").append(methodName).append('\n'); - if (parameterTypes != null) - builder.append("\tparameterTypes: ").append(parameterTypes).append('\n'); - if (returnType != null) - builder.append("\treturnType: ").append(returnType).append('\n'); - builder.append("}"); - return builder.toString(); - } + private final String methodName; + private final Type[] parameterTypes; + private final Type returnType; + private final TypeAdapterFactory returnTypeAdapterFactory; + private final boolean isNotification; + private JsonRpcMethod( + String methodName, + Type[] parameterTypes, + Type returnType, + TypeAdapterFactory returnTypeAdapterFactory, + boolean isNotification) { + if (methodName == null) throw new NullPointerException("methodName"); + this.methodName = methodName; + this.parameterTypes = parameterTypes; + this.returnType = returnType; + this.returnTypeAdapterFactory = returnTypeAdapterFactory; + this.isNotification = isNotification; + } + + public String getMethodName() { + return methodName; + } + + public Type[] getParameterTypes() { + return parameterTypes; + } + + public Type getReturnType() { + return returnType; + } + + public TypeAdapterFactory getReturnTypeAdapterFactory() { + return returnTypeAdapterFactory; + } + + public boolean isNotification() { + return isNotification; + } + + public static JsonRpcMethod notification(String name, Type... parameterTypes) { + return new JsonRpcMethod(name, parameterTypes, Void.class, null, true); + } + + public static JsonRpcMethod request(String name, Type returnType, Type... parameterTypes) { + return new JsonRpcMethod(name, parameterTypes, returnType, null, false); + } + + public static JsonRpcMethod request( + String name, + Type returnType, + TypeAdapterFactory returnTypeAdapterFactory, + Type... parameterTypes) { + return new JsonRpcMethod(name, parameterTypes, returnType, returnTypeAdapterFactory, false); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + if (isNotification) builder.append("JsonRpcMethod (notification) {\n"); + else builder.append("JsonRpcMethod (request) {\n"); + builder.append("\tmethodName: ").append(methodName).append('\n'); + if (parameterTypes != null) + builder.append("\tparameterTypes: ").append(parameterTypes).append('\n'); + if (returnType != null) builder.append("\treturnType: ").append(returnType).append('\n'); + builder.append("}"); + return builder.toString(); + } } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/JsonRpcMethodProvider.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/JsonRpcMethodProvider.java index a7312003e..10b8ea944 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/JsonRpcMethodProvider.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/JsonRpcMethodProvider.java @@ -1,12 +1,12 @@ /****************************************************************************** * Copyright (c) 2016 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc.json; @@ -16,11 +16,10 @@ import java.util.Map; import org.eclipse.lsp4j.jsonrpc.Endpoint; /** - * Provides {@link JsonRpcMethod}. Can be implemented by {@link Endpoint}s to - * provide information about the supported methods. + * Provides {@link JsonRpcMethod}. Can be implemented by {@link Endpoint}s to provide information + * about the supported methods. */ public interface JsonRpcMethodProvider { - Map supportedMethods(); - + Map supportedMethods(); } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/MessageConstants.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/MessageConstants.java index e6676fa1a..e9d58fe91 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/MessageConstants.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/MessageConstants.java @@ -1,22 +1,21 @@ /****************************************************************************** * Copyright (c) 2016 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc.json; public interface MessageConstants { - - public static String JSONRPC_VERSION = "2.0"; - public static String CONTENT_LENGTH_HEADER = "Content-Length"; - public static String CONTENT_TYPE_HEADER = "Content-Type"; - public static String JSON_MIME_TYPE = "application/json"; - public static String CRLF = "\r\n"; + public static String JSONRPC_VERSION = "2.0"; + public static String CONTENT_LENGTH_HEADER = "Content-Length"; + public static String CONTENT_TYPE_HEADER = "Content-Type"; + public static String JSON_MIME_TYPE = "application/json"; + public static String CRLF = "\r\n"; } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/MessageJsonHandler.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/MessageJsonHandler.java index f7584c810..7f46098f6 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/MessageJsonHandler.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/MessageJsonHandler.java @@ -1,12 +1,12 @@ /****************************************************************************** * Copyright (c) 2016 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc.json; @@ -40,125 +40,127 @@ import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonToken; import com.google.gson.stream.MalformedJsonException; -/** - * A wrapper around Gson that includes configuration required for JSON-RPC messages. - */ +/** A wrapper around Gson that includes configuration required for JSON-RPC messages. */ public class MessageJsonHandler { - - public static final JsonRpcMethod CANCEL_METHOD = JsonRpcMethod.notification("$/cancelRequest", CancelParams.class); - private final Gson gson; - - private final Map supportedMethods; - - private MethodProvider methodProvider; - - /** - * @param supportedMethods - a map used to resolve RPC methods in {@link #getJsonRpcMethod(String)} - */ - public MessageJsonHandler(Map supportedMethods) { - this.supportedMethods = supportedMethods; - this.gson = getDefaultGsonBuilder().create(); - } - - /** - * @param supportedMethods - a map used to resolve RPC methods in {@link #getJsonRpcMethod(String)} - * @param configureGson - a function that contributes to the GsonBuilder created by {@link #getDefaultGsonBuilder()} - */ - public MessageJsonHandler(Map supportedMethods, Consumer configureGson) { - this.supportedMethods = supportedMethods; - GsonBuilder gsonBuilder = getDefaultGsonBuilder(); - configureGson.accept(gsonBuilder); - this.gson = gsonBuilder.create(); - } - - /** - * Create a {@link GsonBuilder} with default settings for parsing JSON-RPC messages. - */ - public GsonBuilder getDefaultGsonBuilder() { - return new GsonBuilder() - .registerTypeAdapterFactory(new CollectionTypeAdapter.Factory()) - .registerTypeAdapterFactory(new ThrowableTypeAdapter.Factory()) - .registerTypeAdapterFactory(new EitherTypeAdapter.Factory()) - .registerTypeAdapterFactory(new TupleTypeAdapters.TwoTypeAdapterFactory()) - .registerTypeAdapterFactory(new EnumTypeAdapter.Factory()) - .registerTypeAdapterFactory(new MessageTypeAdapter.Factory(this)); - } - - public Gson getGson() { - return gson; - } - - /** - * Resolve an RPC method by name. - */ - public JsonRpcMethod getJsonRpcMethod(String name) { - JsonRpcMethod result = supportedMethods.get(name); - if (result != null) - return result; - else if (CANCEL_METHOD.getMethodName().equals(name)) - return CANCEL_METHOD; - return null; - } - - public MethodProvider getMethodProvider() { - return methodProvider; - } - - public void setMethodProvider(MethodProvider methodProvider) { - this.methodProvider = methodProvider; - } - - public Message parseMessage(CharSequence input) throws JsonParseException { - StringReader reader = new StringReader(input.toString()); - return parseMessage(reader); - } - - public Message parseMessage(Reader input) throws JsonParseException { - JsonReader jsonReader = new JsonReader(input); - Message message = gson.fromJson(jsonReader, Message.class); - - if (message != null) { - // Check whether the input has been fully consumed - try { - if (jsonReader.peek() != JsonToken.END_DOCUMENT) { - MessageIssue issue = new MessageIssue("JSON document was not fully consumed.", ResponseErrorCode.ParseError.getValue()); - throw new MessageIssueException(message, issue); - } - } catch (MalformedJsonException e) { - MessageIssue issue = new MessageIssue("Message could not be parsed.", ResponseErrorCode.ParseError.getValue(), e); - throw new MessageIssueException(message, issue); - } catch (IOException e) { - throw new JsonIOException(e); - } - } - return message; - } - - public String serialize(Message message) { - StringWriter writer = new StringWriter(); - serialize(message, writer); - return writer.toString(); - } - - public void serialize(Message message, Writer output) throws JsonIOException { - gson.toJson(message, Message.class, output); - } - - - private static MessageJsonHandler toStringInstance; - - /** - * Perform JSON serialization of the given object using the default configuration of JSON-RPC messages - * enhanced with the pretty printing option. - */ - public static String toString(Object object) { - if (toStringInstance == null) { - toStringInstance = new MessageJsonHandler(Collections.emptyMap(), gsonBuilder -> { - gsonBuilder.setPrettyPrinting(); - }); - } - return toStringInstance.gson.toJson(object); - } - + public static final JsonRpcMethod CANCEL_METHOD = + JsonRpcMethod.notification("$/cancelRequest", CancelParams.class); + + private final Gson gson; + + private final Map supportedMethods; + + private MethodProvider methodProvider; + + /** + * @param supportedMethods - a map used to resolve RPC methods in {@link + * #getJsonRpcMethod(String)} + */ + public MessageJsonHandler(Map supportedMethods) { + this.supportedMethods = supportedMethods; + this.gson = getDefaultGsonBuilder().create(); + } + + /** + * @param supportedMethods - a map used to resolve RPC methods in {@link + * #getJsonRpcMethod(String)} + * @param configureGson - a function that contributes to the GsonBuilder created by {@link + * #getDefaultGsonBuilder()} + */ + public MessageJsonHandler( + Map supportedMethods, Consumer configureGson) { + this.supportedMethods = supportedMethods; + GsonBuilder gsonBuilder = getDefaultGsonBuilder(); + configureGson.accept(gsonBuilder); + this.gson = gsonBuilder.create(); + } + + /** Create a {@link GsonBuilder} with default settings for parsing JSON-RPC messages. */ + public GsonBuilder getDefaultGsonBuilder() { + return new GsonBuilder() + .registerTypeAdapterFactory(new CollectionTypeAdapter.Factory()) + .registerTypeAdapterFactory(new ThrowableTypeAdapter.Factory()) + .registerTypeAdapterFactory(new EitherTypeAdapter.Factory()) + .registerTypeAdapterFactory(new TupleTypeAdapters.TwoTypeAdapterFactory()) + .registerTypeAdapterFactory(new EnumTypeAdapter.Factory()) + .registerTypeAdapterFactory(new MessageTypeAdapter.Factory(this)); + } + + public Gson getGson() { + return gson; + } + + /** Resolve an RPC method by name. */ + public JsonRpcMethod getJsonRpcMethod(String name) { + JsonRpcMethod result = supportedMethods.get(name); + if (result != null) return result; + else if (CANCEL_METHOD.getMethodName().equals(name)) return CANCEL_METHOD; + return null; + } + + public MethodProvider getMethodProvider() { + return methodProvider; + } + + public void setMethodProvider(MethodProvider methodProvider) { + this.methodProvider = methodProvider; + } + + public Message parseMessage(CharSequence input) throws JsonParseException { + StringReader reader = new StringReader(input.toString()); + return parseMessage(reader); + } + + public Message parseMessage(Reader input) throws JsonParseException { + JsonReader jsonReader = new JsonReader(input); + Message message = gson.fromJson(jsonReader, Message.class); + + if (message != null) { + // Check whether the input has been fully consumed + try { + if (jsonReader.peek() != JsonToken.END_DOCUMENT) { + MessageIssue issue = + new MessageIssue( + "JSON document was not fully consumed.", ResponseErrorCode.ParseError.getValue()); + throw new MessageIssueException(message, issue); + } + } catch (MalformedJsonException e) { + MessageIssue issue = + new MessageIssue( + "Message could not be parsed.", ResponseErrorCode.ParseError.getValue(), e); + throw new MessageIssueException(message, issue); + } catch (IOException e) { + throw new JsonIOException(e); + } + } + return message; + } + + public String serialize(Message message) { + StringWriter writer = new StringWriter(); + serialize(message, writer); + return writer.toString(); + } + + public void serialize(Message message, Writer output) throws JsonIOException { + gson.toJson(message, Message.class, output); + } + + private static MessageJsonHandler toStringInstance; + + /** + * Perform JSON serialization of the given object using the default configuration of JSON-RPC + * messages enhanced with the pretty printing option. + */ + public static String toString(Object object) { + if (toStringInstance == null) { + toStringInstance = + new MessageJsonHandler( + Collections.emptyMap(), + gsonBuilder -> { + gsonBuilder.setPrettyPrinting(); + }); + } + return toStringInstance.gson.toJson(object); + } } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/MethodProvider.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/MethodProvider.java index 3ece1f93c..5f18b9545 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/MethodProvider.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/MethodProvider.java @@ -1,23 +1,22 @@ /****************************************************************************** * Copyright (c) 2016 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc.json; public interface MethodProvider { - - /** - * Returns the method name for a given request id, or null if such request id is unknown. - * - * @return method name or {@code null} - */ - String resolveMethod(String requestId); - + + /** + * Returns the method name for a given request id, or null if such request id is unknown. + * + * @return method name or {@code null} + */ + String resolveMethod(String requestId); } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/ResponseJsonAdapter.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/ResponseJsonAdapter.java index 3fe62d0c4..ba22dcdc5 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/ResponseJsonAdapter.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/ResponseJsonAdapter.java @@ -1,12 +1,12 @@ /****************************************************************************** * Copyright (c) 2018 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc.json; @@ -26,5 +26,4 @@ import com.google.gson.TypeAdapterFactory; public @interface ResponseJsonAdapter { Class value(); - -} \ No newline at end of file +} diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/StreamMessageConsumer.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/StreamMessageConsumer.java index 313c47233..62e741520 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/StreamMessageConsumer.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/StreamMessageConsumer.java @@ -1,12 +1,12 @@ /****************************************************************************** * Copyright (c) 2016 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc.json; @@ -19,80 +19,76 @@ import org.eclipse.lsp4j.jsonrpc.JsonRpcException; import org.eclipse.lsp4j.jsonrpc.MessageConsumer; import org.eclipse.lsp4j.jsonrpc.messages.Message; -/** - * A message consumer that serializes messages to JSON and sends them to an output stream. - */ +/** A message consumer that serializes messages to JSON and sends them to an output stream. */ public class StreamMessageConsumer implements MessageConsumer, MessageConstants { - private final String encoding; - private final MessageJsonHandler jsonHandler; + private final String encoding; + private final MessageJsonHandler jsonHandler; - private final Object outputLock = new Object(); + private final Object outputLock = new Object(); - private OutputStream output; + private OutputStream output; - public StreamMessageConsumer(MessageJsonHandler jsonHandler) { - this(null, StandardCharsets.UTF_8.name(), jsonHandler); - } + public StreamMessageConsumer(MessageJsonHandler jsonHandler) { + this(null, StandardCharsets.UTF_8.name(), jsonHandler); + } - public StreamMessageConsumer(OutputStream output, MessageJsonHandler jsonHandler) { - this(output, StandardCharsets.UTF_8.name(), jsonHandler); - } + public StreamMessageConsumer(OutputStream output, MessageJsonHandler jsonHandler) { + this(output, StandardCharsets.UTF_8.name(), jsonHandler); + } - public StreamMessageConsumer(OutputStream output, String encoding, MessageJsonHandler jsonHandler) { - this.output = output; - this.encoding = encoding; - this.jsonHandler = jsonHandler; - } + public StreamMessageConsumer( + OutputStream output, String encoding, MessageJsonHandler jsonHandler) { + this.output = output; + this.encoding = encoding; + this.jsonHandler = jsonHandler; + } - public OutputStream getOutput() { - return output; - } + public OutputStream getOutput() { + return output; + } - public void setOutput(OutputStream output) { - this.output = output; - } + public void setOutput(OutputStream output) { + this.output = output; + } - @Override - public void consume(Message message) { - try { - String content = jsonHandler.serialize(message); - byte[] contentBytes = content.getBytes(encoding); - int contentLength = contentBytes.length; + @Override + public void consume(Message message) { + try { + String content = jsonHandler.serialize(message); + byte[] contentBytes = content.getBytes(encoding); + int contentLength = contentBytes.length; - String header = getHeader(contentLength); - byte[] headerBytes = header.getBytes(StandardCharsets.US_ASCII); + String header = getHeader(contentLength); + byte[] headerBytes = header.getBytes(StandardCharsets.US_ASCII); - synchronized (outputLock) { - output.write(headerBytes); - output.write(contentBytes); - output.flush(); - } - } catch (IOException exception) { - throw new JsonRpcException(exception); - } - } + synchronized (outputLock) { + output.write(headerBytes); + output.write(contentBytes); + output.flush(); + } + } catch (IOException exception) { + throw new JsonRpcException(exception); + } + } - /** - * Construct a header to be prepended to the actual content. This implementation writes - * {@code Content-Length} and {@code Content-Type} attributes according to the LSP specification. - */ - protected String getHeader(int contentLength) { - StringBuilder headerBuilder = new StringBuilder(); - appendHeader(headerBuilder, CONTENT_LENGTH_HEADER, contentLength).append(CRLF); - if (!StandardCharsets.UTF_8.name().equals(encoding)) { - appendHeader(headerBuilder, CONTENT_TYPE_HEADER, JSON_MIME_TYPE); - headerBuilder.append("; charset=").append(encoding).append(CRLF); - } - headerBuilder.append(CRLF); - return headerBuilder.toString(); - } - - /** - * Append a header attribute to the given builder. - */ - protected StringBuilder appendHeader(StringBuilder builder, String name, Object value) { - return builder.append(name).append(": ").append(value); - } + /** + * Construct a header to be prepended to the actual content. This implementation writes {@code + * Content-Length} and {@code Content-Type} attributes according to the LSP specification. + */ + protected String getHeader(int contentLength) { + StringBuilder headerBuilder = new StringBuilder(); + appendHeader(headerBuilder, CONTENT_LENGTH_HEADER, contentLength).append(CRLF); + if (!StandardCharsets.UTF_8.name().equals(encoding)) { + appendHeader(headerBuilder, CONTENT_TYPE_HEADER, JSON_MIME_TYPE); + headerBuilder.append("; charset=").append(encoding).append(CRLF); + } + headerBuilder.append(CRLF); + return headerBuilder.toString(); + } + /** Append a header attribute to the given builder. */ + protected StringBuilder appendHeader(StringBuilder builder, String name, Object value) { + return builder.append(name).append(": ").append(value); + } } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/CollectionTypeAdapter.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/CollectionTypeAdapter.java index 96d34f80a..7bc37bdd8 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/CollectionTypeAdapter.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/CollectionTypeAdapter.java @@ -1,12 +1,12 @@ /****************************************************************************** * Copyright (c) 2016-2017 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc.json.adapters; @@ -34,113 +34,113 @@ import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonWriter; -/** - * A specialized type adapter for collections that can handle single values. - */ +/** A specialized type adapter for collections that can handle single values. */ public class CollectionTypeAdapter extends TypeAdapter> { - - public static class Factory implements TypeAdapterFactory { - @Override - @SuppressWarnings({ "unchecked" }) - public TypeAdapter create(Gson gson, TypeToken typeToken) { - if (!Collection.class.isAssignableFrom(typeToken.getRawType())) - return null; + public static class Factory implements TypeAdapterFactory { - Type[] elementTypes = TypeUtils.getElementTypes(typeToken, Collection.class); - if (elementTypes.length != 1) - return null; - Type elementType = elementTypes[0]; - TypeAdapter elementTypeAdapter; - if (elementType == Object.class) - elementTypeAdapter = new JsonElementTypeAdapter(gson); - else - elementTypeAdapter = gson.getAdapter(TypeToken.get(elementType)); - Supplier> constructor = getConstructor((Class>) typeToken.getRawType()); - return (TypeAdapter) create(gson, elementType, elementTypeAdapter, constructor); - } + @Override + @SuppressWarnings({"unchecked"}) + public TypeAdapter create(Gson gson, TypeToken typeToken) { + if (!Collection.class.isAssignableFrom(typeToken.getRawType())) return null; - @SuppressWarnings({ "unchecked", "rawtypes" }) - protected TypeAdapter create(Gson gson, Type elementType, TypeAdapter elementTypeAdapter, Supplier> constructor) { - return new CollectionTypeAdapter(gson, elementType, elementTypeAdapter, constructor); - } + Type[] elementTypes = TypeUtils.getElementTypes(typeToken, Collection.class); + if (elementTypes.length != 1) return null; + Type elementType = elementTypes[0]; + TypeAdapter elementTypeAdapter; + if (elementType == Object.class) elementTypeAdapter = new JsonElementTypeAdapter(gson); + else elementTypeAdapter = gson.getAdapter(TypeToken.get(elementType)); + Supplier> constructor = + getConstructor((Class>) typeToken.getRawType()); + return (TypeAdapter) create(gson, elementType, elementTypeAdapter, constructor); + } - protected Supplier> getConstructor(Class> rawType) { - try { - Constructor> constructor = rawType.getDeclaredConstructor(); - return () -> { - try { - return constructor.newInstance(); - } catch (Exception e) { - throw new JsonParseException(e); - } - }; - } catch (Exception e) { - if (SortedSet.class.isAssignableFrom(rawType)) - return () -> new TreeSet(); - else if (Set.class.isAssignableFrom(rawType)) - return () -> new LinkedHashSet(); - else if (Queue.class.isAssignableFrom(rawType)) - return () -> new LinkedList(); - else - return () -> new ArrayList(); - } - } + @SuppressWarnings({"unchecked", "rawtypes"}) + protected TypeAdapter create( + Gson gson, + Type elementType, + TypeAdapter elementTypeAdapter, + Supplier> constructor) { + return new CollectionTypeAdapter(gson, elementType, elementTypeAdapter, constructor); + } - } + protected Supplier> getConstructor(Class> rawType) { + try { + Constructor> constructor = rawType.getDeclaredConstructor(); + return () -> { + try { + return constructor.newInstance(); + } catch (Exception e) { + throw new JsonParseException(e); + } + }; + } catch (Exception e) { + if (SortedSet.class.isAssignableFrom(rawType)) return () -> new TreeSet(); + else if (Set.class.isAssignableFrom(rawType)) return () -> new LinkedHashSet(); + else if (Queue.class.isAssignableFrom(rawType)) return () -> new LinkedList(); + else return () -> new ArrayList(); + } + } + } - private final Gson gson; - private final Type elementType; - private final TypeAdapter elementTypeAdapter; - private final Supplier> constructor; + private final Gson gson; + private final Type elementType; + private final TypeAdapter elementTypeAdapter; + private final Supplier> constructor; - public CollectionTypeAdapter(Gson gson, Type elementType, TypeAdapter elementTypeAdapter, Supplier> constructor) { - this.gson = gson; - this.elementType = elementType; - this.elementTypeAdapter = elementTypeAdapter; - this.constructor = constructor; - } + public CollectionTypeAdapter( + Gson gson, + Type elementType, + TypeAdapter elementTypeAdapter, + Supplier> constructor) { + this.gson = gson; + this.elementType = elementType; + this.elementTypeAdapter = elementTypeAdapter; + this.constructor = constructor; + } - @Override - public Collection read(JsonReader in) throws IOException { - JsonToken peek = in.peek(); - if (peek == JsonToken.NULL) { - in.nextNull(); - return null; - } else if (peek == JsonToken.BEGIN_ARRAY) { - Collection collection = constructor.get(); - in.beginArray(); - while (in.hasNext()) { - E instance = elementTypeAdapter.read(in); - collection.add(instance); - } - in.endArray(); - return collection; - } else { - Collection collection = constructor.get(); - E instance = elementTypeAdapter.read(in); - collection.add(instance); - return collection; - } - } + @Override + public Collection read(JsonReader in) throws IOException { + JsonToken peek = in.peek(); + if (peek == JsonToken.NULL) { + in.nextNull(); + return null; + } else if (peek == JsonToken.BEGIN_ARRAY) { + Collection collection = constructor.get(); + in.beginArray(); + while (in.hasNext()) { + E instance = elementTypeAdapter.read(in); + collection.add(instance); + } + in.endArray(); + return collection; + } else { + Collection collection = constructor.get(); + E instance = elementTypeAdapter.read(in); + collection.add(instance); + return collection; + } + } - @Override - public void write(JsonWriter out, Collection collection) throws IOException { - if (collection == null) { - out.nullValue(); - return; - } - out.beginArray(); - for (E element : collection) { - if (element != null && elementType != element.getClass() - && (elementType instanceof TypeVariable || elementType instanceof Class)) { - @SuppressWarnings("unchecked") - TypeAdapter runtimeTypeAdapter = (TypeAdapter) gson.getAdapter(TypeToken.get(element.getClass())); - runtimeTypeAdapter.write(out, element); - } else { - elementTypeAdapter.write(out, element); - } - } - out.endArray(); - } + @Override + public void write(JsonWriter out, Collection collection) throws IOException { + if (collection == null) { + out.nullValue(); + return; + } + out.beginArray(); + for (E element : collection) { + if (element != null + && elementType != element.getClass() + && (elementType instanceof TypeVariable || elementType instanceof Class)) { + @SuppressWarnings("unchecked") + TypeAdapter runtimeTypeAdapter = + (TypeAdapter) gson.getAdapter(TypeToken.get(element.getClass())); + runtimeTypeAdapter.write(out, element); + } else { + elementTypeAdapter.write(out, element); + } + } + out.endArray(); + } } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/CollectionTypeAdapterFactory.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/CollectionTypeAdapterFactory.java index 7a153e285..93c2ccd3f 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/CollectionTypeAdapterFactory.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/CollectionTypeAdapterFactory.java @@ -1,12 +1,12 @@ /****************************************************************************** * Copyright (c) 2016 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc.json.adapters; @@ -23,18 +23,19 @@ import com.google.gson.TypeAdapter; */ @Deprecated public class CollectionTypeAdapterFactory extends CollectionTypeAdapter.Factory { - - /** - * @deprecated Use {@link CollectionTypeAdapter} instead. - */ - @Deprecated - protected static class Adapter extends CollectionTypeAdapter { - public Adapter(Gson gson, Type elementType, TypeAdapter elementTypeAdapter, - Supplier> constructor) { - super(gson, elementType, elementTypeAdapter, constructor); - } - - } - + /** + * @deprecated Use {@link CollectionTypeAdapter} instead. + */ + @Deprecated + protected static class Adapter extends CollectionTypeAdapter { + + public Adapter( + Gson gson, + Type elementType, + TypeAdapter elementTypeAdapter, + Supplier> constructor) { + super(gson, elementType, elementTypeAdapter, constructor); + } + } } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/EitherTypeAdapter.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/EitherTypeAdapter.java index 707c50613..3c126736b 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/EitherTypeAdapter.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/EitherTypeAdapter.java @@ -1,12 +1,12 @@ /****************************************************************************** * Copyright (c) 2016-2018 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc.json.adapters; @@ -34,253 +34,252 @@ import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonWriter; -/** - * Type adapter for {@link Either} and {@link Either3}. - */ +/** Type adapter for {@link Either} and {@link Either3}. */ public class EitherTypeAdapter extends TypeAdapter> { - public static class Factory implements TypeAdapterFactory { + public static class Factory implements TypeAdapterFactory { - @SuppressWarnings({ "rawtypes", "unchecked" }) - @Override - public TypeAdapter create(Gson gson, TypeToken typeToken) { - if (!TypeUtils.isEither(typeToken.getType())) { - return null; - } - return new EitherTypeAdapter(gson, typeToken); - } + @SuppressWarnings({"rawtypes", "unchecked"}) + @Override + public TypeAdapter create(Gson gson, TypeToken typeToken) { + if (!TypeUtils.isEither(typeToken.getType())) { + return null; + } + return new EitherTypeAdapter(gson, typeToken); + } + } - } - - /** - * A predicate that is usedful for checking alternatives in case both the left and the right type - * are JSON object types. - */ - public static class PropertyChecker implements Predicate { - - private final String propertyName; - private final String expectedValue; - private final Class expectedType; - - public PropertyChecker(String propertyName) { - this.propertyName = propertyName; - this.expectedValue = null; - this.expectedType = null; - } - - public PropertyChecker(String propertyName, String expectedValue) { - this.propertyName = propertyName; - this.expectedValue = expectedValue; - this.expectedType = null; - } - - public PropertyChecker(String propertyName, Class expectedType) { - this.propertyName = propertyName; - this.expectedType = expectedType; - this.expectedValue = null; - } + /** + * A predicate that is usedful for checking alternatives in case both the left and the right type + * are JSON object types. + */ + public static class PropertyChecker implements Predicate { - @Override - public boolean test(JsonElement element) { - if (element.isJsonObject()) { - JsonObject object = element.getAsJsonObject(); - JsonElement value = object.get(propertyName); - if (expectedValue != null) - return value != null && value.isJsonPrimitive() && expectedValue.equals(value.getAsString()); - else if (expectedType != null) - return expectedType.isInstance(value); - else - return value != null; - } - return false; - } - - } - - /** - * A predicate for the case that a type alternative is a list. - */ - public static class ListChecker implements Predicate { - - private final Predicate elementChecker; - private final boolean resultIfEmpty; - - public ListChecker(Predicate elementChecker) { - this(elementChecker, false); - } - - public ListChecker(Predicate elementChecker, boolean resultIfEmpty) { - this.elementChecker = elementChecker; - this.resultIfEmpty = resultIfEmpty; - } + private final String propertyName; + private final String expectedValue; + private final Class expectedType; - @Override - public boolean test(JsonElement t) { - if (elementChecker.test(t)) - return true; - if (t.isJsonArray()) { - JsonArray array = t.getAsJsonArray(); - if (array.size() == 0) - return resultIfEmpty; - for (JsonElement e : array) { - if (elementChecker.test(e)) - return true; - } - } - return false; - } - - } + public PropertyChecker(String propertyName) { + this.propertyName = propertyName; + this.expectedValue = null; + this.expectedType = null; + } - protected final TypeToken> typeToken; - protected final EitherTypeArgument left; - protected final EitherTypeArgument right; - protected final Predicate leftChecker; - protected final Predicate rightChecker; + public PropertyChecker(String propertyName, String expectedValue) { + this.propertyName = propertyName; + this.expectedValue = expectedValue; + this.expectedType = null; + } - public EitherTypeAdapter(Gson gson, TypeToken> typeToken) { - this(gson, typeToken, null, null); - } - - public EitherTypeAdapter(Gson gson, TypeToken> typeToken, Predicate leftChecker, Predicate rightChecker) { - this.typeToken = typeToken; - Type[] elementTypes = TypeUtils.getElementTypes(typeToken, Either.class); - this.left = new EitherTypeArgument(gson, elementTypes[0]); - this.right = new EitherTypeArgument(gson, elementTypes[1]); - this.leftChecker = leftChecker; - this.rightChecker = rightChecker; - } + public PropertyChecker(String propertyName, Class expectedType) { + this.propertyName = propertyName; + this.expectedType = expectedType; + this.expectedValue = null; + } - @Override - public void write(JsonWriter out, Either value) throws IOException { - if (value == null) { - out.nullValue(); - } else if (value.isLeft()) { - left.write(out, value.getLeft()); - } else { - right.write(out, value.getRight()); - } - } + @Override + public boolean test(JsonElement element) { + if (element.isJsonObject()) { + JsonObject object = element.getAsJsonObject(); + JsonElement value = object.get(propertyName); + if (expectedValue != null) + return value != null + && value.isJsonPrimitive() + && expectedValue.equals(value.getAsString()); + else if (expectedType != null) return expectedType.isInstance(value); + else return value != null; + } + return false; + } + } - @Override - public Either read(JsonReader in) throws IOException { - JsonToken next = in.peek(); - if (next == JsonToken.NULL) { - in.nextNull(); - return null; - } - return create(next, in); - } + /** A predicate for the case that a type alternative is a list. */ + public static class ListChecker implements Predicate { - protected Either create(JsonToken nextToken, JsonReader in) throws IOException { - boolean matchesLeft = left.isAssignable(nextToken); - boolean matchesRight = right.isAssignable(nextToken); - if (matchesLeft && matchesRight) { - if (leftChecker != null || rightChecker != null) { - JsonElement element = JsonParser.parseReader(in); - if (leftChecker != null && leftChecker.test(element)) - // Parse the left alternative from the JSON element tree - return createLeft(left.read(element)); - if (rightChecker != null && rightChecker.test(element)) - // Parse the right alternative from the JSON element tree - return createRight(right.read(element)); - } - throw new JsonParseException("Ambiguous Either type: token " + nextToken + " matches both alternatives."); - } else if (matchesLeft) { - // Parse the left alternative from the JSON stream - return createLeft(left.read(in)); - } else if (matchesRight) { - // Parse the right alternative from the JSON stream - return createRight(right.read(in)); - } else if (leftChecker != null || rightChecker != null) { - // If result is not the list but directly the only item in the list - JsonElement element = JsonParser.parseReader(in); - if (leftChecker != null && leftChecker.test(element)) - // Parse the left alternative from the JSON element tree - return createLeft(left.read(element)); - if (rightChecker != null && rightChecker.test(element)) - // Parse the right alternative from the JSON element tree - return createRight(right.read(element)); - } - throw new JsonParseException("Unexpected token " + nextToken + ": expected " + left + " | " + right + " tokens."); - } - - @SuppressWarnings("unchecked") - protected Either createLeft(L obj) throws IOException { - if (Either3.class.isAssignableFrom(typeToken.getRawType())) - return (Either) Either3.forLeft3(obj); - return Either.forLeft(obj); - } - - @SuppressWarnings("unchecked") - protected Either createRight(R obj) throws IOException { - if (Either3.class.isAssignableFrom(typeToken.getRawType())) - return (Either) Either3.forRight3((Either) obj); - return Either.forRight(obj); - } + private final Predicate elementChecker; + private final boolean resultIfEmpty; - protected static class EitherTypeArgument { + public ListChecker(Predicate elementChecker) { + this(elementChecker, false); + } - protected final TypeToken typeToken; - protected final TypeAdapter adapter; - protected final Collection expectedTokens; + public ListChecker(Predicate elementChecker, boolean resultIfEmpty) { + this.elementChecker = elementChecker; + this.resultIfEmpty = resultIfEmpty; + } - @SuppressWarnings("unchecked") - public EitherTypeArgument(Gson gson, Type type) { - this.typeToken = (TypeToken) TypeToken.get(type); - this.adapter = (type == Object.class) ? (TypeAdapter) new JsonElementTypeAdapter(gson) : gson.getAdapter(this.typeToken); - this.expectedTokens = new HashSet<>(); - for (Type expectedType : TypeUtils.getExpectedTypes(type)) { - Class rawType = TypeToken.get(expectedType).getRawType(); - JsonToken expectedToken = getExpectedToken(rawType); - expectedTokens.add(expectedToken); - } - } + @Override + public boolean test(JsonElement t) { + if (elementChecker.test(t)) return true; + if (t.isJsonArray()) { + JsonArray array = t.getAsJsonArray(); + if (array.size() == 0) return resultIfEmpty; + for (JsonElement e : array) { + if (elementChecker.test(e)) return true; + } + } + return false; + } + } - protected JsonToken getExpectedToken(Class rawType) { - if (rawType.isArray() || Collection.class.isAssignableFrom(rawType) || Tuple.class.isAssignableFrom(rawType)) { - return JsonToken.BEGIN_ARRAY; - } - if (Boolean.class.isAssignableFrom(rawType)) { - return JsonToken.BOOLEAN; - } - if (Number.class.isAssignableFrom(rawType) || Enum.class.isAssignableFrom(rawType)) { - return JsonToken.NUMBER; - } - if (Character.class.isAssignableFrom(rawType) || String.class.isAssignableFrom(rawType)) { - return JsonToken.STRING; - } - return JsonToken.BEGIN_OBJECT; - } + protected final TypeToken> typeToken; + protected final EitherTypeArgument left; + protected final EitherTypeArgument right; + protected final Predicate leftChecker; + protected final Predicate rightChecker; - public boolean isAssignable(JsonToken jsonToken) { - return this.expectedTokens.contains(jsonToken); - } + public EitherTypeAdapter(Gson gson, TypeToken> typeToken) { + this(gson, typeToken, null, null); + } - public void write(JsonWriter out, T value) throws IOException { - this.adapter.write(out, value); - } + public EitherTypeAdapter( + Gson gson, + TypeToken> typeToken, + Predicate leftChecker, + Predicate rightChecker) { + this.typeToken = typeToken; + Type[] elementTypes = TypeUtils.getElementTypes(typeToken, Either.class); + this.left = new EitherTypeArgument(gson, elementTypes[0]); + this.right = new EitherTypeArgument(gson, elementTypes[1]); + this.leftChecker = leftChecker; + this.rightChecker = rightChecker; + } - public T read(JsonReader in) throws IOException { - return this.adapter.read(in); - } - - public T read(JsonElement element) throws IOException { - return this.adapter.fromJsonTree(element); - } + @Override + public void write(JsonWriter out, Either value) throws IOException { + if (value == null) { + out.nullValue(); + } else if (value.isLeft()) { + left.write(out, value.getLeft()); + } else { + right.write(out, value.getRight()); + } + } - @Override - public String toString() { - StringBuilder builder = new StringBuilder(); - for (JsonToken expectedToken : expectedTokens) { - if (builder.length() != 0) { - builder.append(" | "); - } - builder.append(expectedToken); - } - return builder.toString(); - } + @Override + public Either read(JsonReader in) throws IOException { + JsonToken next = in.peek(); + if (next == JsonToken.NULL) { + in.nextNull(); + return null; + } + return create(next, in); + } - } + protected Either create(JsonToken nextToken, JsonReader in) throws IOException { + boolean matchesLeft = left.isAssignable(nextToken); + boolean matchesRight = right.isAssignable(nextToken); + if (matchesLeft && matchesRight) { + if (leftChecker != null || rightChecker != null) { + JsonElement element = JsonParser.parseReader(in); + if (leftChecker != null && leftChecker.test(element)) + // Parse the left alternative from the JSON element tree + return createLeft(left.read(element)); + if (rightChecker != null && rightChecker.test(element)) + // Parse the right alternative from the JSON element tree + return createRight(right.read(element)); + } + throw new JsonParseException( + "Ambiguous Either type: token " + nextToken + " matches both alternatives."); + } else if (matchesLeft) { + // Parse the left alternative from the JSON stream + return createLeft(left.read(in)); + } else if (matchesRight) { + // Parse the right alternative from the JSON stream + return createRight(right.read(in)); + } else if (leftChecker != null || rightChecker != null) { + // If result is not the list but directly the only item in the list + JsonElement element = JsonParser.parseReader(in); + if (leftChecker != null && leftChecker.test(element)) + // Parse the left alternative from the JSON element tree + return createLeft(left.read(element)); + if (rightChecker != null && rightChecker.test(element)) + // Parse the right alternative from the JSON element tree + return createRight(right.read(element)); + } + throw new JsonParseException( + "Unexpected token " + nextToken + ": expected " + left + " | " + right + " tokens."); + } + @SuppressWarnings("unchecked") + protected Either createLeft(L obj) throws IOException { + if (Either3.class.isAssignableFrom(typeToken.getRawType())) + return (Either) Either3.forLeft3(obj); + return Either.forLeft(obj); + } + + @SuppressWarnings("unchecked") + protected Either createRight(R obj) throws IOException { + if (Either3.class.isAssignableFrom(typeToken.getRawType())) + return (Either) Either3.forRight3((Either) obj); + return Either.forRight(obj); + } + + protected static class EitherTypeArgument { + + protected final TypeToken typeToken; + protected final TypeAdapter adapter; + protected final Collection expectedTokens; + + @SuppressWarnings("unchecked") + public EitherTypeArgument(Gson gson, Type type) { + this.typeToken = (TypeToken) TypeToken.get(type); + this.adapter = + (type == Object.class) + ? (TypeAdapter) new JsonElementTypeAdapter(gson) + : gson.getAdapter(this.typeToken); + this.expectedTokens = new HashSet<>(); + for (Type expectedType : TypeUtils.getExpectedTypes(type)) { + Class rawType = TypeToken.get(expectedType).getRawType(); + JsonToken expectedToken = getExpectedToken(rawType); + expectedTokens.add(expectedToken); + } + } + + protected JsonToken getExpectedToken(Class rawType) { + if (rawType.isArray() + || Collection.class.isAssignableFrom(rawType) + || Tuple.class.isAssignableFrom(rawType)) { + return JsonToken.BEGIN_ARRAY; + } + if (Boolean.class.isAssignableFrom(rawType)) { + return JsonToken.BOOLEAN; + } + if (Number.class.isAssignableFrom(rawType) || Enum.class.isAssignableFrom(rawType)) { + return JsonToken.NUMBER; + } + if (Character.class.isAssignableFrom(rawType) || String.class.isAssignableFrom(rawType)) { + return JsonToken.STRING; + } + return JsonToken.BEGIN_OBJECT; + } + + public boolean isAssignable(JsonToken jsonToken) { + return this.expectedTokens.contains(jsonToken); + } + + public void write(JsonWriter out, T value) throws IOException { + this.adapter.write(out, value); + } + + public T read(JsonReader in) throws IOException { + return this.adapter.read(in); + } + + public T read(JsonElement element) throws IOException { + return this.adapter.fromJsonTree(element); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + for (JsonToken expectedToken : expectedTokens) { + if (builder.length() != 0) { + builder.append(" | "); + } + builder.append(expectedToken); + } + return builder.toString(); + } + } } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/EitherTypeAdapterFactory.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/EitherTypeAdapterFactory.java index 5abbe8320..d285b9f01 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/EitherTypeAdapterFactory.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/EitherTypeAdapterFactory.java @@ -1,12 +1,12 @@ /****************************************************************************** * Copyright (c) 2016 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc.json.adapters; @@ -21,17 +21,15 @@ import com.google.gson.reflect.TypeToken; */ @Deprecated public class EitherTypeAdapterFactory extends EitherTypeAdapter.Factory { - - /** - * @deprecated Use {@link EitherTypeAdapter} instead. - */ - @Deprecated - protected static class Adapter extends EitherTypeAdapter { - public Adapter(Gson gson, TypeToken> typeToken) { - super(gson, typeToken); - } - - } - + /** + * @deprecated Use {@link EitherTypeAdapter} instead. + */ + @Deprecated + protected static class Adapter extends EitherTypeAdapter { + + public Adapter(Gson gson, TypeToken> typeToken) { + super(gson, typeToken); + } + } } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/EnumTypeAdapter.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/EnumTypeAdapter.java index c24ded2f8..c326796d4 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/EnumTypeAdapter.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/EnumTypeAdapter.java @@ -1,12 +1,12 @@ /****************************************************************************** * Copyright (c) 2016-2017 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc.json.adapters; @@ -24,82 +24,74 @@ import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonWriter; -/** - * A custom type adapter for enums that uses integer values. - */ +/** A custom type adapter for enums that uses integer values. */ public class EnumTypeAdapter> extends TypeAdapter { - - public static class Factory implements TypeAdapterFactory { - @Override - @SuppressWarnings({ "unchecked", "rawtypes" }) - public TypeAdapter create(Gson gson, TypeToken typeToken) { - Class rawType = typeToken.getRawType(); - if (!Enum.class.isAssignableFrom(rawType) || rawType == Enum.class) - return null; - if (!rawType.isEnum()) - rawType = rawType.getSuperclass(); - try { - return new EnumTypeAdapter(rawType); - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } - } - - } - - private static String VALUE_FIELD_NAME = "value"; - - private final Map nameToConstant = new HashMap<>(); - private final Map valueToConstant = new HashMap<>(); - private final Map constantToValue = new HashMap<>(); - - EnumTypeAdapter(Class classOfT) throws IllegalAccessException { - try { - Field valueField = classOfT.getDeclaredField(VALUE_FIELD_NAME); - if (valueField.getType() != int.class && valueField.getType() != Integer.class) - throw new IllegalArgumentException("The field 'value' must contain an integer value."); - valueField.setAccessible(true); - for (T constant : classOfT.getEnumConstants()) { - nameToConstant.put(constant.name(), constant); - Integer constValue = (Integer) valueField.get(constant); - valueToConstant.put(constValue, constant); - constantToValue.put(constant, constValue); - } - } catch (NoSuchFieldException e) { - for (T constant : classOfT.getEnumConstants()) { - nameToConstant.put(constant.name(), constant); - int constValue = constant.ordinal(); - valueToConstant.put(constValue, constant); - constantToValue.put(constant, constValue); - } - } - } - - @Override - public T read(JsonReader in) throws IOException { - JsonToken peek = in.peek(); - if (peek == JsonToken.NULL) { - in.nextNull(); - return null; - } else if (peek == JsonToken.NUMBER) { - return valueToConstant.get(in.nextInt()); - } else { - String string = in.nextString(); - try { - return valueToConstant.get(Integer.parseInt(string)); - } catch (NumberFormatException e) { - return nameToConstant.get(string); - } - } - } + public static class Factory implements TypeAdapterFactory { - @Override - public void write(JsonWriter out, T value) throws IOException { - if (value != null) - out.value(constantToValue.get(value)); - else - out.value((String) null); - } + @Override + @SuppressWarnings({"unchecked", "rawtypes"}) + public TypeAdapter create(Gson gson, TypeToken typeToken) { + Class rawType = typeToken.getRawType(); + if (!Enum.class.isAssignableFrom(rawType) || rawType == Enum.class) return null; + if (!rawType.isEnum()) rawType = rawType.getSuperclass(); + try { + return new EnumTypeAdapter(rawType); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + } + private static String VALUE_FIELD_NAME = "value"; + + private final Map nameToConstant = new HashMap<>(); + private final Map valueToConstant = new HashMap<>(); + private final Map constantToValue = new HashMap<>(); + + EnumTypeAdapter(Class classOfT) throws IllegalAccessException { + try { + Field valueField = classOfT.getDeclaredField(VALUE_FIELD_NAME); + if (valueField.getType() != int.class && valueField.getType() != Integer.class) + throw new IllegalArgumentException("The field 'value' must contain an integer value."); + valueField.setAccessible(true); + for (T constant : classOfT.getEnumConstants()) { + nameToConstant.put(constant.name(), constant); + Integer constValue = (Integer) valueField.get(constant); + valueToConstant.put(constValue, constant); + constantToValue.put(constant, constValue); + } + } catch (NoSuchFieldException e) { + for (T constant : classOfT.getEnumConstants()) { + nameToConstant.put(constant.name(), constant); + int constValue = constant.ordinal(); + valueToConstant.put(constValue, constant); + constantToValue.put(constant, constValue); + } + } + } + + @Override + public T read(JsonReader in) throws IOException { + JsonToken peek = in.peek(); + if (peek == JsonToken.NULL) { + in.nextNull(); + return null; + } else if (peek == JsonToken.NUMBER) { + return valueToConstant.get(in.nextInt()); + } else { + String string = in.nextString(); + try { + return valueToConstant.get(Integer.parseInt(string)); + } catch (NumberFormatException e) { + return nameToConstant.get(string); + } + } + } + + @Override + public void write(JsonWriter out, T value) throws IOException { + if (value != null) out.value(constantToValue.get(value)); + else out.value((String) null); + } } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/EnumTypeAdapterFactory.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/EnumTypeAdapterFactory.java index ee23ddeb2..1e5acfd58 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/EnumTypeAdapterFactory.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/EnumTypeAdapterFactory.java @@ -1,12 +1,12 @@ /****************************************************************************** * Copyright (c) 2016 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc.json.adapters; @@ -15,6 +15,4 @@ package org.eclipse.lsp4j.jsonrpc.json.adapters; * @deprecated Use {@link EnumTypeAdapter.Factory} instead. */ @Deprecated -public class EnumTypeAdapterFactory extends EnumTypeAdapter.Factory { - -} +public class EnumTypeAdapterFactory extends EnumTypeAdapter.Factory {} diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/JsonElementTypeAdapter.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/JsonElementTypeAdapter.java index 0e19daa9a..fa9c1d52f 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/JsonElementTypeAdapter.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/JsonElementTypeAdapter.java @@ -1,12 +1,12 @@ /****************************************************************************** * Copyright (c) 2018 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc.json.adapters; @@ -21,48 +21,45 @@ import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; -/** - * A type adapter that reads every input into a tree of {@link JsonElement}s. - */ +/** A type adapter that reads every input into a tree of {@link JsonElement}s. */ public class JsonElementTypeAdapter extends TypeAdapter { - - /** - * This factory should not be registered with a GsonBuilder because it always matches. - * Use it as argument to a {@link com.google.gson.annotations.JsonAdapter} annotation like this: - * {@code @JsonAdapter(JsonElementTypeAdapter.Factory.class)} - */ - public static class Factory implements TypeAdapterFactory { - @SuppressWarnings("unchecked") - @Override - public TypeAdapter create(Gson gson, TypeToken type) { - return (TypeAdapter) new JsonElementTypeAdapter(gson); - } - - } - - private final Gson gson; - private final TypeAdapter adapter; + /** + * This factory should not be registered with a GsonBuilder because it always matches. Use it as + * argument to a {@link com.google.gson.annotations.JsonAdapter} annotation like this: + * {@code @JsonAdapter(JsonElementTypeAdapter.Factory.class)} + */ + public static class Factory implements TypeAdapterFactory { - public JsonElementTypeAdapter(Gson gson) { - this.gson = gson; - this.adapter = gson.getAdapter(JsonElement.class); - } - - @Override - public JsonElement read(JsonReader in) throws IOException { - return adapter.read(in); - } + @SuppressWarnings("unchecked") + @Override + public TypeAdapter create(Gson gson, TypeToken type) { + return (TypeAdapter) new JsonElementTypeAdapter(gson); + } + } - @Override - public void write(JsonWriter out, Object value) throws IOException { - if (value == null) { - out.nullValue(); - } else if (value instanceof JsonElement) { - adapter.write(out, (JsonElement) value); - } else { - gson.toJson(value, value.getClass(), out);; - } - } + private final Gson gson; + private final TypeAdapter adapter; + public JsonElementTypeAdapter(Gson gson) { + this.gson = gson; + this.adapter = gson.getAdapter(JsonElement.class); + } + + @Override + public JsonElement read(JsonReader in) throws IOException { + return adapter.read(in); + } + + @Override + public void write(JsonWriter out, Object value) throws IOException { + if (value == null) { + out.nullValue(); + } else if (value instanceof JsonElement) { + adapter.write(out, (JsonElement) value); + } else { + gson.toJson(value, value.getClass(), out); + ; + } + } } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/MessageTypeAdapter.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/MessageTypeAdapter.java index bffdecb1e..9970ed919 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/MessageTypeAdapter.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/MessageTypeAdapter.java @@ -1,12 +1,12 @@ /****************************************************************************** * Copyright (c) 2016-2018 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc.json.adapters; @@ -49,411 +49,403 @@ import com.google.gson.stream.JsonWriter; import com.google.gson.stream.MalformedJsonException; /** - * The type adapter for messages dispatches between the different message types: {@link RequestMessage}, - * {@link ResponseMessage}, and {@link NotificationMessage}. + * The type adapter for messages dispatches between the different message types: {@link + * RequestMessage}, {@link ResponseMessage}, and {@link NotificationMessage}. */ public class MessageTypeAdapter extends TypeAdapter { - - public static class Factory implements TypeAdapterFactory { - - private final MessageJsonHandler handler; - - public Factory(MessageJsonHandler handler) { - this.handler = handler; - } - @Override - @SuppressWarnings("unchecked") - public TypeAdapter create(Gson gson, TypeToken typeToken) { - if (!Message.class.isAssignableFrom(typeToken.getRawType())) - return null; - return (TypeAdapter) new MessageTypeAdapter(handler, gson); - } - - } - - private static Type[] EMPTY_TYPE_ARRAY = {}; + public static class Factory implements TypeAdapterFactory { - private final MessageJsonHandler handler; - private final Gson gson; - - public MessageTypeAdapter(MessageJsonHandler handler, Gson gson) { - this.handler = handler; - this.gson = gson; - } + private final MessageJsonHandler handler; - @Override - public Message read(JsonReader in) throws IOException, JsonIOException, JsonSyntaxException { - if (in.peek() == JsonToken.NULL) { - in.nextNull(); - return null; - } - - in.beginObject(); - String jsonrpc = null, method = null; - Either id = null; - Object rawParams = null; - Object rawResult = null; - ResponseError responseError = null; - try { - - while (in.hasNext()) { - String name = in.nextName(); - switch (name) { - case "jsonrpc": { - jsonrpc = in.nextString(); - break; - } - case "id": { - if (in.peek() == JsonToken.NUMBER) - id = Either.forRight(in.nextInt()); - else - id = Either.forLeft(in.nextString()); - break; - } - case "method": { - method = in.nextString(); - break; - } - case "params": { - rawParams = parseParams(in, method); - break; - } - case "result": { - rawResult = parseResult(in, id != null ? id.get().toString() : null); - break; - } - case "error": { - responseError = gson.fromJson(in, ResponseError.class); - break; - } - default: - in.skipValue(); - } - } - Object params = parseParams(rawParams, method); - Object result = parseResult(rawResult, id != null ? id.get().toString() : null); - - in.endObject(); - return createMessage(jsonrpc, id, method, params, result, responseError); - - } catch (JsonSyntaxException | MalformedJsonException | EOFException exception) { - if (id != null || method != null) { - // Create a message and bundle it to an exception with an issue that wraps the original exception - Message message = createMessage(jsonrpc, id, method, rawParams, rawResult, responseError); - MessageIssue issue = new MessageIssue("Message could not be parsed.", ResponseErrorCode.ParseError.getValue(), exception); - throw new MessageIssueException(message, issue); - } else { - throw exception; - } - } - } + public Factory(MessageJsonHandler handler) { + this.handler = handler; + } - /** - * Convert the json input into the result object corresponding to the call made - * by id. - * - * If the id is not known until after parsing, call - * {@link #parseResult(Object, String)} on the return value of this call for a - * second chance conversion. - * - * @param in - * json input to read from - * @param id - * id of request message this is in response to - * @return correctly typed object if the correct expected type can be - * determined, or a JsonElement representing the result - */ - protected Object parseResult(JsonReader in, String id) throws JsonIOException, JsonSyntaxException { - Type type = null; - MethodProvider methodProvider = handler.getMethodProvider(); - if (methodProvider != null && id != null) { - String resolvedMethod = methodProvider.resolveMethod(id); - if (resolvedMethod != null) { - JsonRpcMethod jsonRpcMethod = handler.getJsonRpcMethod(resolvedMethod); - if (jsonRpcMethod != null) { - type = jsonRpcMethod.getReturnType(); - if (jsonRpcMethod.getReturnTypeAdapterFactory() != null) { - TypeAdapter typeAdapter = jsonRpcMethod.getReturnTypeAdapterFactory().create(gson, TypeToken.get(type)); - try { - if (typeAdapter != null) - return typeAdapter.read(in); - } catch (IOException exception) { - throw new JsonIOException(exception); - } - } - } - } - } - return fromJson(in, type); - } + @Override + @SuppressWarnings("unchecked") + public TypeAdapter create(Gson gson, TypeToken typeToken) { + if (!Message.class.isAssignableFrom(typeToken.getRawType())) return null; + return (TypeAdapter) new MessageTypeAdapter(handler, gson); + } + } - /** - * Convert the JsonElement into the result object corresponding to the call made - * by id. If the result is already converted, does nothing. - * - * @param result - * json element to read from - * @param id - * id of request message this is in response to - * @return correctly typed object if the correct expected type can be - * determined, or result unmodified if no conversion can be done. - */ - protected Object parseResult(Object result, String id) throws JsonSyntaxException { - if (result instanceof JsonElement) { - // Type of result could not be resolved - try again with the parsed JSON tree - Type type = null; - MethodProvider methodProvider = handler.getMethodProvider(); - if (methodProvider != null) { - String resolvedMethod = methodProvider.resolveMethod(id); - if (resolvedMethod != null) { - JsonRpcMethod jsonRpcMethod = handler.getJsonRpcMethod(resolvedMethod); - if (jsonRpcMethod != null) { - type = jsonRpcMethod.getReturnType(); - if (jsonRpcMethod.getReturnTypeAdapterFactory() != null) { - TypeAdapter typeAdapter = jsonRpcMethod.getReturnTypeAdapterFactory().create(gson, TypeToken.get(type)); - if (typeAdapter != null) - return typeAdapter.fromJsonTree((JsonElement) result); - } - } - } - } - return fromJson((JsonElement) result, type); - } - return result; - } + private static Type[] EMPTY_TYPE_ARRAY = {}; - /** - * Convert the json input into the parameters object corresponding to the call - * made by method. - * - * If the method is not known until after parsing, call - * {@link #parseParams(Object, String)} on the return value of this call for a - * second chance conversion. - * - * @param in - * json input to read from - * @param method - * method name of request - * @return correctly typed object if the correct expected type can be - * determined, or a JsonElement representing the parameters - */ - protected Object parseParams(JsonReader in, String method) throws IOException, JsonIOException { - JsonToken next = in.peek(); - if (next == JsonToken.NULL) { - in.nextNull(); - return null; - } - Type[] parameterTypes = getParameterTypes(method); - if (parameterTypes.length == 1) { - return fromJson(in, parameterTypes[0]); - } - if (parameterTypes.length > 1 && next == JsonToken.BEGIN_ARRAY) { - List parameters = new ArrayList(parameterTypes.length); - int index = 0; - in.beginArray(); - while (in.hasNext()) { - Type parameterType = index < parameterTypes.length ? parameterTypes[index] : null; - Object parameter = fromJson(in, parameterType); - parameters.add(parameter); - index++; - } - in.endArray(); - while (index < parameterTypes.length) { - parameters.add(null); - index++; - } - return parameters; - } - JsonElement rawParams = JsonParser.parseReader(in); - if (method != null && parameterTypes.length == 0 && ( - rawParams.isJsonArray() && rawParams.getAsJsonArray().size() == 0 - || rawParams.isJsonObject() && rawParams.getAsJsonObject().size() == 0)) { - return null; - } - return rawParams; - } + private final MessageJsonHandler handler; + private final Gson gson; - /** - * Convert the JsonElement into the parameters object corresponding to the call made - * by method. If the result is already converted, does nothing. - * - * @param params - * json element to read from - * @param method - * method name of request - * @return correctly typed object if the correct expected type can be - * determined, or params unmodified if no conversion can be done. - */ - protected Object parseParams(Object params, String method) { - if (isNull(params)) { - return null; - } - if (!(params instanceof JsonElement)) { - return params; - } - JsonElement rawParams = (JsonElement) params; - Type[] parameterTypes = getParameterTypes(method); - if (parameterTypes.length == 1) { - return fromJson(rawParams, parameterTypes[0]); - } - if (parameterTypes.length > 1 && rawParams instanceof JsonArray) { - JsonArray array = (JsonArray) rawParams; - List parameters = new ArrayList(Math.max(array.size(), parameterTypes.length)); - int index = 0; - Iterator iterator = array.iterator(); - while (iterator.hasNext()) { - Type parameterType = index < parameterTypes.length ? parameterTypes[index] : null; - Object parameter = fromJson(iterator.next(), parameterType); - parameters.add(parameter); - index++; - } - while (index < parameterTypes.length) { - parameters.add(null); - index++; - } - return parameters; - } - if (method != null && parameterTypes.length == 0 && ( - rawParams.isJsonArray() && rawParams.getAsJsonArray().size() == 0 - || rawParams.isJsonObject() && rawParams.getAsJsonObject().size() == 0)) { - return null; - } - return rawParams; - } + public MessageTypeAdapter(MessageJsonHandler handler, Gson gson) { + this.handler = handler; + this.gson = gson; + } - protected Object fromJson(JsonReader in, Type type) throws JsonIOException { - if (isNullOrVoidType(type)) { - return JsonParser.parseReader(in); - } - return gson.fromJson(in, type); - } - - protected Object fromJson(JsonElement element, Type type) { - if (isNull(element)) { - return null; - } - if (isNullOrVoidType(type)) { - return element; - } - Object value = gson.fromJson(element, type); - if (isNull(value)) { - return null; - } - return value; - } + @Override + public Message read(JsonReader in) throws IOException, JsonIOException, JsonSyntaxException { + if (in.peek() == JsonToken.NULL) { + in.nextNull(); + return null; + } - protected boolean isNull(Object value) { - return value == null || value instanceof JsonNull; - } - - protected boolean isNullOrVoidType(Type type) { - return type == null || Void.class == type; - } + in.beginObject(); + String jsonrpc = null, method = null; + Either id = null; + Object rawParams = null; + Object rawResult = null; + ResponseError responseError = null; + try { - protected Type[] getParameterTypes(String method) { - if (method != null) { - JsonRpcMethod jsonRpcMethod = handler.getJsonRpcMethod(method); - if (jsonRpcMethod != null) - return jsonRpcMethod.getParameterTypes(); - } - return EMPTY_TYPE_ARRAY; - } - - protected Message createMessage(String jsonrpc, Either id, String method, Object params, - Object responseResult, ResponseError responseError) throws JsonParseException { - if (id != null && method != null) { - RequestMessage message = new RequestMessage(); - message.setJsonrpc(jsonrpc); - message.setRawId(id); - message.setMethod(method); - message.setParams(params); - return message; - } else if (id != null) { - ResponseMessage message = new ResponseMessage(); - message.setJsonrpc(jsonrpc); - message.setRawId(id); - if (responseError != null) - message.setError(responseError); - else - message.setResult(responseResult); - return message; - } else if (method != null) { - NotificationMessage message = new NotificationMessage(); - message.setJsonrpc(jsonrpc); - message.setMethod(method); - message.setParams(params); - return message; - } else { - throw new JsonParseException("Unable to identify the input message."); - } - } + while (in.hasNext()) { + String name = in.nextName(); + switch (name) { + case "jsonrpc": + { + jsonrpc = in.nextString(); + break; + } + case "id": + { + if (in.peek() == JsonToken.NUMBER) id = Either.forRight(in.nextInt()); + else id = Either.forLeft(in.nextString()); + break; + } + case "method": + { + method = in.nextString(); + break; + } + case "params": + { + rawParams = parseParams(in, method); + break; + } + case "result": + { + rawResult = parseResult(in, id != null ? id.get().toString() : null); + break; + } + case "error": + { + responseError = gson.fromJson(in, ResponseError.class); + break; + } + default: + in.skipValue(); + } + } + Object params = parseParams(rawParams, method); + Object result = parseResult(rawResult, id != null ? id.get().toString() : null); - @Override - public void write(JsonWriter out, Message message) throws IOException { - out.beginObject(); - out.name("jsonrpc"); - out.value(message.getJsonrpc() == null ? MessageConstants.JSONRPC_VERSION : message.getJsonrpc()); - - if (message instanceof RequestMessage) { - RequestMessage requestMessage = (RequestMessage) message; - out.name("id"); - writeId(out, requestMessage.getRawId()); - out.name("method"); - out.value(requestMessage.getMethod()); - out.name("params"); - Object params = requestMessage.getParams(); - if (params == null) - writeNullValue(out); - else - gson.toJson(params, params.getClass(), out); - } else if (message instanceof ResponseMessage) { - ResponseMessage responseMessage = (ResponseMessage) message; - out.name("id"); - writeId(out, responseMessage.getRawId()); - if (responseMessage.getError() != null) { - out.name("error"); - gson.toJson(responseMessage.getError(), ResponseError.class, out); - } else { - out.name("result"); - Object result = responseMessage.getResult(); - if (result == null) - writeNullValue(out); - else - gson.toJson(result, result.getClass(), out); - } - } else if (message instanceof NotificationMessage) { - NotificationMessage notificationMessage = (NotificationMessage) message; - out.name("method"); - out.value(notificationMessage.getMethod()); - out.name("params"); - Object params = notificationMessage.getParams(); - if (params == null) - writeNullValue(out); - else - gson.toJson(params, params.getClass(), out); - } - - out.endObject(); - } - - protected void writeId(JsonWriter out, Either id) throws IOException { - if (id == null) - writeNullValue(out); - else if (id.isLeft()) - out.value(id.getLeft()); - else if (id.isRight()) - out.value(id.getRight()); - } - - /** - * Use this method to write a {@code null} value even if the JSON writer is set to not serialize {@code null}. - */ - protected void writeNullValue(JsonWriter out) throws IOException { - boolean previousSerializeNulls = out.getSerializeNulls(); - out.setSerializeNulls(true); - out.nullValue(); - out.setSerializeNulls(previousSerializeNulls); - } - + in.endObject(); + return createMessage(jsonrpc, id, method, params, result, responseError); + + } catch (JsonSyntaxException | MalformedJsonException | EOFException exception) { + if (id != null || method != null) { + // Create a message and bundle it to an exception with an issue that wraps the original + // exception + Message message = createMessage(jsonrpc, id, method, rawParams, rawResult, responseError); + MessageIssue issue = + new MessageIssue( + "Message could not be parsed.", ResponseErrorCode.ParseError.getValue(), exception); + throw new MessageIssueException(message, issue); + } else { + throw exception; + } + } + } + + /** + * Convert the json input into the result object corresponding to the call made by id. + * + *

    If the id is not known until after parsing, call {@link #parseResult(Object, String)} on the + * return value of this call for a second chance conversion. + * + * @param in json input to read from + * @param id id of request message this is in response to + * @return correctly typed object if the correct expected type can be determined, or a JsonElement + * representing the result + */ + protected Object parseResult(JsonReader in, String id) + throws JsonIOException, JsonSyntaxException { + Type type = null; + MethodProvider methodProvider = handler.getMethodProvider(); + if (methodProvider != null && id != null) { + String resolvedMethod = methodProvider.resolveMethod(id); + if (resolvedMethod != null) { + JsonRpcMethod jsonRpcMethod = handler.getJsonRpcMethod(resolvedMethod); + if (jsonRpcMethod != null) { + type = jsonRpcMethod.getReturnType(); + if (jsonRpcMethod.getReturnTypeAdapterFactory() != null) { + TypeAdapter typeAdapter = + jsonRpcMethod.getReturnTypeAdapterFactory().create(gson, TypeToken.get(type)); + try { + if (typeAdapter != null) return typeAdapter.read(in); + } catch (IOException exception) { + throw new JsonIOException(exception); + } + } + } + } + } + return fromJson(in, type); + } + + /** + * Convert the JsonElement into the result object corresponding to the call made by id. If the + * result is already converted, does nothing. + * + * @param result json element to read from + * @param id id of request message this is in response to + * @return correctly typed object if the correct expected type can be determined, or result + * unmodified if no conversion can be done. + */ + protected Object parseResult(Object result, String id) throws JsonSyntaxException { + if (result instanceof JsonElement) { + // Type of result could not be resolved - try again with the parsed JSON tree + Type type = null; + MethodProvider methodProvider = handler.getMethodProvider(); + if (methodProvider != null) { + String resolvedMethod = methodProvider.resolveMethod(id); + if (resolvedMethod != null) { + JsonRpcMethod jsonRpcMethod = handler.getJsonRpcMethod(resolvedMethod); + if (jsonRpcMethod != null) { + type = jsonRpcMethod.getReturnType(); + if (jsonRpcMethod.getReturnTypeAdapterFactory() != null) { + TypeAdapter typeAdapter = + jsonRpcMethod.getReturnTypeAdapterFactory().create(gson, TypeToken.get(type)); + if (typeAdapter != null) return typeAdapter.fromJsonTree((JsonElement) result); + } + } + } + } + return fromJson((JsonElement) result, type); + } + return result; + } + + /** + * Convert the json input into the parameters object corresponding to the call made by method. + * + *

    If the method is not known until after parsing, call {@link #parseParams(Object, String)} on + * the return value of this call for a second chance conversion. + * + * @param in json input to read from + * @param method method name of request + * @return correctly typed object if the correct expected type can be determined, or a JsonElement + * representing the parameters + */ + protected Object parseParams(JsonReader in, String method) throws IOException, JsonIOException { + JsonToken next = in.peek(); + if (next == JsonToken.NULL) { + in.nextNull(); + return null; + } + Type[] parameterTypes = getParameterTypes(method); + if (parameterTypes.length == 1) { + return fromJson(in, parameterTypes[0]); + } + if (parameterTypes.length > 1 && next == JsonToken.BEGIN_ARRAY) { + List parameters = new ArrayList(parameterTypes.length); + int index = 0; + in.beginArray(); + while (in.hasNext()) { + Type parameterType = index < parameterTypes.length ? parameterTypes[index] : null; + Object parameter = fromJson(in, parameterType); + parameters.add(parameter); + index++; + } + in.endArray(); + while (index < parameterTypes.length) { + parameters.add(null); + index++; + } + return parameters; + } + JsonElement rawParams = JsonParser.parseReader(in); + if (method != null + && parameterTypes.length == 0 + && (rawParams.isJsonArray() && rawParams.getAsJsonArray().size() == 0 + || rawParams.isJsonObject() && rawParams.getAsJsonObject().size() == 0)) { + return null; + } + return rawParams; + } + + /** + * Convert the JsonElement into the parameters object corresponding to the call made by method. If + * the result is already converted, does nothing. + * + * @param params json element to read from + * @param method method name of request + * @return correctly typed object if the correct expected type can be determined, or params + * unmodified if no conversion can be done. + */ + protected Object parseParams(Object params, String method) { + if (isNull(params)) { + return null; + } + if (!(params instanceof JsonElement)) { + return params; + } + JsonElement rawParams = (JsonElement) params; + Type[] parameterTypes = getParameterTypes(method); + if (parameterTypes.length == 1) { + return fromJson(rawParams, parameterTypes[0]); + } + if (parameterTypes.length > 1 && rawParams instanceof JsonArray) { + JsonArray array = (JsonArray) rawParams; + List parameters = + new ArrayList(Math.max(array.size(), parameterTypes.length)); + int index = 0; + Iterator iterator = array.iterator(); + while (iterator.hasNext()) { + Type parameterType = index < parameterTypes.length ? parameterTypes[index] : null; + Object parameter = fromJson(iterator.next(), parameterType); + parameters.add(parameter); + index++; + } + while (index < parameterTypes.length) { + parameters.add(null); + index++; + } + return parameters; + } + if (method != null + && parameterTypes.length == 0 + && (rawParams.isJsonArray() && rawParams.getAsJsonArray().size() == 0 + || rawParams.isJsonObject() && rawParams.getAsJsonObject().size() == 0)) { + return null; + } + return rawParams; + } + + protected Object fromJson(JsonReader in, Type type) throws JsonIOException { + if (isNullOrVoidType(type)) { + return JsonParser.parseReader(in); + } + return gson.fromJson(in, type); + } + + protected Object fromJson(JsonElement element, Type type) { + if (isNull(element)) { + return null; + } + if (isNullOrVoidType(type)) { + return element; + } + Object value = gson.fromJson(element, type); + if (isNull(value)) { + return null; + } + return value; + } + + protected boolean isNull(Object value) { + return value == null || value instanceof JsonNull; + } + + protected boolean isNullOrVoidType(Type type) { + return type == null || Void.class == type; + } + + protected Type[] getParameterTypes(String method) { + if (method != null) { + JsonRpcMethod jsonRpcMethod = handler.getJsonRpcMethod(method); + if (jsonRpcMethod != null) return jsonRpcMethod.getParameterTypes(); + } + return EMPTY_TYPE_ARRAY; + } + + protected Message createMessage( + String jsonrpc, + Either id, + String method, + Object params, + Object responseResult, + ResponseError responseError) + throws JsonParseException { + if (id != null && method != null) { + RequestMessage message = new RequestMessage(); + message.setJsonrpc(jsonrpc); + message.setRawId(id); + message.setMethod(method); + message.setParams(params); + return message; + } else if (id != null) { + ResponseMessage message = new ResponseMessage(); + message.setJsonrpc(jsonrpc); + message.setRawId(id); + if (responseError != null) message.setError(responseError); + else message.setResult(responseResult); + return message; + } else if (method != null) { + NotificationMessage message = new NotificationMessage(); + message.setJsonrpc(jsonrpc); + message.setMethod(method); + message.setParams(params); + return message; + } else { + throw new JsonParseException("Unable to identify the input message."); + } + } + + @Override + public void write(JsonWriter out, Message message) throws IOException { + out.beginObject(); + out.name("jsonrpc"); + out.value( + message.getJsonrpc() == null ? MessageConstants.JSONRPC_VERSION : message.getJsonrpc()); + + if (message instanceof RequestMessage) { + RequestMessage requestMessage = (RequestMessage) message; + out.name("id"); + writeId(out, requestMessage.getRawId()); + out.name("method"); + out.value(requestMessage.getMethod()); + out.name("params"); + Object params = requestMessage.getParams(); + if (params == null) writeNullValue(out); + else gson.toJson(params, params.getClass(), out); + } else if (message instanceof ResponseMessage) { + ResponseMessage responseMessage = (ResponseMessage) message; + out.name("id"); + writeId(out, responseMessage.getRawId()); + if (responseMessage.getError() != null) { + out.name("error"); + gson.toJson(responseMessage.getError(), ResponseError.class, out); + } else { + out.name("result"); + Object result = responseMessage.getResult(); + if (result == null) writeNullValue(out); + else gson.toJson(result, result.getClass(), out); + } + } else if (message instanceof NotificationMessage) { + NotificationMessage notificationMessage = (NotificationMessage) message; + out.name("method"); + out.value(notificationMessage.getMethod()); + out.name("params"); + Object params = notificationMessage.getParams(); + if (params == null) writeNullValue(out); + else gson.toJson(params, params.getClass(), out); + } + + out.endObject(); + } + + protected void writeId(JsonWriter out, Either id) throws IOException { + if (id == null) writeNullValue(out); + else if (id.isLeft()) out.value(id.getLeft()); + else if (id.isRight()) out.value(id.getRight()); + } + + /** + * Use this method to write a {@code null} value even if the JSON writer is set to not serialize + * {@code null}. + */ + protected void writeNullValue(JsonWriter out) throws IOException { + boolean previousSerializeNulls = out.getSerializeNulls(); + out.setSerializeNulls(true); + out.nullValue(); + out.setSerializeNulls(previousSerializeNulls); + } } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/MessageTypeAdapterFactory.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/MessageTypeAdapterFactory.java index 8afa239e4..7ad3fd6af 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/MessageTypeAdapterFactory.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/MessageTypeAdapterFactory.java @@ -1,12 +1,12 @@ /****************************************************************************** * Copyright (c) 2016 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc.json.adapters; @@ -19,8 +19,7 @@ import org.eclipse.lsp4j.jsonrpc.json.MessageJsonHandler; @Deprecated public class MessageTypeAdapterFactory extends MessageTypeAdapter.Factory { - public MessageTypeAdapterFactory(MessageJsonHandler handler) { - super(handler); - } - + public MessageTypeAdapterFactory(MessageJsonHandler handler) { + super(handler); + } } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/ThrowableTypeAdapter.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/ThrowableTypeAdapter.java index caaaf94eb..4f27a38e1 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/ThrowableTypeAdapter.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/ThrowableTypeAdapter.java @@ -1,12 +1,12 @@ /****************************************************************************** * Copyright (c) 2018 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc.json.adapters; @@ -27,110 +27,108 @@ import com.google.gson.stream.JsonWriter; * A type adapter for {@link Throwable}. This is used to report issues to the sender of a request. */ public class ThrowableTypeAdapter extends TypeAdapter { - - public static class Factory implements TypeAdapterFactory { - @SuppressWarnings({ "unchecked" }) - @Override - public TypeAdapter create(Gson gson, TypeToken typeToken) { - if (!Throwable.class.isAssignableFrom(typeToken.getRawType())) - return null; - - return (TypeAdapter) new ThrowableTypeAdapter((TypeToken) typeToken); - } + public static class Factory implements TypeAdapterFactory { - } - - private final TypeToken typeToken; - - public ThrowableTypeAdapter(TypeToken typeToken) { - this.typeToken = typeToken; - } + @SuppressWarnings({"unchecked"}) + @Override + public TypeAdapter create(Gson gson, TypeToken typeToken) { + if (!Throwable.class.isAssignableFrom(typeToken.getRawType())) return null; - @SuppressWarnings("unchecked") - @Override - public Throwable read(JsonReader in) throws IOException { - if (in.peek() == JsonToken.NULL) { - in.nextNull(); - return null; - } - - in.beginObject(); - String message = null; - Throwable cause = null; - while (in.hasNext()) { - String name = in.nextName(); - switch (name) { - case "message": { - message = in.nextString(); - break; - } - case "cause": { - cause = read(in); - break; - } - default: - in.skipValue(); - } - } - in.endObject(); - - try { - Constructor constructor; - if (message == null && cause == null) { - constructor = (Constructor) typeToken.getRawType().getDeclaredConstructor(); - return constructor.newInstance(); - } else if (message == null) { - constructor = (Constructor) typeToken.getRawType().getDeclaredConstructor(Throwable.class); - return constructor.newInstance(cause); - } else if (cause == null) { - constructor = (Constructor) typeToken.getRawType().getDeclaredConstructor(String.class); - return constructor.newInstance(message); - } else { - constructor = (Constructor) typeToken.getRawType().getDeclaredConstructor(String.class, Throwable.class); - return constructor.newInstance(message, cause); - } - } catch (NoSuchMethodException e) { - if (message == null && cause == null) - return new RuntimeException(); - else if (message == null) - return new RuntimeException(cause); - else if (cause == null) - return new RuntimeException(message); - else - return new RuntimeException(message, cause); - } catch (Exception e) { - throw new JsonParseException(e); - } - } + return (TypeAdapter) new ThrowableTypeAdapter((TypeToken) typeToken); + } + } - @Override - public void write(JsonWriter out, Throwable throwable) throws IOException { - if (throwable == null) { - out.nullValue(); - } else if (throwable.getMessage() == null && throwable.getCause() != null) { - write(out, throwable.getCause()); - } else { - out.beginObject(); - if (throwable.getMessage() != null) { - out.name("message"); - out.value(throwable.getMessage()); - } - if (shouldWriteCause(throwable)) { - out.name("cause"); - write(out, throwable.getCause()); - } - out.endObject(); - } - } - - private boolean shouldWriteCause(Throwable throwable) { - Throwable cause = throwable.getCause(); - if (cause == null || cause.getMessage() == null || cause == throwable) - return false; - if (throwable.getMessage() != null && throwable.getMessage().contains(cause.getMessage())) - return false; - return true; - } + private final TypeToken typeToken; + public ThrowableTypeAdapter(TypeToken typeToken) { + this.typeToken = typeToken; + } + + @SuppressWarnings("unchecked") + @Override + public Throwable read(JsonReader in) throws IOException { + if (in.peek() == JsonToken.NULL) { + in.nextNull(); + return null; + } + + in.beginObject(); + String message = null; + Throwable cause = null; + while (in.hasNext()) { + String name = in.nextName(); + switch (name) { + case "message": + { + message = in.nextString(); + break; + } + case "cause": + { + cause = read(in); + break; + } + default: + in.skipValue(); + } + } + in.endObject(); + + try { + Constructor constructor; + if (message == null && cause == null) { + constructor = (Constructor) typeToken.getRawType().getDeclaredConstructor(); + return constructor.newInstance(); + } else if (message == null) { + constructor = + (Constructor) typeToken.getRawType().getDeclaredConstructor(Throwable.class); + return constructor.newInstance(cause); + } else if (cause == null) { + constructor = + (Constructor) typeToken.getRawType().getDeclaredConstructor(String.class); + return constructor.newInstance(message); + } else { + constructor = + (Constructor) + typeToken.getRawType().getDeclaredConstructor(String.class, Throwable.class); + return constructor.newInstance(message, cause); + } + } catch (NoSuchMethodException e) { + if (message == null && cause == null) return new RuntimeException(); + else if (message == null) return new RuntimeException(cause); + else if (cause == null) return new RuntimeException(message); + else return new RuntimeException(message, cause); + } catch (Exception e) { + throw new JsonParseException(e); + } + } + + @Override + public void write(JsonWriter out, Throwable throwable) throws IOException { + if (throwable == null) { + out.nullValue(); + } else if (throwable.getMessage() == null && throwable.getCause() != null) { + write(out, throwable.getCause()); + } else { + out.beginObject(); + if (throwable.getMessage() != null) { + out.name("message"); + out.value(throwable.getMessage()); + } + if (shouldWriteCause(throwable)) { + out.name("cause"); + write(out, throwable.getCause()); + } + out.endObject(); + } + } + + private boolean shouldWriteCause(Throwable throwable) { + Throwable cause = throwable.getCause(); + if (cause == null || cause.getMessage() == null || cause == throwable) return false; + if (throwable.getMessage() != null && throwable.getMessage().contains(cause.getMessage())) + return false; + return true; + } } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/TupleTypeAdapters.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/TupleTypeAdapters.java index 2214f03de..a8da05187 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/TupleTypeAdapters.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/TupleTypeAdapters.java @@ -14,61 +14,59 @@ import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonWriter; public final class TupleTypeAdapters { - - private TupleTypeAdapters() {} - public static class TwoTypeAdapter extends TypeAdapter> { - - protected final TypeAdapter first; - protected final TypeAdapter second; - - @SuppressWarnings("unchecked") - public TwoTypeAdapter(Gson gson, TypeToken> typeToken) { - Type[] elementTypes = TypeUtils.getElementTypes(typeToken, Two.class); - this.first = gson.getAdapter((TypeToken) TypeToken.get(elementTypes[0])); - this.second = gson.getAdapter((TypeToken) TypeToken.get(elementTypes[1])); - } + private TupleTypeAdapters() {} - @Override - public void write(final JsonWriter out, final Two value) throws IOException { - if (value == null) { - out.nullValue(); - } else { - out.beginArray(); - first.write(out, value.getFirst()); - second.write(out, value.getSecond()); - out.endArray(); - } - } - - @Override - public Two read(final JsonReader in) throws IOException { - JsonToken next = in.peek(); - if (next == JsonToken.NULL) { - in.nextNull(); - return null; - } - in.beginArray(); - F f = first.read(in); - S s = second.read(in); - Two result = new Two(f, s); - in.endArray(); - return result; - } - - } + public static class TwoTypeAdapter + extends TypeAdapter> { - public static class TwoTypeAdapterFactory implements TypeAdapterFactory { + protected final TypeAdapter first; + protected final TypeAdapter second; - @SuppressWarnings({ "rawtypes", "unchecked" }) - @Override - public TypeAdapter create(Gson gson, TypeToken typeToken) { - if (!TypeUtils.isTwoTuple(typeToken.getType())) { - return null; - } - return new TwoTypeAdapter(gson, typeToken); - } + @SuppressWarnings("unchecked") + public TwoTypeAdapter(Gson gson, TypeToken> typeToken) { + Type[] elementTypes = TypeUtils.getElementTypes(typeToken, Two.class); + this.first = gson.getAdapter((TypeToken) TypeToken.get(elementTypes[0])); + this.second = gson.getAdapter((TypeToken) TypeToken.get(elementTypes[1])); + } - } + @Override + public void write(final JsonWriter out, final Two value) throws IOException { + if (value == null) { + out.nullValue(); + } else { + out.beginArray(); + first.write(out, value.getFirst()); + second.write(out, value.getSecond()); + out.endArray(); + } + } + @Override + public Two read(final JsonReader in) throws IOException { + JsonToken next = in.peek(); + if (next == JsonToken.NULL) { + in.nextNull(); + return null; + } + in.beginArray(); + F f = first.read(in); + S s = second.read(in); + Two result = new Two(f, s); + in.endArray(); + return result; + } + } + + public static class TwoTypeAdapterFactory implements TypeAdapterFactory { + + @SuppressWarnings({"rawtypes", "unchecked"}) + @Override + public TypeAdapter create(Gson gson, TypeToken typeToken) { + if (!TypeUtils.isTwoTuple(typeToken.getType())) { + return null; + } + return new TwoTypeAdapter(gson, typeToken); + } + } } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/TypeUtils.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/TypeUtils.java index 11e453088..3f74e6b89 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/TypeUtils.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/json/adapters/TypeUtils.java @@ -1,12 +1,12 @@ /****************************************************************************** * Copyright (c) 2017 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc.json.adapters; @@ -27,216 +27,203 @@ import org.eclipse.lsp4j.jsonrpc.messages.Tuple; import com.google.gson.reflect.TypeToken; -/** - * Utilities for handling types in the JSON parser / serializer. - */ +/** Utilities for handling types in the JSON parser / serializer. */ public final class TypeUtils { - - private TypeUtils() {} - - /** - * Determine the actual type arguments of the given type token with regard to the given target type. - */ - public static Type[] getElementTypes(TypeToken typeToken, Class targetType) { - return getElementTypes(typeToken.getType(), typeToken.getRawType(), targetType); - } - - private static Type[] getElementTypes(Type type, Class rawType, Class targetType) { - if (targetType.equals(rawType) && type instanceof ParameterizedType) { - Type mappedType; - if (type instanceof ParameterizedTypeImpl) - mappedType = type; - else - // Transform wildcards in the actual type arguments - mappedType = getMappedType(type, Collections.emptyMap()); - return ((ParameterizedType) mappedType).getActualTypeArguments(); - } - // Map the parameters of the raw type to the actual type arguments - Map varMapping = createVariableMapping(type, rawType); - if (targetType.isInterface()) { - // Look for superinterfaces that extend the target interface - Class[] interfaces = rawType.getInterfaces(); - for (int i = 0; i < interfaces.length; i++) { - if (Collection.class.isAssignableFrom(interfaces[i])) { - Type genericInterface = rawType.getGenericInterfaces()[i]; - Type mappedInterface = getMappedType(genericInterface, varMapping); - return getElementTypes(mappedInterface, interfaces[i], targetType); - } - } - } - if (!rawType.isInterface()) { - // Visit the superclass if it extends the target class / implements the target interface - Class rawSupertype = rawType.getSuperclass(); - if (targetType.isAssignableFrom(rawSupertype)) { - Type genericSuperclass = rawType.getGenericSuperclass(); - Type mappedSuperclass = getMappedType(genericSuperclass, varMapping); - return getElementTypes(mappedSuperclass, rawSupertype, targetType); - } - } - // No luck, return an array of Object types - Type[] result = new Type[targetType.getTypeParameters().length]; - Arrays.fill(result, Object.class); - return result; - } - - private static Map createVariableMapping(Type type, Class rawType) { - if (type instanceof ParameterizedType) { - TypeVariable>[] vars = rawType.getTypeParameters(); - Type[] args = ((ParameterizedType) type).getActualTypeArguments(); - Map newVarMapping = new HashMap<>(capacity(vars.length)); - for (int i = 0; i < vars.length; i++) { - Type actualType = Object.class; - if (i < args.length) { - actualType = args[i]; - if (actualType instanceof WildcardType) - actualType = ((WildcardType) actualType).getUpperBounds()[0]; - } - newVarMapping.put(vars[i].getName(), actualType); - } - return newVarMapping; - } - return Collections.emptyMap(); - } - - private static int capacity(int expectedSize) { - if (expectedSize < 3) - return expectedSize + 1; - else - return expectedSize + expectedSize / 3; - } - - private static Type getMappedType(Type type, Map varMapping) { - if (type instanceof TypeVariable) { - String name = ((TypeVariable) type).getName(); - if (varMapping.containsKey(name)) - return varMapping.get(name); - } - if (type instanceof WildcardType) { - return getMappedType(((WildcardType) type).getUpperBounds()[0], varMapping); - } - if (type instanceof ParameterizedType) { - ParameterizedType pt = (ParameterizedType) type; - Type[] origArgs = pt.getActualTypeArguments(); - Type[] mappedArgs = new Type[origArgs.length]; - for (int i = 0; i < origArgs.length; i++) { - mappedArgs[i] = getMappedType(origArgs[i], varMapping); - } - return new ParameterizedTypeImpl(pt, mappedArgs); - } - return type; - } - private static class ParameterizedTypeImpl implements ParameterizedType { - - private final Type ownerType; - private final Type rawType; - private final Type[] actualTypeArguments; - - ParameterizedTypeImpl(ParameterizedType original, Type[] typeArguments) { - this(original.getOwnerType(), original.getRawType(), typeArguments); - } - - ParameterizedTypeImpl(Type ownerType, Type rawType, Type[] typeArguments) { - this.ownerType = ownerType; - this.rawType = rawType; - this.actualTypeArguments = typeArguments; - } - - @Override - public Type getOwnerType() { - return ownerType; - } - - @Override - public Type getRawType() { - return rawType; - } + private TypeUtils() {} - @Override - public Type[] getActualTypeArguments() { - return actualTypeArguments; - } - - @Override - public String toString() { - StringBuilder result = new StringBuilder(); - if (ownerType != null) { - result.append(toString(ownerType)); - result.append('$'); - } - result.append(toString(rawType)); - result.append('<'); - for (int i = 0; i < actualTypeArguments.length; i++) { - if (i > 0) - result.append(", "); - result.append(toString(actualTypeArguments[i])); - } - result.append('>'); - return result.toString(); - } - - private String toString(Type type) { - if (type instanceof Class) - return ((Class) type).getName(); - else - return String.valueOf(type); - } - - } - - /** - * Return all possible types that can be expected when an element of the given type is parsed. - * If the type satisfies {@link #isEither(Type)}, a list of the corresponding type arguments is returned, - * otherwise a list containg the type itself is returned. Type parameters are not resolved - * by this method (use {@link #getElementTypes(TypeToken, Class)} to get resolved parameters). - */ - public static Collection getExpectedTypes(Type type) { - Collection result = new ArrayList<>(); - collectExpectedTypes(type, result); - return result; - } + /** + * Determine the actual type arguments of the given type token with regard to the given target + * type. + */ + public static Type[] getElementTypes(TypeToken typeToken, Class targetType) { + return getElementTypes(typeToken.getType(), typeToken.getRawType(), targetType); + } - private static void collectExpectedTypes(Type type, Collection types) { - if (isEither(type)) { - if (type instanceof ParameterizedType) { - for (Type typeArgument : ((ParameterizedType) type).getActualTypeArguments()) { - collectExpectedTypes(typeArgument, types); - } - } - if (type instanceof Class) { - for (Type typeParameter : ((Class) type).getTypeParameters()) { - collectExpectedTypes(typeParameter, types); - } - } - } else { - types.add(type); - } - } + private static Type[] getElementTypes(Type type, Class rawType, Class targetType) { + if (targetType.equals(rawType) && type instanceof ParameterizedType) { + Type mappedType; + if (type instanceof ParameterizedTypeImpl) mappedType = type; + else + // Transform wildcards in the actual type arguments + mappedType = getMappedType(type, Collections.emptyMap()); + return ((ParameterizedType) mappedType).getActualTypeArguments(); + } + // Map the parameters of the raw type to the actual type arguments + Map varMapping = createVariableMapping(type, rawType); + if (targetType.isInterface()) { + // Look for superinterfaces that extend the target interface + Class[] interfaces = rawType.getInterfaces(); + for (int i = 0; i < interfaces.length; i++) { + if (Collection.class.isAssignableFrom(interfaces[i])) { + Type genericInterface = rawType.getGenericInterfaces()[i]; + Type mappedInterface = getMappedType(genericInterface, varMapping); + return getElementTypes(mappedInterface, interfaces[i], targetType); + } + } + } + if (!rawType.isInterface()) { + // Visit the superclass if it extends the target class / implements the target interface + Class rawSupertype = rawType.getSuperclass(); + if (targetType.isAssignableFrom(rawSupertype)) { + Type genericSuperclass = rawType.getGenericSuperclass(); + Type mappedSuperclass = getMappedType(genericSuperclass, varMapping); + return getElementTypes(mappedSuperclass, rawSupertype, targetType); + } + } + // No luck, return an array of Object types + Type[] result = new Type[targetType.getTypeParameters().length]; + Arrays.fill(result, Object.class); + return result; + } - /** - * Test whether the given type is Either. - */ - public static boolean isEither(Type type) { - if (type instanceof ParameterizedType) { - return isEither(((ParameterizedType) type).getRawType()); - } - if (type instanceof Class) { - return Either.class.isAssignableFrom((Class) type); - } - return false; - } - - /** - * Test whether the given type is a two-tuple (pair). - */ - public static boolean isTwoTuple(Type type) { - if (type instanceof ParameterizedType) { - return isTwoTuple(((ParameterizedType) type).getRawType()); - } - if (type instanceof Class) { - return Tuple.Two.class.isAssignableFrom((Class) type); - } - return false; - } + private static Map createVariableMapping(Type type, Class rawType) { + if (type instanceof ParameterizedType) { + TypeVariable>[] vars = rawType.getTypeParameters(); + Type[] args = ((ParameterizedType) type).getActualTypeArguments(); + Map newVarMapping = new HashMap<>(capacity(vars.length)); + for (int i = 0; i < vars.length; i++) { + Type actualType = Object.class; + if (i < args.length) { + actualType = args[i]; + if (actualType instanceof WildcardType) + actualType = ((WildcardType) actualType).getUpperBounds()[0]; + } + newVarMapping.put(vars[i].getName(), actualType); + } + return newVarMapping; + } + return Collections.emptyMap(); + } + private static int capacity(int expectedSize) { + if (expectedSize < 3) return expectedSize + 1; + else return expectedSize + expectedSize / 3; + } + + private static Type getMappedType(Type type, Map varMapping) { + if (type instanceof TypeVariable) { + String name = ((TypeVariable) type).getName(); + if (varMapping.containsKey(name)) return varMapping.get(name); + } + if (type instanceof WildcardType) { + return getMappedType(((WildcardType) type).getUpperBounds()[0], varMapping); + } + if (type instanceof ParameterizedType) { + ParameterizedType pt = (ParameterizedType) type; + Type[] origArgs = pt.getActualTypeArguments(); + Type[] mappedArgs = new Type[origArgs.length]; + for (int i = 0; i < origArgs.length; i++) { + mappedArgs[i] = getMappedType(origArgs[i], varMapping); + } + return new ParameterizedTypeImpl(pt, mappedArgs); + } + return type; + } + + private static class ParameterizedTypeImpl implements ParameterizedType { + + private final Type ownerType; + private final Type rawType; + private final Type[] actualTypeArguments; + + ParameterizedTypeImpl(ParameterizedType original, Type[] typeArguments) { + this(original.getOwnerType(), original.getRawType(), typeArguments); + } + + ParameterizedTypeImpl(Type ownerType, Type rawType, Type[] typeArguments) { + this.ownerType = ownerType; + this.rawType = rawType; + this.actualTypeArguments = typeArguments; + } + + @Override + public Type getOwnerType() { + return ownerType; + } + + @Override + public Type getRawType() { + return rawType; + } + + @Override + public Type[] getActualTypeArguments() { + return actualTypeArguments; + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + if (ownerType != null) { + result.append(toString(ownerType)); + result.append('$'); + } + result.append(toString(rawType)); + result.append('<'); + for (int i = 0; i < actualTypeArguments.length; i++) { + if (i > 0) result.append(", "); + result.append(toString(actualTypeArguments[i])); + } + result.append('>'); + return result.toString(); + } + + private String toString(Type type) { + if (type instanceof Class) return ((Class) type).getName(); + else return String.valueOf(type); + } + } + + /** + * Return all possible types that can be expected when an element of the given type is parsed. If + * the type satisfies {@link #isEither(Type)}, a list of the corresponding type arguments is + * returned, otherwise a list containg the type itself is returned. Type parameters are + * not resolved by this method (use {@link #getElementTypes(TypeToken, Class)} to get + * resolved parameters). + */ + public static Collection getExpectedTypes(Type type) { + Collection result = new ArrayList<>(); + collectExpectedTypes(type, result); + return result; + } + + private static void collectExpectedTypes(Type type, Collection types) { + if (isEither(type)) { + if (type instanceof ParameterizedType) { + for (Type typeArgument : ((ParameterizedType) type).getActualTypeArguments()) { + collectExpectedTypes(typeArgument, types); + } + } + if (type instanceof Class) { + for (Type typeParameter : ((Class) type).getTypeParameters()) { + collectExpectedTypes(typeParameter, types); + } + } + } else { + types.add(type); + } + } + + /** Test whether the given type is Either. */ + public static boolean isEither(Type type) { + if (type instanceof ParameterizedType) { + return isEither(((ParameterizedType) type).getRawType()); + } + if (type instanceof Class) { + return Either.class.isAssignableFrom((Class) type); + } + return false; + } + + /** Test whether the given type is a two-tuple (pair). */ + public static boolean isTwoTuple(Type type) { + if (type instanceof ParameterizedType) { + return isTwoTuple(((ParameterizedType) type).getRawType()); + } + if (type instanceof Class) { + return Tuple.Two.class.isAssignableFrom((Class) type); + } + return false; + } } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/CancelParams.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/CancelParams.java index 2c6a35ff6..6911ee7c0 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/CancelParams.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/CancelParams.java @@ -1,12 +1,12 @@ /****************************************************************************** * Copyright (c) 2016 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc.messages; @@ -14,73 +14,59 @@ package org.eclipse.lsp4j.jsonrpc.messages; import org.eclipse.lsp4j.jsonrpc.json.MessageJsonHandler; import org.eclipse.lsp4j.jsonrpc.validation.NonNull; -/** - * To cancel a request a notification message with the following properties is sent. - */ +/** To cancel a request a notification message with the following properties is sent. */ public class CancelParams { - - /** - * The request id to cancel. - */ - @NonNull - private Either id; - @NonNull - public String getId() { - if (id == null) - return null; - if (id.isLeft()) - return id.getLeft(); - if (id.isRight()) - return id.getRight().toString(); - return null; - } - - @NonNull - public Either getRawId() { - return id; - } + /** The request id to cancel. */ + @NonNull private Either id; - public void setId(@NonNull String id) { - this.id = Either.forLeft(id); - } - - public void setId(@NonNull int id) { - this.id = Either.forRight(id); - } - - public void setRawId(@NonNull Either id) { - this.id = id; - } - - @Override - public String toString() { - return MessageJsonHandler.toString(this); - } + @NonNull + public String getId() { + if (id == null) return null; + if (id.isLeft()) return id.getLeft(); + if (id.isRight()) return id.getRight().toString(); + return null; + } - @Override - public boolean equals(final Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - CancelParams other = (CancelParams) obj; - if (this.id == null) { - if (other.id != null) - return false; - } else if (!this.id.equals(other.id)) - return false; - return true; - } + @NonNull + public Either getRawId() { + return id; + } - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((this.id == null) ? 0 : this.id.hashCode()); - return result; - } + public void setId(@NonNull String id) { + this.id = Either.forLeft(id); + } + public void setId(@NonNull int id) { + this.id = Either.forRight(id); + } + + public void setRawId(@NonNull Either id) { + this.id = id; + } + + @Override + public String toString() { + return MessageJsonHandler.toString(this); + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + CancelParams other = (CancelParams) obj; + if (this.id == null) { + if (other.id != null) return false; + } else if (!this.id.equals(other.id)) return false; + return true; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((this.id == null) ? 0 : this.id.hashCode()); + return result; + } } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/Either.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/Either.java index 0b5f48447..91f566646 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/Either.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/Either.java @@ -1,12 +1,12 @@ /****************************************************************************** * Copyright (c) 2016 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc.messages; @@ -19,205 +19,205 @@ import java.util.function.Function; import org.eclipse.lsp4j.jsonrpc.validation.NonNull; -/** - * An either type maps union types in protocol specifications. - */ +/** An either type maps union types in protocol specifications. */ public class Either { - public static Either forLeft(@NonNull L left) { - return new Either<>(left, null); - } + public static Either forLeft(@NonNull L left) { + return new Either<>(left, null); + } - public static Either forRight(@NonNull R right) { - return new Either<>(null, right); - } + public static Either forRight(@NonNull R right) { + return new Either<>(null, right); + } - private final L left; - private final R right; + private final L left; + private final R right; - protected Either(L left, R right) { - super(); - this.left = left; - this.right = right; - } + protected Either(L left, R right) { + super(); + this.left = left; + this.right = right; + } - public L getLeft() { - return left; - } + public L getLeft() { + return left; + } - public R getRight() { - return right; - } - - public Object get() { - if (left != null) - return left; - if (right != null) - return right; - return null; - } + public R getRight() { + return right; + } - public boolean isLeft() { - return left != null; - } + public Object get() { + if (left != null) return left; + if (right != null) return right; + return null; + } - public boolean isRight() { - return right != null; - } + public boolean isLeft() { + return left != null; + } - public T map( - @NonNull Function mapLeft, - @NonNull Function mapRight) { - if (isLeft()) { - return mapLeft.apply(getLeft()); - } - if (isRight()) { - return mapRight.apply(getRight()); - } - return null; - } + public boolean isRight() { + return right != null; + } - @Override - public boolean equals(Object obj) { - if (obj instanceof Either) { - Either other = (Either) obj; - return (this.left == other.left && this.right == other.right) - || (this.left != null && other.left != null && this.left.equals(other.left)) - || (this.right != null && other.right != null && this.right.equals(other.right)); - } - return false; - } - - @Override - public int hashCode() { - if (this.left != null) - return this.left.hashCode(); - if (this.right != null) - return this.right.hashCode(); - return 0; - } + public T map( + @NonNull Function mapLeft, + @NonNull Function mapRight) { + if (isLeft()) { + return mapLeft.apply(getLeft()); + } + if (isRight()) { + return mapRight.apply(getRight()); + } + return null; + } - public String toString() { - StringBuilder builder = new StringBuilder("Either [").append(System.lineSeparator()); - builder.append(" left = ").append(left).append(System.lineSeparator()); - builder.append(" right = ").append(right).append(System.lineSeparator()); - return builder.append("]").toString(); - } + @Override + public boolean equals(Object obj) { + if (obj instanceof Either) { + Either other = (Either) obj; + return (this.left == other.left && this.right == other.right) + || (this.left != null && other.left != null && this.left.equals(other.left)) + || (this.right != null && other.right != null && this.right.equals(other.right)); + } + return false; + } - /** - * Return a left disjoint type if the given type is either. - * - * @deprecated Use {@link org.eclipse.lsp4j.jsonrpc.json.adapters.TypeUtils#getElementTypes(Type, Class, Class)} instead - */ - @Deprecated - public static Type getLeftDisjointType(Type type) { - if (isEither(type)) { - if (type instanceof ParameterizedType) { - final ParameterizedType parameterizedType = (ParameterizedType) type; - return parameterizedType.getActualTypeArguments()[0]; - } - if (type instanceof Class) { - final Class cls = (Class) type; - return cls.getTypeParameters()[0]; - } - } - return null; - } + @Override + public int hashCode() { + if (this.left != null) return this.left.hashCode(); + if (this.right != null) return this.right.hashCode(); + return 0; + } - /** - * Return a right disjoint type if the given type is either. - * - * @deprecated Use {@link org.eclipse.lsp4j.jsonrpc.json.adapters.TypeUtils#getElementTypes(Type, Class, Class)} instead - */ - @Deprecated - public static Type getRightDisjointType(Type type) { - if (isEither(type)) { - if (type instanceof ParameterizedType) { - final ParameterizedType parameterizedType = (ParameterizedType) type; - return parameterizedType.getActualTypeArguments()[1]; - } - if (type instanceof Class) { - final Class cls = (Class) type; - return cls.getTypeParameters()[1]; - } - } - return null; - } - - /** - * Return all disjoint types. - * - * @deprecated Use {@link org.eclipse.lsp4j.jsonrpc.json.adapters.TypeUtils#getExpectedTypes(Type)} instead - */ - @Deprecated - public static Collection getAllDisjoinTypes(Type type) { - return collectDisjoinTypes(type, new ArrayList<>()); - } + public String toString() { + StringBuilder builder = new StringBuilder("Either [").append(System.lineSeparator()); + builder.append(" left = ").append(left).append(System.lineSeparator()); + builder.append(" right = ").append(right).append(System.lineSeparator()); + return builder.append("]").toString(); + } - @Deprecated - protected static Collection collectDisjoinTypes(Type type, Collection types) { - if (isEither(type)) { - if (type instanceof ParameterizedType) { - return collectDisjoinTypes((ParameterizedType) type, types); - } - if (type instanceof Class) { - return collectDisjoinTypes((Class) type, types); - } - } - types.add(type); - return types; - } + /** + * Return a left disjoint type if the given type is either. + * + * @deprecated Use {@link org.eclipse.lsp4j.jsonrpc.json.adapters.TypeUtils#getElementTypes(Type, + * Class, Class)} instead + */ + @Deprecated + public static Type getLeftDisjointType(Type type) { + if (isEither(type)) { + if (type instanceof ParameterizedType) { + final ParameterizedType parameterizedType = (ParameterizedType) type; + return parameterizedType.getActualTypeArguments()[0]; + } + if (type instanceof Class) { + final Class cls = (Class) type; + return cls.getTypeParameters()[0]; + } + } + return null; + } - @Deprecated - protected static Collection collectDisjoinTypes(ParameterizedType type, Collection types) { - for (Type typeArgument : type.getActualTypeArguments()) { - collectDisjoinTypes(typeArgument, types); - } - return types; - } + /** + * Return a right disjoint type if the given type is either. + * + * @deprecated Use {@link org.eclipse.lsp4j.jsonrpc.json.adapters.TypeUtils#getElementTypes(Type, + * Class, Class)} instead + */ + @Deprecated + public static Type getRightDisjointType(Type type) { + if (isEither(type)) { + if (type instanceof ParameterizedType) { + final ParameterizedType parameterizedType = (ParameterizedType) type; + return parameterizedType.getActualTypeArguments()[1]; + } + if (type instanceof Class) { + final Class cls = (Class) type; + return cls.getTypeParameters()[1]; + } + } + return null; + } - @Deprecated - protected static Collection collectDisjoinTypes(Class type, Collection types) { - for (Type typeParameter : type.getTypeParameters()) { - collectDisjoinTypes(typeParameter, types); - } - return types; - } + /** + * Return all disjoint types. + * + * @deprecated Use {@link + * org.eclipse.lsp4j.jsonrpc.json.adapters.TypeUtils#getExpectedTypes(Type)} instead + */ + @Deprecated + public static Collection getAllDisjoinTypes(Type type) { + return collectDisjoinTypes(type, new ArrayList<>()); + } - /** - * Test whether the given type is Either. - * - * @deprecated Use {@link org.eclipse.lsp4j.jsonrpc.json.adapters.TypeUtils#isEither(Type)} instead - */ - @Deprecated - public static boolean isEither(Type type) { - if (type instanceof ParameterizedType) { - return isEither((ParameterizedType) type); - } - if (type instanceof Class) { - return isEither((Class) type); - } - return false; - } + @Deprecated + protected static Collection collectDisjoinTypes(Type type, Collection types) { + if (isEither(type)) { + if (type instanceof ParameterizedType) { + return collectDisjoinTypes((ParameterizedType) type, types); + } + if (type instanceof Class) { + return collectDisjoinTypes((Class) type, types); + } + } + types.add(type); + return types; + } - /** - * Test whether the given type is Either. - * - * @deprecated Use {@link org.eclipse.lsp4j.jsonrpc.json.adapters.TypeUtils#isEither(Type)} instead - */ - @Deprecated - public static boolean isEither(ParameterizedType type) { - return isEither(type.getRawType()); - } + @Deprecated + protected static Collection collectDisjoinTypes( + ParameterizedType type, Collection types) { + for (Type typeArgument : type.getActualTypeArguments()) { + collectDisjoinTypes(typeArgument, types); + } + return types; + } - /** - * Test whether the given class is Either. - * - * @deprecated Use {@link org.eclipse.lsp4j.jsonrpc.json.adapters.TypeUtils#isEither(Type)} instead - */ - @Deprecated - public static boolean isEither(Class cls) { - return Either.class.isAssignableFrom(cls); - } + @Deprecated + protected static Collection collectDisjoinTypes(Class type, Collection types) { + for (Type typeParameter : type.getTypeParameters()) { + collectDisjoinTypes(typeParameter, types); + } + return types; + } + /** + * Test whether the given type is Either. + * + * @deprecated Use {@link org.eclipse.lsp4j.jsonrpc.json.adapters.TypeUtils#isEither(Type)} + * instead + */ + @Deprecated + public static boolean isEither(Type type) { + if (type instanceof ParameterizedType) { + return isEither((ParameterizedType) type); + } + if (type instanceof Class) { + return isEither((Class) type); + } + return false; + } + + /** + * Test whether the given type is Either. + * + * @deprecated Use {@link org.eclipse.lsp4j.jsonrpc.json.adapters.TypeUtils#isEither(Type)} + * instead + */ + @Deprecated + public static boolean isEither(ParameterizedType type) { + return isEither(type.getRawType()); + } + + /** + * Test whether the given class is Either. + * + * @deprecated Use {@link org.eclipse.lsp4j.jsonrpc.json.adapters.TypeUtils#isEither(Type)} + * instead + */ + @Deprecated + public static boolean isEither(Class cls) { + return Either.class.isAssignableFrom(cls); + } } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/Either3.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/Either3.java index 81f385ec2..8c31b5041 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/Either3.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/Either3.java @@ -1,12 +1,12 @@ /****************************************************************************** * Copyright (c) 2017 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc.messages; @@ -15,97 +15,89 @@ import java.util.function.Function; import org.eclipse.lsp4j.jsonrpc.validation.NonNull; -/** - * Union type for three types. - */ +/** Union type for three types. */ public class Either3 extends Either> { - - public static Either3 forFirst(@NonNull T1 first) { - return new Either3(first, null); - } - public static Either3 forSecond(@NonNull T2 second) { - return new Either3(null, new Either(second, null)); - } - - public static Either3 forThird(@NonNull T3 third) { - return new Either3(null, new Either(null, third)); - } - - public static Either3 forLeft3(@NonNull T1 first) { - return new Either3(first, null); - } - - public static Either3 forRight3(@NonNull Either right) { - return new Either3(null, right); - } + public static Either3 forFirst(@NonNull T1 first) { + return new Either3(first, null); + } - protected Either3(T1 left, Either right) { - super(left, right); - } - - public T1 getFirst() { - return getLeft(); - } - - public T2 getSecond() { - Either right = getRight(); - if (right == null) - return null; - else - return right.getLeft(); - } - - public T3 getThird() { - Either right = getRight(); - if (right == null) - return null; - else - return right.getRight(); - } - - @Override - public Object get() { - if (isRight()) - return getRight().get(); - return super.get(); - } - - public boolean isFirst() { - return isLeft(); - } - - public boolean isSecond() { - return isRight() && getRight().isLeft(); - } - - public boolean isThird() { - return isRight() && getRight().isRight(); - } + public static Either3 forSecond(@NonNull T2 second) { + return new Either3(null, new Either(second, null)); + } - public T map( - @NonNull Function mapFirst, - @NonNull Function mapSecond, - @NonNull Function mapThird) { - if (isFirst()) { - return mapFirst.apply(getFirst()); - } - if (isSecond()) { - return mapSecond.apply(getSecond()); - } - if (isThird()) { - return mapThird.apply(getThird()); - } - return null; - } + public static Either3 forThird(@NonNull T3 third) { + return new Either3(null, new Either(null, third)); + } - @Override - public String toString() { - StringBuilder builder = new StringBuilder("Either3 [").append(System.lineSeparator()); - builder.append(" first = ").append(getFirst()).append(System.lineSeparator()); - builder.append(" second = ").append(getSecond()).append(System.lineSeparator()); - builder.append(" third = ").append(getThird()).append(System.lineSeparator()); - return builder.append("]").toString(); - } + public static Either3 forLeft3(@NonNull T1 first) { + return new Either3(first, null); + } + public static Either3 forRight3(@NonNull Either right) { + return new Either3(null, right); + } + + protected Either3(T1 left, Either right) { + super(left, right); + } + + public T1 getFirst() { + return getLeft(); + } + + public T2 getSecond() { + Either right = getRight(); + if (right == null) return null; + else return right.getLeft(); + } + + public T3 getThird() { + Either right = getRight(); + if (right == null) return null; + else return right.getRight(); + } + + @Override + public Object get() { + if (isRight()) return getRight().get(); + return super.get(); + } + + public boolean isFirst() { + return isLeft(); + } + + public boolean isSecond() { + return isRight() && getRight().isLeft(); + } + + public boolean isThird() { + return isRight() && getRight().isRight(); + } + + public T map( + @NonNull Function mapFirst, + @NonNull Function mapSecond, + @NonNull Function mapThird) { + if (isFirst()) { + return mapFirst.apply(getFirst()); + } + if (isSecond()) { + return mapSecond.apply(getSecond()); + } + if (isThird()) { + return mapThird.apply(getThird()); + } + return null; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("Either3 [").append(System.lineSeparator()); + builder.append(" first = ").append(getFirst()).append(System.lineSeparator()); + builder.append(" second = ").append(getSecond()).append(System.lineSeparator()); + builder.append(" third = ").append(getThird()).append(System.lineSeparator()); + return builder.append("]").toString(); + } } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/IdentifiableMessage.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/IdentifiableMessage.java index 61faf65a4..26808be8a 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/IdentifiableMessage.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/IdentifiableMessage.java @@ -1,81 +1,66 @@ /****************************************************************************** * Copyright (c) 2018 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc.messages; import org.eclipse.lsp4j.jsonrpc.validation.NonNull; -/** - * A message with an {@code id} property. - */ +/** A message with an {@code id} property. */ public abstract class IdentifiableMessage extends Message { - - /** - * The message identifier. - */ - @NonNull - private Either id; - public String getId() { - if (id == null) - return null; - if (id.isLeft()) - return id.getLeft(); - if (id.isRight()) - return id.getRight().toString(); - return null; - } - - @NonNull - public Either getRawId() { - return id; - } + /** The message identifier. */ + @NonNull private Either id; - public void setId(String id) { - this.id = Either.forLeft(id); - } - - public void setId(int id) { - this.id = Either.forRight(id); - } - - public void setRawId(@NonNull Either id) { - this.id = id; - } + public String getId() { + if (id == null) return null; + if (id.isLeft()) return id.getLeft(); + if (id.isRight()) return id.getRight().toString(); + return null; + } - @Override - public boolean equals(final Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - if (!super.equals(obj)) - return false; - IdentifiableMessage other = (IdentifiableMessage) obj; - if (this.id == null) { - if (other.id != null) - return false; - } else if (!this.id.equals(other.id)) - return false; - return true; - } + @NonNull + public Either getRawId() { + return id; + } - @Override - public int hashCode() { - final int prime = 31; - int result = super.hashCode(); - result = prime * result + ((this.id == null) ? 0 : this.id.hashCode()); - return result; - } + public void setId(String id) { + this.id = Either.forLeft(id); + } + public void setId(int id) { + this.id = Either.forRight(id); + } + + public void setRawId(@NonNull Either id) { + this.id = id; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + if (!super.equals(obj)) return false; + IdentifiableMessage other = (IdentifiableMessage) obj; + if (this.id == null) { + if (other.id != null) return false; + } else if (!this.id.equals(other.id)) return false; + return true; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((this.id == null) ? 0 : this.id.hashCode()); + return result; + } } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/Message.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/Message.java index 5d4be746c..81801f71e 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/Message.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/Message.java @@ -1,12 +1,12 @@ /****************************************************************************** * Copyright (c) 2016 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc.messages; @@ -16,51 +16,44 @@ import org.eclipse.lsp4j.jsonrpc.json.MessageJsonHandler; import org.eclipse.lsp4j.jsonrpc.validation.NonNull; /** - * A general message as defined by JSON-RPC. The language server protocol always - * uses "2.0" as the jsonrpc version. + * A general message as defined by JSON-RPC. The language server protocol always uses "2.0" as the + * jsonrpc version. */ public abstract class Message { - @NonNull - private String jsonrpc = MessageConstants.JSONRPC_VERSION; + @NonNull private String jsonrpc = MessageConstants.JSONRPC_VERSION; - @NonNull - public String getJsonrpc() { - return this.jsonrpc; - } + @NonNull + public String getJsonrpc() { + return this.jsonrpc; + } - public void setJsonrpc(@NonNull String jsonrpc) { - this.jsonrpc = jsonrpc; - } - - @Override - public String toString() { - return MessageJsonHandler.toString(this); - } + public void setJsonrpc(@NonNull String jsonrpc) { + this.jsonrpc = jsonrpc; + } - @Override - public boolean equals(final Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - Message other = (Message) obj; - if (this.jsonrpc == null) { - if (other.jsonrpc != null) - return false; - } else if (!this.jsonrpc.equals(other.jsonrpc)) - return false; - return true; - } + @Override + public String toString() { + return MessageJsonHandler.toString(this); + } - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((this.jsonrpc == null) ? 0 : this.jsonrpc.hashCode()); - return result; - } + @Override + public boolean equals(final Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + Message other = (Message) obj; + if (this.jsonrpc == null) { + if (other.jsonrpc != null) return false; + } else if (!this.jsonrpc.equals(other.jsonrpc)) return false; + return true; + } + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((this.jsonrpc == null) ? 0 : this.jsonrpc.hashCode()); + return result; + } } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/MessageIssue.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/MessageIssue.java index 122025532..a0342897b 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/MessageIssue.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/MessageIssue.java @@ -1,60 +1,56 @@ /****************************************************************************** * Copyright (c) 2018 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc.messages; import org.eclipse.lsp4j.jsonrpc.validation.NonNull; -/** - * Describes an issue found while parsing or validating a message. - */ +/** Describes an issue found while parsing or validating a message. */ public class MessageIssue { - @NonNull - private String text; + @NonNull private String text; - private int code; - - private Exception cause; - - public MessageIssue(@NonNull String text) { - this(text, 0, null); - } + private int code; - public MessageIssue(@NonNull String text, int code) { - this(text, code, null); - } - - public MessageIssue(@NonNull String text, int code, Exception cause) { - this.text = text; - this.code = code; - this.cause = cause; - } - - @NonNull - public String getText() { - return text; - } - - public int getIssueCode() { - return code; - } + private Exception cause; - public Exception getCause() { - return cause; - } - - @Override - public String toString() { - return getText(); - } - + public MessageIssue(@NonNull String text) { + this(text, 0, null); + } + + public MessageIssue(@NonNull String text, int code) { + this(text, code, null); + } + + public MessageIssue(@NonNull String text, int code, Exception cause) { + this.text = text; + this.code = code; + this.cause = cause; + } + + @NonNull + public String getText() { + return text; + } + + public int getIssueCode() { + return code; + } + + public Exception getCause() { + return cause; + } + + @Override + public String toString() { + return getText(); + } } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/NotificationMessage.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/NotificationMessage.java index b10f9d465..7069aea3e 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/NotificationMessage.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/NotificationMessage.java @@ -1,12 +1,12 @@ /****************************************************************************** * Copyright (c) 2016 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc.messages; @@ -14,70 +14,56 @@ package org.eclipse.lsp4j.jsonrpc.messages; import org.eclipse.lsp4j.jsonrpc.validation.NonNull; /** - * A notification message. A processed notification message must not send a - * response back. They work like events. + * A notification message. A processed notification message must not send a response back. They work + * like events. */ public class NotificationMessage extends Message { - /** - * The method to be invoked. - */ - @NonNull - private String method; + /** The method to be invoked. */ + @NonNull private String method; - @NonNull - public String getMethod() { - return this.method; - } + @NonNull + public String getMethod() { + return this.method; + } - public void setMethod(@NonNull String method) { - this.method = method; - } + public void setMethod(@NonNull String method) { + this.method = method; + } - /** - * The method's params. - */ - private Object params; + /** The method's params. */ + private Object params; - public Object getParams() { - return this.params; - } + public Object getParams() { + return this.params; + } - public void setParams(Object params) { - this.params = params; - } + public void setParams(Object params) { + this.params = params; + } - @Override - public boolean equals(final Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - if (!super.equals(obj)) - return false; - NotificationMessage other = (NotificationMessage) obj; - if (this.method == null) { - if (other.method != null) - return false; - } else if (!this.method.equals(other.method)) - return false; - if (this.params == null) { - if (other.params != null) - return false; - } else if (!this.params.equals(other.params)) - return false; - return true; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = super.hashCode(); - result = prime * result + ((this.method == null) ? 0 : this.method.hashCode()); - result = prime * result + ((this.params == null) ? 0 : this.params.hashCode()); - return result; - } + @Override + public boolean equals(final Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + if (!super.equals(obj)) return false; + NotificationMessage other = (NotificationMessage) obj; + if (this.method == null) { + if (other.method != null) return false; + } else if (!this.method.equals(other.method)) return false; + if (this.params == null) { + if (other.params != null) return false; + } else if (!this.params.equals(other.params)) return false; + return true; + } + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((this.method == null) ? 0 : this.method.hashCode()); + result = prime * result + ((this.params == null) ? 0 : this.params.hashCode()); + return result; + } } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/RequestMessage.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/RequestMessage.java index 42d76c26e..59e20ae2f 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/RequestMessage.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/RequestMessage.java @@ -1,12 +1,12 @@ /****************************************************************************** * Copyright (c) 2016 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc.messages; @@ -14,71 +14,56 @@ package org.eclipse.lsp4j.jsonrpc.messages; import org.eclipse.lsp4j.jsonrpc.validation.NonNull; /** - * A request message to describe a request between the client and the server. - * Every processed request must send a response back to the sender of the - * request. + * A request message to describe a request between the client and the server. Every processed + * request must send a response back to the sender of the request. */ public class RequestMessage extends IdentifiableMessage { - /** - * The method to be invoked. - */ - @NonNull - private String method; + /** The method to be invoked. */ + @NonNull private String method; - @NonNull - public String getMethod() { - return this.method; - } + @NonNull + public String getMethod() { + return this.method; + } - public void setMethod(@NonNull String method) { - this.method = method; - } + public void setMethod(@NonNull String method) { + this.method = method; + } - /** - * The method's parameters. The object type depends on the chosen method. - */ - private Object params; + /** The method's parameters. The object type depends on the chosen method. */ + private Object params; - public Object getParams() { - return this.params; - } + public Object getParams() { + return this.params; + } - public void setParams(Object params) { - this.params = params; - } + public void setParams(Object params) { + this.params = params; + } - @Override - public boolean equals(final Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - if (!super.equals(obj)) - return false; - RequestMessage other = (RequestMessage) obj; - if (this.method == null) { - if (other.method != null) - return false; - } else if (!this.method.equals(other.method)) - return false; - if (this.params == null) { - if (other.params != null) - return false; - } else if (!this.params.equals(other.params)) - return false; - return true; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = super.hashCode(); - result = prime * result + ((this.method == null) ? 0 : this.method.hashCode()); - result = prime * result + ((this.params == null) ? 0 : this.params.hashCode()); - return result; - } + @Override + public boolean equals(final Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + if (!super.equals(obj)) return false; + RequestMessage other = (RequestMessage) obj; + if (this.method == null) { + if (other.method != null) return false; + } else if (!this.method.equals(other.method)) return false; + if (this.params == null) { + if (other.params != null) return false; + } else if (!this.params.equals(other.params)) return false; + return true; + } + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((this.method == null) ? 0 : this.method.hashCode()); + result = prime * result + ((this.params == null) ? 0 : this.params.hashCode()); + return result; + } } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/ResponseError.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/ResponseError.java index 64b8888d5..0d9ebe10e 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/ResponseError.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/ResponseError.java @@ -1,12 +1,12 @@ /****************************************************************************** * Copyright (c) 2016 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc.messages; @@ -19,105 +19,89 @@ import com.google.gson.annotations.JsonAdapter; public class ResponseError { - /** - * A number indicating the error type that occurred. - */ - @NonNull - private int code; + /** A number indicating the error type that occurred. */ + @NonNull private int code; - @NonNull - public int getCode() { - return this.code; - } + @NonNull + public int getCode() { + return this.code; + } - public void setCode(@NonNull int code) { - this.code = code; - } + public void setCode(@NonNull int code) { + this.code = code; + } - public void setCode(ResponseErrorCode code) { - this.setCode(code.getValue()); - } + public void setCode(ResponseErrorCode code) { + this.setCode(code.getValue()); + } - /** - * A string providing a short description of the error. - */ - @NonNull - private String message; + /** A string providing a short description of the error. */ + @NonNull private String message; - @NonNull - public String getMessage() { - return this.message; - } + @NonNull + public String getMessage() { + return this.message; + } - public void setMessage(@NonNull String message) { - this.message = message; - } + public void setMessage(@NonNull String message) { + this.message = message; + } - /** - * A Primitive or Structured value that contains additional information - * about the error. Can be omitted. - */ - @JsonAdapter(JsonElementTypeAdapter.Factory.class) - private Object data; + /** + * A Primitive or Structured value that contains additional information about the error. Can be + * omitted. + */ + @JsonAdapter(JsonElementTypeAdapter.Factory.class) + private Object data; - public Object getData() { - return this.data; - } + public Object getData() { + return this.data; + } - public void setData(Object data) { - this.data = data; - } + public void setData(Object data) { + this.data = data; + } - public ResponseError() { - } + public ResponseError() {} - public ResponseError(ResponseErrorCode code, String message, Object data) { - this(code.getValue(), message, data); - } + public ResponseError(ResponseErrorCode code, String message, Object data) { + this(code.getValue(), message, data); + } - public ResponseError(int code, String message, Object data) { - this.code = code; - this.message = message; - this.data = data; - } + public ResponseError(int code, String message, Object data) { + this.code = code; + this.message = message; + this.data = data; + } - @Override - public String toString() { - return MessageJsonHandler.toString(this); - } + @Override + public String toString() { + return MessageJsonHandler.toString(this); + } - @Override - public boolean equals(final Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - ResponseError other = (ResponseError) obj; - if (other.code != this.code) - return false; - if (this.message == null) { - if (other.message != null) - return false; - } else if (!this.message.equals(other.message)) - return false; - if (this.data == null) { - if (other.data != null) - return false; - } else if (!this.data.equals(other.data)) - return false; - return true; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + this.code; - result = prime * result + ((this.message == null) ? 0 : this.message.hashCode()); - result = prime * result + ((this.data == null) ? 0 : this.data.hashCode()); - return result; - } + @Override + public boolean equals(final Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + ResponseError other = (ResponseError) obj; + if (other.code != this.code) return false; + if (this.message == null) { + if (other.message != null) return false; + } else if (!this.message.equals(other.message)) return false; + if (this.data == null) { + if (other.data != null) return false; + } else if (!this.data.equals(other.data)) return false; + return true; + } + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + this.code; + result = prime * result + ((this.message == null) ? 0 : this.message.hashCode()); + result = prime * result + ((this.data == null) ? 0 : this.data.hashCode()); + return result; + } } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/ResponseErrorCode.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/ResponseErrorCode.java index 14761c8cf..0a21a3fa7 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/ResponseErrorCode.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/ResponseErrorCode.java @@ -1,129 +1,118 @@ /****************************************************************************** * Copyright (c) 2016 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc.messages; -/** - * A number indicating the error type that occurred. - */ +/** A number indicating the error type that occurred. */ public enum ResponseErrorCode { - - ParseError(-32700), - - InvalidRequest(-32600), - - MethodNotFound(-32601), - - InvalidParams(-32602), - - InternalError(-32603), - - /** - * This is the start range of JSON RPC reserved error codes. - * It doesn't denote a real error code. No LSP error codes should - * be defined between the start and end range. For backwards - * compatibility the {@link #serverNotInitialized} and the - * {@link #UnknownErrorCode} are left in the range. - *

    - * Since 3.16.0 - */ - jsonrpcReservedErrorRangeStart(-32099), - - /** @deprecated use {@link #jsonrpcReservedErrorRangeStart} */ - @Deprecated - serverErrorStart(-32099), - - /** - * Error code indicating that a server received a notification or - * request before the server has received the {@code initialize} request. - *

    - * Should be {@code ServerNotInitialized} - */ - serverNotInitialized(-32002), - - UnknownErrorCode(-32001), - - /** - * This is the end range of JSON RPC reserved error codes. - * It doesn't denote a real error code. - *

    - * Since 3.16.0 - */ - jsonrpcReservedErrorRangeEnd(-32000), - - /** @deprecated use {@link #jsonrpcReservedErrorRangeEnd} */ - @Deprecated - serverErrorEnd(-32000), - - /** - * This is the start range of LSP reserved error codes. - * It doesn't denote a real error code. - *

    - * Since 3.16.0 - */ - lspReservedErrorRangeStart(-32899), + ParseError(-32700), - /** - * A request failed but it was syntactically correct, e.g the - * method name was known and the parameters were valid. The error - * message should contain human readable information about why - * the request failed. - *

    - * Since 3.17.0 - */ - RequestFailed(-32803), - - /** - * The server cancelled the request. This error code should - * only be used for requests that explicitly support being - * server cancellable. - *

    - * Since 3.17.0 - */ - ServerCancelled(-32802), - - /** - * The server detected that the content of a document got - * modified outside normal conditions. A server should - * NOT send this error code if it detects a content change - * in it unprocessed messages. The result even computed - * on an older state might still be useful for the client. - *

    - * If a client decides that a result is not of any use anymore - * the client should cancel the request. - */ - ContentModified(-32801), - - /** - * The client has canceled a request and a server as detected - * the cancel. - */ - RequestCancelled(-32800), - - /** - * This is the end range of LSP reserved error codes. - * It doesn't denote a real error code. - *

    - * Since 3.16.0 - */ - lspReservedErrorRangeEnd(-32800); - - private final int value; - - ResponseErrorCode(int value) { - this.value = value; - } - - public int getValue() { - return value; - } + InvalidRequest(-32600), + MethodNotFound(-32601), + + InvalidParams(-32602), + + InternalError(-32603), + + /** + * This is the start range of JSON RPC reserved error codes. It doesn't denote a real error code. + * No LSP error codes should be defined between the start and end range. For backwards + * compatibility the {@link #serverNotInitialized} and the {@link #UnknownErrorCode} are left in + * the range. + * + *

    Since 3.16.0 + */ + jsonrpcReservedErrorRangeStart(-32099), + + /** + * @deprecated use {@link #jsonrpcReservedErrorRangeStart} + */ + @Deprecated + serverErrorStart(-32099), + + /** + * Error code indicating that a server received a notification or request before the server has + * received the {@code initialize} request. + * + *

    Should be {@code ServerNotInitialized} + */ + serverNotInitialized(-32002), + + UnknownErrorCode(-32001), + + /** + * This is the end range of JSON RPC reserved error codes. It doesn't denote a real error code. + * + *

    Since 3.16.0 + */ + jsonrpcReservedErrorRangeEnd(-32000), + + /** + * @deprecated use {@link #jsonrpcReservedErrorRangeEnd} + */ + @Deprecated + serverErrorEnd(-32000), + + /** + * This is the start range of LSP reserved error codes. It doesn't denote a real error code. + * + *

    Since 3.16.0 + */ + lspReservedErrorRangeStart(-32899), + + /** + * A request failed but it was syntactically correct, e.g the method name was known and the + * parameters were valid. The error message should contain human readable information about why + * the request failed. + * + *

    Since 3.17.0 + */ + RequestFailed(-32803), + + /** + * The server cancelled the request. This error code should only be used for requests that + * explicitly support being server cancellable. + * + *

    Since 3.17.0 + */ + ServerCancelled(-32802), + + /** + * The server detected that the content of a document got modified outside normal conditions. A + * server should NOT send this error code if it detects a content change in it unprocessed + * messages. The result even computed on an older state might still be useful for the client. + * + *

    If a client decides that a result is not of any use anymore the client should cancel the + * request. + */ + ContentModified(-32801), + + /** The client has canceled a request and a server as detected the cancel. */ + RequestCancelled(-32800), + + /** + * This is the end range of LSP reserved error codes. It doesn't denote a real error code. + * + *

    Since 3.16.0 + */ + lspReservedErrorRangeEnd(-32800); + + private final int value; + + ResponseErrorCode(int value) { + this.value = value; + } + + public int getValue() { + return value; + } } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/ResponseMessage.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/ResponseMessage.java index baf33e3b9..1c8ffbfec 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/ResponseMessage.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/ResponseMessage.java @@ -1,83 +1,72 @@ /****************************************************************************** * Copyright (c) 2016 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc.messages; /** - * Response message sent as a result of a request. If a request doesn't provide - * a result value the receiver of a request still needs to return a response - * message to conform to the JSON RPC specification. The result property of the - * ResponseMessage should be set to null in this case to signal a successful - * request. A response message is linked to a request via their {@code id} properties. + * Response message sent as a result of a request. If a request doesn't provide a result value the + * receiver of a request still needs to return a response message to conform to the JSON RPC + * specification. The result property of the ResponseMessage should be set to null in this case to + * signal a successful request. A response message is linked to a request via their {@code id} + * properties. */ public class ResponseMessage extends IdentifiableMessage { - /** - * The result of a request. This can be omitted in the case of an error. - * The object type depends on the method of the corresponding request. - */ - private Object result; + /** + * The result of a request. This can be omitted in the case of an error. The object type depends + * on the method of the corresponding request. + */ + private Object result; - public Object getResult() { - return this.result; - } + public Object getResult() { + return this.result; + } - public void setResult(Object result) { - this.result = result; - } + public void setResult(Object result) { + this.result = result; + } - /** - * The error object in case a request fails. - */ - private ResponseError error; + /** The error object in case a request fails. */ + private ResponseError error; - public ResponseError getError() { - return this.error; - } + public ResponseError getError() { + return this.error; + } - public void setError(ResponseError error) { - this.error = error; - } + public void setError(ResponseError error) { + this.error = error; + } - @Override - public boolean equals(final Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - if (!super.equals(obj)) - return false; - ResponseMessage other = (ResponseMessage) obj; - if (this.result == null) { - if (other.result != null) - return false; - } else if (!this.result.equals(other.result)) - return false; - if (this.error == null) { - if (other.error != null) - return false; - } else if (!this.error.equals(other.error)) - return false; - return true; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = super.hashCode(); - result = prime * result + ((this.result == null) ? 0 : this.result.hashCode()); - result = prime * result + ((this.error == null) ? 0 : this.error.hashCode()); - return result; - } + @Override + public boolean equals(final Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + if (!super.equals(obj)) return false; + ResponseMessage other = (ResponseMessage) obj; + if (this.result == null) { + if (other.result != null) return false; + } else if (!this.result.equals(other.result)) return false; + if (this.error == null) { + if (other.error != null) return false; + } else if (!this.error.equals(other.error)) return false; + return true; + } + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((this.result == null) ? 0 : this.result.hashCode()); + result = prime * result + ((this.error == null) ? 0 : this.error.hashCode()); + return result; + } } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/Tuple.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/Tuple.java index 03618ceb0..9f8afab69 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/Tuple.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/messages/Tuple.java @@ -8,74 +8,62 @@ import com.google.gson.TypeAdapter; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; -/** - * Representation of tuple types. - */ +/** Representation of tuple types. */ @SuppressWarnings("all") public interface Tuple { - public static Two two(F first, S second) { - return new Two(first, second); - } - - /** - * A two-tuple, i.e. a pair. - */ - public static class Two implements Tuple { - - private final F first; - private final S second; + public static Two two(F first, S second) { + return new Two(first, second); + } - public Two(F first, S second) { - this.first = first; - this.second = second; - } + /** A two-tuple, i.e. a pair. */ + public static class Two implements Tuple { - public F getFirst() { - return this.first; - } + private final F first; + private final S second; - public S getSecond() { - return this.second; - } + public Two(F first, S second) { + this.first = first; + this.second = second; + } - @Override - public boolean equals(final Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - Tuple.Two other = (Tuple.Two) obj; - if (this.first == null) { - if (other.first != null) - return false; - } else if (!this.first.equals(other.first)) - return false; - if (this.second == null) { - if (other.second != null) - return false; - } else if (!this.second.equals(other.second)) - return false; - return true; - } + public F getFirst() { + return this.first; + } - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((this.first == null) ? 0 : this.first.hashCode()); - return prime * result + ((this.second == null) ? 0 : this.second.hashCode()); - } + public S getSecond() { + return this.second; + } - @Override - public String toString() { - StringBuilder builder = new StringBuilder("Tuples.Two [").append(System.lineSeparator()); - builder.append(" first = ").append(first).append(System.lineSeparator()); - builder.append(" second = ").append(second).append(System.lineSeparator()); - return builder.append("]").toString(); - } - } - + @Override + public boolean equals(final Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + Tuple.Two other = (Tuple.Two) obj; + if (this.first == null) { + if (other.first != null) return false; + } else if (!this.first.equals(other.first)) return false; + if (this.second == null) { + if (other.second != null) return false; + } else if (!this.second.equals(other.second)) return false; + return true; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((this.first == null) ? 0 : this.first.hashCode()); + return prime * result + ((this.second == null) ? 0 : this.second.hashCode()); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("Tuples.Two [").append(System.lineSeparator()); + builder.append(" first = ").append(first).append(System.lineSeparator()); + builder.append(" second = ").append(second).append(System.lineSeparator()); + return builder.append("]").toString(); + } + } } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/services/AnnotationUtil.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/services/AnnotationUtil.java index 48f6fc2f3..09d83cd02 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/services/AnnotationUtil.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/services/AnnotationUtil.java @@ -1,12 +1,12 @@ /****************************************************************************** * Copyright (c) 2016 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc.services; @@ -18,114 +18,118 @@ import java.util.Set; import java.util.function.Consumer; public final class AnnotationUtil { - private AnnotationUtil() {} - - public static void findDelegateSegments(Class clazz, Set> visited, Consumer acceptor) { - if (clazz == null || !visited.add(clazz)) - return; - findDelegateSegments(clazz.getSuperclass(), visited, acceptor); - for (Class interf : clazz.getInterfaces()) { - findDelegateSegments(interf, visited, acceptor); - } - for (Method method : clazz.getDeclaredMethods()) { - if (isDelegateMethod(method)) { - acceptor.accept(method); - } - } - } + private AnnotationUtil() {} - public static boolean isDelegateMethod(Method method) { - if (!method.isSynthetic()) { - JsonDelegate jsonDelegate = method.getAnnotation(JsonDelegate.class); - if (jsonDelegate != null) { - if (!(method.getParameterCount() == 0 && method.getReturnType().isInterface())) { - throw new IllegalStateException( - "The method " + method.toString() + " is not a proper @JsonDelegate method."); - } - return true; - } - } - return false; - } - + public static void findDelegateSegments( + Class clazz, Set> visited, Consumer acceptor) { + if (clazz == null || !visited.add(clazz)) return; + findDelegateSegments(clazz.getSuperclass(), visited, acceptor); + for (Class interf : clazz.getInterfaces()) { + findDelegateSegments(interf, visited, acceptor); + } + for (Method method : clazz.getDeclaredMethods()) { + if (isDelegateMethod(method)) { + acceptor.accept(method); + } + } + } - /** - * Depth first search for annotated methods in hierarchy. - */ - public static void findRpcMethods(Class clazz, Set> visited, Consumer acceptor) { - if (clazz == null || !visited.add(clazz)) - return; - findRpcMethods(clazz.getSuperclass(), visited, acceptor); - for (Class interf : clazz.getInterfaces()) { - findRpcMethods(interf, visited, acceptor); - } - String segment = getSegment(clazz); - for (Method method : clazz.getDeclaredMethods()) { - MethodInfo methodInfo = createMethodInfo(method, segment); - if (methodInfo != null) { - acceptor.accept(methodInfo); - } - } - } + public static boolean isDelegateMethod(Method method) { + if (!method.isSynthetic()) { + JsonDelegate jsonDelegate = method.getAnnotation(JsonDelegate.class); + if (jsonDelegate != null) { + if (!(method.getParameterCount() == 0 && method.getReturnType().isInterface())) { + throw new IllegalStateException( + "The method " + method.toString() + " is not a proper @JsonDelegate method."); + } + return true; + } + } + return false; + } - protected static String getSegment(Class clazz) { - JsonSegment jsonSegment = clazz.getAnnotation(JsonSegment.class); - return jsonSegment == null ? "" : jsonSegment.value() + "/"; - } - - protected static MethodInfo createMethodInfo(Method method, String segment) { - if (!method.isSynthetic()) { - JsonRequest jsonRequest = method.getAnnotation(JsonRequest.class); - if (jsonRequest != null) { - return createRequestInfo(method, segment, jsonRequest); - } - JsonNotification jsonNotification = method.getAnnotation(JsonNotification.class); - if (jsonNotification != null) { - return createNotificationInfo(method, segment, jsonNotification); - } - } - return null; - } + /** Depth first search for annotated methods in hierarchy. */ + public static void findRpcMethods( + Class clazz, Set> visited, Consumer acceptor) { + if (clazz == null || !visited.add(clazz)) return; + findRpcMethods(clazz.getSuperclass(), visited, acceptor); + for (Class interf : clazz.getInterfaces()) { + findRpcMethods(interf, visited, acceptor); + } + String segment = getSegment(clazz); + for (Method method : clazz.getDeclaredMethods()) { + MethodInfo methodInfo = createMethodInfo(method, segment); + if (methodInfo != null) { + acceptor.accept(methodInfo); + } + } + } - protected static MethodInfo createNotificationInfo(Method method, String segment, JsonNotification jsonNotification) { - MethodInfo methodInfo = createMethodInfo(method, jsonNotification.useSegment(), segment, jsonNotification.value()); - methodInfo.isNotification = true; - return methodInfo; - } - - protected static MethodInfo createRequestInfo(Method method, String segment, JsonRequest jsonRequest) { - return createMethodInfo(method, jsonRequest.useSegment(), segment, jsonRequest.value()); - } - - protected static MethodInfo createMethodInfo(Method method, boolean useSegment, String segment, String value) { - method.setAccessible(true); + protected static String getSegment(Class clazz) { + JsonSegment jsonSegment = clazz.getAnnotation(JsonSegment.class); + return jsonSegment == null ? "" : jsonSegment.value() + "/"; + } - MethodInfo methodInfo = new MethodInfo(); - methodInfo.method = method; - methodInfo.parameterTypes = getParameterTypes(method); - methodInfo.name = getMethodName(method, useSegment, segment, value); - return methodInfo; - } - - protected static String getMethodName(Method method, boolean useSegment, String segment, String value) { - String name = value != null && value.length() > 0 ? value : method.getName(); - return useSegment ? segment + name : name; - } + protected static MethodInfo createMethodInfo(Method method, String segment) { + if (!method.isSynthetic()) { + JsonRequest jsonRequest = method.getAnnotation(JsonRequest.class); + if (jsonRequest != null) { + return createRequestInfo(method, segment, jsonRequest); + } + JsonNotification jsonNotification = method.getAnnotation(JsonNotification.class); + if (jsonNotification != null) { + return createNotificationInfo(method, segment, jsonNotification); + } + } + return null; + } - protected static Type[] getParameterTypes(Method method) { - return Arrays.stream(method.getParameters()).map(t -> t.getParameterizedType()).toArray(Type[]::new); - } - - static class MethodInfo { - private static Type[] EMPTY_TYPE_ARRAY = {}; - public String name; - public Method method; - public Type[] parameterTypes = EMPTY_TYPE_ARRAY; - public boolean isNotification = false; - } + protected static MethodInfo createNotificationInfo( + Method method, String segment, JsonNotification jsonNotification) { + MethodInfo methodInfo = + createMethodInfo(method, jsonNotification.useSegment(), segment, jsonNotification.value()); + methodInfo.isNotification = true; + return methodInfo; + } - static class DelegateInfo { - public Method method; - public Object delegate; - } + protected static MethodInfo createRequestInfo( + Method method, String segment, JsonRequest jsonRequest) { + return createMethodInfo(method, jsonRequest.useSegment(), segment, jsonRequest.value()); + } + + protected static MethodInfo createMethodInfo( + Method method, boolean useSegment, String segment, String value) { + method.setAccessible(true); + + MethodInfo methodInfo = new MethodInfo(); + methodInfo.method = method; + methodInfo.parameterTypes = getParameterTypes(method); + methodInfo.name = getMethodName(method, useSegment, segment, value); + return methodInfo; + } + + protected static String getMethodName( + Method method, boolean useSegment, String segment, String value) { + String name = value != null && value.length() > 0 ? value : method.getName(); + return useSegment ? segment + name : name; + } + + protected static Type[] getParameterTypes(Method method) { + return Arrays.stream(method.getParameters()) + .map(t -> t.getParameterizedType()) + .toArray(Type[]::new); + } + + static class MethodInfo { + private static Type[] EMPTY_TYPE_ARRAY = {}; + public String name; + public Method method; + public Type[] parameterTypes = EMPTY_TYPE_ARRAY; + public boolean isNotification = false; + } + + static class DelegateInfo { + public Method method; + public Object delegate; + } } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/services/EndpointProxy.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/services/EndpointProxy.java index bb1609c78..9feaade6a 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/services/EndpointProxy.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/services/EndpointProxy.java @@ -1,12 +1,12 @@ /****************************************************************************** * Copyright (c) 2016 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc.services; @@ -29,102 +29,105 @@ import org.eclipse.lsp4j.jsonrpc.services.AnnotationUtil.MethodInfo; * containing {@link JsonNotification} and {@link JsonRequest} methods. */ public class EndpointProxy implements InvocationHandler { - - private final Method object_equals; - private final Method object_hashCode; - private final Method object_toString; - private final Endpoint delegate; - private final LinkedHashMap methodInfos; - private final LinkedHashMap delegatedSegments; + private final Method object_equals; + private final Method object_hashCode; + private final Method object_toString; - public EndpointProxy(Endpoint delegate, Class interface_) { - this(delegate, Collections.singletonList(interface_)); - } - - public EndpointProxy(Endpoint delegate, Collection> interfaces) { - if (delegate == null) - throw new NullPointerException("delegate"); - if (interfaces == null) - throw new NullPointerException("interfaces"); - if (interfaces.isEmpty()) - throw new IllegalArgumentException("interfaces must not be empty."); - - this.delegate = delegate; - try { - object_equals = Object.class.getDeclaredMethod("equals", Object.class); - object_hashCode = Object.class.getDeclaredMethod("hashCode"); - object_toString = Object.class.getDeclaredMethod("toString"); - } catch (NoSuchMethodException | SecurityException exception) { - throw new RuntimeException(exception); - } - methodInfos = new LinkedHashMap<>(); - delegatedSegments = new LinkedHashMap<>(); - for (Class interf : interfaces) { - AnnotationUtil.findRpcMethods(interf, new HashSet>(), (methodInfo) -> { - if (methodInfos.put(methodInfo.method.getName(), methodInfo) != null) { - throw new IllegalStateException("Duplicate RPC method " + methodInfo.method); - } - }); - AnnotationUtil.findDelegateSegments(interf, new HashSet>(), (method) -> { - Object delegateProxy = ServiceEndpoints.toServiceObject(delegate, method.getReturnType()); - DelegateInfo info = new DelegateInfo(); - info.delegate = delegateProxy; - info.method = method; - if (delegatedSegments.put(method.getName(), info) != null) { - throw new IllegalStateException("Duplicate RPC method " + method); - } - }); - } - } + private final Endpoint delegate; + private final LinkedHashMap methodInfos; + private final LinkedHashMap delegatedSegments; - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - args = args == null ? new Object[0] : args; - MethodInfo methodInfo = this.methodInfos.get(method.getName()); - if (methodInfo != null) { - Object params = getParams(args, methodInfo); - if (methodInfo.isNotification) { - delegate.notify(methodInfo.name, params); - return null; - } - return delegate.request(methodInfo.name, params); - } - DelegateInfo delegateInfo = this.delegatedSegments.get(method.getName()); - if (delegateInfo != null) { - return delegateInfo.delegate; - } - if (object_equals.equals(method) && args.length == 1) { - if(args[0] != null ) { - try { - return this.equals(Proxy.getInvocationHandler(args[0])); - } catch (IllegalArgumentException exception) { - } - } - return this.equals(args[0]); - } - if (object_hashCode.equals(method)) { - return this.hashCode(); - } - if (object_toString.equals(method)) { - return this.toString(); - } - return method.invoke(delegate, args); - } + public EndpointProxy(Endpoint delegate, Class interface_) { + this(delegate, Collections.singletonList(interface_)); + } - protected Object getParams(Object[] args, MethodInfo methodInfo) { - if (args.length == 0) { - return null; - } - if (args.length == 1) { - return args[0]; - } - return Arrays.asList(args); - } - - @Override - public String toString() { - return getClass().getSimpleName() + " for " + delegate.toString(); - } + public EndpointProxy(Endpoint delegate, Collection> interfaces) { + if (delegate == null) throw new NullPointerException("delegate"); + if (interfaces == null) throw new NullPointerException("interfaces"); + if (interfaces.isEmpty()) throw new IllegalArgumentException("interfaces must not be empty."); + this.delegate = delegate; + try { + object_equals = Object.class.getDeclaredMethod("equals", Object.class); + object_hashCode = Object.class.getDeclaredMethod("hashCode"); + object_toString = Object.class.getDeclaredMethod("toString"); + } catch (NoSuchMethodException | SecurityException exception) { + throw new RuntimeException(exception); + } + methodInfos = new LinkedHashMap<>(); + delegatedSegments = new LinkedHashMap<>(); + for (Class interf : interfaces) { + AnnotationUtil.findRpcMethods( + interf, + new HashSet>(), + (methodInfo) -> { + if (methodInfos.put(methodInfo.method.getName(), methodInfo) != null) { + throw new IllegalStateException("Duplicate RPC method " + methodInfo.method); + } + }); + AnnotationUtil.findDelegateSegments( + interf, + new HashSet>(), + (method) -> { + Object delegateProxy = + ServiceEndpoints.toServiceObject(delegate, method.getReturnType()); + DelegateInfo info = new DelegateInfo(); + info.delegate = delegateProxy; + info.method = method; + if (delegatedSegments.put(method.getName(), info) != null) { + throw new IllegalStateException("Duplicate RPC method " + method); + } + }); + } + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + args = args == null ? new Object[0] : args; + MethodInfo methodInfo = this.methodInfos.get(method.getName()); + if (methodInfo != null) { + Object params = getParams(args, methodInfo); + if (methodInfo.isNotification) { + delegate.notify(methodInfo.name, params); + return null; + } + return delegate.request(methodInfo.name, params); + } + DelegateInfo delegateInfo = this.delegatedSegments.get(method.getName()); + if (delegateInfo != null) { + return delegateInfo.delegate; + } + if (object_equals.equals(method) && args.length == 1) { + if (args[0] != null) { + try { + return this.equals(Proxy.getInvocationHandler(args[0])); + } catch (IllegalArgumentException exception) { + } + } + return this.equals(args[0]); + } + if (object_hashCode.equals(method)) { + return this.hashCode(); + } + if (object_toString.equals(method)) { + return this.toString(); + } + return method.invoke(delegate, args); + } + + protected Object getParams(Object[] args, MethodInfo methodInfo) { + if (args.length == 0) { + return null; + } + if (args.length == 1) { + return args[0]; + } + return Arrays.asList(args); + } + + @Override + public String toString() { + return getClass().getSimpleName() + " for " + delegate.toString(); + } } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/services/JsonDelegate.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/services/JsonDelegate.java index 5a6b2249e..c161d5854 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/services/JsonDelegate.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/services/JsonDelegate.java @@ -1,12 +1,12 @@ /****************************************************************************** * Copyright (c) 2016 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc.services; @@ -17,12 +17,10 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * A method annotated with {@link JsonDelegate} is treated as a delegate method. - * As a result jsonrpc methods of the delegate will be considered, too. - * If an annotated method returns `null` - * then jsonrpc methods of the delegate are not considered. + * A method annotated with {@link JsonDelegate} is treated as a delegate method. As a result jsonrpc + * methods of the delegate will be considered, too. If an annotated method returns `null` then + * jsonrpc methods of the delegate are not considered. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) -public @interface JsonDelegate { -} +public @interface JsonDelegate {} diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/services/JsonNotification.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/services/JsonNotification.java index 75ad20bd2..1e19c6e72 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/services/JsonNotification.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/services/JsonNotification.java @@ -18,17 +18,14 @@ import java.lang.annotation.Target; /** * Annotation to mark a notification method on an interface or class. - *

    - * A notification method must be of type void and have zero or one - * argument. - *

    - * According to jsonrpc an argument must be an 'object' (a java bean, not e,g. - * String). - *

    - * The name of the jsonrpc notification will be the optional segment, followed - * by the name of the Java method that is annotated with JsonNotification. The - * name of the jsonrpc notification can be customized by using the - * {@link #value()} field of this annotation. To specify the whole name, + * + *

    A notification method must be of type void and have zero or one argument. + * + *

    According to jsonrpc an argument must be an 'object' (a java bean, not e,g. String). + * + *

    The name of the jsonrpc notification will be the optional segment, followed by the name of the + * Java method that is annotated with JsonNotification. The name of the jsonrpc notification can be + * customized by using the {@link #value()} field of this annotation. To specify the whole name, * including the segment, in the value, set {@link #useSegment()} to false. * * @see JsonSegment @@ -36,17 +33,16 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface JsonNotification { - /** - * The name of the the jsonrpc request method. If empty, uses the name of the - * annotated method. - */ - String value() default ""; + /** + * The name of the the jsonrpc request method. If empty, uses the name of the annotated method. + */ + String value() default ""; - /** - * When using segments, useSegment will be true to prepend the segment name to - * the name of the request. - * - * @see JsonSegment - */ - boolean useSegment() default true; + /** + * When using segments, useSegment will be true to prepend the segment name to the name of the + * request. + * + * @see JsonSegment + */ + boolean useSegment() default true; } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/services/JsonRequest.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/services/JsonRequest.java index 4e9b801b3..8f5743576 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/services/JsonRequest.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/services/JsonRequest.java @@ -19,35 +19,32 @@ import java.util.concurrent.CompletableFuture; /** * Annotation to mark a request method on an interface or class. - *

    - * A request method must have the return type {@link CompletableFuture} with an - * object parameter type or Void and have zero or one argument. - *

    - * According to jsonrpc an argument must be an 'object' (a java bean, not e,g. - * String). - *

    - * The name of the jsonrpc request will be the optional segment, followed by the - * name of the Java method that is annotated with JsonRequest. The name of the - * jsonrpc request can be customized by using the {@link #value()} field of this - * annotation. To specify the whole name, including the segment, in the value, - * set {@link #useSegment()} to false. + * + *

    A request method must have the return type {@link CompletableFuture} with an object parameter + * type or Void and have zero or one argument. + * + *

    According to jsonrpc an argument must be an 'object' (a java bean, not e,g. String). + * + *

    The name of the jsonrpc request will be the optional segment, followed by the name of the Java + * method that is annotated with JsonRequest. The name of the jsonrpc request can be customized by + * using the {@link #value()} field of this annotation. To specify the whole name, including the + * segment, in the value, set {@link #useSegment()} to false. * * @see JsonSegment */ @Retention(RetentionPolicy.RUNTIME) -@Target({ ElementType.METHOD, ElementType.TYPE }) +@Target({ElementType.METHOD, ElementType.TYPE}) public @interface JsonRequest { - /** - * The name of the the jsonrpc request method. If empty, uses the name of the - * annotated method. - */ - String value() default ""; + /** + * The name of the the jsonrpc request method. If empty, uses the name of the annotated method. + */ + String value() default ""; - /** - * When using segments, useSegment will be true to prepend the segment name to - * the name of the request. - * - * @see JsonSegment - */ - boolean useSegment() default true; + /** + * When using segments, useSegment will be true to prepend the segment name to the name of the + * request. + * + * @see JsonSegment + */ + boolean useSegment() default true; } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/services/JsonSegment.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/services/JsonSegment.java index 2b731979c..84ab05eb3 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/services/JsonSegment.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/services/JsonSegment.java @@ -1,12 +1,12 @@ /****************************************************************************** * Copyright (c) 2016 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc.services; @@ -17,10 +17,11 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * Use on a class or interface to prefix all declared {@link JsonRequest} and {@link JsonNotification} methods. + * Use on a class or interface to prefix all declared {@link JsonRequest} and {@link + * JsonNotification} methods. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface JsonSegment { - String value() default ""; + String value() default ""; } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/services/ServiceEndpoints.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/services/ServiceEndpoints.java index c8ad3f011..bc4ded683 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/services/ServiceEndpoints.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/services/ServiceEndpoints.java @@ -1,12 +1,12 @@ /****************************************************************************** * Copyright (c) 2016 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc.services; @@ -27,102 +27,114 @@ import org.eclipse.lsp4j.jsonrpc.json.ResponseJsonAdapter; import com.google.gson.TypeAdapterFactory; public final class ServiceEndpoints { - private ServiceEndpoints() {} - - /** - * Wraps a given {@link Endpoint} in the given service interface. - * - * @return the wrapped service object - */ - @SuppressWarnings("unchecked") - public static T toServiceObject(Endpoint endpoint, Class interface_) { - Class[] interfArray = new Class[]{interface_, Endpoint.class}; - EndpointProxy invocationHandler = new EndpointProxy(endpoint, interface_); - return (T) Proxy.newProxyInstance(interface_.getClassLoader(), interfArray, invocationHandler); - } - - /** - * Wraps a given {@link Endpoint} in the given service interfaces. - * - * @return the wrapped service object - */ - public static Object toServiceObject(Endpoint endpoint, Collection> interfaces, ClassLoader classLoader) { - Class[] interfArray = new Class[interfaces.size() + 1]; - interfaces.toArray(interfArray); - interfArray[interfArray.length - 1] = Endpoint.class; - EndpointProxy invocationHandler = new EndpointProxy(endpoint, interfaces); - return Proxy.newProxyInstance(classLoader, interfArray, invocationHandler); - } - - /** - * Wraps a given object with service annotations behind an {@link Endpoint} interface. - * - * @return the wrapped service endpoint - */ - public static Endpoint toEndpoint(Object serviceObject) { - return new GenericEndpoint(serviceObject); - } - - /** - * Wraps a collection of objects with service annotations behind an {@link Endpoint} interface. - * - * @return the wrapped service endpoint - */ - public static Endpoint toEndpoint(Collection serviceObjects) { - return new GenericEndpoint(serviceObjects); - } - - /** - * Finds all Json RPC methods on a given class. - * - * @return the supported JsonRpcMethods - */ - public static Map getSupportedMethods(Class type) { - Set> visitedTypes = new HashSet<>(); - return getSupportedMethods(type, visitedTypes); - } - - /** - * Finds all Json RPC methods on a given type - */ - private static Map getSupportedMethods(Class type, Set> visitedTypes) { - Map result = new LinkedHashMap(); - AnnotationUtil.findRpcMethods(type, visitedTypes, (methodInfo) -> { - JsonRpcMethod meth; - if (methodInfo.isNotification) { - meth = JsonRpcMethod.notification(methodInfo.name, methodInfo.parameterTypes); - } else { - Type genericReturnType = methodInfo.method.getGenericReturnType(); - if (genericReturnType instanceof ParameterizedType) { - Type returnType = ((ParameterizedType) genericReturnType).getActualTypeArguments()[0]; - TypeAdapterFactory responseTypeAdapter = null; - ResponseJsonAdapter responseTypeAdapterAnnotation = methodInfo.method.getAnnotation(ResponseJsonAdapter.class); - if (responseTypeAdapterAnnotation != null) { - try { - responseTypeAdapter = responseTypeAdapterAnnotation.value().newInstance(); - } catch (InstantiationException | IllegalAccessException e) { - throw new RuntimeException(e); - } - } - meth = JsonRpcMethod.request(methodInfo.name, returnType, responseTypeAdapter, methodInfo.parameterTypes); - } else { - throw new IllegalStateException("Expecting return type of CompletableFuture but was : " + genericReturnType); - } - } - if (result.put(methodInfo.name, meth) != null) { - throw new IllegalStateException("Duplicate RPC method "+methodInfo.name+"."); - }; - }); - - AnnotationUtil.findDelegateSegments(type, new HashSet<>(), (method)-> { - Map supportedDelegateMethods = getSupportedMethods(method.getReturnType(), visitedTypes); - for (JsonRpcMethod meth : supportedDelegateMethods.values()) { - if (result.put(meth.getMethodName(), meth) != null) { - throw new IllegalStateException("Duplicate RPC method "+meth.getMethodName()+"."); - }; - } - }); - return result; - } - + private ServiceEndpoints() {} + + /** + * Wraps a given {@link Endpoint} in the given service interface. + * + * @return the wrapped service object + */ + @SuppressWarnings("unchecked") + public static T toServiceObject(Endpoint endpoint, Class interface_) { + Class[] interfArray = new Class[] {interface_, Endpoint.class}; + EndpointProxy invocationHandler = new EndpointProxy(endpoint, interface_); + return (T) Proxy.newProxyInstance(interface_.getClassLoader(), interfArray, invocationHandler); + } + + /** + * Wraps a given {@link Endpoint} in the given service interfaces. + * + * @return the wrapped service object + */ + public static Object toServiceObject( + Endpoint endpoint, Collection> interfaces, ClassLoader classLoader) { + Class[] interfArray = new Class[interfaces.size() + 1]; + interfaces.toArray(interfArray); + interfArray[interfArray.length - 1] = Endpoint.class; + EndpointProxy invocationHandler = new EndpointProxy(endpoint, interfaces); + return Proxy.newProxyInstance(classLoader, interfArray, invocationHandler); + } + + /** + * Wraps a given object with service annotations behind an {@link Endpoint} interface. + * + * @return the wrapped service endpoint + */ + public static Endpoint toEndpoint(Object serviceObject) { + return new GenericEndpoint(serviceObject); + } + + /** + * Wraps a collection of objects with service annotations behind an {@link Endpoint} interface. + * + * @return the wrapped service endpoint + */ + public static Endpoint toEndpoint(Collection serviceObjects) { + return new GenericEndpoint(serviceObjects); + } + + /** + * Finds all Json RPC methods on a given class. + * + * @return the supported JsonRpcMethods + */ + public static Map getSupportedMethods(Class type) { + Set> visitedTypes = new HashSet<>(); + return getSupportedMethods(type, visitedTypes); + } + + /** Finds all Json RPC methods on a given type */ + private static Map getSupportedMethods( + Class type, Set> visitedTypes) { + Map result = new LinkedHashMap(); + AnnotationUtil.findRpcMethods( + type, + visitedTypes, + (methodInfo) -> { + JsonRpcMethod meth; + if (methodInfo.isNotification) { + meth = JsonRpcMethod.notification(methodInfo.name, methodInfo.parameterTypes); + } else { + Type genericReturnType = methodInfo.method.getGenericReturnType(); + if (genericReturnType instanceof ParameterizedType) { + Type returnType = ((ParameterizedType) genericReturnType).getActualTypeArguments()[0]; + TypeAdapterFactory responseTypeAdapter = null; + ResponseJsonAdapter responseTypeAdapterAnnotation = + methodInfo.method.getAnnotation(ResponseJsonAdapter.class); + if (responseTypeAdapterAnnotation != null) { + try { + responseTypeAdapter = responseTypeAdapterAnnotation.value().newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + meth = + JsonRpcMethod.request( + methodInfo.name, returnType, responseTypeAdapter, methodInfo.parameterTypes); + } else { + throw new IllegalStateException( + "Expecting return type of CompletableFuture but was : " + genericReturnType); + } + } + if (result.put(methodInfo.name, meth) != null) { + throw new IllegalStateException("Duplicate RPC method " + methodInfo.name + "."); + } + ; + }); + + AnnotationUtil.findDelegateSegments( + type, + new HashSet<>(), + (method) -> { + Map supportedDelegateMethods = + getSupportedMethods(method.getReturnType(), visitedTypes); + for (JsonRpcMethod meth : supportedDelegateMethods.values()) { + if (result.put(meth.getMethodName(), meth) != null) { + throw new IllegalStateException("Duplicate RPC method " + meth.getMethodName() + "."); + } + ; + } + }); + return result; + } } diff --git a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/validation/NonNull.java b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/validation/NonNull.java index 9771e754d..3db6e6311 100644 --- a/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/validation/NonNull.java +++ b/build-tools/jsonrpc/src/main/java/org/eclipse/lsp4j/jsonrpc/validation/NonNull.java @@ -1,12 +1,12 @@ /****************************************************************************** * Copyright (c) 2016 TypeFox and others. - * + * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, * or the Eclipse Distribution License v. 1.0 which is available at * http://www.eclipse.org/org/documents/edl-v10.php. - * + * * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause ******************************************************************************/ package org.eclipse.lsp4j.jsonrpc.validation; @@ -17,15 +17,14 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * Indicates that the annotated field or parameter must not be set to {@code null}. If validation - * is active, an exception is thrown when a message is received where a {@code NonNull} field - * has a {@code null} or {@code undefined} value. - * - * In order to achieve consistent behavior, for every field with this annotation the corresponding - * getter method as well as the parameter of the corresponding setter method are also expected to - * be annotated. + * Indicates that the annotated field or parameter must not be set to {@code null}. If validation is + * active, an exception is thrown when a message is received where a {@code NonNull} field has a + * {@code null} or {@code undefined} value. + * + *

    In order to achieve consistent behavior, for every field with this annotation the + * corresponding getter method as well as the parameter of the corresponding setter method are also + * expected to be annotated. */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE_PARAMETER}) -public @interface NonNull { -} +public @interface NonNull {} diff --git a/build-tools/logger/src/main/java/com/itsaky/androidide/models/LogLine.java b/build-tools/logger/src/main/java/com/itsaky/androidide/models/LogLine.java index ec266bca7..0b617dafb 100644 --- a/build-tools/logger/src/main/java/com/itsaky/androidide/models/LogLine.java +++ b/build-tools/logger/src/main/java/com/itsaky/androidide/models/LogLine.java @@ -19,162 +19,161 @@ package com.itsaky.androidide.models; public class LogLine { - // It makes things easier in LogLanguageImpl - public static int INFO; - public static int DEBUG; - public static int ERROR; - public static int WARNING; + // It makes things easier in LogLanguageImpl + public static int INFO; + public static int DEBUG; + public static int ERROR; + public static int WARNING; - static { - try { - final var klass = - Class.forName("com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE"); - INFO = klass.getField("LOG_INFO").getInt(null); - DEBUG = klass.getField("LOG_DEBUG").getInt(null); - ERROR = klass.getField("LOG_ERROR").getInt(null); - WARNING = klass.getField("LOG_WARNING").getInt(null); - } catch (Throwable err) { - INFO = 0; - DEBUG = 1; - ERROR = 2; - WARNING = 3; - } + static { + try { + final var klass = Class.forName("com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE"); + INFO = klass.getField("LOG_INFO").getInt(null); + DEBUG = klass.getField("LOG_DEBUG").getInt(null); + ERROR = klass.getField("LOG_ERROR").getInt(null); + WARNING = klass.getField("LOG_WARNING").getInt(null); + } catch (Throwable err) { + INFO = 0; + DEBUG = 1; + ERROR = 2; + WARNING = 3; + } + } + + public String unformatted; + public String date; + public String time; + public String pid; + public String tid; + public String tag; + public String message; + public char priorityChar; + public int priority; + public boolean formatted; + + // For JSONRpc + @SuppressWarnings("unused") + private LogLine() {} + + public LogLine(char priorityChar, String tag, String message) { + this("", "", "", "", priorityChar, tag, message); + } + + public LogLine( + String date, + String time, + String pid, + String tid, + char priorityChar, + String tag, + String message) { + this(date, time, pid, tid, priorityChar, tag, message, true); + } + + public LogLine( + String date, + String time, + String pid, + String tid, + char priorityChar, + String tag, + String message, + boolean formatted) { + this.date = date; + this.time = time; + this.pid = pid; + this.tid = tid; + this.priorityChar = priorityChar; + this.tag = tag; + this.message = message; + this.priority = parsePriority(priorityChar); + this.formatted = formatted; + + if (tag.length() > 25) { + this.tag = "...".concat(tag.substring(tag.length() - 25)); + } + } + + private int parsePriority(char s) { + final var c = Character.toLowerCase(s); + if (c == 'w') { + return WARNING; + } else if (c == 'e') { + return ERROR; + } else if (c == 'i') { + return INFO; + } else { + return DEBUG; + } + } + + public LogLine(String unformatted) { + this.unformatted = unformatted; + this.formatted = false; + } + + public static LogLine forLogString(final String log) { + try { + final var split = log.split("\\s", 7); + return new LogLine( + split[0], // date + split[1], // time + split[2], // process id + split[3], // thread id + split[4].charAt(0), // priority + split[5], // tag + split[6] // message + ); + } catch (Throwable th) { + return new LogLine(log); + } + } + + public int getColor() { + int id = 0xffffffff; + if (priority == WARNING) { + id = 0xffff7043; + } else if (priority == ERROR) { + id = 0xffc50e29; + } else if (priority == INFO) { + id = 0xff4caf50; + } + return id; + } + + @Override + public String toString() { + return this.formatted + ? String.format( + "%-6s %-13s %-6s %-6s %-2s %-35s %s", + date, time, pid, tid, priorityChar, trimIfNeeded(tag, 35), message) + : this.unformatted; + } + + private String trimIfNeeded(String tag, int maxLength) { + final var sb = new StringBuilder(tag); + final var length = tag.length(); + if (length > maxLength) { + final var start = length - maxLength; + sb.delete(0, start); + + // When the tag is long enough, prefix the tag with '..' + sb.setCharAt(0, '.'); + sb.setCharAt(1, '.'); } - public String unformatted; - public String date; - public String time; - public String pid; - public String tid; - public String tag; - public String message; - public char priorityChar; - public int priority; - public boolean formatted; + return sb.toString(); + } - // For JSONRpc - @SuppressWarnings("unused") - private LogLine() {} + public String toSimpleString() { + return this.formatted + ? String.format("%-25s %-2s %s", trimIfNeeded(tag, 25), priorityChar, message) + : this.unformatted; + } - public LogLine(char priorityChar, String tag, String message) { - this("", "", "", "", priorityChar, tag, message); - } - - public LogLine( - String date, - String time, - String pid, - String tid, - char priorityChar, - String tag, - String message) { - this(date, time, pid, tid, priorityChar, tag, message, true); - } - - public LogLine( - String date, - String time, - String pid, - String tid, - char priorityChar, - String tag, - String message, - boolean formatted) { - this.date = date; - this.time = time; - this.pid = pid; - this.tid = tid; - this.priorityChar = priorityChar; - this.tag = tag; - this.message = message; - this.priority = parsePriority(priorityChar); - this.formatted = formatted; - - if (tag.length() > 25) { - this.tag = "...".concat(tag.substring(tag.length() - 25)); - } - } - - private int parsePriority(char s) { - final var c = Character.toLowerCase(s); - if (c == 'w') { - return WARNING; - } else if (c == 'e') { - return ERROR; - } else if (c == 'i') { - return INFO; - } else { - return DEBUG; - } - } - - public LogLine(String unformatted) { - this.unformatted = unformatted; - this.formatted = false; - } - - public static LogLine forLogString(final String log) { - try { - final var split = log.split("\\s", 7); - return new LogLine( - split[0], // date - split[1], // time - split[2], // process id - split[3], // thread id - split[4].charAt(0), // priority - split[5], // tag - split[6] // message - ); - } catch (Throwable th) { - return new LogLine(log); - } - } - - public int getColor() { - int id = 0xffffffff; - if (priority == WARNING) { - id = 0xffff7043; - } else if (priority == ERROR) { - id = 0xffc50e29; - } else if (priority == INFO) { - id = 0xff4caf50; - } - return id; - } - - @Override - public String toString() { - return this.formatted - ? String.format( - "%-6s %-13s %-6s %-6s %-2s %-35s %s", - date, time, pid, tid, priorityChar, trimIfNeeded(tag, 35), message) - : this.unformatted; - } - - private String trimIfNeeded(String tag, int maxLength) { - final var sb = new StringBuilder(tag); - final var length = tag.length(); - if (length > maxLength) { - final var start = length - maxLength; - sb.delete(0, start); - - // When the tag is long enough, prefix the tag with '..' - sb.setCharAt(0, '.'); - sb.setCharAt(1, '.'); - } - - return sb.toString(); - } - - public String toSimpleString() { - return this.formatted - ? String.format("%-25s %-2s %s", trimIfNeeded(tag, 25), priorityChar, message) - : this.unformatted; - } - - public String formattedTagAndMessage() { - return this.formatted - ? String.format("%-25s %s", trimIfNeeded(tag, 25), message) - : this.unformatted; - } + public String formattedTagAndMessage() { + return this.formatted + ? String.format("%-25s %s", trimIfNeeded(tag, 25), message) + : this.unformatted; + } } diff --git a/build-tools/logger/src/main/java/com/itsaky/androidide/utils/AndroidLogger.java b/build-tools/logger/src/main/java/com/itsaky/androidide/utils/AndroidLogger.java index e50ef18dc..fb8b8543f 100644 --- a/build-tools/logger/src/main/java/com/itsaky/androidide/utils/AndroidLogger.java +++ b/build-tools/logger/src/main/java/com/itsaky/androidide/utils/AndroidLogger.java @@ -26,29 +26,29 @@ import android.util.Log; */ public class AndroidLogger extends ILogger { - protected AndroidLogger(String tag) { - super(tag); - } + protected AndroidLogger(String tag) { + super(tag); + } - @Override - protected void doLog(int priority, String message) { - switch (priority) { - case ERROR: - Log.e(TAG, message); - break; - case WARNING: - Log.w(TAG, message); - break; - case VERBOSE: - Log.v(TAG, message); - break; - case INFO: - Log.i(TAG, message); - break; - case DEBUG: - default: - Log.d(TAG, message); - break; - } + @Override + protected void doLog(int priority, String message) { + switch (priority) { + case ERROR: + Log.e(TAG, message); + break; + case WARNING: + Log.w(TAG, message); + break; + case VERBOSE: + Log.v(TAG, message); + break; + case INFO: + Log.i(TAG, message); + break; + case DEBUG: + default: + Log.d(TAG, message); + break; } + } } diff --git a/build-tools/logger/src/main/java/com/itsaky/androidide/utils/ILogger.java b/build-tools/logger/src/main/java/com/itsaky/androidide/utils/ILogger.java index da429370d..fe3b3880f 100644 --- a/build-tools/logger/src/main/java/com/itsaky/androidide/utils/ILogger.java +++ b/build-tools/logger/src/main/java/com/itsaky/androidide/utils/ILogger.java @@ -48,213 +48,212 @@ import java.util.Objects; */ public abstract class ILogger { - public static final int DEBUG = 0; - public static final int WARNING = 1; - public static final int ERROR = 2; - public static final int INFO = 3; - public static final int VERBOSE = 4; - private static final String MSG_SEPARATOR = " "; // Separate messages with a space. - private static final List logListeners = new ArrayList<>(); - private static ILogger instance; - protected final String TAG; + public static final int DEBUG = 0; + public static final int WARNING = 1; + public static final int ERROR = 2; + public static final int INFO = 3; + public static final int VERBOSE = 4; + private static final String MSG_SEPARATOR = " "; // Separate messages with a space. + private static final List logListeners = new ArrayList<>(); + private static ILogger instance; + protected final String TAG; - protected ILogger(String tag) { - TAG = tag; + protected ILogger(String tag) { + TAG = tag; + } + + public static ILogger instance() { + return instance == null ? instance = createInstance("AndroidIDE") : instance; + } + + private static ILogger createInstance(String tag) { + return newPlatformDependentLogger(tag); + } + + private static ILogger newPlatformDependentLogger(String tag) { + return LogUtils.isJvm() ? new JvmLogger(tag) : new AndroidLogger(tag); + } + + public static void addLogListener(LogListener listener) { + logListeners.add(Objects.requireNonNull(listener)); + } + + public static void removeLogListener(LogListener listener) { + logListeners.remove(Objects.requireNonNull(listener)); + } + + public static ILogger newInstance(String tag) { + return createInstance(tag); + } + + public static String priorityText(int priority) { + switch (priority) { + case DEBUG: + return "DEBUG"; + case INFO: + return "INFO"; + case VERBOSE: + return "VERBOSE"; + case ERROR: + return "ERROR"; + case WARNING: + return "WARNING"; + default: + return ""; + } + } + + public static char priorityChar(int priority) { + switch (priority) { + case INFO: + return 'I'; + case VERBOSE: + return 'V'; + case ERROR: + return 'E'; + case WARNING: + return 'W'; + default: + return 'D'; + } + } + + public static int priority(char priorityChar) { + switch (priorityChar) { + case 'I': + return INFO; + case 'V': + return VERBOSE; + case 'E': + return ERROR; + case 'W': + return WARNING; + case 'D': + default: + return DEBUG; + } + } + + /** + * Log error messages. + * + * @param messages The messages to log. + * @return This logger instance. + */ + public ILogger error(Object... messages) { + return log(ERROR, messages); + } + + /** + * Log messages with the given priority. + * + * @param priority The priority of the log messages. + * @param messages The messages to log. + * @return This logger instance. + */ + public ILogger log(int priority, Object... messages) { + logAndNotify(priority, generateMessage(messages)); + return this; + } + + private void logAndNotify(int priority, String msg) { + doLog(priority, msg); + for (final var listener : logListeners) { + listener.log(priority, TAG, msg); + } + } + + protected String generateMessage(Object... messages) { + StringBuilder sb = new StringBuilder(); + if (messages == null) { + return "null"; } - public static ILogger instance() { - return instance == null ? instance = createInstance("AndroidIDE") : instance; + for (Object msg : messages) { + sb.append(msg instanceof Throwable ? "\n" : MSG_SEPARATOR); + sb.append(msg instanceof Throwable ? LogUtils.getFullStackTrace(((Throwable) msg)) : msg); + sb.append(msg instanceof Throwable ? "\n" : MSG_SEPARATOR); } - private static ILogger createInstance(String tag) { - return newPlatformDependentLogger(tag); + return sb.toString(); + } + + /** + * Log the message to an appropriate stream where the user can see the log messages. + * + * @param priority The priority for this log message. + * @param message The full generated message for this log. Might contain new lines. + * @see ILogger#DEBUG + * @see ILogger#ERROR + * @see ILogger#WARNING + * @see ILogger#VERBOSE + * @see ILogger#INFO + */ + protected abstract void doLog(int priority, String message); + + /** + * Log warning messages. + * + * @param messages The messages to log. + * @return This logger instance. + */ + public ILogger warn(Object... messages) { + return log(WARNING, messages); + } + + /** + * Log verbose messages. + * + * @param messages The messages to log. + * @return This logger instance. + */ + public ILogger verbose(Object... messages) { + return log(VERBOSE, messages); + } + + /** + * Log information messages. + * + * @param messages The messages to log. + * @return This logger instance. + */ + public ILogger info(Object... messages) { + return log(INFO, messages); + } + + /** Logs the name of method and class which calls this method. */ + public void logThis() { + debug(getCallerClassDescription()); + } + + /** + * Log debug messages. + * + * @param messages The messages to log. + * @return This logger instance. + */ + public ILogger debug(Object... messages) { + return log(DEBUG, messages); + } + + protected String getCallerClassDescription() { + final var elements = Thread.currentThread().getStackTrace(); + for (int i = 1, elementsLength = elements.length; i < elementsLength; i++) { + final var element = elements[i]; + final var klass = element.getClassName(); + final var method = element.getMethodName(); + if (ILogger.class.getName().equals(klass) || klass.contains("java.lang.Thread")) { + continue; + } + + return String.format("%s [%s]", method, klass); } - private static ILogger newPlatformDependentLogger(String tag) { - return LogUtils.isJvm() ? new JvmLogger(tag) : new AndroidLogger(tag); - } + return " "; + } - public static void addLogListener(LogListener listener) { - logListeners.add(Objects.requireNonNull(listener)); - } - - public static void removeLogListener(LogListener listener) { - logListeners.remove(Objects.requireNonNull(listener)); - } - - public static ILogger newInstance(String tag) { - return createInstance(tag); - } - - public static String priorityText(int priority) { - switch (priority) { - case DEBUG: - return "DEBUG"; - case INFO: - return "INFO"; - case VERBOSE: - return "VERBOSE"; - case ERROR: - return "ERROR"; - case WARNING: - return "WARNING"; - default: - return ""; - } - } - - public static char priorityChar(int priority) { - switch (priority) { - case INFO: - return 'I'; - case VERBOSE: - return 'V'; - case ERROR: - return 'E'; - case WARNING: - return 'W'; - default: - return 'D'; - } - } - - public static int priority(char priorityChar) { - switch (priorityChar) { - case 'I': - return INFO; - case 'V': - return VERBOSE; - case 'E': - return ERROR; - case 'W': - return WARNING; - case 'D': - default: - return DEBUG; - } - } - - /** - * Log error messages. - * - * @param messages The messages to log. - * @return This logger instance. - */ - public ILogger error(Object... messages) { - return log(ERROR, messages); - } - - /** - * Log messages with the given priority. - * - * @param priority The priority of the log messages. - * @param messages The messages to log. - * @return This logger instance. - */ - public ILogger log(int priority, Object... messages) { - logAndNotify(priority, generateMessage(messages)); - return this; - } - - private void logAndNotify(int priority, String msg) { - doLog(priority, msg); - for (final var listener : logListeners) { - listener.log(priority, TAG, msg); - } - } - - protected String generateMessage(Object... messages) { - StringBuilder sb = new StringBuilder(); - if (messages == null) { - return "null"; - } - - for (Object msg : messages) { - sb.append(msg instanceof Throwable ? "\n" : MSG_SEPARATOR); - sb.append( - msg instanceof Throwable ? LogUtils.getFullStackTrace(((Throwable) msg)) : msg); - sb.append(msg instanceof Throwable ? "\n" : MSG_SEPARATOR); - } - - return sb.toString(); - } - - /** - * Log the message to an appropriate stream where the user can see the log messages. - * - * @param priority The priority for this log message. - * @param message The full generated message for this log. Might contain new lines. - * @see ILogger#DEBUG - * @see ILogger#ERROR - * @see ILogger#WARNING - * @see ILogger#VERBOSE - * @see ILogger#INFO - */ - protected abstract void doLog(int priority, String message); - - /** - * Log warning messages. - * - * @param messages The messages to log. - * @return This logger instance. - */ - public ILogger warn(Object... messages) { - return log(WARNING, messages); - } - - /** - * Log verbose messages. - * - * @param messages The messages to log. - * @return This logger instance. - */ - public ILogger verbose(Object... messages) { - return log(VERBOSE, messages); - } - - /** - * Log information messages. - * - * @param messages The messages to log. - * @return This logger instance. - */ - public ILogger info(Object... messages) { - return log(INFO, messages); - } - - /** Logs the name of method and class which calls this method. */ - public void logThis() { - debug(getCallerClassDescription()); - } - - /** - * Log debug messages. - * - * @param messages The messages to log. - * @return This logger instance. - */ - public ILogger debug(Object... messages) { - return log(DEBUG, messages); - } - - protected String getCallerClassDescription() { - final var elements = Thread.currentThread().getStackTrace(); - for (int i = 1, elementsLength = elements.length; i < elementsLength; i++) { - final var element = elements[i]; - final var klass = element.getClassName(); - final var method = element.getMethodName(); - if (ILogger.class.getName().equals(klass) || klass.contains("java.lang.Thread")) { - continue; - } - - return String.format("%s [%s]", method, klass); - } - - return " "; - } - - /** A listener which can be used to listen to log events. */ - public interface LogListener { - void log(int priority, String tag, String message); - } + /** A listener which can be used to listen to log events. */ + public interface LogListener { + void log(int priority, String tag, String message); + } } diff --git a/build-tools/logger/src/main/java/com/itsaky/androidide/utils/JvmLogger.java b/build-tools/logger/src/main/java/com/itsaky/androidide/utils/JvmLogger.java index 6b2352ca4..56ba978fa 100644 --- a/build-tools/logger/src/main/java/com/itsaky/androidide/utils/JvmLogger.java +++ b/build-tools/logger/src/main/java/com/itsaky/androidide/utils/JvmLogger.java @@ -26,23 +26,23 @@ import com.itsaky.androidide.models.LogLine; */ public class JvmLogger extends ILogger { - public static LogInterceptor interceptor; + public static LogInterceptor interceptor; - protected JvmLogger(String tag) { - super(tag); - } + protected JvmLogger(String tag) { + super(tag); + } - @Override - protected void doLog(int priority, String message) { - final var log = new LogLine(priorityChar(priority), TAG, message); - if (interceptor != null) { - interceptor.onLog(log); - } else { - System.err.println(log.toSimpleString()); - } + @Override + protected void doLog(int priority, String message) { + final var log = new LogLine(priorityChar(priority), TAG, message); + if (interceptor != null) { + interceptor.onLog(log); + } else { + System.err.println(log.toSimpleString()); } + } - public interface LogInterceptor { - void onLog(LogLine line); - } + public interface LogInterceptor { + void onLog(LogLine line); + } } diff --git a/build-tools/logger/src/main/java/com/itsaky/androidide/utils/LogUtils.java b/build-tools/logger/src/main/java/com/itsaky/androidide/utils/LogUtils.java index ebec00ae6..378f4f99e 100644 --- a/build-tools/logger/src/main/java/com/itsaky/androidide/utils/LogUtils.java +++ b/build-tools/logger/src/main/java/com/itsaky/androidide/utils/LogUtils.java @@ -29,85 +29,85 @@ import java.util.StringTokenizer; * @author Akash Yadav */ class LogUtils { - private static final String LINE_SEP = System.getProperty("line.separator"); + private static final String LINE_SEP = System.getProperty("line.separator"); - /** - * @return true if the current platform is JVM, false otherwise. - */ - public static boolean isJvm() { - try { - Class.forName("android.content.Context"); - return false; - } catch (ClassNotFoundException e) { - return true; - } + /** + * @return true if the current platform is JVM, false otherwise. + */ + public static boolean isJvm() { + try { + Class.forName("android.content.Context"); + return false; + } catch (ClassNotFoundException e) { + return true; } + } - public static String getFullStackTrace(Throwable throwable) { - final List throwableList = new ArrayList<>(); - while (throwable != null && !throwableList.contains(throwable)) { - throwableList.add(throwable); - throwable = throwable.getCause(); - } - final int size = throwableList.size(); - final List frames = new ArrayList<>(); - List nextTrace = getStackFrameList(throwableList.get(size - 1)); - for (int i = size; --i >= 0; ) { - final List trace = nextTrace; - if (i != 0) { - nextTrace = getStackFrameList(throwableList.get(i - 1)); - removeCommonFrames(trace, nextTrace); - } - if (i == size - 1) { - frames.add(throwableList.get(i).toString()); - } else { - frames.add(" Caused by: " + throwableList.get(i).toString()); - } - frames.addAll(trace); - } - StringBuilder sb = new StringBuilder(); - for (final String element : frames) { - sb.append(element).append(LINE_SEP); - } - return sb.toString(); + public static String getFullStackTrace(Throwable throwable) { + final List throwableList = new ArrayList<>(); + while (throwable != null && !throwableList.contains(throwable)) { + throwableList.add(throwable); + throwable = throwable.getCause(); } + final int size = throwableList.size(); + final List frames = new ArrayList<>(); + List nextTrace = getStackFrameList(throwableList.get(size - 1)); + for (int i = size; --i >= 0; ) { + final List trace = nextTrace; + if (i != 0) { + nextTrace = getStackFrameList(throwableList.get(i - 1)); + removeCommonFrames(trace, nextTrace); + } + if (i == size - 1) { + frames.add(throwableList.get(i).toString()); + } else { + frames.add(" Caused by: " + throwableList.get(i).toString()); + } + frames.addAll(trace); + } + StringBuilder sb = new StringBuilder(); + for (final String element : frames) { + sb.append(element).append(LINE_SEP); + } + return sb.toString(); + } - private static List getStackFrameList(final Throwable throwable) { - final StringWriter sw = new StringWriter(); - final PrintWriter pw = new PrintWriter(sw, true); - throwable.printStackTrace(pw); - final String stackTrace = sw.toString(); - final StringTokenizer frames = new StringTokenizer(stackTrace, LINE_SEP); - final List list = new ArrayList<>(); - boolean traceStarted = false; - while (frames.hasMoreTokens()) { - final String token = frames.nextToken(); - // Determine if the line starts with at - final int at = token.indexOf("at"); - if (at != -1 && token.substring(0, at).trim().isEmpty()) { - traceStarted = true; - list.add(token); - } else if (traceStarted) { - break; - } - } - return list; + private static List getStackFrameList(final Throwable throwable) { + final StringWriter sw = new StringWriter(); + final PrintWriter pw = new PrintWriter(sw, true); + throwable.printStackTrace(pw); + final String stackTrace = sw.toString(); + final StringTokenizer frames = new StringTokenizer(stackTrace, LINE_SEP); + final List list = new ArrayList<>(); + boolean traceStarted = false; + while (frames.hasMoreTokens()) { + final String token = frames.nextToken(); + // Determine if the line starts with at + final int at = token.indexOf("at"); + if (at != -1 && token.substring(0, at).trim().isEmpty()) { + traceStarted = true; + list.add(token); + } else if (traceStarted) { + break; + } } + return list; + } - private static void removeCommonFrames( - final List causeFrames, final List wrapperFrames) { - int causeFrameIndex = causeFrames.size() - 1; - int wrapperFrameIndex = wrapperFrames.size() - 1; - while (causeFrameIndex >= 0 && wrapperFrameIndex >= 0) { - // Remove the frame from the cause trace if it is the same - // as in the wrapper trace - final String causeFrame = causeFrames.get(causeFrameIndex); - final String wrapperFrame = wrapperFrames.get(wrapperFrameIndex); - if (causeFrame.equals(wrapperFrame)) { - causeFrames.remove(causeFrameIndex); - } - causeFrameIndex--; - wrapperFrameIndex--; - } + private static void removeCommonFrames( + final List causeFrames, final List wrapperFrames) { + int causeFrameIndex = causeFrames.size() - 1; + int wrapperFrameIndex = wrapperFrames.size() - 1; + while (causeFrameIndex >= 0 && wrapperFrameIndex >= 0) { + // Remove the frame from the cause trace if it is the same + // as in the wrapper trace + final String causeFrame = causeFrames.get(causeFrameIndex); + final String wrapperFrame = wrapperFrames.get(wrapperFrameIndex); + if (causeFrame.equals(wrapperFrame)) { + causeFrames.remove(causeFrameIndex); + } + causeFrameIndex--; + wrapperFrameIndex--; } + } } diff --git a/build-tools/logger/src/test/java/com/itsaky/androidide/utils/LoggerTest.java b/build-tools/logger/src/test/java/com/itsaky/androidide/utils/LoggerTest.java index 51b64283f..7976afa92 100644 --- a/build-tools/logger/src/test/java/com/itsaky/androidide/utils/LoggerTest.java +++ b/build-tools/logger/src/test/java/com/itsaky/androidide/utils/LoggerTest.java @@ -28,10 +28,10 @@ import org.junit.runners.JUnit4; */ @RunWith(JUnit4.class) public class LoggerTest { - @Test - public void testLoggerInstance() { - final var log = ILogger.newInstance("LoggerTest"); - log.debug ("Logger.newInstance() on JVM returns:", log.getClass().getName()); - assertThat(log).isInstanceOf(JvmLogger.class); - } + @Test + public void testLoggerInstance() { + final var log = ILogger.newInstance("LoggerTest"); + log.debug("Logger.newInstance() on JVM returns:", log.getClass().getName()); + assertThat(log).isInstanceOf(JvmLogger.class); + } } diff --git a/build-tools/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/util/ProjectReader.java b/build-tools/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/util/ProjectReader.java index 8559961f8..5583cee5b 100644 --- a/build-tools/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/util/ProjectReader.java +++ b/build-tools/tooling-api-impl/src/main/java/com/itsaky/androidide/tooling/impl/util/ProjectReader.java @@ -96,7 +96,7 @@ public class ProjectReader { builder.setProjectDir(gradle.getProjectDirectory()); builder.setBuildDir(gradle.getBuildDirectory()); builder.setBuildScript(gradle.getBuildScript().getSourceFile()); - builder.setSimpleVariants (copier.asSimpleVariants (android.getVariants ())); + builder.setSimpleVariants(copier.asSimpleVariants(android.getVariants())); builder.setJavaCompileOptions(copier.copy(android.getJavaCompileOptions())); builder.setResourcePrefix(android.getResourcePrefix()); builder.setDynamicFeatures(android.getDynamicFeatures()); diff --git a/build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/util/RuntimeTypeAdapterFactory.java b/build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/util/RuntimeTypeAdapterFactory.java index f9976729d..fb5da340d 100644 --- a/build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/util/RuntimeTypeAdapterFactory.java +++ b/build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/util/RuntimeTypeAdapterFactory.java @@ -158,160 +158,156 @@ import java.util.Map; * } */ public final class RuntimeTypeAdapterFactory implements TypeAdapterFactory { - private final Class baseType; - private final String typeFieldName; - private final Map> labelToSubtype = new LinkedHashMap<>(); - private final Map, String> subtypeToLabel = new LinkedHashMap<>(); - private final boolean maintainType; + private final Class baseType; + private final String typeFieldName; + private final Map> labelToSubtype = new LinkedHashMap<>(); + private final Map, String> subtypeToLabel = new LinkedHashMap<>(); + private final boolean maintainType; - private RuntimeTypeAdapterFactory( - Class baseType, String typeFieldName, boolean maintainType) { - if (typeFieldName == null || baseType == null) { - throw new NullPointerException(); + private RuntimeTypeAdapterFactory(Class baseType, String typeFieldName, boolean maintainType) { + if (typeFieldName == null || baseType == null) { + throw new NullPointerException(); + } + this.baseType = baseType; + this.typeFieldName = typeFieldName; + this.maintainType = maintainType; + } + + /** + * Creates a new runtime type adapter using for {@code baseType} using {@code typeFieldName} as + * the type field name. Type field names are case sensitive. {@code maintainType} flag decide if + * the type will be stored in pojo or not. + */ + public static RuntimeTypeAdapterFactory of( + Class baseType, String typeFieldName, boolean maintainType) { + return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName, maintainType); + } + + /** + * Creates a new runtime type adapter using for {@code baseType} using {@code typeFieldName} as + * the type field name. Type field names are case sensitive. + */ + public static RuntimeTypeAdapterFactory of(Class baseType, String typeFieldName) { + return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName, false); + } + + /** + * Creates a new runtime type adapter for {@code baseType} using {@code "type"} as the type field + * name. + */ + public static RuntimeTypeAdapterFactory of(Class baseType) { + return new RuntimeTypeAdapterFactory<>(baseType, "type", false); + } + + /** + * Registers {@code type} identified by its {@link Class#getSimpleName simple name}. Labels are + * case sensitive. + * + * @throws IllegalArgumentException if either {@code type} or its simple name have already been + * registered on this type adapter. + */ + public RuntimeTypeAdapterFactory registerSubtype(Class type) { + return registerSubtype(type, type.getSimpleName()); + } + + /** + * Registers {@code type} identified by {@code label}. Labels are case sensitive. + * + * @throws IllegalArgumentException if either {@code type} or {@code label} have already been + * registered on this type adapter. + */ + public RuntimeTypeAdapterFactory registerSubtype(Class type, String label) { + if (type == null || label == null) { + throw new NullPointerException(); + } + if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) { + throw new IllegalArgumentException("types and labels must be unique"); + } + labelToSubtype.put(label, type); + subtypeToLabel.put(type, label); + return this; + } + + @Override + public TypeAdapter create(Gson gson, TypeToken type) { + if (type.getRawType() != baseType) { + return null; + } + + final TypeAdapter jsonElementAdapter = gson.getAdapter(JsonElement.class); + final Map> labelToDelegate = new LinkedHashMap<>(); + final Map, TypeAdapter> subtypeToDelegate = new LinkedHashMap<>(); + for (Map.Entry> entry : labelToSubtype.entrySet()) { + TypeAdapter delegate = gson.getDelegateAdapter(this, TypeToken.get(entry.getValue())); + labelToDelegate.put(entry.getKey(), delegate); + subtypeToDelegate.put(entry.getValue(), delegate); + } + + return new TypeAdapter() { + @Override + public void write(JsonWriter out, R value) throws IOException { + Class srcType = value.getClass(); + String label = subtypeToLabel.get(srcType); + @SuppressWarnings("unchecked") // registration requires that subtype extends T + TypeAdapter delegate = (TypeAdapter) subtypeToDelegate.get(srcType); + if (delegate == null) { + throw new JsonParseException( + "cannot serialize " + srcType.getName() + "; did you forget to register a subtype?"); } - this.baseType = baseType; - this.typeFieldName = typeFieldName; - this.maintainType = maintainType; - } + JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject(); - /** - * Creates a new runtime type adapter using for {@code baseType} using {@code typeFieldName} as - * the type field name. Type field names are case sensitive. {@code maintainType} flag decide if - * the type will be stored in pojo or not. - */ - public static RuntimeTypeAdapterFactory of( - Class baseType, String typeFieldName, boolean maintainType) { - return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName, maintainType); - } - - /** - * Creates a new runtime type adapter using for {@code baseType} using {@code typeFieldName} as - * the type field name. Type field names are case sensitive. - */ - public static RuntimeTypeAdapterFactory of(Class baseType, String typeFieldName) { - return new RuntimeTypeAdapterFactory<>(baseType, typeFieldName, false); - } - - /** - * Creates a new runtime type adapter for {@code baseType} using {@code "type"} as the type - * field name. - */ - public static RuntimeTypeAdapterFactory of(Class baseType) { - return new RuntimeTypeAdapterFactory<>(baseType, "type", false); - } - - /** - * Registers {@code type} identified by its {@link Class#getSimpleName simple name}. Labels are - * case sensitive. - * - * @throws IllegalArgumentException if either {@code type} or its simple name have already been - * registered on this type adapter. - */ - public RuntimeTypeAdapterFactory registerSubtype(Class type) { - return registerSubtype(type, type.getSimpleName()); - } - - /** - * Registers {@code type} identified by {@code label}. Labels are case sensitive. - * - * @throws IllegalArgumentException if either {@code type} or {@code label} have already been - * registered on this type adapter. - */ - public RuntimeTypeAdapterFactory registerSubtype(Class type, String label) { - if (type == null || label == null) { - throw new NullPointerException(); - } - if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) { - throw new IllegalArgumentException("types and labels must be unique"); - } - labelToSubtype.put(label, type); - subtypeToLabel.put(type, label); - return this; - } - - @Override - public TypeAdapter create(Gson gson, TypeToken type) { - if (type.getRawType() != baseType) { - return null; + if (maintainType) { + jsonElementAdapter.write(out, jsonObject); + return; } - final TypeAdapter jsonElementAdapter = gson.getAdapter(JsonElement.class); - final Map> labelToDelegate = new LinkedHashMap<>(); - final Map, TypeAdapter> subtypeToDelegate = new LinkedHashMap<>(); - for (Map.Entry> entry : labelToSubtype.entrySet()) { - TypeAdapter delegate = - gson.getDelegateAdapter(this, TypeToken.get(entry.getValue())); - labelToDelegate.put(entry.getKey(), delegate); - subtypeToDelegate.put(entry.getValue(), delegate); + JsonObject clone = new JsonObject(); + + if (jsonObject.has(typeFieldName)) { + throw new JsonParseException( + "cannot serialize " + + srcType.getName() + + " because it already defines a field named " + + typeFieldName); + } + clone.add(typeFieldName, new JsonPrimitive(label)); + + for (Map.Entry e : jsonObject.entrySet()) { + clone.add(e.getKey(), e.getValue()); + } + jsonElementAdapter.write(out, clone); + } + + @Override + public R read(JsonReader in) throws IOException { + JsonElement jsonElement = jsonElementAdapter.read(in); + JsonElement labelJsonElement; + if (maintainType) { + labelJsonElement = jsonElement.getAsJsonObject().get(typeFieldName); + } else { + labelJsonElement = jsonElement.getAsJsonObject().remove(typeFieldName); } - return new TypeAdapter() { - @Override - public void write(JsonWriter out, R value) throws IOException { - Class srcType = value.getClass(); - String label = subtypeToLabel.get(srcType); - @SuppressWarnings("unchecked") // registration requires that subtype extends T - TypeAdapter delegate = (TypeAdapter) subtypeToDelegate.get(srcType); - if (delegate == null) { - throw new JsonParseException( - "cannot serialize " - + srcType.getName() - + "; did you forget to register a subtype?"); - } - JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject(); - - if (maintainType) { - jsonElementAdapter.write(out, jsonObject); - return; - } - - JsonObject clone = new JsonObject(); - - if (jsonObject.has(typeFieldName)) { - throw new JsonParseException( - "cannot serialize " - + srcType.getName() - + " because it already defines a field named " - + typeFieldName); - } - clone.add(typeFieldName, new JsonPrimitive(label)); - - for (Map.Entry e : jsonObject.entrySet()) { - clone.add(e.getKey(), e.getValue()); - } - jsonElementAdapter.write(out, clone); - } - - @Override - public R read(JsonReader in) throws IOException { - JsonElement jsonElement = jsonElementAdapter.read(in); - JsonElement labelJsonElement; - if (maintainType) { - labelJsonElement = jsonElement.getAsJsonObject().get(typeFieldName); - } else { - labelJsonElement = jsonElement.getAsJsonObject().remove(typeFieldName); - } - - if (labelJsonElement == null) { - throw new JsonParseException( - "cannot deserialize " - + baseType - + " because it does not define a field named " - + typeFieldName); - } - String label = labelJsonElement.getAsString(); - @SuppressWarnings("unchecked") // registration requires that subtype extends T - TypeAdapter delegate = (TypeAdapter) labelToDelegate.get(label); - if (delegate == null) { - throw new JsonParseException( - "cannot deserialize " - + baseType - + " subtype named " - + label - + "; did you forget to register a subtype?"); - } - return delegate.fromJsonTree(jsonElement); - } - }.nullSafe(); - } + if (labelJsonElement == null) { + throw new JsonParseException( + "cannot deserialize " + + baseType + + " because it does not define a field named " + + typeFieldName); + } + String label = labelJsonElement.getAsString(); + @SuppressWarnings("unchecked") // registration requires that subtype extends T + TypeAdapter delegate = (TypeAdapter) labelToDelegate.get(label); + if (delegate == null) { + throw new JsonParseException( + "cannot deserialize " + + baseType + + " subtype named " + + label + + "; did you forget to register a subtype?"); + } + return delegate.fromJsonTree(jsonElement); + } + }.nullSafe(); + } } diff --git a/build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/util/ToolingApiLauncher.java b/build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/util/ToolingApiLauncher.java index a7eda1bd0..ab853bef7 100644 --- a/build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/util/ToolingApiLauncher.java +++ b/build-tools/tooling-api/src/main/java/com/itsaky/androidide/tooling/api/util/ToolingApiLauncher.java @@ -87,178 +87,142 @@ import java.util.concurrent.Executors; */ public class ToolingApiLauncher { - public static Launcher createIOLauncher( - Object local, Class remote, InputStream in, OutputStream out) { - return new Launcher.Builder() - .setInput(in) - .setOutput(out) - .setLocalService(local) - .setRemoteInterface(remote) - .configureGson(ToolingApiLauncher::configureGson) - .create(); - } + public static Launcher createIOLauncher( + Object local, Class remote, InputStream in, OutputStream out) { + return new Launcher.Builder() + .setInput(in) + .setOutput(out) + .setLocalService(local) + .setRemoteInterface(remote) + .configureGson(ToolingApiLauncher::configureGson) + .create(); + } - public static void configureGson(GsonBuilder builder) { - builder.registerTypeAdapterFactory( - RuntimeTypeAdapterFactory.of(IdeGradleProject.class, "gsonType", true) - .registerSubtype(IdeAndroidModule.class, IdeAndroidModule.class.getName()) - .registerSubtype(IdeJavaModule.class, IdeJavaModule.class.getName()) - .registerSubtype(IdeGradleProject.class, IdeGradleProject.class.getName())); - builder.registerTypeAdapterFactory( - RuntimeTypeAdapterFactory.of(IdeLaunchable.class, "gsonType", true) - .registerSubtype(IdeGradleTask.class, IdeGradleTask.class.getName()) - .registerSubtype(IdeLaunchable.class, IdeLaunchable.class.getName())); - builder.registerTypeAdapterFactory( - RuntimeTypeAdapterFactory.of(JavaModuleDependency.class, "gsonType", true) - .registerSubtype( - JavaModuleExternalDependency.class, - JavaModuleExternalDependency.class.getName()) - .registerSubtype( - JavaModuleProjectDependency.class, - JavaModuleProjectDependency.class.getName())); + public static void configureGson(GsonBuilder builder) { + builder.registerTypeAdapterFactory( + RuntimeTypeAdapterFactory.of(IdeGradleProject.class, "gsonType", true) + .registerSubtype(IdeAndroidModule.class, IdeAndroidModule.class.getName()) + .registerSubtype(IdeJavaModule.class, IdeJavaModule.class.getName()) + .registerSubtype(IdeGradleProject.class, IdeGradleProject.class.getName())); + builder.registerTypeAdapterFactory( + RuntimeTypeAdapterFactory.of(IdeLaunchable.class, "gsonType", true) + .registerSubtype(IdeGradleTask.class, IdeGradleTask.class.getName()) + .registerSubtype(IdeLaunchable.class, IdeLaunchable.class.getName())); + builder.registerTypeAdapterFactory( + RuntimeTypeAdapterFactory.of(JavaModuleDependency.class, "gsonType", true) + .registerSubtype( + JavaModuleExternalDependency.class, JavaModuleExternalDependency.class.getName()) + .registerSubtype( + JavaModuleProjectDependency.class, JavaModuleProjectDependency.class.getName())); - builder.registerTypeAdapterFactory( - RuntimeTypeAdapterFactory.of(ProgressEvent.class, "gsonType", true) + builder.registerTypeAdapterFactory( + RuntimeTypeAdapterFactory.of(ProgressEvent.class, "gsonType", true) - // Project configuration - .registerSubtype( - ProjectConfigurationProgressEvent.class, - ProjectConfigurationProgressEvent.class.getName()) - .registerSubtype( - ProjectConfigurationStartEvent.class, - ProjectConfigurationStartEvent.class.getName()) - .registerSubtype( - ProjectConfigurationFinishEvent.class, - ProjectConfigurationFinishEvent.class.getName()) + // Project configuration + .registerSubtype( + ProjectConfigurationProgressEvent.class, + ProjectConfigurationProgressEvent.class.getName()) + .registerSubtype( + ProjectConfigurationStartEvent.class, + ProjectConfigurationStartEvent.class.getName()) + .registerSubtype( + ProjectConfigurationFinishEvent.class, + ProjectConfigurationFinishEvent.class.getName()) - // File download - .registerSubtype( - FileDownloadProgressEvent.class, - FileDownloadProgressEvent.class.getName()) - .registerSubtype( - FileDownloadStartEvent.class, - FileDownloadStartEvent.class.getName()) - .registerSubtype( - FileDownloadFinishEvent.class, - FileDownloadFinishEvent.class.getName()) + // File download + .registerSubtype( + FileDownloadProgressEvent.class, FileDownloadProgressEvent.class.getName()) + .registerSubtype(FileDownloadStartEvent.class, FileDownloadStartEvent.class.getName()) + .registerSubtype(FileDownloadFinishEvent.class, FileDownloadFinishEvent.class.getName()) - // Task execution - .registerSubtype(TaskProgressEvent.class, TaskProgressEvent.class.getName()) - .registerSubtype(TaskStartEvent.class, TaskStartEvent.class.getName()) - .registerSubtype(TaskFinishEvent.class, TaskFinishEvent.class.getName()) + // Task execution + .registerSubtype(TaskProgressEvent.class, TaskProgressEvent.class.getName()) + .registerSubtype(TaskStartEvent.class, TaskStartEvent.class.getName()) + .registerSubtype(TaskFinishEvent.class, TaskFinishEvent.class.getName()) - // Test execution - .registerSubtype(TestProgressEvent.class, TestProgressEvent.class.getName()) - .registerSubtype(TestStartEvent.class, TestStartEvent.class.getName()) - .registerSubtype(TestFinishEvent.class, TestFinishEvent.class.getName()) + // Test execution + .registerSubtype(TestProgressEvent.class, TestProgressEvent.class.getName()) + .registerSubtype(TestStartEvent.class, TestStartEvent.class.getName()) + .registerSubtype(TestFinishEvent.class, TestFinishEvent.class.getName()) - // Transform - .registerSubtype( - TransformProgressEvent.class, - TransformProgressEvent.class.getName()) - .registerSubtype( - TransformStartEvent.class, TransformStartEvent.class.getName()) - .registerSubtype( - TransformFinishEvent.class, TransformFinishEvent.class.getName()) + // Transform + .registerSubtype(TransformProgressEvent.class, TransformProgressEvent.class.getName()) + .registerSubtype(TransformStartEvent.class, TransformStartEvent.class.getName()) + .registerSubtype(TransformFinishEvent.class, TransformFinishEvent.class.getName()) - // Work item - .registerSubtype( - WorkItemProgressEvent.class, WorkItemProgressEvent.class.getName()) - .registerSubtype( - WorkItemStartEvent.class, WorkItemStartEvent.class.getName()) - .registerSubtype( - WorkItemFinishEvent.class, WorkItemFinishEvent.class.getName()) + // Work item + .registerSubtype(WorkItemProgressEvent.class, WorkItemProgressEvent.class.getName()) + .registerSubtype(WorkItemStartEvent.class, WorkItemStartEvent.class.getName()) + .registerSubtype(WorkItemFinishEvent.class, WorkItemFinishEvent.class.getName()) - // Default implementations - .registerSubtype( - DefaultProgressEvent.class, DefaultProgressEvent.class.getName()) - .registerSubtype(DefaultStartEvent.class, DefaultStartEvent.class.getName()) - .registerSubtype( - DefaultFinishEvent.class, DefaultFinishEvent.class.getName()) + // Default implementations + .registerSubtype(DefaultProgressEvent.class, DefaultProgressEvent.class.getName()) + .registerSubtype(DefaultStartEvent.class, DefaultStartEvent.class.getName()) + .registerSubtype(DefaultFinishEvent.class, DefaultFinishEvent.class.getName()) - // Status event - .registerSubtype(StatusEvent.class, StatusEvent.class.getName())); + // Status event + .registerSubtype(StatusEvent.class, StatusEvent.class.getName())); - builder.registerTypeAdapterFactory( - RuntimeTypeAdapterFactory.of(OperationDescriptor.class, "gsonType", true) - .registerSubtype( - ProjectConfigurationOperationDescriptor.class, - ProjectConfigurationOperationDescriptor.class.getName()) - .registerSubtype( - FileDownloadOperationDescriptor.class, - FileDownloadOperationDescriptor.class.getName()) - .registerSubtype( - TaskOperationDescriptor.class, - TaskOperationDescriptor.class.getName()) - .registerSubtype( - TestOperationDescriptor.class, - TestOperationDescriptor.class.getName()) - .registerSubtype( - TransformOperationDescriptor.class, - TransformOperationDescriptor.class.getName()) - .registerSubtype( - WorkItemOperationDescriptor.class, - WorkItemOperationDescriptor.class.getName()) - .registerSubtype( - DefaultOperationDescriptor.class, - DefaultOperationDescriptor.class.getName())); + builder.registerTypeAdapterFactory( + RuntimeTypeAdapterFactory.of(OperationDescriptor.class, "gsonType", true) + .registerSubtype( + ProjectConfigurationOperationDescriptor.class, + ProjectConfigurationOperationDescriptor.class.getName()) + .registerSubtype( + FileDownloadOperationDescriptor.class, + FileDownloadOperationDescriptor.class.getName()) + .registerSubtype(TaskOperationDescriptor.class, TaskOperationDescriptor.class.getName()) + .registerSubtype(TestOperationDescriptor.class, TestOperationDescriptor.class.getName()) + .registerSubtype( + TransformOperationDescriptor.class, TransformOperationDescriptor.class.getName()) + .registerSubtype( + WorkItemOperationDescriptor.class, WorkItemOperationDescriptor.class.getName()) + .registerSubtype( + DefaultOperationDescriptor.class, DefaultOperationDescriptor.class.getName())); - builder.registerTypeAdapterFactory( - RuntimeTypeAdapterFactory.of(OperationResult.class, "gsonType", true) - .registerSubtype( - ProjectConfigurationOperationResult.class, - ProjectConfigurationOperationResult.class.getName()) - .registerSubtype( - FileDownloadResult.class, FileDownloadResult.class.getName()) - .registerSubtype( - TaskOperationResult.class, TaskOperationResult.class.getName()) - .registerSubtype( - TestOperationResult.class, TestOperationResult.class.getName()) - .registerSubtype( - TransformOperationResult.class, - TransformOperationResult.class.getName()) - .registerSubtype( - WorkItemOperationResult.class, - WorkItemOperationResult.class.getName()) - .registerSubtype( - DefaultOperationResult.class, - DefaultOperationResult.class.getName())); + builder.registerTypeAdapterFactory( + RuntimeTypeAdapterFactory.of(OperationResult.class, "gsonType", true) + .registerSubtype( + ProjectConfigurationOperationResult.class, + ProjectConfigurationOperationResult.class.getName()) + .registerSubtype(FileDownloadResult.class, FileDownloadResult.class.getName()) + .registerSubtype(TaskOperationResult.class, TaskOperationResult.class.getName()) + .registerSubtype(TestOperationResult.class, TestOperationResult.class.getName()) + .registerSubtype( + TransformOperationResult.class, TransformOperationResult.class.getName()) + .registerSubtype(WorkItemOperationResult.class, WorkItemOperationResult.class.getName()) + .registerSubtype(DefaultOperationResult.class, DefaultOperationResult.class.getName())); - builder.registerTypeAdapterFactory( - RuntimeTypeAdapterFactory.of(TaskOperationResult.class, "gsonType", true) - .registerSubtype(TaskFailureResult.class, TaskFailureResult.class.getName()) - .registerSubtype(TaskSkippedResult.class, TaskSkippedResult.class.getName()) - .registerSubtype( - TaskExecutionResult.class, TaskExecutionResult.class.getName()) - .registerSubtype( - TaskSuccessResult.class, TaskSuccessResult.class.getName())); - } + builder.registerTypeAdapterFactory( + RuntimeTypeAdapterFactory.of(TaskOperationResult.class, "gsonType", true) + .registerSubtype(TaskFailureResult.class, TaskFailureResult.class.getName()) + .registerSubtype(TaskSkippedResult.class, TaskSkippedResult.class.getName()) + .registerSubtype(TaskExecutionResult.class, TaskExecutionResult.class.getName()) + .registerSubtype(TaskSuccessResult.class, TaskSuccessResult.class.getName())); + } - public static Launcher newClientLauncher( - IToolingApiClient client, InputStream in, OutputStream out) { - return newIoLauncher( - new Object[] {client}, - new Class[] {IToolingApiServer.class, IProject.class}, - in, - out); - } + public static Launcher newClientLauncher( + IToolingApiClient client, InputStream in, OutputStream out) { + return newIoLauncher( + new Object[] {client}, new Class[] {IToolingApiServer.class, IProject.class}, in, out); + } - public static Launcher newIoLauncher( - Object[] locals, Class[] remotes, InputStream in, OutputStream out) { - return new Launcher.Builder<>() - .setInput(in) - .setOutput(out) - .setExecutorService(Executors.newCachedThreadPool()) - .setLocalServices(Arrays.asList(locals)) - .setRemoteInterfaces(Arrays.asList(remotes)) - .configureGson(ToolingApiLauncher::configureGson) - .setClassLoader(locals[0].getClass().getClassLoader()) - .create(); - } + public static Launcher newIoLauncher( + Object[] locals, Class[] remotes, InputStream in, OutputStream out) { + return new Launcher.Builder<>() + .setInput(in) + .setOutput(out) + .setExecutorService(Executors.newCachedThreadPool()) + .setLocalServices(Arrays.asList(locals)) + .setRemoteInterfaces(Arrays.asList(remotes)) + .configureGson(ToolingApiLauncher::configureGson) + .setClassLoader(locals[0].getClass().getClassLoader()) + .create(); + } - public static Launcher newServerLauncher( - IToolingApiServer server, IProject project, InputStream in, OutputStream out) { - return newIoLauncher( - new Object[] {server, project}, new Class[] {IToolingApiClient.class}, in, out); - } + public static Launcher newServerLauncher( + IToolingApiServer server, IProject project, InputStream in, OutputStream out) { + return newIoLauncher( + new Object[] {server, project}, new Class[] {IToolingApiClient.class}, in, out); + } } diff --git a/build-tools/xml-formatter/src/main/java/com/android/SdkConstants.java b/build-tools/xml-formatter/src/main/java/com/android/SdkConstants.java index e82ae0565..fc83cbad9 100644 --- a/build-tools/xml-formatter/src/main/java/com/android/SdkConstants.java +++ b/build-tools/xml-formatter/src/main/java/com/android/SdkConstants.java @@ -1,1250 +1,1342 @@ package com.android; -//import com.tyron.builder.util.CharSequences; +// import com.tyron.builder.util.CharSequences; import java.io.File; /** * Constant definition class.
    *
    * Most constants have a prefix defining the content. + * *
      - *
    • OS_ OS path constant. These paths are different depending on the platform.
    • - *
    • FN_ File name constant.
    • - *
    • FD_ Folder name constant.
    • - *
    • TAG_ XML element tag name
    • - *
    • ATTR_ XML attribute name
    • - *
    • VALUE_ XML attribute value
    • - *
    • CLASS_ Class name
    • - *
    • DOT_ File name extension, including the dot
    • - *
    • EXT_ File name extension, without the dot
    • + *
    • OS_ OS path constant. These paths are different depending on the platform. + *
    • FN_ File name constant. + *
    • FD_ Folder name constant. + *
    • TAG_ XML element tag name + *
    • ATTR_ XML attribute name + *
    • VALUE_ XML attribute value + *
    • CLASS_ Class name + *
    • DOT_ File name extension, including the dot + *
    • EXT_ File name extension, without the dot *
    */ @SuppressWarnings({"javadoc", "unused"}) // Not documenting all the fields here public final class SdkConstants { - public static final int PLATFORM_UNKNOWN = 0; - public static final int PLATFORM_LINUX = 1; - public static final int PLATFORM_WINDOWS = 2; - public static final int PLATFORM_DARWIN = 3; - /** - * Returns current platform, one of {@link #PLATFORM_WINDOWS}, {@link #PLATFORM_DARWIN}, - * {@link #PLATFORM_LINUX} or {@link #PLATFORM_UNKNOWN}. - */ - public static final int CURRENT_PLATFORM = currentPlatform(); - /** Environment variable that specifies the path of an Android SDK. */ - public static final String ANDROID_HOME_ENV = "ANDROID_HOME"; - /** Property in local.properties file that specifies the path of the Android SDK. */ - public static final String SDK_DIR_PROPERTY = "sdk.dir"; - /** Property in local.properties file that specifies the path of the Android NDK. */ - public static final String NDK_DIR_PROPERTY = "ndk.dir"; - /** Property in gradle-wrapper.properties file that specifies the URL to the correct Gradle distribution. */ - public static final String GRADLE_DISTRIBUTION_URL_PROPERTY = "distributionUrl"; //$NON-NLS-1$ - /** - * The encoding we strive to use for all files we write. - *

    - * When possible, use the APIs which take a {@link java.nio.charset.Charset} and pass in - * {@link com.google.common.base.Charsets#UTF_8} instead of using the String encoding - * method. - */ - public static final String UTF_8 = "UTF-8"; //$NON-NLS-1$ - /** - * Charset for the ini file handled by the SDK. - */ - public static final String INI_CHARSET = UTF_8; - /** Path separator used by Gradle */ - public static final String GRADLE_PATH_SEPARATOR = ":"; //$NON-NLS-1$ - /** An SDK Project's AndroidManifest.xml file */ - public static final String FN_ANDROID_MANIFEST_XML= "AndroidManifest.xml"; //$NON-NLS-1$ - /** pre-dex jar filename. i.e. "classes.jar" */ - public static final String TOOLS_NS_NAME = "tools"; - public static final String FN_CLASSES_JAR = "classes.jar"; - //$NON-NLS-1$ - /** Dex filename inside the APK. i.e. "classes.dex" */ - public static final String FN_APK_CLASSES_DEX = "classes.dex"; //$NON-NLS-1$ - /** Dex filename inside the APK. i.e. "classes.dex" */ - public static final String FN_APK_CLASSES_N_DEX = "classes%d.dex"; //$NON-NLS-1$ - /** An SDK Project's build.xml file */ - public static final String FN_BUILD_XML = "build.xml"; //$NON-NLS-1$ - /** An SDK Project's build.gradle file */ - public static final String FN_BUILD_GRADLE = "build.gradle"; //$NON-NLS-1$ - /** An SDK Project's settings.gradle file */ - public static final String FN_SETTINGS_GRADLE = "settings.gradle"; //$NON-NLS-1$ - /** An SDK Project's gradle.properties file */ - public static final String FN_GRADLE_PROPERTIES = "gradle.properties"; //$NON-NLS-1$ - /** An SDK Project's gradle daemon executable */ - public static final String FN_GRADLE_UNIX = "gradle"; //$NON-NLS-1$ - /** An SDK Project's gradle.bat daemon executable (gradle for windows) */ - public static final String FN_GRADLE_WIN = FN_GRADLE_UNIX + ".bat"; //$NON-NLS-1$ - /** An SDK Project's gradlew file */ - public static final String FN_GRADLE_WRAPPER_UNIX = "gradlew"; //$NON-NLS-1$ - /** An SDK Project's gradlew.bat file (gradlew for windows) */ - public static final String FN_GRADLE_WRAPPER_WIN = FN_GRADLE_WRAPPER_UNIX + ".bat"; //$NON-NLS-1$ - /** An SDK Project's gradle wrapper library */ - public static final String FN_GRADLE_WRAPPER_JAR = "gradle-wrapper.jar"; //$NON-NLS-1$ - /** Name of the framework library, i.e. "android.jar" */ - public static final String FN_FRAMEWORK_LIBRARY = "android.jar"; //$NON-NLS-1$ - /** Name of the framework library, i.e. "uiautomator.jar" */ - public static final String FN_UI_AUTOMATOR_LIBRARY = "uiautomator.jar"; //$NON-NLS-1$ - /** Name of the layout attributes, i.e. "attrs.xml" */ - public static final String FN_ATTRS_XML = "attrs.xml"; //$NON-NLS-1$ - /** Name of the layout attributes, i.e. "attrs_manifest.xml" */ - public static final String FN_ATTRS_MANIFEST_XML = "attrs_manifest.xml"; //$NON-NLS-1$ - /** framework aidl import file */ - public static final String FN_FRAMEWORK_AIDL = "framework.aidl"; //$NON-NLS-1$ - /** framework renderscript folder */ - public static final String FN_FRAMEWORK_RENDERSCRIPT = "renderscript"; //$NON-NLS-1$ - /** framework include folder */ - public static final String FN_FRAMEWORK_INCLUDE = "include"; //$NON-NLS-1$ - /** framework include (clang) folder */ - public static final String FN_FRAMEWORK_INCLUDE_CLANG = "clang-include"; //$NON-NLS-1$ - /** layoutlib.jar file */ - public static final String FN_LAYOUTLIB_JAR = "layoutlib.jar"; //$NON-NLS-1$ - /** widget list file */ - public static final String FN_WIDGETS = "widgets.txt"; //$NON-NLS-1$ - /** Intent activity actions list file */ - public static final String FN_INTENT_ACTIONS_ACTIVITY = "activity_actions.txt"; //$NON-NLS-1$ - /** Intent broadcast actions list file */ - public static final String FN_INTENT_ACTIONS_BROADCAST = "broadcast_actions.txt"; //$NON-NLS-1$ - /** Intent service actions list file */ - public static final String FN_INTENT_ACTIONS_SERVICE = "service_actions.txt"; //$NON-NLS-1$ - /** Intent category list file */ - public static final String FN_INTENT_CATEGORIES = "categories.txt"; //$NON-NLS-1$ - /** annotations support jar */ - public static final String FN_ANNOTATIONS_JAR = "annotations.jar"; //$NON-NLS-1$ - /** platform build property file */ - public static final String FN_BUILD_PROP = "build.prop"; //$NON-NLS-1$ - /** plugin properties file */ - public static final String FN_PLUGIN_PROP = "plugin.prop"; //$NON-NLS-1$ - /** add-on manifest file */ - public static final String FN_MANIFEST_INI = "manifest.ini"; //$NON-NLS-1$ - /** add-on layout device XML file. */ - public static final String FN_DEVICES_XML = "devices.xml"; //$NON-NLS-1$ - /** hardware properties definition file */ - public static final String FN_HARDWARE_INI = "hardware-properties.ini"; //$NON-NLS-1$ - /** project property file */ - public static final String FN_PROJECT_PROPERTIES = "project.properties"; //$NON-NLS-1$ - /** project local property file */ - public static final String FN_LOCAL_PROPERTIES = "local.properties"; //$NON-NLS-1$ - /** project ant property file */ - public static final String FN_ANT_PROPERTIES = "ant.properties"; //$NON-NLS-1$ - /** project local property file */ - public static final String FN_GRADLE_WRAPPER_PROPERTIES = "gradle-wrapper.properties"; //$NON-NLS-1$ - /** Skin layout file */ - public static final String FN_SKIN_LAYOUT = "layout"; //$NON-NLS-1$ - /** dx.jar file */ - public static final String FN_DX_JAR = "dx.jar"; //$NON-NLS-1$ - /** dx executable (with extension for the current OS) */ - public static final String FN_DX = - "dx" + ext(".bat", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - /** aapt executable (with extension for the current OS) */ - public static final String FN_AAPT = - "aapt" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - /** aidl executable (with extension for the current OS) */ - public static final String FN_AIDL = - "aidl" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - /** renderscript executable (with extension for the current OS) */ - public static final String FN_RENDERSCRIPT = - "llvm-rs-cc" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - /** renderscript support exe (with extension for the current OS) */ - public static final String FN_BCC_COMPAT = - "bcc_compat" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - /** renderscript support linker for ARM (with extension for the current OS) */ - public static final String FN_LD_ARM = - "arm-linux-androideabi-ld" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - /** renderscript support linker for X86 (with extension for the current OS) */ - public static final String FN_LD_X86 = - "i686-linux-android-ld" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - /** renderscript support linker for MIPS (with extension for the current OS) */ - public static final String FN_LD_MIPS = - "mipsel-linux-android-ld" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - /** adb executable (with extension for the current OS) */ - public static final String FN_ADB = - "adb" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - /** emulator executable for the current OS */ - public static final String FN_EMULATOR = - "emulator" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - /** zipalign executable (with extension for the current OS) */ - public static final String FN_ZIPALIGN = - "zipalign" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - /** dexdump executable (with extension for the current OS) */ - public static final String FN_DEXDUMP = - "dexdump" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - /** proguard executable (with extension for the current OS) */ - public static final String FN_PROGUARD = - "proguard" + ext(".bat", ".sh"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - /** find_lock for Windows (with extension for the current OS) */ - public static final String FN_FIND_LOCK = - "find_lock" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - /** hprof-conv executable (with extension for the current OS) */ - public static final String FN_HPROF_CONV = - "hprof-conv" + ext(".exe", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - /** jack.jar */ - public static final String FN_JACK = "jack.jar"; //$NON-NLS-1$ - /** jill.jar */ - public static final String FN_JILL = "jill.jar"; //$NON-NLS-1$ - /** split-select */ - public static final String FN_SPLIT_SELECT = "split-select" + ext(".exe", ""); - /** properties file for SDK Updater packages */ - public static final String FN_SOURCE_PROP = "source.properties"; //$NON-NLS-1$ - /** properties file for content hash of installed packages */ - public static final String FN_CONTENT_HASH_PROP = "content_hash.properties"; //$NON-NLS-1$ - /** properties file for the SDK */ - public static final String FN_SDK_PROP = "sdk.properties"; //$NON-NLS-1$ - public static final String FN_RENDERSCRIPT_V8_JAR = "renderscript-v8.jar"; //$NON-NLS-1$ - /** - * filename for gdbserver. - */ - public static final String FN_GDBSERVER = "gdbserver"; //$NON-NLS-1$ - public static final String FN_GDB_SETUP = "gdb.setup"; //$NON-NLS-1$ - /** global Android proguard config file */ - public static final String FN_ANDROID_PROGUARD_FILE = "proguard-android.txt"; //$NON-NLS-1$ - /** global Android proguard config file with optimization enabled */ - public static final String FN_ANDROID_OPT_PROGUARD_FILE = "proguard-android-optimize.txt"; //$NON-NLS-1$ - /** default proguard config file with new file extension (for project specific stuff) */ - public static final String FN_PROJECT_PROGUARD_FILE = "proguard-project.txt"; //$NON-NLS-1$ - /* Folder Names for Android Projects . */ - /** Resources folder name, i.e. "res". */ - public static final String FD_RESOURCES = "res"; //$NON-NLS-1$ - /** Assets folder name, i.e. "assets" */ - public static final String FD_ASSETS = "assets"; //$NON-NLS-1$ - /** Default source folder name in an SDK project, i.e. "src". - *

    - * Note: this is not the same as {@link #FD_PKG_SOURCES} - * which is an SDK sources folder for packages. */ - public static final String FD_SOURCES = "src"; //$NON-NLS-1$ - /** Default main source set folder name, i.e. "main" */ - public static final String FD_MAIN = "main"; //$NON-NLS-1$ - /** Default test source set folder name, i.e. "androidTest" */ - public static final String FD_TEST = "androidTest"; //$NON-NLS-1$ - /** Default java code folder name, i.e. "java" */ - public static final String FD_JAVA = "java"; //$NON-NLS-1$ - /** Default native code folder name, i.e. "jni" */ - public static final String FD_JNI = "jni"; //$NON-NLS-1$ - /** Default gradle folder name, i.e. "gradle" */ - public static final String FD_GRADLE = "gradle"; //$NON-NLS-1$ - /** Default gradle wrapper folder name, i.e. "gradle/wrapper" */ - public static final String FD_GRADLE_WRAPPER = FD_GRADLE + File.separator + "wrapper"; //$NON-NLS-1$ - /** Default generated source folder name, i.e. "gen" */ - public static final String FD_GEN_SOURCES = "gen"; //$NON-NLS-1$ - /** Default native library folder name inside the project, i.e. "libs" - * While the folder inside the .apk is "lib", we call that one libs because - * that's what we use in ant for both .jar and .so and we need to make the 2 development ways - * compatible. */ - public static final String FD_NATIVE_LIBS = "libs"; //$NON-NLS-1$ - /** Native lib folder inside the APK: "lib" */ - public static final String FD_APK_NATIVE_LIBS = "lib"; //$NON-NLS-1$ - /** Default output folder name, i.e. "bin" */ - public static final String FD_OUTPUT = "bin"; //$NON-NLS-1$ - /** Classes output folder name, i.e. "classes" */ - public static final String FD_CLASSES_OUTPUT = "classes"; //$NON-NLS-1$ - /** proguard output folder for mapping, etc.. files */ - public static final String FD_PROGUARD = "proguard"; //$NON-NLS-1$ - /** aidl output folder for copied aidl files */ - public static final String FD_AIDL = "aidl"; //$NON-NLS-1$ - /** rs Libs output folder for support mode */ - public static final String FD_RS_LIBS = "rsLibs"; //$NON-NLS-1$ - /** rs Libs output folder for support mode */ - public static final String FD_RS_OBJ = "rsObj"; //$NON-NLS-1$ - /** jars folder */ - public static final String FD_JARS = "jars"; //$NON-NLS-1$ - /* Folder Names for the Android SDK */ - /** Name of the SDK platforms folder. */ - public static final String FD_PLATFORMS = "platforms"; //$NON-NLS-1$ - /** Name of the SDK addons folder. */ - public static final String FD_ADDONS = "add-ons"; //$NON-NLS-1$ - /** Name of the SDK system-images folder. */ - public static final String FD_SYSTEM_IMAGES = "system-images"; //$NON-NLS-1$ - /** Name of the SDK sources folder where source packages are installed. - *

    - * Note this is not the same as {@link #FD_SOURCES} which is the folder name where sources - * are installed inside a project. */ - public static final String FD_PKG_SOURCES = "sources"; //$NON-NLS-1$ - /** Name of the SDK tools folder. */ - public static final String FD_TOOLS = "tools"; //$NON-NLS-1$ - /** Name of the SDK tools/support folder. */ - public static final String FD_SUPPORT = "support"; //$NON-NLS-1$ - /** Name of the SDK platform tools folder. */ - public static final String FD_PLATFORM_TOOLS = "platform-tools"; //$NON-NLS-1$ - /** Name of the SDK build tools folder. */ - public static final String FD_BUILD_TOOLS = "build-tools"; //$NON-NLS-1$ - /** Name of the SDK tools/lib folder. */ - public static final String FD_LIB = "lib"; //$NON-NLS-1$ - /** Name of the SDK docs folder. */ - public static final String FD_DOCS = "docs"; //$NON-NLS-1$ - /** Name of the doc folder containing API reference doc (javadoc) */ - public static final String FD_DOCS_REFERENCE = "reference"; //$NON-NLS-1$ - /** Name of the SDK images folder. */ - public static final String FD_IMAGES = "images"; //$NON-NLS-1$ - /** Name of the ABI to support. */ - public static final String ABI_ARMEABI = "armeabi"; //$NON-NLS-1$ - public static final String ABI_ARMEABI_V7A = "armeabi-v7a"; //$NON-NLS-1$ - public static final String ABI_ARM64_V8A = "arm64-v8a"; //$NON-NLS-1$ - public static final String ABI_INTEL_ATOM = "x86"; //$NON-NLS-1$ - public static final String ABI_INTEL_ATOM64 = "x86_64"; //$NON-NLS-1$ - public static final String ABI_MIPS = "mips"; //$NON-NLS-1$ - public static final String ABI_MIPS64 = "mips64"; //$NON-NLS-1$ - /** Name of the CPU arch to support. */ - public static final String CPU_ARCH_ARM = "arm"; //$NON-NLS-1$ - public static final String CPU_ARCH_ARM64 = "arm64"; //$NON-NLS-1$ - public static final String CPU_ARCH_INTEL_ATOM = "x86"; //$NON-NLS-1$ - public static final String CPU_ARCH_INTEL_ATOM64 = "x86_64"; //$NON-NLS-1$ - public static final String CPU_ARCH_MIPS = "mips"; //$NON-NLS-1$ - /** TODO double-check this is appropriate value for mips64 */ - public static final String CPU_ARCH_MIPS64 = "mips64"; //$NON-NLS-1$ - /** Name of the CPU model to support. */ - public static final String CPU_MODEL_CORTEX_A8 = "cortex-a8"; //$NON-NLS-1$ - /** Name of the SDK skins folder. */ - public static final String FD_SKINS = "skins"; //$NON-NLS-1$ - /** Name of the SDK samples folder. */ - public static final String FD_SAMPLES = "samples"; //$NON-NLS-1$ - /** Name of the SDK extras folder. */ - public static final String FD_EXTRAS = "extras"; //$NON-NLS-1$ - public static final String FD_M2_REPOSITORY = "m2repository"; //$NON-NLS-1$ - public static final String FD_NDK = "ndk-bundle"; //$NON-NLS-1$ - /** - * Name of an extra's sample folder. - * Ideally extras should have one {@link #FD_SAMPLES} folder containing - * one or more sub-folders (one per sample). However some older extras - * might contain a single "sample" folder with directly the samples files - * in it. When possible we should encourage extras' owners to move to the - * multi-samples format. - */ - public static final String FD_SAMPLE = "sample"; //$NON-NLS-1$ - /** Name of the SDK templates folder, i.e. "templates" */ - public static final String FD_TEMPLATES = "templates"; //$NON-NLS-1$ - /** Name of the SDK Ant folder, i.e. "ant" */ - public static final String FD_ANT = "ant"; //$NON-NLS-1$ - /** Name of the SDK data folder, i.e. "data" */ - public static final String FD_DATA = "data"; //$NON-NLS-1$ - /** Name of the SDK renderscript folder, i.e. "rs" */ - public static final String FD_RENDERSCRIPT = "rs"; //$NON-NLS-1$ - /** Name of the Java resources folder, i.e. "resources" */ - public static final String FD_JAVA_RES = "resources"; //$NON-NLS-1$ - /** Name of the SDK resources folder, i.e. "res" */ - public static final String FD_RES = "res"; //$NON-NLS-1$ - /** Name of the SDK font folder, i.e. "fonts" */ - public static final String FD_FONTS = "fonts"; //$NON-NLS-1$ - /** Name of the android sources directory and the root of the SDK sources package folder. */ - public static final String FD_ANDROID_SOURCES = "sources"; //$NON-NLS-1$ - /** Name of the addon libs folder. */ - public static final String FD_ADDON_LIBS = "libs"; //$NON-NLS-1$ - /** Name of the cache folder in the $HOME/.android. */ - public static final String FD_CACHE = "cache"; //$NON-NLS-1$ - /** API codename of a release (non preview) system image or platform. **/ - public static final String CODENAME_RELEASE = "REL"; //$NON-NLS-1$ - /** Namespace for the resource XML, i.e. "http://schemas.android.com/apk/res/android" */ - public static final String NS_RESOURCES = - "http://schemas.android.com/apk/res/android"; //$NON-NLS-1$ - /** - * Namespace pattern for the custom resource XML, i.e. "http://schemas.android.com/apk/res/%s" - *

    - * This string contains a %s. It must be combined with the desired Java package, e.g.: - *

    -     *    String.format(SdkConstants.NS_CUSTOM_RESOURCES_S, "android");
    -     *    String.format(SdkConstants.NS_CUSTOM_RESOURCES_S, "com.test.mycustomapp");
    -     * 
    - * - * Note: if you need an URI specifically for the "android" namespace, consider using - * {@link SdkConstants#NS_RESOURCES} instead. - */ - public static final String NS_CUSTOM_RESOURCES_S = "http://schemas.android.com/apk/res/%1$s"; //$NON-NLS-1$ - /** The name of the uses-library that provides "android.test.runner" */ - public static final String ANDROID_TEST_RUNNER_LIB = - "android.test.runner"; //$NON-NLS-1$ - /* Folder path relative to the SDK root */ - /** Path of the documentation directory relative to the sdk folder. - * This is an OS path, ending with a separator. */ - public static final String OS_SDK_DOCS_FOLDER = FD_DOCS + File.separator; - /** Path of the tools directory relative to the sdk folder, or to a platform folder. - * This is an OS path, ending with a separator. */ - public static final String OS_SDK_TOOLS_FOLDER = FD_TOOLS + File.separator; - /** Path of the lib directory relative to the sdk folder, or to a platform folder. - * This is an OS path, ending with a separator. */ - public static final String OS_SDK_TOOLS_LIB_FOLDER = - OS_SDK_TOOLS_FOLDER + FD_LIB + File.separator; - /** - * Path of the lib directory relative to the sdk folder, or to a platform - * folder. This is an OS path, ending with a separator. - */ - public static final String OS_SDK_TOOLS_LIB_EMULATOR_FOLDER = OS_SDK_TOOLS_LIB_FOLDER - + "emulator" + File.separator; //$NON-NLS-1$ - /** Path of the platform tools directory relative to the sdk folder. - * This is an OS path, ending with a separator. */ - public static final String OS_SDK_PLATFORM_TOOLS_FOLDER = FD_PLATFORM_TOOLS + File.separator; - /** Path of the build tools directory relative to the sdk folder. - * This is an OS path, ending with a separator. */ - public static final String OS_SDK_BUILD_TOOLS_FOLDER = FD_BUILD_TOOLS + File.separator; - /** Path of the Platform tools Lib directory relative to the sdk folder. - * This is an OS path, ending with a separator. */ - public static final String OS_SDK_PLATFORM_TOOLS_LIB_FOLDER = - OS_SDK_PLATFORM_TOOLS_FOLDER + FD_LIB + File.separator; - /** Path of the bin folder of proguard folder relative to the sdk folder. - * This is an OS path, ending with a separator. */ - public static final String OS_SDK_TOOLS_PROGUARD_BIN_FOLDER = - OS_SDK_TOOLS_FOLDER + - "proguard" + File.separator + //$NON-NLS-1$ - "bin" + File.separator; //$NON-NLS-1$ - /** Path of the template gradle wrapper folder relative to the sdk folder. - * This is an OS path, ending with a separator. */ - public static final String OS_SDK_TOOLS_TEMPLATES_GRADLE_WRAPPER_FOLDER = - OS_SDK_TOOLS_FOLDER + FD_TEMPLATES + File.separator + FD_GRADLE_WRAPPER + File.separator; - /* Folder paths relative to a platform or add-on folder */ - /** Path of the images directory relative to a platform or addon folder. - * This is an OS path, ending with a separator. */ - public static final String OS_IMAGES_FOLDER = FD_IMAGES + File.separator; - /** Path of the skin directory relative to a platform or addon folder. - * This is an OS path, ending with a separator. */ - public static final String OS_SKINS_FOLDER = FD_SKINS + File.separator; - /* Folder paths relative to a Platform folder */ - /** Path of the data directory relative to a platform folder. - * This is an OS path, ending with a separator. */ - public static final String OS_PLATFORM_DATA_FOLDER = FD_DATA + File.separator; - /** Path of the renderscript directory relative to a platform folder. - * This is an OS path, ending with a separator. */ - public static final String OS_PLATFORM_RENDERSCRIPT_FOLDER = FD_RENDERSCRIPT + File.separator; - /** Path of the samples directory relative to a platform folder. - * This is an OS path, ending with a separator. */ - public static final String OS_PLATFORM_SAMPLES_FOLDER = FD_SAMPLES + File.separator; - /** Path of the resources directory relative to a platform folder. - * This is an OS path, ending with a separator. */ - public static final String OS_PLATFORM_RESOURCES_FOLDER = - OS_PLATFORM_DATA_FOLDER + FD_RES + File.separator; - /** Path of the fonts directory relative to a platform folder. - * This is an OS path, ending with a separator. */ - public static final String OS_PLATFORM_FONTS_FOLDER = - OS_PLATFORM_DATA_FOLDER + FD_FONTS + File.separator; - /** Path of the android source directory relative to a platform folder. - * This is an OS path, ending with a separator. */ - public static final String OS_PLATFORM_SOURCES_FOLDER = FD_ANDROID_SOURCES + File.separator; - /** Path of the android templates directory relative to a platform folder. - * This is an OS path, ending with a separator. */ - public static final String OS_PLATFORM_TEMPLATES_FOLDER = FD_TEMPLATES + File.separator; - /** Path of the Ant build rules directory relative to a platform folder. - * This is an OS path, ending with a separator. */ - public static final String OS_PLATFORM_ANT_FOLDER = FD_ANT + File.separator; - /** Path of the attrs.xml file relative to a platform folder. */ - public static final String OS_PLATFORM_ATTRS_XML = - OS_PLATFORM_RESOURCES_FOLDER + SdkConstants.FD_RES_VALUES + File.separator + - FN_ATTRS_XML; - /** Path of the attrs_manifest.xml file relative to a platform folder. */ - public static final String OS_PLATFORM_ATTRS_MANIFEST_XML = - OS_PLATFORM_RESOURCES_FOLDER + SdkConstants.FD_RES_VALUES + File.separator + - FN_ATTRS_MANIFEST_XML; - /** Path of the layoutlib.jar file relative to a platform folder. */ - public static final String OS_PLATFORM_LAYOUTLIB_JAR = - OS_PLATFORM_DATA_FOLDER + FN_LAYOUTLIB_JAR; - /** Path of the renderscript include folder relative to a platform folder. */ - public static final String OS_FRAMEWORK_RS = - FN_FRAMEWORK_RENDERSCRIPT + File.separator + FN_FRAMEWORK_INCLUDE; - /** Path of the renderscript (clang) include folder relative to a platform folder. */ - public static final String OS_FRAMEWORK_RS_CLANG = - FN_FRAMEWORK_RENDERSCRIPT + File.separator + FN_FRAMEWORK_INCLUDE_CLANG; - /* Folder paths relative to a addon folder */ - /** Path of the images directory relative to a folder folder. - * This is an OS path, ending with a separator. */ - public static final String OS_ADDON_LIBS_FOLDER = FD_ADDON_LIBS + File.separator; - /** Skin default **/ - public static final String SKIN_DEFAULT = "default"; //$NON-NLS-1$ - /** SDK property: ant templates revision */ - public static final String PROP_SDK_ANT_TEMPLATES_REVISION = - "sdk.ant.templates.revision"; //$NON-NLS-1$ - /** SDK property: default skin */ - public static final String PROP_SDK_DEFAULT_SKIN = "sdk.skin.default"; //$NON-NLS-1$ - /* Android Class Constants */ - public static final String CLASS_ACTIVITY = "android.app.Activity"; //$NON-NLS-1$ - public static final String CLASS_APPLICATION = "android.app.Application"; //$NON-NLS-1$ - public static final String CLASS_SERVICE = "android.app.Service"; //$NON-NLS-1$ - public static final String CLASS_BROADCASTRECEIVER = "android.content.BroadcastReceiver"; //$NON-NLS-1$ - public static final String CLASS_CONTENTPROVIDER = "android.content.ContentProvider"; //$NON-NLS-1$ - public static final String CLASS_ATTRIBUTE_SET = "android.util.AttributeSet"; //$NON-NLS-1$ - public static final String CLASS_INSTRUMENTATION = "android.app.Instrumentation"; //$NON-NLS-1$ - public static final String CLASS_INSTRUMENTATION_RUNNER = - "android.test.InstrumentationTestRunner"; //$NON-NLS-1$ - public static final String CLASS_BUNDLE = "android.os.Bundle"; //$NON-NLS-1$ - public static final String CLASS_R = "android.R"; //$NON-NLS-1$ - public static final String CLASS_R_PREFIX = CLASS_R + "."; //$NON-NLS-1$ - public static final String CLASS_MANIFEST_PERMISSION = "android.Manifest$permission"; //$NON-NLS-1$ - public static final String CLASS_INTENT = "android.content.Intent"; //$NON-NLS-1$ - public static final String CLASS_CONTEXT = "android.content.Context"; //$NON-NLS-1$ - public static final String CLASS_VIEW = "android.view.View"; //$NON-NLS-1$ - public static final String CLASS_VIEWGROUP = "android.view.ViewGroup"; //$NON-NLS-1$ - public static final String CLASS_NAME_LAYOUTPARAMS = "LayoutParams"; //$NON-NLS-1$ - public static final String CLASS_VIEWGROUP_LAYOUTPARAMS = - CLASS_VIEWGROUP + "$" + CLASS_NAME_LAYOUTPARAMS; //$NON-NLS-1$ - public static final String CLASS_NAME_FRAMELAYOUT = "FrameLayout"; //$NON-NLS-1$ - public static final String CLASS_FRAMELAYOUT = - "android.widget." + CLASS_NAME_FRAMELAYOUT; //$NON-NLS-1$ - public static final String CLASS_PREFERENCE = "android.preference.Preference"; //$NON-NLS-1$ - public static final String CLASS_NAME_PREFERENCE_SCREEN = "PreferenceScreen"; //$NON-NLS-1$ - public static final String CLASS_PREFERENCES = - "android.preference." + CLASS_NAME_PREFERENCE_SCREEN; //$NON-NLS-1$ - public static final String CLASS_PREFERENCEGROUP = "android.preference.PreferenceGroup"; //$NON-NLS-1$ - public static final String CLASS_PARCELABLE = "android.os.Parcelable"; //$NON-NLS-1$ - public static final String CLASS_PARCEL = "android.os.Parcel"; //$NON-NLS-1$ - public static final String CLASS_FRAGMENT = "android.app.Fragment"; //$NON-NLS-1$ - public static final String CLASS_V4_FRAGMENT = "android.support.v4.app.Fragment"; //$NON-NLS-1$ - public static final String CLASS_ACTION_PROVIDER = "android.view.ActionProvider"; //$NON-NLS-1$ - public static final String CLASS_BACKUP_AGENT = "android.app.backup.BackupAgent"; //$NON-NLS-1$ - /** MockView is part of the layoutlib bridge and used to display classes that have - * no rendering in the graphical layout editor. */ - public static final String CLASS_MOCK_VIEW = "com.android.layoutlib.bridge.MockView"; //$NON-NLS-1$ - public static final String CLASS_LAYOUT_INFLATER = "android.view.LayoutInflater"; //$NON-NLS-1$ - /* Android Design Support Class Constants */ - public static final String CLASS_COORDINATOR_LAYOUT = "android.support.design.widget.CoordinatorLayout"; //$NON-NLS-1$ - public static final String CLASS_APP_BAR_LAYOUT = "android.support.design.widget.AppBarLayout"; //$NON-NLS-1$ - public static final String CLASS_FLOATING_ACTION_BUTTON = "android.support.design.widget.FloatingActionButton"; //$NON-NLS-1$ - public static final String CLASS_COLLAPSING_TOOLBAR_LAYOUT = "android.support.design.widget.CollapsingToolbarLayout"; //$NON-NLS-1$ - public static final String CLASS_NAVIGATION_VIEW = "android.support.design.widget.NavigationView"; //$NON-NLS-1$ - public static final String CLASS_SNACKBAR = "android.support.design.widget.Snackbar"; //$NON-NLS-1$ - public static final String CLASS_TAB_LAYOUT = "android.support.design.widget.TabLayout"; //$NON-NLS-1$ - public static final String CLASS_TEXT_INPUT_LAYOUT = "android.support.design.widget.TextInputLayout"; //$NON-NLS-1$ - public static final String CLASS_NESTED_SCROLL_VIEW = "android.support.v4.widget.NestedScrollView"; //$NON-NLS-1$ - public static final String URI_DOMAIN_PREFIX = "http://schemas.android.com/"; + public static final int PLATFORM_UNKNOWN = 0; + public static final int PLATFORM_LINUX = 1; + public static final int PLATFORM_WINDOWS = 2; + public static final int PLATFORM_DARWIN = 3; + /** + * Returns current platform, one of {@link #PLATFORM_WINDOWS}, {@link #PLATFORM_DARWIN}, {@link + * #PLATFORM_LINUX} or {@link #PLATFORM_UNKNOWN}. + */ + public static final int CURRENT_PLATFORM = currentPlatform(); + /** Environment variable that specifies the path of an Android SDK. */ + public static final String ANDROID_HOME_ENV = "ANDROID_HOME"; + /** Property in local.properties file that specifies the path of the Android SDK. */ + public static final String SDK_DIR_PROPERTY = "sdk.dir"; + /** Property in local.properties file that specifies the path of the Android NDK. */ + public static final String NDK_DIR_PROPERTY = "ndk.dir"; + /** + * Property in gradle-wrapper.properties file that specifies the URL to the correct Gradle + * distribution. + */ + public static final String GRADLE_DISTRIBUTION_URL_PROPERTY = "distributionUrl"; // $NON-NLS-1$ + /** + * The encoding we strive to use for all files we write. + * + *

    When possible, use the APIs which take a {@link java.nio.charset.Charset} and pass in {@link + * com.google.common.base.Charsets#UTF_8} instead of using the String encoding method. + */ + public static final String UTF_8 = "UTF-8"; // $NON-NLS-1$ + /** Charset for the ini file handled by the SDK. */ + public static final String INI_CHARSET = UTF_8; + /** Path separator used by Gradle */ + public static final String GRADLE_PATH_SEPARATOR = ":"; // $NON-NLS-1$ + /** An SDK Project's AndroidManifest.xml file */ + public static final String FN_ANDROID_MANIFEST_XML = "AndroidManifest.xml"; // $NON-NLS-1$ + /** pre-dex jar filename. i.e. "classes.jar" */ + public static final String TOOLS_NS_NAME = "tools"; - /** Returns the appropriate name for the 'android' command, which is 'android.exe' for - * Windows and 'android' for all other platforms. */ - public static String androidCmdName() { - String os = System.getProperty("os.name"); //$NON-NLS-1$ - String cmd = "android"; //$NON-NLS-1$ - if (os.startsWith("Windows")) { //$NON-NLS-1$ - cmd += ".bat"; //$NON-NLS-1$ - } - return cmd; + public static final String FN_CLASSES_JAR = "classes.jar"; + //$NON-NLS-1$ + /** Dex filename inside the APK. i.e. "classes.dex" */ + public static final String FN_APK_CLASSES_DEX = "classes.dex"; // $NON-NLS-1$ + /** Dex filename inside the APK. i.e. "classes.dex" */ + public static final String FN_APK_CLASSES_N_DEX = "classes%d.dex"; // $NON-NLS-1$ + /** An SDK Project's build.xml file */ + public static final String FN_BUILD_XML = "build.xml"; // $NON-NLS-1$ + /** An SDK Project's build.gradle file */ + public static final String FN_BUILD_GRADLE = "build.gradle"; // $NON-NLS-1$ + /** An SDK Project's settings.gradle file */ + public static final String FN_SETTINGS_GRADLE = "settings.gradle"; // $NON-NLS-1$ + /** An SDK Project's gradle.properties file */ + public static final String FN_GRADLE_PROPERTIES = "gradle.properties"; // $NON-NLS-1$ + /** An SDK Project's gradle daemon executable */ + public static final String FN_GRADLE_UNIX = "gradle"; // $NON-NLS-1$ + /** An SDK Project's gradle.bat daemon executable (gradle for windows) */ + public static final String FN_GRADLE_WIN = FN_GRADLE_UNIX + ".bat"; // $NON-NLS-1$ + /** An SDK Project's gradlew file */ + public static final String FN_GRADLE_WRAPPER_UNIX = "gradlew"; // $NON-NLS-1$ + /** An SDK Project's gradlew.bat file (gradlew for windows) */ + public static final String FN_GRADLE_WRAPPER_WIN = FN_GRADLE_WRAPPER_UNIX + ".bat"; // $NON-NLS-1$ + /** An SDK Project's gradle wrapper library */ + public static final String FN_GRADLE_WRAPPER_JAR = "gradle-wrapper.jar"; // $NON-NLS-1$ + /** Name of the framework library, i.e. "android.jar" */ + public static final String FN_FRAMEWORK_LIBRARY = "android.jar"; // $NON-NLS-1$ + /** Name of the framework library, i.e. "uiautomator.jar" */ + public static final String FN_UI_AUTOMATOR_LIBRARY = "uiautomator.jar"; // $NON-NLS-1$ + /** Name of the layout attributes, i.e. "attrs.xml" */ + public static final String FN_ATTRS_XML = "attrs.xml"; // $NON-NLS-1$ + /** Name of the layout attributes, i.e. "attrs_manifest.xml" */ + public static final String FN_ATTRS_MANIFEST_XML = "attrs_manifest.xml"; // $NON-NLS-1$ + /** framework aidl import file */ + public static final String FN_FRAMEWORK_AIDL = "framework.aidl"; // $NON-NLS-1$ + /** framework renderscript folder */ + public static final String FN_FRAMEWORK_RENDERSCRIPT = "renderscript"; // $NON-NLS-1$ + /** framework include folder */ + public static final String FN_FRAMEWORK_INCLUDE = "include"; // $NON-NLS-1$ + /** framework include (clang) folder */ + public static final String FN_FRAMEWORK_INCLUDE_CLANG = "clang-include"; // $NON-NLS-1$ + /** layoutlib.jar file */ + public static final String FN_LAYOUTLIB_JAR = "layoutlib.jar"; // $NON-NLS-1$ + /** widget list file */ + public static final String FN_WIDGETS = "widgets.txt"; // $NON-NLS-1$ + /** Intent activity actions list file */ + public static final String FN_INTENT_ACTIONS_ACTIVITY = "activity_actions.txt"; // $NON-NLS-1$ + /** Intent broadcast actions list file */ + public static final String FN_INTENT_ACTIONS_BROADCAST = "broadcast_actions.txt"; // $NON-NLS-1$ + /** Intent service actions list file */ + public static final String FN_INTENT_ACTIONS_SERVICE = "service_actions.txt"; // $NON-NLS-1$ + /** Intent category list file */ + public static final String FN_INTENT_CATEGORIES = "categories.txt"; // $NON-NLS-1$ + /** annotations support jar */ + public static final String FN_ANNOTATIONS_JAR = "annotations.jar"; // $NON-NLS-1$ + /** platform build property file */ + public static final String FN_BUILD_PROP = "build.prop"; // $NON-NLS-1$ + /** plugin properties file */ + public static final String FN_PLUGIN_PROP = "plugin.prop"; // $NON-NLS-1$ + /** add-on manifest file */ + public static final String FN_MANIFEST_INI = "manifest.ini"; // $NON-NLS-1$ + /** add-on layout device XML file. */ + public static final String FN_DEVICES_XML = "devices.xml"; // $NON-NLS-1$ + /** hardware properties definition file */ + public static final String FN_HARDWARE_INI = "hardware-properties.ini"; // $NON-NLS-1$ + /** project property file */ + public static final String FN_PROJECT_PROPERTIES = "project.properties"; // $NON-NLS-1$ + /** project local property file */ + public static final String FN_LOCAL_PROPERTIES = "local.properties"; // $NON-NLS-1$ + /** project ant property file */ + public static final String FN_ANT_PROPERTIES = "ant.properties"; // $NON-NLS-1$ + /** project local property file */ + public static final String FN_GRADLE_WRAPPER_PROPERTIES = + "gradle-wrapper.properties"; //$NON-NLS-1$ + /** Skin layout file */ + public static final String FN_SKIN_LAYOUT = "layout"; // $NON-NLS-1$ + /** dx.jar file */ + public static final String FN_DX_JAR = "dx.jar"; // $NON-NLS-1$ + /** dx executable (with extension for the current OS) */ + public static final String FN_DX = + "dx" + ext(".bat", ""); // $NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + /** aapt executable (with extension for the current OS) */ + public static final String FN_AAPT = + "aapt" + ext(".exe", ""); // $NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + /** aidl executable (with extension for the current OS) */ + public static final String FN_AIDL = + "aidl" + ext(".exe", ""); // $NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + /** renderscript executable (with extension for the current OS) */ + public static final String FN_RENDERSCRIPT = + "llvm-rs-cc" + ext(".exe", ""); // $NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + /** renderscript support exe (with extension for the current OS) */ + public static final String FN_BCC_COMPAT = + "bcc_compat" + ext(".exe", ""); // $NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + /** renderscript support linker for ARM (with extension for the current OS) */ + public static final String FN_LD_ARM = + "arm-linux-androideabi-ld" + ext(".exe", ""); // $NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + /** renderscript support linker for X86 (with extension for the current OS) */ + public static final String FN_LD_X86 = + "i686-linux-android-ld" + ext(".exe", ""); // $NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + /** renderscript support linker for MIPS (with extension for the current OS) */ + public static final String FN_LD_MIPS = + "mipsel-linux-android-ld" + ext(".exe", ""); // $NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + /** adb executable (with extension for the current OS) */ + public static final String FN_ADB = + "adb" + ext(".exe", ""); // $NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + /** emulator executable for the current OS */ + public static final String FN_EMULATOR = + "emulator" + ext(".exe", ""); // $NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + /** zipalign executable (with extension for the current OS) */ + public static final String FN_ZIPALIGN = + "zipalign" + ext(".exe", ""); // $NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + /** dexdump executable (with extension for the current OS) */ + public static final String FN_DEXDUMP = + "dexdump" + ext(".exe", ""); // $NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + /** proguard executable (with extension for the current OS) */ + public static final String FN_PROGUARD = + "proguard" + ext(".bat", ".sh"); // $NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + /** find_lock for Windows (with extension for the current OS) */ + public static final String FN_FIND_LOCK = + "find_lock" + ext(".exe", ""); // $NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + /** hprof-conv executable (with extension for the current OS) */ + public static final String FN_HPROF_CONV = + "hprof-conv" + ext(".exe", ""); // $NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + /** jack.jar */ + public static final String FN_JACK = "jack.jar"; // $NON-NLS-1$ + /** jill.jar */ + public static final String FN_JILL = "jill.jar"; // $NON-NLS-1$ + /** split-select */ + public static final String FN_SPLIT_SELECT = "split-select" + ext(".exe", ""); + /** properties file for SDK Updater packages */ + public static final String FN_SOURCE_PROP = "source.properties"; // $NON-NLS-1$ + /** properties file for content hash of installed packages */ + public static final String FN_CONTENT_HASH_PROP = "content_hash.properties"; // $NON-NLS-1$ + /** properties file for the SDK */ + public static final String FN_SDK_PROP = "sdk.properties"; // $NON-NLS-1$ + + public static final String FN_RENDERSCRIPT_V8_JAR = "renderscript-v8.jar"; // $NON-NLS-1$ + /** filename for gdbserver. */ + public static final String FN_GDBSERVER = "gdbserver"; // $NON-NLS-1$ + + public static final String FN_GDB_SETUP = "gdb.setup"; // $NON-NLS-1$ + /** global Android proguard config file */ + public static final String FN_ANDROID_PROGUARD_FILE = "proguard-android.txt"; // $NON-NLS-1$ + /** global Android proguard config file with optimization enabled */ + public static final String FN_ANDROID_OPT_PROGUARD_FILE = + "proguard-android-optimize.txt"; //$NON-NLS-1$ + /** default proguard config file with new file extension (for project specific stuff) */ + public static final String FN_PROJECT_PROGUARD_FILE = "proguard-project.txt"; // $NON-NLS-1$ + /* Folder Names for Android Projects . */ + /** Resources folder name, i.e. "res". */ + public static final String FD_RESOURCES = "res"; // $NON-NLS-1$ + /** Assets folder name, i.e. "assets" */ + public static final String FD_ASSETS = "assets"; // $NON-NLS-1$ + /** + * Default source folder name in an SDK project, i.e. "src". + * + *

    Note: this is not the same as {@link #FD_PKG_SOURCES} which is an SDK sources folder for + * packages. + */ + public static final String FD_SOURCES = "src"; // $NON-NLS-1$ + /** Default main source set folder name, i.e. "main" */ + public static final String FD_MAIN = "main"; // $NON-NLS-1$ + /** Default test source set folder name, i.e. "androidTest" */ + public static final String FD_TEST = "androidTest"; // $NON-NLS-1$ + /** Default java code folder name, i.e. "java" */ + public static final String FD_JAVA = "java"; // $NON-NLS-1$ + /** Default native code folder name, i.e. "jni" */ + public static final String FD_JNI = "jni"; // $NON-NLS-1$ + /** Default gradle folder name, i.e. "gradle" */ + public static final String FD_GRADLE = "gradle"; // $NON-NLS-1$ + /** Default gradle wrapper folder name, i.e. "gradle/wrapper" */ + public static final String FD_GRADLE_WRAPPER = + FD_GRADLE + File.separator + "wrapper"; // $NON-NLS-1$ + /** Default generated source folder name, i.e. "gen" */ + public static final String FD_GEN_SOURCES = "gen"; // $NON-NLS-1$ + /** + * Default native library folder name inside the project, i.e. "libs" While the folder inside the + * .apk is "lib", we call that one libs because that's what we use in ant for both .jar and .so + * and we need to make the 2 development ways compatible. + */ + public static final String FD_NATIVE_LIBS = "libs"; // $NON-NLS-1$ + /** Native lib folder inside the APK: "lib" */ + public static final String FD_APK_NATIVE_LIBS = "lib"; // $NON-NLS-1$ + /** Default output folder name, i.e. "bin" */ + public static final String FD_OUTPUT = "bin"; // $NON-NLS-1$ + /** Classes output folder name, i.e. "classes" */ + public static final String FD_CLASSES_OUTPUT = "classes"; // $NON-NLS-1$ + /** proguard output folder for mapping, etc.. files */ + public static final String FD_PROGUARD = "proguard"; // $NON-NLS-1$ + /** aidl output folder for copied aidl files */ + public static final String FD_AIDL = "aidl"; // $NON-NLS-1$ + /** rs Libs output folder for support mode */ + public static final String FD_RS_LIBS = "rsLibs"; // $NON-NLS-1$ + /** rs Libs output folder for support mode */ + public static final String FD_RS_OBJ = "rsObj"; // $NON-NLS-1$ + /** jars folder */ + public static final String FD_JARS = "jars"; // $NON-NLS-1$ + /* Folder Names for the Android SDK */ + /** Name of the SDK platforms folder. */ + public static final String FD_PLATFORMS = "platforms"; // $NON-NLS-1$ + /** Name of the SDK addons folder. */ + public static final String FD_ADDONS = "add-ons"; // $NON-NLS-1$ + /** Name of the SDK system-images folder. */ + public static final String FD_SYSTEM_IMAGES = "system-images"; // $NON-NLS-1$ + /** + * Name of the SDK sources folder where source packages are installed. + * + *

    Note this is not the same as {@link #FD_SOURCES} which is the folder name where sources are + * installed inside a project. + */ + public static final String FD_PKG_SOURCES = "sources"; // $NON-NLS-1$ + /** Name of the SDK tools folder. */ + public static final String FD_TOOLS = "tools"; // $NON-NLS-1$ + /** Name of the SDK tools/support folder. */ + public static final String FD_SUPPORT = "support"; // $NON-NLS-1$ + /** Name of the SDK platform tools folder. */ + public static final String FD_PLATFORM_TOOLS = "platform-tools"; // $NON-NLS-1$ + /** Name of the SDK build tools folder. */ + public static final String FD_BUILD_TOOLS = "build-tools"; // $NON-NLS-1$ + /** Name of the SDK tools/lib folder. */ + public static final String FD_LIB = "lib"; // $NON-NLS-1$ + /** Name of the SDK docs folder. */ + public static final String FD_DOCS = "docs"; // $NON-NLS-1$ + /** Name of the doc folder containing API reference doc (javadoc) */ + public static final String FD_DOCS_REFERENCE = "reference"; // $NON-NLS-1$ + /** Name of the SDK images folder. */ + public static final String FD_IMAGES = "images"; // $NON-NLS-1$ + /** Name of the ABI to support. */ + public static final String ABI_ARMEABI = "armeabi"; // $NON-NLS-1$ + + public static final String ABI_ARMEABI_V7A = "armeabi-v7a"; // $NON-NLS-1$ + public static final String ABI_ARM64_V8A = "arm64-v8a"; // $NON-NLS-1$ + public static final String ABI_INTEL_ATOM = "x86"; // $NON-NLS-1$ + public static final String ABI_INTEL_ATOM64 = "x86_64"; // $NON-NLS-1$ + public static final String ABI_MIPS = "mips"; // $NON-NLS-1$ + public static final String ABI_MIPS64 = "mips64"; // $NON-NLS-1$ + /** Name of the CPU arch to support. */ + public static final String CPU_ARCH_ARM = "arm"; // $NON-NLS-1$ + + public static final String CPU_ARCH_ARM64 = "arm64"; // $NON-NLS-1$ + public static final String CPU_ARCH_INTEL_ATOM = "x86"; // $NON-NLS-1$ + public static final String CPU_ARCH_INTEL_ATOM64 = "x86_64"; // $NON-NLS-1$ + public static final String CPU_ARCH_MIPS = "mips"; // $NON-NLS-1$ + /** TODO double-check this is appropriate value for mips64 */ + public static final String CPU_ARCH_MIPS64 = "mips64"; // $NON-NLS-1$ + /** Name of the CPU model to support. */ + public static final String CPU_MODEL_CORTEX_A8 = "cortex-a8"; // $NON-NLS-1$ + /** Name of the SDK skins folder. */ + public static final String FD_SKINS = "skins"; // $NON-NLS-1$ + /** Name of the SDK samples folder. */ + public static final String FD_SAMPLES = "samples"; // $NON-NLS-1$ + /** Name of the SDK extras folder. */ + public static final String FD_EXTRAS = "extras"; // $NON-NLS-1$ + + public static final String FD_M2_REPOSITORY = "m2repository"; // $NON-NLS-1$ + public static final String FD_NDK = "ndk-bundle"; // $NON-NLS-1$ + /** + * Name of an extra's sample folder. Ideally extras should have one {@link #FD_SAMPLES} folder + * containing one or more sub-folders (one per sample). However some older extras might contain a + * single "sample" folder with directly the samples files in it. When possible we should encourage + * extras' owners to move to the multi-samples format. + */ + public static final String FD_SAMPLE = "sample"; // $NON-NLS-1$ + /** Name of the SDK templates folder, i.e. "templates" */ + public static final String FD_TEMPLATES = "templates"; // $NON-NLS-1$ + /** Name of the SDK Ant folder, i.e. "ant" */ + public static final String FD_ANT = "ant"; // $NON-NLS-1$ + /** Name of the SDK data folder, i.e. "data" */ + public static final String FD_DATA = "data"; // $NON-NLS-1$ + /** Name of the SDK renderscript folder, i.e. "rs" */ + public static final String FD_RENDERSCRIPT = "rs"; // $NON-NLS-1$ + /** Name of the Java resources folder, i.e. "resources" */ + public static final String FD_JAVA_RES = "resources"; // $NON-NLS-1$ + /** Name of the SDK resources folder, i.e. "res" */ + public static final String FD_RES = "res"; // $NON-NLS-1$ + /** Name of the SDK font folder, i.e. "fonts" */ + public static final String FD_FONTS = "fonts"; // $NON-NLS-1$ + /** Name of the android sources directory and the root of the SDK sources package folder. */ + public static final String FD_ANDROID_SOURCES = "sources"; // $NON-NLS-1$ + /** Name of the addon libs folder. */ + public static final String FD_ADDON_LIBS = "libs"; // $NON-NLS-1$ + /** Name of the cache folder in the $HOME/.android. */ + public static final String FD_CACHE = "cache"; // $NON-NLS-1$ + /** API codename of a release (non preview) system image or platform. * */ + public static final String CODENAME_RELEASE = "REL"; // $NON-NLS-1$ + /** Namespace for the resource XML, i.e. "http://schemas.android.com/apk/res/android" */ + public static final String NS_RESOURCES = + "http://schemas.android.com/apk/res/android"; //$NON-NLS-1$ + /** + * Namespace pattern for the custom resource XML, i.e. "http://schemas.android.com/apk/res/%s" + * + *

    This string contains a %s. It must be combined with the desired Java package, e.g.: + * + *

    +   *    String.format(SdkConstants.NS_CUSTOM_RESOURCES_S, "android");
    +   *    String.format(SdkConstants.NS_CUSTOM_RESOURCES_S, "com.test.mycustomapp");
    +   * 
    + * + * Note: if you need an URI specifically for the "android" namespace, consider using {@link + * SdkConstants#NS_RESOURCES} instead. + */ + public static final String NS_CUSTOM_RESOURCES_S = + "http://schemas.android.com/apk/res/%1$s"; //$NON-NLS-1$ + /** The name of the uses-library that provides "android.test.runner" */ + public static final String ANDROID_TEST_RUNNER_LIB = "android.test.runner"; // $NON-NLS-1$ + /* Folder path relative to the SDK root */ + /** + * Path of the documentation directory relative to the sdk folder. This is an OS path, ending with + * a separator. + */ + public static final String OS_SDK_DOCS_FOLDER = FD_DOCS + File.separator; + /** + * Path of the tools directory relative to the sdk folder, or to a platform folder. This is an OS + * path, ending with a separator. + */ + public static final String OS_SDK_TOOLS_FOLDER = FD_TOOLS + File.separator; + /** + * Path of the lib directory relative to the sdk folder, or to a platform folder. This is an OS + * path, ending with a separator. + */ + public static final String OS_SDK_TOOLS_LIB_FOLDER = + OS_SDK_TOOLS_FOLDER + FD_LIB + File.separator; + /** + * Path of the lib directory relative to the sdk folder, or to a platform folder. This is an OS + * path, ending with a separator. + */ + public static final String OS_SDK_TOOLS_LIB_EMULATOR_FOLDER = + OS_SDK_TOOLS_LIB_FOLDER + "emulator" + File.separator; // $NON-NLS-1$ + /** + * Path of the platform tools directory relative to the sdk folder. This is an OS path, ending + * with a separator. + */ + public static final String OS_SDK_PLATFORM_TOOLS_FOLDER = FD_PLATFORM_TOOLS + File.separator; + /** + * Path of the build tools directory relative to the sdk folder. This is an OS path, ending with a + * separator. + */ + public static final String OS_SDK_BUILD_TOOLS_FOLDER = FD_BUILD_TOOLS + File.separator; + /** + * Path of the Platform tools Lib directory relative to the sdk folder. This is an OS path, ending + * with a separator. + */ + public static final String OS_SDK_PLATFORM_TOOLS_LIB_FOLDER = + OS_SDK_PLATFORM_TOOLS_FOLDER + FD_LIB + File.separator; + /** + * Path of the bin folder of proguard folder relative to the sdk folder. This is an OS path, + * ending with a separator. + */ + public static final String OS_SDK_TOOLS_PROGUARD_BIN_FOLDER = + OS_SDK_TOOLS_FOLDER + + "proguard" + + File.separator + + //$NON-NLS-1$ + "bin" + + File.separator; // $NON-NLS-1$ + /** + * Path of the template gradle wrapper folder relative to the sdk folder. This is an OS path, + * ending with a separator. + */ + public static final String OS_SDK_TOOLS_TEMPLATES_GRADLE_WRAPPER_FOLDER = + OS_SDK_TOOLS_FOLDER + FD_TEMPLATES + File.separator + FD_GRADLE_WRAPPER + File.separator; + /* Folder paths relative to a platform or add-on folder */ + /** + * Path of the images directory relative to a platform or addon folder. This is an OS path, ending + * with a separator. + */ + public static final String OS_IMAGES_FOLDER = FD_IMAGES + File.separator; + /** + * Path of the skin directory relative to a platform or addon folder. This is an OS path, ending + * with a separator. + */ + public static final String OS_SKINS_FOLDER = FD_SKINS + File.separator; + /* Folder paths relative to a Platform folder */ + /** + * Path of the data directory relative to a platform folder. This is an OS path, ending with a + * separator. + */ + public static final String OS_PLATFORM_DATA_FOLDER = FD_DATA + File.separator; + /** + * Path of the renderscript directory relative to a platform folder. This is an OS path, ending + * with a separator. + */ + public static final String OS_PLATFORM_RENDERSCRIPT_FOLDER = FD_RENDERSCRIPT + File.separator; + /** + * Path of the samples directory relative to a platform folder. This is an OS path, ending with a + * separator. + */ + public static final String OS_PLATFORM_SAMPLES_FOLDER = FD_SAMPLES + File.separator; + /** + * Path of the resources directory relative to a platform folder. This is an OS path, ending with + * a separator. + */ + public static final String OS_PLATFORM_RESOURCES_FOLDER = + OS_PLATFORM_DATA_FOLDER + FD_RES + File.separator; + /** + * Path of the fonts directory relative to a platform folder. This is an OS path, ending with a + * separator. + */ + public static final String OS_PLATFORM_FONTS_FOLDER = + OS_PLATFORM_DATA_FOLDER + FD_FONTS + File.separator; + /** + * Path of the android source directory relative to a platform folder. This is an OS path, ending + * with a separator. + */ + public static final String OS_PLATFORM_SOURCES_FOLDER = FD_ANDROID_SOURCES + File.separator; + /** + * Path of the android templates directory relative to a platform folder. This is an OS path, + * ending with a separator. + */ + public static final String OS_PLATFORM_TEMPLATES_FOLDER = FD_TEMPLATES + File.separator; + /** + * Path of the Ant build rules directory relative to a platform folder. This is an OS path, ending + * with a separator. + */ + public static final String OS_PLATFORM_ANT_FOLDER = FD_ANT + File.separator; + /** Path of the attrs.xml file relative to a platform folder. */ + public static final String OS_PLATFORM_ATTRS_XML = + OS_PLATFORM_RESOURCES_FOLDER + SdkConstants.FD_RES_VALUES + File.separator + FN_ATTRS_XML; + /** Path of the attrs_manifest.xml file relative to a platform folder. */ + public static final String OS_PLATFORM_ATTRS_MANIFEST_XML = + OS_PLATFORM_RESOURCES_FOLDER + + SdkConstants.FD_RES_VALUES + + File.separator + + FN_ATTRS_MANIFEST_XML; + /** Path of the layoutlib.jar file relative to a platform folder. */ + public static final String OS_PLATFORM_LAYOUTLIB_JAR = OS_PLATFORM_DATA_FOLDER + FN_LAYOUTLIB_JAR; + /** Path of the renderscript include folder relative to a platform folder. */ + public static final String OS_FRAMEWORK_RS = + FN_FRAMEWORK_RENDERSCRIPT + File.separator + FN_FRAMEWORK_INCLUDE; + /** Path of the renderscript (clang) include folder relative to a platform folder. */ + public static final String OS_FRAMEWORK_RS_CLANG = + FN_FRAMEWORK_RENDERSCRIPT + File.separator + FN_FRAMEWORK_INCLUDE_CLANG; + /* Folder paths relative to a addon folder */ + /** + * Path of the images directory relative to a folder folder. This is an OS path, ending with a + * separator. + */ + public static final String OS_ADDON_LIBS_FOLDER = FD_ADDON_LIBS + File.separator; + /** Skin default * */ + public static final String SKIN_DEFAULT = "default"; // $NON-NLS-1$ + /** SDK property: ant templates revision */ + public static final String PROP_SDK_ANT_TEMPLATES_REVISION = + "sdk.ant.templates.revision"; //$NON-NLS-1$ + /** SDK property: default skin */ + public static final String PROP_SDK_DEFAULT_SKIN = "sdk.skin.default"; // $NON-NLS-1$ + /* Android Class Constants */ + public static final String CLASS_ACTIVITY = "android.app.Activity"; // $NON-NLS-1$ + public static final String CLASS_APPLICATION = "android.app.Application"; // $NON-NLS-1$ + public static final String CLASS_SERVICE = "android.app.Service"; // $NON-NLS-1$ + public static final String CLASS_BROADCASTRECEIVER = + "android.content.BroadcastReceiver"; //$NON-NLS-1$ + public static final String CLASS_CONTENTPROVIDER = + "android.content.ContentProvider"; //$NON-NLS-1$ + public static final String CLASS_ATTRIBUTE_SET = "android.util.AttributeSet"; // $NON-NLS-1$ + public static final String CLASS_INSTRUMENTATION = "android.app.Instrumentation"; // $NON-NLS-1$ + public static final String CLASS_INSTRUMENTATION_RUNNER = + "android.test.InstrumentationTestRunner"; //$NON-NLS-1$ + public static final String CLASS_BUNDLE = "android.os.Bundle"; // $NON-NLS-1$ + public static final String CLASS_R = "android.R"; // $NON-NLS-1$ + public static final String CLASS_R_PREFIX = CLASS_R + "."; // $NON-NLS-1$ + public static final String CLASS_MANIFEST_PERMISSION = + "android.Manifest$permission"; //$NON-NLS-1$ + public static final String CLASS_INTENT = "android.content.Intent"; // $NON-NLS-1$ + public static final String CLASS_CONTEXT = "android.content.Context"; // $NON-NLS-1$ + public static final String CLASS_VIEW = "android.view.View"; // $NON-NLS-1$ + public static final String CLASS_VIEWGROUP = "android.view.ViewGroup"; // $NON-NLS-1$ + public static final String CLASS_NAME_LAYOUTPARAMS = "LayoutParams"; // $NON-NLS-1$ + public static final String CLASS_VIEWGROUP_LAYOUTPARAMS = + CLASS_VIEWGROUP + "$" + CLASS_NAME_LAYOUTPARAMS; // $NON-NLS-1$ + public static final String CLASS_NAME_FRAMELAYOUT = "FrameLayout"; // $NON-NLS-1$ + public static final String CLASS_FRAMELAYOUT = + "android.widget." + CLASS_NAME_FRAMELAYOUT; // $NON-NLS-1$ + public static final String CLASS_PREFERENCE = "android.preference.Preference"; // $NON-NLS-1$ + public static final String CLASS_NAME_PREFERENCE_SCREEN = "PreferenceScreen"; // $NON-NLS-1$ + public static final String CLASS_PREFERENCES = + "android.preference." + CLASS_NAME_PREFERENCE_SCREEN; // $NON-NLS-1$ + public static final String CLASS_PREFERENCEGROUP = + "android.preference.PreferenceGroup"; //$NON-NLS-1$ + public static final String CLASS_PARCELABLE = "android.os.Parcelable"; // $NON-NLS-1$ + public static final String CLASS_PARCEL = "android.os.Parcel"; // $NON-NLS-1$ + public static final String CLASS_FRAGMENT = "android.app.Fragment"; // $NON-NLS-1$ + public static final String CLASS_V4_FRAGMENT = "android.support.v4.app.Fragment"; // $NON-NLS-1$ + public static final String CLASS_ACTION_PROVIDER = "android.view.ActionProvider"; // $NON-NLS-1$ + public static final String CLASS_BACKUP_AGENT = "android.app.backup.BackupAgent"; // $NON-NLS-1$ + /** + * MockView is part of the layoutlib bridge and used to display classes that have no rendering in + * the graphical layout editor. + */ + public static final String CLASS_MOCK_VIEW = + "com.android.layoutlib.bridge.MockView"; //$NON-NLS-1$ + + public static final String CLASS_LAYOUT_INFLATER = "android.view.LayoutInflater"; // $NON-NLS-1$ + /* Android Design Support Class Constants */ + public static final String CLASS_COORDINATOR_LAYOUT = + "android.support.design.widget.CoordinatorLayout"; //$NON-NLS-1$ + public static final String CLASS_APP_BAR_LAYOUT = + "android.support.design.widget.AppBarLayout"; //$NON-NLS-1$ + public static final String CLASS_FLOATING_ACTION_BUTTON = + "android.support.design.widget.FloatingActionButton"; //$NON-NLS-1$ + public static final String CLASS_COLLAPSING_TOOLBAR_LAYOUT = + "android.support.design.widget.CollapsingToolbarLayout"; //$NON-NLS-1$ + public static final String CLASS_NAVIGATION_VIEW = + "android.support.design.widget.NavigationView"; //$NON-NLS-1$ + public static final String CLASS_SNACKBAR = + "android.support.design.widget.Snackbar"; //$NON-NLS-1$ + public static final String CLASS_TAB_LAYOUT = + "android.support.design.widget.TabLayout"; //$NON-NLS-1$ + public static final String CLASS_TEXT_INPUT_LAYOUT = + "android.support.design.widget.TextInputLayout"; //$NON-NLS-1$ + public static final String CLASS_NESTED_SCROLL_VIEW = + "android.support.v4.widget.NestedScrollView"; //$NON-NLS-1$ + public static final String URI_DOMAIN_PREFIX = "http://schemas.android.com/"; + + /** + * Returns the appropriate name for the 'android' command, which is 'android.exe' for Windows and + * 'android' for all other platforms. + */ + public static String androidCmdName() { + String os = System.getProperty("os.name"); // $NON-NLS-1$ + String cmd = "android"; // $NON-NLS-1$ + if (os.startsWith("Windows")) { // $NON-NLS-1$ + cmd += ".bat"; // $NON-NLS-1$ } - /** Returns the appropriate name for the 'mksdcard' command, which is 'mksdcard.exe' for - * Windows and 'mkdsdcard' for all other platforms. */ - public static String mkSdCardCmdName() { - String os = System.getProperty("os.name"); //$NON-NLS-1$ - String cmd = "mksdcard"; //$NON-NLS-1$ - if (os.startsWith("Windows")) { //$NON-NLS-1$ - cmd += ".exe"; //$NON-NLS-1$ - } - return cmd; + return cmd; + } + /** + * Returns the appropriate name for the 'mksdcard' command, which is 'mksdcard.exe' for Windows + * and 'mkdsdcard' for all other platforms. + */ + public static String mkSdCardCmdName() { + String os = System.getProperty("os.name"); // $NON-NLS-1$ + String cmd = "mksdcard"; // $NON-NLS-1$ + if (os.startsWith("Windows")) { // $NON-NLS-1$ + cmd += ".exe"; // $NON-NLS-1$ } - /** - * Returns current platform - * - * @return one of {@link #PLATFORM_WINDOWS}, {@link #PLATFORM_DARWIN}, - * {@link #PLATFORM_LINUX} or {@link #PLATFORM_UNKNOWN}. - */ - public static int currentPlatform() { - String os = System.getProperty("os.name"); //$NON-NLS-1$ - if (os.startsWith("Mac OS")) { //$NON-NLS-1$ - return PLATFORM_DARWIN; - } else if (os.startsWith("Windows")) { //$NON-NLS-1$ - return PLATFORM_WINDOWS; - } else if (os.startsWith("Linux")) { //$NON-NLS-1$ - return PLATFORM_LINUX; - } - return PLATFORM_UNKNOWN; + return cmd; + } + /** + * Returns current platform + * + * @return one of {@link #PLATFORM_WINDOWS}, {@link #PLATFORM_DARWIN}, {@link #PLATFORM_LINUX} or + * {@link #PLATFORM_UNKNOWN}. + */ + public static int currentPlatform() { + String os = System.getProperty("os.name"); // $NON-NLS-1$ + if (os.startsWith("Mac OS")) { // $NON-NLS-1$ + return PLATFORM_DARWIN; + } else if (os.startsWith("Windows")) { // $NON-NLS-1$ + return PLATFORM_WINDOWS; + } else if (os.startsWith("Linux")) { // $NON-NLS-1$ + return PLATFORM_LINUX; } - /** - * Returns current platform's UI name - * - * @return one of "Windows", "Mac OS X", "Linux" or "other". - */ - public static String currentPlatformName() { - String os = System.getProperty("os.name"); //$NON-NLS-1$ - if (os.startsWith("Mac OS")) { //$NON-NLS-1$ - return "Mac OS X"; //$NON-NLS-1$ - } else if (os.startsWith("Windows")) { //$NON-NLS-1$ - return "Windows"; //$NON-NLS-1$ - } else if (os.startsWith("Linux")) { //$NON-NLS-1$ - return "Linux"; //$NON-NLS-1$ - } - return "Other"; + return PLATFORM_UNKNOWN; + } + /** + * Returns current platform's UI name + * + * @return one of "Windows", "Mac OS X", "Linux" or "other". + */ + public static String currentPlatformName() { + String os = System.getProperty("os.name"); // $NON-NLS-1$ + if (os.startsWith("Mac OS")) { // $NON-NLS-1$ + return "Mac OS X"; //$NON-NLS-1$ + } else if (os.startsWith("Windows")) { // $NON-NLS-1$ + return "Windows"; //$NON-NLS-1$ + } else if (os.startsWith("Linux")) { // $NON-NLS-1$ + return "Linux"; //$NON-NLS-1$ } - private static String ext(String windowsExtension, String nonWindowsExtension) { - if (CURRENT_PLATFORM == PLATFORM_WINDOWS) { - return windowsExtension; - } else { - return nonWindowsExtension; - } + return "Other"; + } + + private static String ext(String windowsExtension, String nonWindowsExtension) { + if (CURRENT_PLATFORM == PLATFORM_WINDOWS) { + return windowsExtension; + } else { + return nonWindowsExtension; } - /** Default anim resource folder name, i.e. "anim" */ - public static final String FD_RES_ANIM = "anim"; //$NON-NLS-1$ - /** Default animator resource folder name, i.e. "animator" */ - public static final String FD_RES_ANIMATOR = "animator"; //$NON-NLS-1$ - /** Default color resource folder name, i.e. "color" */ - public static final String FD_RES_COLOR = "color"; //$NON-NLS-1$ - /** Default drawable resource folder name, i.e. "drawable" */ - public static final String FD_RES_DRAWABLE = "drawable"; //$NON-NLS-1$ - /** Default interpolator resource folder name, i.e. "interpolator" */ - public static final String FD_RES_INTERPOLATOR = "interpolator"; //$NON-NLS-1$ - /** Default layout resource folder name, i.e. "layout" */ - public static final String FD_RES_LAYOUT = "layout"; //$NON-NLS-1$ - /** Default menu resource folder name, i.e. "menu" */ - public static final String FD_RES_MENU = "menu"; //$NON-NLS-1$ - /** Default menu resource folder name, i.e. "mipmap" */ - public static final String FD_RES_MIPMAP = "mipmap"; //$NON-NLS-1$ - /** Default values resource folder name, i.e. "values" */ - public static final String FD_RES_VALUES = "values"; //$NON-NLS-1$ - /** Default xml resource folder name, i.e. "xml" */ - public static final String FD_RES_XML = "xml"; //$NON-NLS-1$ - /** Default raw resource folder name, i.e. "raw" */ - public static final String FD_RES_RAW = "raw"; //$NON-NLS-1$ - /** Separator between the resource folder qualifier. */ - public static final String RES_QUALIFIER_SEP = "-"; //$NON-NLS-1$ - /** Namespace used in XML files for Android attributes */ - // ---- XML ---- - public static final String AAPT_URI = "http://schemas.android.com/aapt"; - /** URI of the reserved "xmlns" prefix */ - public static final String XMLNS_URI = "http://www.w3.org/2000/xmlns/"; //$NON-NLS-1$ - /** The "xmlns" attribute name */ - public static final String XMLNS = "xmlns"; //$NON-NLS-1$ - /** The default prefix used for the {@link #XMLNS_URI} */ - public static final String XMLNS_PREFIX = "xmlns:"; //$NON-NLS-1$ - /** Qualified name of the xmlns android declaration element */ - public static final String XMLNS_ANDROID = "xmlns:android"; //$NON-NLS-1$ - /** The default prefix used for the {@link #ANDROID_URI} name space */ - public static final String ANDROID_NS_NAME = "android"; //$NON-NLS-1$ - /** The default prefix used for the {@link #ANDROID_URI} name space including the colon */ - public static final String ANDROID_NS_NAME_PREFIX = "android:"; //$NON-NLS-1$ - public static final int ANDROID_NS_NAME_PREFIX_LEN = ANDROID_NS_NAME_PREFIX.length(); - /** The default prefix used for the app */ - public static final String APP_PREFIX = "app"; //$NON-NLS-1$ - /** The entity for the ampersand character */ - public static final String AMP_ENTITY = "&"; //$NON-NLS-1$ - /** The entity for the quote character */ - public static final String QUOT_ENTITY = """; //$NON-NLS-1$ - /** The entity for the apostrophe character */ - public static final String APOS_ENTITY = "'"; //$NON-NLS-1$ - /** The entity for the less than character */ - public static final String LT_ENTITY = "<"; //$NON-NLS-1$ - /** The entity for the greater than character */ - public static final String GT_ENTITY = ">"; //$NON-NLS-1$ - public static final String NEWLINE_ENTITY = " "; - // ---- Elements and Attributes ---- - /** Namespace prefix used for all resources */ - public static final String URI_PREFIX = - "http://schemas.android.com/apk/res/"; //$NON-NLS-1$ - /** Namespace used in XML files for Android attributes */ - public static final String ANDROID_URI = - "http://schemas.android.com/apk/res/android"; //$NON-NLS-1$ - /** Namespace used in XML files for Android Tooling attributes */ - public static final String TOOLS_URI = - "http://schemas.android.com/tools"; //$NON-NLS-1$ - /** Namespace used for auto-adjusting namespaces */ - public static final String AUTO_URI = - "http://schemas.android.com/apk/res-auto"; //$NON-NLS-1$ - /** Default prefix used for tools attributes */ - public static final String TOOLS_PREFIX = "tools"; //$NON-NLS-1$ - public static final String R_CLASS = "R"; //$NON-NLS-1$ - public static final String ANDROID_PKG = "android"; //$NON-NLS-1$ - // Tags: Manifest - public static final String TAG_SERVICE = "service"; //$NON-NLS-1$ - public static final String TAG_PERMISSION = "permission"; //$NON-NLS-1$ - public static final String TAG_USES_FEATURE = "uses-feature"; //$NON-NLS-1$ - public static final String TAG_USES_PERMISSION = "uses-permission";//$NON-NLS-1$ - public static final String TAG_USES_LIBRARY = "uses-library"; //$NON-NLS-1$ - public static final String TAG_APPLICATION = "application"; //$NON-NLS-1$ - public static final String TAG_INTENT_FILTER = "intent-filter"; //$NON-NLS-1$ - public static final String TAG_USES_SDK = "uses-sdk"; //$NON-NLS-1$ - public static final String TAG_ACTIVITY = "activity"; //$NON-NLS-1$ - public static final String TAG_RECEIVER = "receiver"; //$NON-NLS-1$ - public static final String TAG_PROVIDER = "provider"; //$NON-NLS-1$ - public static final String TAG_GRANT_PERMISSION = "grant-uri-permission"; //$NON-NLS-1$ - public static final String TAG_PATH_PERMISSION = "path-permission"; //$NON-NLS-1$ - // Tags: Resources - public static final String TAG_RESOURCES = "resources"; //$NON-NLS-1$ - public static final String TAG_STRING = "string"; //$NON-NLS-1$ - public static final String TAG_ARRAY = "array"; //$NON-NLS-1$ - public static final String TAG_STYLE = "style"; //$NON-NLS-1$ - public static final String TAG_ITEM = "item"; //$NON-NLS-1$ - public static final String TAG_GROUP = "group"; //$NON-NLS-1$ - public static final String TAG_STRING_ARRAY = "string-array"; //$NON-NLS-1$ - public static final String TAG_PLURALS = "plurals"; //$NON-NLS-1$ - public static final String TAG_INTEGER_ARRAY = "integer-array"; //$NON-NLS-1$ - public static final String TAG_COLOR = "color"; //$NON-NLS-1$ - public static final String TAG_DIMEN = "dimen"; //$NON-NLS-1$ - public static final String TAG_DRAWABLE = "drawable"; //$NON-NLS-1$ - public static final String TAG_MENU = "menu"; //$NON-NLS-1$ - public static final String TAG_ENUM = "enum"; //$NON-NLS-1$ - public static final String TAG_FLAG = "flag"; //$NON-NLS-1$ - public static final String TAG_ATTR = "attr"; //$NON-NLS-1$ - public static final String TAG_DECLARE_STYLEABLE = "declare-styleable"; //$NON-NLS-1$ - public static final String TAG_PUBLIC = "public"; //$NON-NLS-1$ - public static final String TAG_EAT_COMMENT = "eat-comment"; //$NON-NLS-1$ - public static final String TAG_SKIP = "skip"; //$NON-NLS-1$ - public static final String TAG_SELECTOR = "selector"; //$NON-NLS-1$ - // Tags: XML - public static final String TAG_HEADER = "header"; //$NON-NLS-1$ - public static final String TAG_APPWIDGET_PROVIDER = "appwidget-provider"; //$NON-NLS-1$ - public static final String TAG_PREFERENCE_SCREEN = "PreferenceScreen"; //$NON-NLS-1$ - // Tags: Layouts - public static final String VIEW_TAG = "view"; //$NON-NLS-1$ - public static final String VIEW_INCLUDE = "include"; //$NON-NLS-1$ - public static final String VIEW_MERGE = "merge"; //$NON-NLS-1$ - public static final String VIEW_FRAGMENT = "fragment"; //$NON-NLS-1$ - public static final String REQUEST_FOCUS = "requestFocus"; //$NON-NLS-1$ - public static final String TAG = "tag"; //$NON-NLS-1$ - public static final String VIEW = "View"; //$NON-NLS-1$ - public static final String VIEW_GROUP = "ViewGroup"; //$NON-NLS-1$ - public static final String FRAME_LAYOUT = "FrameLayout"; //$NON-NLS-1$ - public static final String LINEAR_LAYOUT = "LinearLayout"; //$NON-NLS-1$ - public static final String RELATIVE_LAYOUT = "RelativeLayout"; //$NON-NLS-1$ - public static final String GRID_LAYOUT = "GridLayout"; //$NON-NLS-1$ - public static final String SCROLL_VIEW = "ScrollView"; //$NON-NLS-1$ - public static final String BUTTON = "Button"; //$NON-NLS-1$ - public static final String COMPOUND_BUTTON = "CompoundButton"; //$NON-NLS-1$ - public static final String ADAPTER_VIEW = "AdapterView"; //$NON-NLS-1$ - public static final String GALLERY = "Gallery"; //$NON-NLS-1$ - public static final String GRID_VIEW = "GridView"; //$NON-NLS-1$ - public static final String TAB_HOST = "TabHost"; //$NON-NLS-1$ - public static final String RADIO_GROUP = "RadioGroup"; //$NON-NLS-1$ - public static final String RADIO_BUTTON = "RadioButton"; //$NON-NLS-1$ - public static final String SWITCH = "Switch"; //$NON-NLS-1$ - public static final String EDIT_TEXT = "EditText"; //$NON-NLS-1$ - public static final String LIST_VIEW = "ListView"; //$NON-NLS-1$ - public static final String TEXT_VIEW = "TextView"; //$NON-NLS-1$ - public static final String CHECKED_TEXT_VIEW = "CheckedTextView"; //$NON-NLS-1$ - public static final String IMAGE_VIEW = "ImageView"; //$NON-NLS-1$ - public static final String SURFACE_VIEW = "SurfaceView"; //$NON-NLS-1$ - public static final String ABSOLUTE_LAYOUT = "AbsoluteLayout"; //$NON-NLS-1$ - public static final String TABLE_LAYOUT = "TableLayout"; //$NON-NLS-1$ - public static final String TABLE_ROW = "TableRow"; //$NON-NLS-1$ - public static final String TAB_WIDGET = "TabWidget"; //$NON-NLS-1$ - public static final String IMAGE_BUTTON = "ImageButton"; //$NON-NLS-1$ - public static final String SEEK_BAR = "SeekBar"; //$NON-NLS-1$ - public static final String VIEW_STUB = "ViewStub"; //$NON-NLS-1$ - public static final String SPINNER = "Spinner"; //$NON-NLS-1$ - public static final String WEB_VIEW = "WebView"; //$NON-NLS-1$ - public static final String TOGGLE_BUTTON = "ToggleButton"; //$NON-NLS-1$ - public static final String CHECK_BOX = "CheckBox"; //$NON-NLS-1$ - public static final String ABS_LIST_VIEW = "AbsListView"; //$NON-NLS-1$ - public static final String PROGRESS_BAR = "ProgressBar"; //$NON-NLS-1$ - public static final String ABS_SPINNER = "AbsSpinner"; //$NON-NLS-1$ - public static final String ABS_SEEK_BAR = "AbsSeekBar"; //$NON-NLS-1$ - public static final String VIEW_ANIMATOR = "ViewAnimator"; //$NON-NLS-1$ - public static final String VIEW_SWITCHER = "ViewSwitcher"; //$NON-NLS-1$ - public static final String EXPANDABLE_LIST_VIEW = "ExpandableListView"; //$NON-NLS-1$ - public static final String HORIZONTAL_SCROLL_VIEW = "HorizontalScrollView"; //$NON-NLS-1$ - public static final String MULTI_AUTO_COMPLETE_TEXT_VIEW = "MultiAutoCompleteTextView"; //$NON-NLS-1$ - public static final String AUTO_COMPLETE_TEXT_VIEW = "AutoCompleteTextView"; //$NON-NLS-1$ - public static final String CHECKABLE = "Checkable"; //$NON-NLS-1$ - public static final String TEXTURE_VIEW = "TextureView"; //$NON-NLS-1$ - /* Android Design Support Tag Constants */ - public static final String COORDINATOR_LAYOUT = CLASS_COORDINATOR_LAYOUT; - public static final String APP_BAR_LAYOUT = CLASS_APP_BAR_LAYOUT; - public static final String FLOATING_ACTION_BUTTON = CLASS_FLOATING_ACTION_BUTTON; - public static final String COLLAPSING_TOOLBAR_LAYOUT = CLASS_COLLAPSING_TOOLBAR_LAYOUT; - public static final String NAVIGATION_VIEW = CLASS_NAVIGATION_VIEW; - public static final String SNACKBAR = CLASS_SNACKBAR; - public static final String TAB_LAYOUT = CLASS_TAB_LAYOUT; - public static final String TEXT_INPUT_LAYOUT = CLASS_TEXT_INPUT_LAYOUT; - // Tags: Drawables - public static final String TAG_BITMAP = "bitmap"; //$NON-NLS-1$ - // Tags: Data-Binding - public static final String TAG_LAYOUT = "layout"; //$NON-NLS-1$ - public static final String TAG_DATA = "data"; //$NON-NLS-1$ - public static final String TAG_VARIABLE = "variable"; //$NON-NLS-1$ - public static final String TAG_IMPORT = "import"; //$NON-NLS-1$ - // Attributes: Manifest - public static final String ATTR_EXPORTED = "exported"; //$NON-NLS-1$ - public static final String ATTR_PERMISSION = "permission"; //$NON-NLS-1$ - public static final String ATTR_MIN_SDK_VERSION = "minSdkVersion"; //$NON-NLS-1$ - public static final String ATTR_TARGET_SDK_VERSION = "targetSdkVersion"; //$NON-NLS-1$ - public static final String ATTR_ICON = "icon"; //$NON-NLS-1$ - public static final String ATTR_PACKAGE = "package"; //$NON-NLS-1$ - public static final String ATTR_CORE_APP = "coreApp"; //$NON-NLS-1$ - public static final String ATTR_THEME = "theme"; //$NON-NLS-1$ - public static final String ATTR_SCHEME = "scheme"; //$NON_NLS-1$ - public static final String ATTR_HOST = "host"; //$NON_NLS-1$ - public static final String ATTR_PATH = "path"; //$NON-NLS-1$ - public static final String ATTR_PATH_PREFIX = "pathPrefix"; //$NON-NLS-1$ - public static final String ATTR_PATH_PATTERN = "pathPattern"; //$NON-NLS-1$ - public static final String ATTR_ALLOW_BACKUP = "allowBackup"; //$NON_NLS-1$ - public static final String ATTR_DEBUGGABLE = "debuggable"; //$NON-NLS-1$ - public static final String ATTR_READ_PERMISSION = "readPermission"; //$NON_NLS-1$ - public static final String ATTR_WRITE_PERMISSION = "writePermission"; //$NON_NLS-1$ - public static final String ATTR_VERSION_CODE = "versionCode"; //$NON_NLS-1$ - public static final String ATTR_VERSION_NAME = "versionName"; //$NON_NLS-1$ - // Attributes: Resources - public static final String ATTR_NAME = "name"; //$NON-NLS-1$ - public static final String ATTR_FRAGMENT = "fragment"; //$NON-NLS-1$ - public static final String ATTR_TYPE = "type"; //$NON-NLS-1$ - public static final String ATTR_PARENT = "parent"; //$NON-NLS-1$ - public static final String ATTR_TRANSLATABLE = "translatable"; //$NON-NLS-1$ - public static final String ATTR_COLOR = "color"; //$NON-NLS-1$ - public static final String ATTR_DRAWABLE = "drawable"; //$NON-NLS-1$ - public static final String ATTR_VALUE = "value"; //$NON-NLS-1$ - public static final String ATTR_QUANTITY = "quantity"; //$NON-NLS-1$ - public static final String ATTR_FORMAT = "format"; //$NON-NLS-1$ - public static final String ATTR_PREPROCESSING = "preprocessing"; //$NON-NLS-1$ - // Attributes: Data-Binding - public static final String ATTR_ALIAS = "alias"; //$NON-NLS-1$ - // Attributes: Layout - public static final String ATTR_LAYOUT_RESOURCE_PREFIX = "layout_";//$NON-NLS-1$ - public static final String ATTR_CLASS = "class"; //$NON-NLS-1$ - public static final String ATTR_STYLE = "style"; //$NON-NLS-1$ - public static final String ATTR_CONTEXT = "context"; //$NON-NLS-1$ - public static final String ATTR_ID = "id"; //$NON-NLS-1$ - public static final String ATTR_TEXT = "text"; //$NON-NLS-1$ - public static final String ATTR_TEXT_SIZE = "textSize"; //$NON-NLS-1$ - public static final String ATTR_LABEL = "label"; //$NON-NLS-1$ - public static final String ATTR_HINT = "hint"; //$NON-NLS-1$ - public static final String ATTR_PROMPT = "prompt"; //$NON-NLS-1$ - public static final String ATTR_ON_CLICK = "onClick"; //$NON-NLS-1$ - public static final String ATTR_INPUT_TYPE = "inputType"; //$NON-NLS-1$ - public static final String ATTR_INPUT_METHOD = "inputMethod"; //$NON-NLS-1$ - public static final String ATTR_LAYOUT_GRAVITY = "layout_gravity"; //$NON-NLS-1$ - public static final String ATTR_LAYOUT_WIDTH = "layout_width"; //$NON-NLS-1$ - public static final String ATTR_LAYOUT_HEIGHT = "layout_height"; //$NON-NLS-1$ - public static final String ATTR_LAYOUT_WEIGHT = "layout_weight"; //$NON-NLS-1$ - public static final String ATTR_PADDING = "padding"; //$NON-NLS-1$ - public static final String ATTR_PADDING_BOTTOM = "paddingBottom"; //$NON-NLS-1$ - public static final String ATTR_PADDING_TOP = "paddingTop"; //$NON-NLS-1$ - public static final String ATTR_PADDING_RIGHT = "paddingRight"; //$NON-NLS-1$ - public static final String ATTR_PADDING_LEFT = "paddingLeft"; //$NON-NLS-1$ - public static final String ATTR_PADDING_START = "paddingStart"; //$NON-NLS-1$ - public static final String ATTR_PADDING_END = "paddingEnd"; //$NON-NLS-1$ - public static final String ATTR_FOREGROUND = "foreground"; //$NON-NLS-1$ - public static final String ATTR_BACKGROUND = "background"; //$NON-NLS-1$ - public static final String ATTR_ORIENTATION = "orientation"; //$NON-NLS-1$ - public static final String ATTR_LAYOUT = "layout"; //$NON-NLS-1$ - public static final String ATTR_ROW_COUNT = "rowCount"; //$NON-NLS-1$ - public static final String ATTR_COLUMN_COUNT = "columnCount"; //$NON-NLS-1$ - public static final String ATTR_LABEL_FOR = "labelFor"; //$NON-NLS-1$ - public static final String ATTR_BASELINE_ALIGNED = "baselineAligned"; //$NON-NLS-1$ - public static final String ATTR_CONTENT_DESCRIPTION = "contentDescription"; //$NON-NLS-1$ - public static final String ATTR_IME_ACTION_LABEL = "imeActionLabel"; //$NON-NLS-1$ - public static final String ATTR_PRIVATE_IME_OPTIONS = "privateImeOptions"; //$NON-NLS-1$ - public static final String VALUE_NONE = "none"; //$NON-NLS-1$ - public static final String VALUE_NO = "no"; //$NON-NLS-1$ - public static final String ATTR_NUMERIC = "numeric"; //$NON-NLS-1$ - public static final String ATTR_IME_ACTION_ID = "imeActionId"; //$NON-NLS-1$ - public static final String ATTR_IME_OPTIONS = "imeOptions"; //$NON-NLS-1$ - public static final String ATTR_FREEZES_TEXT = "freezesText"; //$NON-NLS-1$ - public static final String ATTR_EDITOR_EXTRAS = "editorExtras"; //$NON-NLS-1$ - public static final String ATTR_EDITABLE = "editable"; //$NON-NLS-1$ - public static final String ATTR_DIGITS = "digits"; //$NON-NLS-1$ - public static final String ATTR_CURSOR_VISIBLE = "cursorVisible"; //$NON-NLS-1$ - public static final String ATTR_CAPITALIZE = "capitalize"; //$NON-NLS-1$ - public static final String ATTR_PHONE_NUMBER = "phoneNumber"; //$NON-NLS-1$ - public static final String ATTR_PASSWORD = "password"; //$NON-NLS-1$ - public static final String ATTR_BUFFER_TYPE = "bufferType"; //$NON-NLS-1$ - public static final String ATTR_AUTO_TEXT = "autoText"; //$NON-NLS-1$ - public static final String ATTR_ENABLED = "enabled"; //$NON-NLS-1$ - public static final String ATTR_SINGLE_LINE = "singleLine"; //$NON-NLS-1$ - public static final String ATTR_SCALE_TYPE = "scaleType"; //$NON-NLS-1$ - public static final String ATTR_VISIBILITY = "visibility"; //$NON-NLS-1$ - public static final String ATTR_TEXT_IS_SELECTABLE = - "textIsSelectable"; //$NON-NLS-1$ - public static final String ATTR_IMPORTANT_FOR_ACCESSIBILITY = - "importantForAccessibility"; //$NON-NLS-1$ - public static final String ATTR_LIST_PREFERRED_ITEM_PADDING_LEFT = - "listPreferredItemPaddingLeft"; //$NON-NLS-1$ - public static final String ATTR_LIST_PREFERRED_ITEM_PADDING_RIGHT = - "listPreferredItemPaddingRight"; //$NON-NLS-1$ - public static final String ATTR_LIST_PREFERRED_ITEM_PADDING_START = - "listPreferredItemPaddingStart"; //$NON-NLS-1$ - public static final String ATTR_LIST_PREFERRED_ITEM_PADDING_END = - "listPreferredItemPaddingEnd"; //$NON-NLS-1$ - public static final String ATTR_INDEX = "index"; //$NON-NLS-1$ - // AbsoluteLayout layout params - public static final String ATTR_LAYOUT_Y = "layout_y"; //$NON-NLS-1$ - public static final String ATTR_LAYOUT_X = "layout_x"; //$NON-NLS-1$ - // GridLayout layout params - public static final String ATTR_LAYOUT_ROW = "layout_row"; //$NON-NLS-1$ - public static final String ATTR_LAYOUT_ROW_SPAN = "layout_rowSpan";//$NON-NLS-1$ - public static final String ATTR_LAYOUT_COLUMN = "layout_column"; //$NON-NLS-1$ - public static final String ATTR_LAYOUT_COLUMN_SPAN = "layout_columnSpan"; //$NON-NLS-1$ - // TableRow - public static final String ATTR_LAYOUT_SPAN = "layout_span"; //$NON-NLS-1$ - // RelativeLayout layout params: - public static final String ATTR_LAYOUT_ALIGN_LEFT = "layout_alignLeft"; //$NON-NLS-1$ - public static final String ATTR_LAYOUT_ALIGN_RIGHT = "layout_alignRight"; //$NON-NLS-1$ - public static final String ATTR_LAYOUT_ALIGN_START = "layout_alignStart"; //$NON-NLS-1$ - public static final String ATTR_LAYOUT_ALIGN_END = "layout_alignEnd"; //$NON-NLS-1$ - public static final String ATTR_LAYOUT_ALIGN_TOP = "layout_alignTop"; //$NON-NLS-1$ - public static final String ATTR_LAYOUT_ALIGN_BOTTOM = "layout_alignBottom"; //$NON-NLS-1$ - public static final String ATTR_LAYOUT_ALIGN_PARENT_LEFT = "layout_alignParentLeft"; //$NON-NLS-1$ - public static final String ATTR_LAYOUT_ALIGN_PARENT_RIGHT = "layout_alignParentRight"; //$NON-NLS-1$ - public static final String ATTR_LAYOUT_ALIGN_PARENT_START = "layout_alignParentStart"; //$NON-NLS-1$ - public static final String ATTR_LAYOUT_ALIGN_PARENT_END = "layout_alignParentEnd"; //$NON-NLS-1$ - public static final String ATTR_LAYOUT_ALIGN_PARENT_TOP = "layout_alignParentTop"; //$NON-NLS-1$ - public static final String ATTR_LAYOUT_ALIGN_PARENT_BOTTOM = "layout_alignParentBottom"; //$NON-NLS-1$ - public static final String ATTR_LAYOUT_ALIGN_WITH_PARENT_MISSING = "layout_alignWithParentIfMissing"; //$NON-NLS-1$ - public static final String ATTR_LAYOUT_ALIGN_BASELINE = "layout_alignBaseline"; //$NON-NLS-1$ - public static final String ATTR_LAYOUT_CENTER_IN_PARENT = "layout_centerInParent"; //$NON-NLS-1$ - public static final String ATTR_LAYOUT_CENTER_VERTICAL = "layout_centerVertical"; //$NON-NLS-1$ - public static final String ATTR_LAYOUT_CENTER_HORIZONTAL = "layout_centerHorizontal"; //$NON-NLS-1$ - public static final String ATTR_LAYOUT_TO_RIGHT_OF = "layout_toRightOf"; //$NON-NLS-1$ - public static final String ATTR_LAYOUT_TO_LEFT_OF = "layout_toLeftOf"; //$NON-NLS-1$ - public static final String ATTR_LAYOUT_TO_START_OF = "layout_toStartOf"; //$NON-NLS-1$ - public static final String ATTR_LAYOUT_TO_END_OF = "layout_toEndOf"; //$NON-NLS-1$ - public static final String ATTR_LAYOUT_BELOW = "layout_below"; //$NON-NLS-1$ - public static final String ATTR_LAYOUT_ABOVE = "layout_above"; //$NON-NLS-1$ - // Margins - public static final String ATTR_LAYOUT_MARGIN = "layout_margin"; //$NON-NLS-1$ - public static final String ATTR_LAYOUT_MARGIN_LEFT = "layout_marginLeft"; //$NON-NLS-1$ - public static final String ATTR_LAYOUT_MARGIN_RIGHT = "layout_marginRight"; //$NON-NLS-1$ - public static final String ATTR_LAYOUT_MARGIN_START = "layout_marginStart"; //$NON-NLS-1$ - public static final String ATTR_LAYOUT_MARGIN_END = "layout_marginEnd"; //$NON-NLS-1$ - public static final String ATTR_LAYOUT_MARGIN_TOP = "layout_marginTop"; //$NON-NLS-1$ - public static final String ATTR_LAYOUT_MARGIN_BOTTOM = "layout_marginBottom"; //$NON-NLS-1$ - // Attributes: Drawables - public static final String ATTR_TILE_MODE = "tileMode"; //$NON-NLS-1$ - // Attributes: CoordinatorLayout - public static final String ATTR_LAYOUT_ANCHOR = "layout_anchor"; //$NON-NLS-1$ - public static final String ATTR_LAYOUT_ANCHOR_GRAVITY = "layout_anchorGravity"; //$NON-NLS-1$ - public static final String ATTR_LAYOUT_BEHAVIOR = "layout_behavior"; //$NON-NLS-1$ - public static final String ATTR_LAYOUT_KEYLINE = "layout_keyline"; //$NON-NLS-1$ - // Values: Manifest - public static final String VALUE_SPLIT_ACTION_BAR_WHEN_NARROW = "splitActionBarWhenNarrow"; // NON-NLS-$1 - // Values: Layouts - public static final String VALUE_FILL_PARENT = "fill_parent"; //$NON-NLS-1$ - public static final String VALUE_MATCH_PARENT = "match_parent"; //$NON-NLS-1$ - public static final String VALUE_VERTICAL = "vertical"; //$NON-NLS-1$ - public static final String VALUE_TRUE = "true"; //$NON-NLS-1$ - public static final String VALUE_EDITABLE = "editable"; //$NON-NLS-1$ - public static final String VALUE_AUTO_FIT = "auto_fit"; //$NON-NLS-1$ - public static final String VALUE_SELECTABLE_ITEM_BACKGROUND = - "?android:attr/selectableItemBackground"; //$NON-NLS-1$ - // Values: Resources - public static final String VALUE_ID = "id"; //$NON-NLS-1$ - // Values: Drawables - public static final String VALUE_DISABLED = "disabled"; //$NON-NLS-1$ - public static final String VALUE_CLAMP = "clamp"; //$NON-NLS-1$ - // Menus - public static final String ATTR_SHOW_AS_ACTION = "showAsAction"; //$NON-NLS-1$ - public static final String ATTR_TITLE = "title"; //$NON-NLS-1$ - public static final String ATTR_VISIBLE = "visible"; //$NON-NLS-1$ - public static final String VALUE_IF_ROOM = "ifRoom"; //$NON-NLS-1$ - public static final String VALUE_ALWAYS = "always"; //$NON-NLS-1$ - // Units - public static final String UNIT_DP = "dp"; //$NON-NLS-1$ - public static final String UNIT_DIP = "dip"; //$NON-NLS-1$ - public static final String UNIT_SP = "sp"; //$NON-NLS-1$ - public static final String UNIT_PX = "px"; //$NON-NLS-1$ - public static final String UNIT_IN = "in"; //$NON-NLS-1$ - public static final String UNIT_MM = "mm"; //$NON-NLS-1$ - public static final String UNIT_PT = "pt"; //$NON-NLS-1$ - // Filenames and folder names - public static final String ANDROID_MANIFEST_XML = "AndroidManifest.xml"; //$NON-NLS-1$ - public static final String OLD_PROGUARD_FILE = "proguard.cfg"; //$NON-NLS-1$ - public static final String CLASS_FOLDER = - "bin" + File.separator + "classes"; //$NON-NLS-1$ //$NON-NLS-2$ - public static final String GEN_FOLDER = "gen"; //$NON-NLS-1$ - public static final String SRC_FOLDER = "src"; //$NON-NLS-1$ - public static final String LIBS_FOLDER = "libs"; //$NON-NLS-1$ - public static final String BIN_FOLDER = "bin"; //$NON-NLS-1$ - public static final String RES_FOLDER = "res"; //$NON-NLS-1$ - public static final String DOT_XML = ".xml"; //$NON-NLS-1$ - public static final String DOT_XSD = ".xsd"; //$NON-NLS-1$ - public static final String DOT_GIF = ".gif"; //$NON-NLS-1$ - public static final String DOT_JPG = ".jpg"; //$NON-NLS-1$ - public static final String DOT_JPEG = ".jpeg"; //$NON-NLS-1$ - public static final String DOT_WEBP = ".webp"; //$NON-NLS-1$ - public static final String DOT_PNG = ".png"; //$NON-NLS-1$ - public static final String DOT_9PNG = ".9.png"; //$NON-NLS-1$ - public static final String DOT_JAVA = ".java"; //$NON-NLS-1$ - public static final String DOT_CLASS = ".class"; //$NON-NLS-1$ - public static final String DOT_JAR = ".jar"; //$NON-NLS-1$ - public static final String DOT_GRADLE = ".gradle"; //$NON-NLS-1$ - public static final String DOT_PROPERTIES = ".properties"; //$NON-NLS-1$ - /** Extension of the Application package Files, i.e. "apk". */ - public static final String EXT_ANDROID_PACKAGE = "apk"; //$NON-NLS-1$ - /** Extension of java files, i.e. "java" */ - public static final String EXT_JAVA = "java"; //$NON-NLS-1$ - /** Extension of compiled java files, i.e. "class" */ - public static final String EXT_CLASS = "class"; //$NON-NLS-1$ - /** Extension of xml files, i.e. "xml" */ - public static final String EXT_XML = "xml"; //$NON-NLS-1$ - /** Extension of gradle files, i.e. "gradle" */ - public static final String EXT_GRADLE = "gradle"; //$NON-NLS-1$ - /** Extension of jar files, i.e. "jar" */ - public static final String EXT_JAR = "jar"; //$NON-NLS-1$ - /** Extension of ZIP files, i.e. "zip" */ - public static final String EXT_ZIP = "zip"; //$NON-NLS-1$ - /** Extension of aidl files, i.e. "aidl" */ - public static final String EXT_AIDL = "aidl"; //$NON-NLS-1$ - /** Extension of Renderscript files, i.e. "rs" */ - public static final String EXT_RS = "rs"; //$NON-NLS-1$ - /** Extension of Renderscript files, i.e. "rsh" */ - public static final String EXT_RSH = "rsh"; //$NON-NLS-1$ - /** Extension of FilterScript files, i.e. "fs" */ - public static final String EXT_FS = "fs"; //$NON-NLS-1$ - /** Extension of Renderscript bitcode files, i.e. "bc" */ - public static final String EXT_BC = "bc"; //$NON-NLS-1$ - /** Extension of dependency files, i.e. "d" */ - public static final String EXT_DEP = "d"; //$NON-NLS-1$ - /** Extension of native libraries, i.e. "so" */ - public static final String EXT_NATIVE_LIB = "so"; //$NON-NLS-1$ - /** Extension of dex files, i.e. "dex" */ - public static final String EXT_DEX = "dex"; //$NON-NLS-1$ - /** Extension for temporary resource files, ie "ap_ */ - public static final String EXT_RES = "ap_"; //$NON-NLS-1$ - /** Extension for pre-processable images. Right now pngs */ - public static final String EXT_PNG = "png"; //$NON-NLS-1$ - /** Extension for Android archive files */ - public static final String EXT_AAR = "aar"; //$NON-NLS-1$ - /** Extension for Java heap dumps. */ - public static final String EXT_HPROF = "hprof"; //$NON-NLS-1$ - private static final String DOT = "."; //$NON-NLS-1$ - /** Dot-Extension of the Application package Files, i.e. ".apk". */ - public static final String DOT_ANDROID_PACKAGE = DOT + EXT_ANDROID_PACKAGE; - /** Dot-Extension of aidl files, i.e. ".aidl" */ - public static final String DOT_AIDL = DOT + EXT_AIDL; - /** Dot-Extension of renderscript files, i.e. ".rs" */ - public static final String DOT_RS = DOT + EXT_RS; - /** Dot-Extension of renderscript header files, i.e. ".rsh" */ - public static final String DOT_RSH = DOT + EXT_RSH; - /** Dot-Extension of FilterScript files, i.e. ".fs" */ - public static final String DOT_FS = DOT + EXT_FS; - /** Dot-Extension of renderscript bitcode files, i.e. ".bc" */ - public static final String DOT_BC = DOT + EXT_BC; - /** Dot-Extension of dependency files, i.e. ".d" */ - public static final String DOT_DEP = DOT + EXT_DEP; - /** Dot-Extension of dex files, i.e. ".dex" */ - public static final String DOT_DEX = DOT + EXT_DEX; - /** Dot-Extension for temporary resource files, ie "ap_ */ - public static final String DOT_RES = DOT + EXT_RES; - /** Dot-Extension for BMP files, i.e. ".bmp" */ - public static final String DOT_BMP = ".bmp"; //$NON-NLS-1$ - /** Dot-Extension for SVG files, i.e. ".svg" */ - public static final String DOT_SVG = ".svg"; //$NON-NLS-1$ - /** Dot-Extension for template files */ - public static final String DOT_FTL = ".ftl"; //$NON-NLS-1$ - /** Dot-Extension of text files, i.e. ".txt" */ - public static final String DOT_TXT = ".txt"; //$NON-NLS-1$ - /** Dot-Extension for Android archive files */ - public static final String DOT_AAR = DOT + EXT_AAR; //$NON-NLS-1$ - /** Dot-Extension for Java heap dumps. */ - public static final String DOT_HPROF = DOT + EXT_HPROF; //$NON-NLS-1$ - /** Resource base name for java files and classes */ - public static final String FN_RESOURCE_BASE = "R"; //$NON-NLS-1$ - /** Resource java class filename, i.e. "R.java" */ - public static final String FN_RESOURCE_CLASS = FN_RESOURCE_BASE + DOT_JAVA; - /** Resource class file filename, i.e. "R.class" */ - public static final String FN_COMPILED_RESOURCE_CLASS = FN_RESOURCE_BASE + DOT_CLASS; - /** Resource text filename, i.e. "R.txt" */ - public static final String FN_RESOURCE_TEXT = FN_RESOURCE_BASE + DOT_TXT; - /** Filename for public resources in AAR archives */ - public static final String FN_PUBLIC_TXT = "public.txt"; - /** Generated manifest class name */ - public static final String FN_MANIFEST_BASE = "Manifest"; //$NON-NLS-1$ - /** Generated BuildConfig class name */ - public static final String FN_BUILD_CONFIG_BASE = "BuildConfig"; //$NON-NLS-1$ - /** Manifest java class filename, i.e. "Manifest.java" */ - public static final String FN_MANIFEST_CLASS = FN_MANIFEST_BASE + DOT_JAVA; - /** BuildConfig java class filename, i.e. "BuildConfig.java" */ - public static final String FN_BUILD_CONFIG = FN_BUILD_CONFIG_BASE + DOT_JAVA; - public static final String DRAWABLE_FOLDER = "drawable"; //$NON-NLS-1$ - public static final String DRAWABLE_XHDPI = "drawable-xhdpi"; //$NON-NLS-1$ - public static final String DRAWABLE_XXHDPI = "drawable-xxhdpi"; //$NON-NLS-1$ - public static final String DRAWABLE_XXXHDPI = "drawable-xxxhdpi"; //$NON-NLS-1$ - public static final String DRAWABLE_HDPI = "drawable-hdpi"; //$NON-NLS-1$ - public static final String DRAWABLE_MDPI = "drawable-mdpi"; //$NON-NLS-1$ - public static final String DRAWABLE_LDPI = "drawable-ldpi"; //$NON-NLS-1$ - // Resources - public static final String PREFIX_RESOURCE_REF = "@"; //$NON-NLS-1$ - public static final String PREFIX_THEME_REF = "?"; //$NON-NLS-1$ - public static final String PREFIX_BINDING_EXPR = "@{"; //$NON-NLS-1$ - public static final String ANDROID_PREFIX = "@android:"; //$NON-NLS-1$ - public static final String ANDROID_THEME_PREFIX = "?android:"; //$NON-NLS-1$ - public static final String LAYOUT_RESOURCE_PREFIX = "@layout/"; //$NON-NLS-1$ - public static final String STYLE_RESOURCE_PREFIX = "@style/"; //$NON-NLS-1$ - public static final String COLOR_RESOURCE_PREFIX = "@color/"; //$NON-NLS-1$ - public static final String NEW_ID_PREFIX = "@+id/"; //$NON-NLS-1$ - public static final String ID_PREFIX = "@id/"; //$NON-NLS-1$ - public static final String DRAWABLE_PREFIX = "@drawable/"; //$NON-NLS-1$ - public static final String STRING_PREFIX = "@string/"; //$NON-NLS-1$ - public static final String DIMEN_PREFIX = "@dimen/"; //$NON-NLS-1$ - public static final String MIPMAP_PREFIX = "@mipmap/"; //$NON-NLS-1$ - public static final String ANDROID_LAYOUT_RESOURCE_PREFIX = "@android:layout/"; //$NON-NLS-1$ - public static final String ANDROID_STYLE_RESOURCE_PREFIX = "@android:style/"; //$NON-NLS-1$ - public static final String ANDROID_COLOR_RESOURCE_PREFIX = "@android:color/"; //$NON-NLS-1$ - public static final String ANDROID_NEW_ID_PREFIX = "@android:+id/"; //$NON-NLS-1$ - public static final String ANDROID_ID_PREFIX = "@android:id/"; //$NON-NLS-1$ - public static final String ANDROID_DRAWABLE_PREFIX = "@android:drawable/"; //$NON-NLS-1$ - public static final String ANDROID_STRING_PREFIX = "@android:string/"; //$NON-NLS-1$ - public static final String RESOURCE_CLZ_ID = "id"; //$NON-NLS-1$ - public static final String RESOURCE_CLZ_COLOR = "color"; //$NON-NLS-1$ - public static final String RESOURCE_CLZ_ARRAY = "array"; //$NON-NLS-1$ - public static final String RESOURCE_CLZ_ATTR = "attr"; //$NON-NLS-1$ - public static final String RESOURCE_CLR_STYLEABLE = "styleable"; //$NON-NLS-1$ - public static final String NULL_RESOURCE = "@null"; //$NON-NLS-1$ - public static final String TRANSPARENT_COLOR = "@android:color/transparent"; //$NON-NLS-1$ - public static final String REFERENCE_STYLE = "style/"; //$NON-NLS-1$ - public static final String PREFIX_ANDROID = "android:"; //$NON-NLS-1$ - // Resource Types - public static final String DRAWABLE_TYPE = "drawable"; //$NON-NLS-1$ - public static final String MENU_TYPE = "menu"; //$NON-NLS-1$ - // Packages - public static final String ANDROID_PKG_PREFIX = "android."; //$NON-NLS-1$ - public static final String WIDGET_PKG_PREFIX = "android.widget."; //$NON-NLS-1$ - public static final String VIEW_PKG_PREFIX = "android.view."; //$NON-NLS-1$ - // Project properties - public static final String ANDROID_LIBRARY = "android.library"; //$NON-NLS-1$ - public static final String PROGUARD_CONFIG = "proguard.config"; //$NON-NLS-1$ - public static final String ANDROID_LIBRARY_REFERENCE_FORMAT = "android.library.reference.%1$d";//$NON-NLS-1$ - public static final String PROJECT_PROPERTIES = "project.properties";//$NON-NLS-1$ - // Java References - public static final String ATTR_REF_PREFIX = "?attr/"; //$NON-NLS-1$ - public static final String R_PREFIX = "R."; //$NON-NLS-1$ - public static final String R_ID_PREFIX = "R.id."; //$NON-NLS-1$ - public static final String R_LAYOUT_RESOURCE_PREFIX = "R.layout."; //$NON-NLS-1$ - public static final String R_DRAWABLE_PREFIX = "R.drawable."; //$NON-NLS-1$ - public static final String R_STYLEABLE_PREFIX = "R.styleable."; //$NON-NLS-1$ - public static final String R_ATTR_PREFIX = "R.attr."; //$NON-NLS-1$ - // Attributes related to tools - public static final String ATTR_IGNORE = "ignore"; //$NON-NLS-1$ - public static final String ATTR_LOCALE = "locale"; //$NON-NLS-1$ - // SuppressLint - public static final String SUPPRESS_ALL = "all"; //$NON-NLS-1$ - public static final String SUPPRESS_LINT = "SuppressLint"; //$NON-NLS-1$ - public static final String TARGET_API = "TargetApi"; //$NON-NLS-1$ - public static final String ATTR_TARGET_API = "targetApi"; //$NON-NLS-1$ - public static final String FQCN_SUPPRESS_LINT = "android.annotation." + SUPPRESS_LINT; //$NON-NLS-1$ - public static final String FQCN_TARGET_API = "android.annotation." + TARGET_API; //$NON-NLS-1$ - // Class Names - public static final String CONSTRUCTOR_NAME = ""; //$NON-NLS-1$ - public static final String CLASS_CONSTRUCTOR = ""; //$NON-NLS-1$ - public static final String FRAGMENT = "android/app/Fragment"; //$NON-NLS-1$ - public static final String FRAGMENT_V4 = "android/support/v4/app/Fragment"; //$NON-NLS-1$ - public static final String ANDROID_APP_ACTIVITY = "android/app/Activity"; //$NON-NLS-1$ - public static final String ANDROID_APP_SERVICE = "android/app/Service"; //$NON-NLS-1$ - public static final String ANDROID_CONTENT_CONTENT_PROVIDER = - "android/content/ContentProvider"; //$NON-NLS-1$ - public static final String ANDROID_CONTENT_BROADCAST_RECEIVER = - "android/content/BroadcastReceiver"; //$NON-NLS-1$ - public static final String ANDROID_VIEW_VIEW = "android/view/View"; //$NON-NLS-1$ - // Method Names - public static final String FORMAT_METHOD = "format"; //$NON-NLS-1$ - public static final String GET_STRING_METHOD = "getString"; //$NON-NLS-1$ - public static final String ATTR_TAG = "tag"; //$NON-NLS-1$ - public static final String ATTR_NUM_COLUMNS = "numColumns"; //$NON-NLS-1$ - // Some common layout element names - public static final String CALENDAR_VIEW = "CalendarView"; //$NON-NLS-1$ - public static final String SPACE = "Space"; //$NON-NLS-1$ - public static final String GESTURE_OVERLAY_VIEW = "GestureOverlayView";//$NON-NLS-1$ - public static final String ATTR_HANDLE = "handle"; //$NON-NLS-1$ - public static final String ATTR_CONTENT = "content"; //$NON-NLS-1$ - public static final String ATTR_CHECKED = "checked"; //$NON-NLS-1$ - // TextView - public static final String ATTR_DRAWABLE_RIGHT = "drawableRight"; //$NON-NLS-1$ - public static final String ATTR_DRAWABLE_LEFT = "drawableLeft"; //$NON-NLS-1$ - public static final String ATTR_DRAWABLE_START = "drawableStart"; //$NON-NLS-1$ - public static final String ATTR_DRAWABLE_END = "drawableEnd"; //$NON-NLS-1$ - public static final String ATTR_DRAWABLE_BOTTOM = "drawableBottom"; //$NON-NLS-1$ - public static final String ATTR_DRAWABLE_TOP = "drawableTop"; //$NON-NLS-1$ - public static final String ATTR_DRAWABLE_PADDING = "drawablePadding"; //$NON-NLS-1$ - public static final String ATTR_USE_DEFAULT_MARGINS = "useDefaultMargins"; //$NON-NLS-1$ - public static final String ATTR_MARGINS_INCLUDED_IN_ALIGNMENT = "marginsIncludedInAlignment"; //$NON-NLS-1$ - public static final String VALUE_WRAP_CONTENT = "wrap_content"; //$NON-NLS-1$ - public static final String VALUE_FALSE= "false"; //$NON-NLS-1$ - public static final String VALUE_N_DP = "%ddp"; //$NON-NLS-1$ - public static final String VALUE_ZERO_DP = "0dp"; //$NON-NLS-1$ - public static final String VALUE_ONE_DP = "1dp"; //$NON-NLS-1$ - public static final String VALUE_TOP = "top"; //$NON-NLS-1$ - public static final String VALUE_BOTTOM = "bottom"; //$NON-NLS-1$ - public static final String VALUE_CENTER_VERTICAL = "center_vertical"; //$NON-NLS-1$ - public static final String VALUE_CENTER_HORIZONTAL = "center_horizontal"; //$NON-NLS-1$ - public static final String VALUE_FILL_HORIZONTAL = "fill_horizontal"; //$NON-NLS-1$ - public static final String VALUE_FILL_VERTICAL = "fill_vertical"; //$NON-NLS-1$ - public static final String VALUE_0 = "0"; //$NON-NLS-1$ - public static final String VALUE_1 = "1"; //$NON-NLS-1$ - // Gravity values. These have the GRAVITY_ prefix in front of value because we already - // have VALUE_CENTER_HORIZONTAL defined for layouts, and its definition conflicts - // (centerHorizontal versus center_horizontal) - public static final String GRAVITY_VALUE_ = "center"; //$NON-NLS-1$ - public static final String GRAVITY_VALUE_CENTER = "center"; //$NON-NLS-1$ - public static final String GRAVITY_VALUE_LEFT = "left"; //$NON-NLS-1$ - public static final String GRAVITY_VALUE_RIGHT = "right"; //$NON-NLS-1$ - public static final String GRAVITY_VALUE_START = "start"; //$NON-NLS-1$ - public static final String GRAVITY_VALUE_END = "end"; //$NON-NLS-1$ - public static final String GRAVITY_VALUE_BOTTOM = "bottom"; //$NON-NLS-1$ - public static final String GRAVITY_VALUE_TOP = "top"; //$NON-NLS-1$ - public static final String GRAVITY_VALUE_FILL_HORIZONTAL = "fill_horizontal"; //$NON-NLS-1$ - public static final String GRAVITY_VALUE_FILL_VERTICAL = "fill_vertical"; //$NON-NLS-1$ - public static final String GRAVITY_VALUE_CENTER_HORIZONTAL = "center_horizontal"; //$NON-NLS-1$ - public static final String GRAVITY_VALUE_CENTER_VERTICAL = "center_vertical"; //$NON-NLS-1$ - public static final String GRAVITY_VALUE_FILL = "fill"; //$NON-NLS-1$ - /** - * The top level android package as a prefix, "android.". - */ - public static final String ANDROID_SUPPORT_PKG_PREFIX = ANDROID_PKG_PREFIX + "support."; //$NON-NLS-1$ - /** The android.view. package prefix */ - public static final String ANDROID_VIEW_PKG = ANDROID_PKG_PREFIX + "view."; //$NON-NLS-1$ - /** The android.widget. package prefix */ - public static final String ANDROID_WIDGET_PREFIX = ANDROID_PKG_PREFIX + "widget."; //$NON-NLS-1$ - /** The android.webkit. package prefix */ - public static final String ANDROID_WEBKIT_PKG = ANDROID_PKG_PREFIX + "webkit."; //$NON-NLS-1$ - /** The LayoutParams inner-class name suffix, .LayoutParams */ - public static final String DOT_LAYOUT_PARAMS = ".LayoutParams"; //$NON-NLS-1$ - /** The fully qualified class name of an EditText view */ - public static final String FQCN_EDIT_TEXT = "android.widget.EditText"; //$NON-NLS-1$ - /** The fully qualified class name of a LinearLayout view */ - public static final String FQCN_LINEAR_LAYOUT = "android.widget.LinearLayout"; //$NON-NLS-1$ - /** The fully qualified class name of a RelativeLayout view */ - public static final String FQCN_RELATIVE_LAYOUT = "android.widget.RelativeLayout"; //$NON-NLS-1$ - /** The fully qualified class name of a RelativeLayout view */ - public static final String FQCN_GRID_LAYOUT = "android.widget.GridLayout"; //$NON-NLS-1$ - public static final String FQCN_GRID_LAYOUT_V7 = "android.support.v7.widget.GridLayout"; //$NON-NLS-1$ - /** The fully qualified class name of a FrameLayout view */ - public static final String FQCN_FRAME_LAYOUT = "android.widget.FrameLayout"; //$NON-NLS-1$ - /** The fully qualified class name of a TableRow view */ - public static final String FQCN_TABLE_ROW = "android.widget.TableRow"; //$NON-NLS-1$ - /** The fully qualified class name of a TableLayout view */ - public static final String FQCN_TABLE_LAYOUT = "android.widget.TableLayout"; //$NON-NLS-1$ - /** The fully qualified class name of a GridView view */ - public static final String FQCN_GRID_VIEW = "android.widget.GridView"; //$NON-NLS-1$ - /** The fully qualified class name of a TabWidget view */ - public static final String FQCN_TAB_WIDGET = "android.widget.TabWidget"; //$NON-NLS-1$ - /** The fully qualified class name of a Button view */ - public static final String FQCN_BUTTON = "android.widget.Button"; //$NON-NLS-1$ - /** The fully qualified class name of a RadioButton view */ - public static final String FQCN_RADIO_BUTTON = "android.widget.RadioButton"; //$NON-NLS-1$ - /** The fully qualified class name of a ToggleButton view */ - public static final String FQCN_TOGGLE_BUTTON = "android.widget.ToggleButton"; //$NON-NLS-1$ - /** The fully qualified class name of a Spinner view */ - public static final String FQCN_SPINNER = "android.widget.Spinner"; //$NON-NLS-1$ - /** The fully qualified class name of an AdapterView */ - public static final String FQCN_ADAPTER_VIEW = "android.widget.AdapterView"; //$NON-NLS-1$ - /** The fully qualified class name of a ListView */ - public static final String FQCN_LIST_VIEW = "android.widget.ListView"; //$NON-NLS-1$ - /** The fully qualified class name of an ExpandableListView */ - public static final String FQCN_EXPANDABLE_LIST_VIEW = "android.widget.ExpandableListView"; //$NON-NLS-1$ - /** The fully qualified class name of a GestureOverlayView */ - public static final String FQCN_GESTURE_OVERLAY_VIEW = "android.gesture.GestureOverlayView"; //$NON-NLS-1$ - /** The fully qualified class name of a DatePicker */ - public static final String FQCN_DATE_PICKER = "android.widget.DatePicker"; //$NON-NLS-1$ - /** The fully qualified class name of a TimePicker */ - public static final String FQCN_TIME_PICKER = "android.widget.TimePicker"; //$NON-NLS-1$ - /** The fully qualified class name of a RadioGroup */ - public static final String FQCN_RADIO_GROUP = "android.widgets.RadioGroup"; //$NON-NLS-1$ - /** The fully qualified class name of a Space */ - public static final String FQCN_SPACE = "android.widget.Space"; //$NON-NLS-1$ - public static final String FQCN_SPACE_V7 = "android.support.v7.widget.Space"; //$NON-NLS-1$ - /** The fully qualified class name of a TextView view */ - public static final String FQCN_TEXT_VIEW = "android.widget.TextView"; //$NON-NLS-1$ - /** The fully qualified class name of an ImageView view */ - public static final String FQCN_IMAGE_VIEW = "android.widget.ImageView"; //$NON-NLS-1$ - public static final String ATTR_SRC = "src"; //$NON-NLS-1$ - public static final String ATTR_GRAVITY = "gravity"; //$NON-NLS-1$ - public static final String ATTR_WEIGHT_SUM = "weightSum"; //$NON-NLS-1$ - public static final String ATTR_EMS = "ems"; //$NON-NLS-1$ - public static final String VALUE_HORIZONTAL = "horizontal"; //$NON-NLS-1$ - public static final String GRADLE_PLUGIN_NAME = "com.android.tools.build:gradle:"; - public static final String GRADLE_MINIMUM_VERSION = "2.2.1"; - public static final String GRADLE_LATEST_VERSION = "2.4"; - public static final String GRADLE_PLUGIN_MINIMUM_VERSION = "1.0.0"; - public static final String GRADLE_PLUGIN_RECOMMENDED_VERSION = "1.3.0"; - public static final String GRADLE_PLUGIN_LATEST_VERSION = GRADLE_PLUGIN_RECOMMENDED_VERSION; - public static final String MIN_BUILD_TOOLS_VERSION = "19.1.0"; - public static final String SUPPORT_LIB_ARTIFACT = "com.android.support:support-v4"; - public static final String APPCOMPAT_LIB_ARTIFACT = "com.android.support:appcompat-v7"; - // Annotations - public static final String SUPPORT_ANNOTATIONS_PREFIX = "android.support.annotation."; - public static final String INT_DEF_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "IntDef"; - public static final String STRING_DEF_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "StringDef"; - public static final String TYPE_DEF_VALUE_ATTRIBUTE = "value"; - public static final String TYPE_DEF_FLAG_ATTRIBUTE = "flag"; - public static final String FN_ANNOTATIONS_ZIP = "annotations.zip"; - // Data Binding MISC - public static final String DATA_BINDING_LIB_ARTIFACT = "com.android.databinding:library"; - public static final String[] TAGS_DATA_BINDING = new String[]{TAG_VARIABLE, - TAG_IMPORT, TAG_LAYOUT, TAG_DATA}; - public static final String[] ATTRS_DATA_BINDING = new String[]{ATTR_NAME, - ATTR_TYPE, ATTR_CLASS, ATTR_ALIAS}; -} \ No newline at end of file + } + /** Default anim resource folder name, i.e. "anim" */ + public static final String FD_RES_ANIM = "anim"; // $NON-NLS-1$ + /** Default animator resource folder name, i.e. "animator" */ + public static final String FD_RES_ANIMATOR = "animator"; // $NON-NLS-1$ + /** Default color resource folder name, i.e. "color" */ + public static final String FD_RES_COLOR = "color"; // $NON-NLS-1$ + /** Default drawable resource folder name, i.e. "drawable" */ + public static final String FD_RES_DRAWABLE = "drawable"; // $NON-NLS-1$ + /** Default interpolator resource folder name, i.e. "interpolator" */ + public static final String FD_RES_INTERPOLATOR = "interpolator"; // $NON-NLS-1$ + /** Default layout resource folder name, i.e. "layout" */ + public static final String FD_RES_LAYOUT = "layout"; // $NON-NLS-1$ + /** Default menu resource folder name, i.e. "menu" */ + public static final String FD_RES_MENU = "menu"; // $NON-NLS-1$ + /** Default menu resource folder name, i.e. "mipmap" */ + public static final String FD_RES_MIPMAP = "mipmap"; // $NON-NLS-1$ + /** Default values resource folder name, i.e. "values" */ + public static final String FD_RES_VALUES = "values"; // $NON-NLS-1$ + /** Default xml resource folder name, i.e. "xml" */ + public static final String FD_RES_XML = "xml"; // $NON-NLS-1$ + /** Default raw resource folder name, i.e. "raw" */ + public static final String FD_RES_RAW = "raw"; // $NON-NLS-1$ + /** Separator between the resource folder qualifier. */ + public static final String RES_QUALIFIER_SEP = "-"; // $NON-NLS-1$ + /** Namespace used in XML files for Android attributes */ + // ---- XML ---- + public static final String AAPT_URI = "http://schemas.android.com/aapt"; + /** URI of the reserved "xmlns" prefix */ + public static final String XMLNS_URI = "http://www.w3.org/2000/xmlns/"; // $NON-NLS-1$ + /** The "xmlns" attribute name */ + public static final String XMLNS = "xmlns"; // $NON-NLS-1$ + /** The default prefix used for the {@link #XMLNS_URI} */ + public static final String XMLNS_PREFIX = "xmlns:"; // $NON-NLS-1$ + /** Qualified name of the xmlns android declaration element */ + public static final String XMLNS_ANDROID = "xmlns:android"; // $NON-NLS-1$ + /** The default prefix used for the {@link #ANDROID_URI} name space */ + public static final String ANDROID_NS_NAME = "android"; // $NON-NLS-1$ + /** The default prefix used for the {@link #ANDROID_URI} name space including the colon */ + public static final String ANDROID_NS_NAME_PREFIX = "android:"; // $NON-NLS-1$ + + public static final int ANDROID_NS_NAME_PREFIX_LEN = ANDROID_NS_NAME_PREFIX.length(); + /** The default prefix used for the app */ + public static final String APP_PREFIX = "app"; // $NON-NLS-1$ + /** The entity for the ampersand character */ + public static final String AMP_ENTITY = "&"; // $NON-NLS-1$ + /** The entity for the quote character */ + public static final String QUOT_ENTITY = """; // $NON-NLS-1$ + /** The entity for the apostrophe character */ + public static final String APOS_ENTITY = "'"; // $NON-NLS-1$ + /** The entity for the less than character */ + public static final String LT_ENTITY = "<"; // $NON-NLS-1$ + /** The entity for the greater than character */ + public static final String GT_ENTITY = ">"; // $NON-NLS-1$ + + public static final String NEWLINE_ENTITY = " "; + // ---- Elements and Attributes ---- + /** Namespace prefix used for all resources */ + public static final String URI_PREFIX = "http://schemas.android.com/apk/res/"; // $NON-NLS-1$ + /** Namespace used in XML files for Android attributes */ + public static final String ANDROID_URI = + "http://schemas.android.com/apk/res/android"; //$NON-NLS-1$ + /** Namespace used in XML files for Android Tooling attributes */ + public static final String TOOLS_URI = "http://schemas.android.com/tools"; // $NON-NLS-1$ + /** Namespace used for auto-adjusting namespaces */ + public static final String AUTO_URI = "http://schemas.android.com/apk/res-auto"; // $NON-NLS-1$ + /** Default prefix used for tools attributes */ + public static final String TOOLS_PREFIX = "tools"; // $NON-NLS-1$ + + public static final String R_CLASS = "R"; // $NON-NLS-1$ + public static final String ANDROID_PKG = "android"; // $NON-NLS-1$ + // Tags: Manifest + public static final String TAG_SERVICE = "service"; // $NON-NLS-1$ + public static final String TAG_PERMISSION = "permission"; // $NON-NLS-1$ + public static final String TAG_USES_FEATURE = "uses-feature"; // $NON-NLS-1$ + public static final String TAG_USES_PERMISSION = "uses-permission"; // $NON-NLS-1$ + public static final String TAG_USES_LIBRARY = "uses-library"; // $NON-NLS-1$ + public static final String TAG_APPLICATION = "application"; // $NON-NLS-1$ + public static final String TAG_INTENT_FILTER = "intent-filter"; // $NON-NLS-1$ + public static final String TAG_USES_SDK = "uses-sdk"; // $NON-NLS-1$ + public static final String TAG_ACTIVITY = "activity"; // $NON-NLS-1$ + public static final String TAG_RECEIVER = "receiver"; // $NON-NLS-1$ + public static final String TAG_PROVIDER = "provider"; // $NON-NLS-1$ + public static final String TAG_GRANT_PERMISSION = "grant-uri-permission"; // $NON-NLS-1$ + public static final String TAG_PATH_PERMISSION = "path-permission"; // $NON-NLS-1$ + // Tags: Resources + public static final String TAG_RESOURCES = "resources"; // $NON-NLS-1$ + public static final String TAG_STRING = "string"; // $NON-NLS-1$ + public static final String TAG_ARRAY = "array"; // $NON-NLS-1$ + public static final String TAG_STYLE = "style"; // $NON-NLS-1$ + public static final String TAG_ITEM = "item"; // $NON-NLS-1$ + public static final String TAG_GROUP = "group"; // $NON-NLS-1$ + public static final String TAG_STRING_ARRAY = "string-array"; // $NON-NLS-1$ + public static final String TAG_PLURALS = "plurals"; // $NON-NLS-1$ + public static final String TAG_INTEGER_ARRAY = "integer-array"; // $NON-NLS-1$ + public static final String TAG_COLOR = "color"; // $NON-NLS-1$ + public static final String TAG_DIMEN = "dimen"; // $NON-NLS-1$ + public static final String TAG_DRAWABLE = "drawable"; // $NON-NLS-1$ + public static final String TAG_MENU = "menu"; // $NON-NLS-1$ + public static final String TAG_ENUM = "enum"; // $NON-NLS-1$ + public static final String TAG_FLAG = "flag"; // $NON-NLS-1$ + public static final String TAG_ATTR = "attr"; // $NON-NLS-1$ + public static final String TAG_DECLARE_STYLEABLE = "declare-styleable"; // $NON-NLS-1$ + public static final String TAG_PUBLIC = "public"; // $NON-NLS-1$ + public static final String TAG_EAT_COMMENT = "eat-comment"; // $NON-NLS-1$ + public static final String TAG_SKIP = "skip"; // $NON-NLS-1$ + public static final String TAG_SELECTOR = "selector"; // $NON-NLS-1$ + // Tags: XML + public static final String TAG_HEADER = "header"; // $NON-NLS-1$ + public static final String TAG_APPWIDGET_PROVIDER = "appwidget-provider"; // $NON-NLS-1$ + public static final String TAG_PREFERENCE_SCREEN = "PreferenceScreen"; // $NON-NLS-1$ + // Tags: Layouts + public static final String VIEW_TAG = "view"; // $NON-NLS-1$ + public static final String VIEW_INCLUDE = "include"; // $NON-NLS-1$ + public static final String VIEW_MERGE = "merge"; // $NON-NLS-1$ + public static final String VIEW_FRAGMENT = "fragment"; // $NON-NLS-1$ + public static final String REQUEST_FOCUS = "requestFocus"; // $NON-NLS-1$ + public static final String TAG = "tag"; // $NON-NLS-1$ + public static final String VIEW = "View"; // $NON-NLS-1$ + public static final String VIEW_GROUP = "ViewGroup"; // $NON-NLS-1$ + public static final String FRAME_LAYOUT = "FrameLayout"; // $NON-NLS-1$ + public static final String LINEAR_LAYOUT = "LinearLayout"; // $NON-NLS-1$ + public static final String RELATIVE_LAYOUT = "RelativeLayout"; // $NON-NLS-1$ + public static final String GRID_LAYOUT = "GridLayout"; // $NON-NLS-1$ + public static final String SCROLL_VIEW = "ScrollView"; // $NON-NLS-1$ + public static final String BUTTON = "Button"; // $NON-NLS-1$ + public static final String COMPOUND_BUTTON = "CompoundButton"; // $NON-NLS-1$ + public static final String ADAPTER_VIEW = "AdapterView"; // $NON-NLS-1$ + public static final String GALLERY = "Gallery"; // $NON-NLS-1$ + public static final String GRID_VIEW = "GridView"; // $NON-NLS-1$ + public static final String TAB_HOST = "TabHost"; // $NON-NLS-1$ + public static final String RADIO_GROUP = "RadioGroup"; // $NON-NLS-1$ + public static final String RADIO_BUTTON = "RadioButton"; // $NON-NLS-1$ + public static final String SWITCH = "Switch"; // $NON-NLS-1$ + public static final String EDIT_TEXT = "EditText"; // $NON-NLS-1$ + public static final String LIST_VIEW = "ListView"; // $NON-NLS-1$ + public static final String TEXT_VIEW = "TextView"; // $NON-NLS-1$ + public static final String CHECKED_TEXT_VIEW = "CheckedTextView"; // $NON-NLS-1$ + public static final String IMAGE_VIEW = "ImageView"; // $NON-NLS-1$ + public static final String SURFACE_VIEW = "SurfaceView"; // $NON-NLS-1$ + public static final String ABSOLUTE_LAYOUT = "AbsoluteLayout"; // $NON-NLS-1$ + public static final String TABLE_LAYOUT = "TableLayout"; // $NON-NLS-1$ + public static final String TABLE_ROW = "TableRow"; // $NON-NLS-1$ + public static final String TAB_WIDGET = "TabWidget"; // $NON-NLS-1$ + public static final String IMAGE_BUTTON = "ImageButton"; // $NON-NLS-1$ + public static final String SEEK_BAR = "SeekBar"; // $NON-NLS-1$ + public static final String VIEW_STUB = "ViewStub"; // $NON-NLS-1$ + public static final String SPINNER = "Spinner"; // $NON-NLS-1$ + public static final String WEB_VIEW = "WebView"; // $NON-NLS-1$ + public static final String TOGGLE_BUTTON = "ToggleButton"; // $NON-NLS-1$ + public static final String CHECK_BOX = "CheckBox"; // $NON-NLS-1$ + public static final String ABS_LIST_VIEW = "AbsListView"; // $NON-NLS-1$ + public static final String PROGRESS_BAR = "ProgressBar"; // $NON-NLS-1$ + public static final String ABS_SPINNER = "AbsSpinner"; // $NON-NLS-1$ + public static final String ABS_SEEK_BAR = "AbsSeekBar"; // $NON-NLS-1$ + public static final String VIEW_ANIMATOR = "ViewAnimator"; // $NON-NLS-1$ + public static final String VIEW_SWITCHER = "ViewSwitcher"; // $NON-NLS-1$ + public static final String EXPANDABLE_LIST_VIEW = "ExpandableListView"; // $NON-NLS-1$ + public static final String HORIZONTAL_SCROLL_VIEW = "HorizontalScrollView"; // $NON-NLS-1$ + public static final String MULTI_AUTO_COMPLETE_TEXT_VIEW = + "MultiAutoCompleteTextView"; //$NON-NLS-1$ + public static final String AUTO_COMPLETE_TEXT_VIEW = "AutoCompleteTextView"; // $NON-NLS-1$ + public static final String CHECKABLE = "Checkable"; // $NON-NLS-1$ + public static final String TEXTURE_VIEW = "TextureView"; // $NON-NLS-1$ + /* Android Design Support Tag Constants */ + public static final String COORDINATOR_LAYOUT = CLASS_COORDINATOR_LAYOUT; + public static final String APP_BAR_LAYOUT = CLASS_APP_BAR_LAYOUT; + public static final String FLOATING_ACTION_BUTTON = CLASS_FLOATING_ACTION_BUTTON; + public static final String COLLAPSING_TOOLBAR_LAYOUT = CLASS_COLLAPSING_TOOLBAR_LAYOUT; + public static final String NAVIGATION_VIEW = CLASS_NAVIGATION_VIEW; + public static final String SNACKBAR = CLASS_SNACKBAR; + public static final String TAB_LAYOUT = CLASS_TAB_LAYOUT; + public static final String TEXT_INPUT_LAYOUT = CLASS_TEXT_INPUT_LAYOUT; + // Tags: Drawables + public static final String TAG_BITMAP = "bitmap"; // $NON-NLS-1$ + // Tags: Data-Binding + public static final String TAG_LAYOUT = "layout"; // $NON-NLS-1$ + public static final String TAG_DATA = "data"; // $NON-NLS-1$ + public static final String TAG_VARIABLE = "variable"; // $NON-NLS-1$ + public static final String TAG_IMPORT = "import"; // $NON-NLS-1$ + // Attributes: Manifest + public static final String ATTR_EXPORTED = "exported"; // $NON-NLS-1$ + public static final String ATTR_PERMISSION = "permission"; // $NON-NLS-1$ + public static final String ATTR_MIN_SDK_VERSION = "minSdkVersion"; // $NON-NLS-1$ + public static final String ATTR_TARGET_SDK_VERSION = "targetSdkVersion"; // $NON-NLS-1$ + public static final String ATTR_ICON = "icon"; // $NON-NLS-1$ + public static final String ATTR_PACKAGE = "package"; // $NON-NLS-1$ + public static final String ATTR_CORE_APP = "coreApp"; // $NON-NLS-1$ + public static final String ATTR_THEME = "theme"; // $NON-NLS-1$ + public static final String ATTR_SCHEME = "scheme"; // $NON_NLS-1$ + public static final String ATTR_HOST = "host"; // $NON_NLS-1$ + public static final String ATTR_PATH = "path"; // $NON-NLS-1$ + public static final String ATTR_PATH_PREFIX = "pathPrefix"; // $NON-NLS-1$ + public static final String ATTR_PATH_PATTERN = "pathPattern"; // $NON-NLS-1$ + public static final String ATTR_ALLOW_BACKUP = "allowBackup"; // $NON_NLS-1$ + public static final String ATTR_DEBUGGABLE = "debuggable"; // $NON-NLS-1$ + public static final String ATTR_READ_PERMISSION = "readPermission"; // $NON_NLS-1$ + public static final String ATTR_WRITE_PERMISSION = "writePermission"; // $NON_NLS-1$ + public static final String ATTR_VERSION_CODE = "versionCode"; // $NON_NLS-1$ + public static final String ATTR_VERSION_NAME = "versionName"; // $NON_NLS-1$ + // Attributes: Resources + public static final String ATTR_NAME = "name"; // $NON-NLS-1$ + public static final String ATTR_FRAGMENT = "fragment"; // $NON-NLS-1$ + public static final String ATTR_TYPE = "type"; // $NON-NLS-1$ + public static final String ATTR_PARENT = "parent"; // $NON-NLS-1$ + public static final String ATTR_TRANSLATABLE = "translatable"; // $NON-NLS-1$ + public static final String ATTR_COLOR = "color"; // $NON-NLS-1$ + public static final String ATTR_DRAWABLE = "drawable"; // $NON-NLS-1$ + public static final String ATTR_VALUE = "value"; // $NON-NLS-1$ + public static final String ATTR_QUANTITY = "quantity"; // $NON-NLS-1$ + public static final String ATTR_FORMAT = "format"; // $NON-NLS-1$ + public static final String ATTR_PREPROCESSING = "preprocessing"; // $NON-NLS-1$ + // Attributes: Data-Binding + public static final String ATTR_ALIAS = "alias"; // $NON-NLS-1$ + // Attributes: Layout + public static final String ATTR_LAYOUT_RESOURCE_PREFIX = "layout_"; // $NON-NLS-1$ + public static final String ATTR_CLASS = "class"; // $NON-NLS-1$ + public static final String ATTR_STYLE = "style"; // $NON-NLS-1$ + public static final String ATTR_CONTEXT = "context"; // $NON-NLS-1$ + public static final String ATTR_ID = "id"; // $NON-NLS-1$ + public static final String ATTR_TEXT = "text"; // $NON-NLS-1$ + public static final String ATTR_TEXT_SIZE = "textSize"; // $NON-NLS-1$ + public static final String ATTR_LABEL = "label"; // $NON-NLS-1$ + public static final String ATTR_HINT = "hint"; // $NON-NLS-1$ + public static final String ATTR_PROMPT = "prompt"; // $NON-NLS-1$ + public static final String ATTR_ON_CLICK = "onClick"; // $NON-NLS-1$ + public static final String ATTR_INPUT_TYPE = "inputType"; // $NON-NLS-1$ + public static final String ATTR_INPUT_METHOD = "inputMethod"; // $NON-NLS-1$ + public static final String ATTR_LAYOUT_GRAVITY = "layout_gravity"; // $NON-NLS-1$ + public static final String ATTR_LAYOUT_WIDTH = "layout_width"; // $NON-NLS-1$ + public static final String ATTR_LAYOUT_HEIGHT = "layout_height"; // $NON-NLS-1$ + public static final String ATTR_LAYOUT_WEIGHT = "layout_weight"; // $NON-NLS-1$ + public static final String ATTR_PADDING = "padding"; // $NON-NLS-1$ + public static final String ATTR_PADDING_BOTTOM = "paddingBottom"; // $NON-NLS-1$ + public static final String ATTR_PADDING_TOP = "paddingTop"; // $NON-NLS-1$ + public static final String ATTR_PADDING_RIGHT = "paddingRight"; // $NON-NLS-1$ + public static final String ATTR_PADDING_LEFT = "paddingLeft"; // $NON-NLS-1$ + public static final String ATTR_PADDING_START = "paddingStart"; // $NON-NLS-1$ + public static final String ATTR_PADDING_END = "paddingEnd"; // $NON-NLS-1$ + public static final String ATTR_FOREGROUND = "foreground"; // $NON-NLS-1$ + public static final String ATTR_BACKGROUND = "background"; // $NON-NLS-1$ + public static final String ATTR_ORIENTATION = "orientation"; // $NON-NLS-1$ + public static final String ATTR_LAYOUT = "layout"; // $NON-NLS-1$ + public static final String ATTR_ROW_COUNT = "rowCount"; // $NON-NLS-1$ + public static final String ATTR_COLUMN_COUNT = "columnCount"; // $NON-NLS-1$ + public static final String ATTR_LABEL_FOR = "labelFor"; // $NON-NLS-1$ + public static final String ATTR_BASELINE_ALIGNED = "baselineAligned"; // $NON-NLS-1$ + public static final String ATTR_CONTENT_DESCRIPTION = "contentDescription"; // $NON-NLS-1$ + public static final String ATTR_IME_ACTION_LABEL = "imeActionLabel"; // $NON-NLS-1$ + public static final String ATTR_PRIVATE_IME_OPTIONS = "privateImeOptions"; // $NON-NLS-1$ + public static final String VALUE_NONE = "none"; // $NON-NLS-1$ + public static final String VALUE_NO = "no"; // $NON-NLS-1$ + public static final String ATTR_NUMERIC = "numeric"; // $NON-NLS-1$ + public static final String ATTR_IME_ACTION_ID = "imeActionId"; // $NON-NLS-1$ + public static final String ATTR_IME_OPTIONS = "imeOptions"; // $NON-NLS-1$ + public static final String ATTR_FREEZES_TEXT = "freezesText"; // $NON-NLS-1$ + public static final String ATTR_EDITOR_EXTRAS = "editorExtras"; // $NON-NLS-1$ + public static final String ATTR_EDITABLE = "editable"; // $NON-NLS-1$ + public static final String ATTR_DIGITS = "digits"; // $NON-NLS-1$ + public static final String ATTR_CURSOR_VISIBLE = "cursorVisible"; // $NON-NLS-1$ + public static final String ATTR_CAPITALIZE = "capitalize"; // $NON-NLS-1$ + public static final String ATTR_PHONE_NUMBER = "phoneNumber"; // $NON-NLS-1$ + public static final String ATTR_PASSWORD = "password"; // $NON-NLS-1$ + public static final String ATTR_BUFFER_TYPE = "bufferType"; // $NON-NLS-1$ + public static final String ATTR_AUTO_TEXT = "autoText"; // $NON-NLS-1$ + public static final String ATTR_ENABLED = "enabled"; // $NON-NLS-1$ + public static final String ATTR_SINGLE_LINE = "singleLine"; // $NON-NLS-1$ + public static final String ATTR_SCALE_TYPE = "scaleType"; // $NON-NLS-1$ + public static final String ATTR_VISIBILITY = "visibility"; // $NON-NLS-1$ + public static final String ATTR_TEXT_IS_SELECTABLE = "textIsSelectable"; // $NON-NLS-1$ + public static final String ATTR_IMPORTANT_FOR_ACCESSIBILITY = + "importantForAccessibility"; //$NON-NLS-1$ + public static final String ATTR_LIST_PREFERRED_ITEM_PADDING_LEFT = + "listPreferredItemPaddingLeft"; //$NON-NLS-1$ + public static final String ATTR_LIST_PREFERRED_ITEM_PADDING_RIGHT = + "listPreferredItemPaddingRight"; //$NON-NLS-1$ + public static final String ATTR_LIST_PREFERRED_ITEM_PADDING_START = + "listPreferredItemPaddingStart"; //$NON-NLS-1$ + public static final String ATTR_LIST_PREFERRED_ITEM_PADDING_END = + "listPreferredItemPaddingEnd"; //$NON-NLS-1$ + public static final String ATTR_INDEX = "index"; // $NON-NLS-1$ + // AbsoluteLayout layout params + public static final String ATTR_LAYOUT_Y = "layout_y"; // $NON-NLS-1$ + public static final String ATTR_LAYOUT_X = "layout_x"; // $NON-NLS-1$ + // GridLayout layout params + public static final String ATTR_LAYOUT_ROW = "layout_row"; // $NON-NLS-1$ + public static final String ATTR_LAYOUT_ROW_SPAN = "layout_rowSpan"; // $NON-NLS-1$ + public static final String ATTR_LAYOUT_COLUMN = "layout_column"; // $NON-NLS-1$ + public static final String ATTR_LAYOUT_COLUMN_SPAN = "layout_columnSpan"; // $NON-NLS-1$ + // TableRow + public static final String ATTR_LAYOUT_SPAN = "layout_span"; // $NON-NLS-1$ + // RelativeLayout layout params: + public static final String ATTR_LAYOUT_ALIGN_LEFT = "layout_alignLeft"; // $NON-NLS-1$ + public static final String ATTR_LAYOUT_ALIGN_RIGHT = "layout_alignRight"; // $NON-NLS-1$ + public static final String ATTR_LAYOUT_ALIGN_START = "layout_alignStart"; // $NON-NLS-1$ + public static final String ATTR_LAYOUT_ALIGN_END = "layout_alignEnd"; // $NON-NLS-1$ + public static final String ATTR_LAYOUT_ALIGN_TOP = "layout_alignTop"; // $NON-NLS-1$ + public static final String ATTR_LAYOUT_ALIGN_BOTTOM = "layout_alignBottom"; // $NON-NLS-1$ + public static final String ATTR_LAYOUT_ALIGN_PARENT_LEFT = + "layout_alignParentLeft"; //$NON-NLS-1$ + public static final String ATTR_LAYOUT_ALIGN_PARENT_RIGHT = + "layout_alignParentRight"; //$NON-NLS-1$ + public static final String ATTR_LAYOUT_ALIGN_PARENT_START = + "layout_alignParentStart"; //$NON-NLS-1$ + public static final String ATTR_LAYOUT_ALIGN_PARENT_END = "layout_alignParentEnd"; // $NON-NLS-1$ + public static final String ATTR_LAYOUT_ALIGN_PARENT_TOP = "layout_alignParentTop"; // $NON-NLS-1$ + public static final String ATTR_LAYOUT_ALIGN_PARENT_BOTTOM = + "layout_alignParentBottom"; //$NON-NLS-1$ + public static final String ATTR_LAYOUT_ALIGN_WITH_PARENT_MISSING = + "layout_alignWithParentIfMissing"; //$NON-NLS-1$ + public static final String ATTR_LAYOUT_ALIGN_BASELINE = "layout_alignBaseline"; // $NON-NLS-1$ + public static final String ATTR_LAYOUT_CENTER_IN_PARENT = "layout_centerInParent"; // $NON-NLS-1$ + public static final String ATTR_LAYOUT_CENTER_VERTICAL = "layout_centerVertical"; // $NON-NLS-1$ + public static final String ATTR_LAYOUT_CENTER_HORIZONTAL = + "layout_centerHorizontal"; //$NON-NLS-1$ + public static final String ATTR_LAYOUT_TO_RIGHT_OF = "layout_toRightOf"; // $NON-NLS-1$ + public static final String ATTR_LAYOUT_TO_LEFT_OF = "layout_toLeftOf"; // $NON-NLS-1$ + public static final String ATTR_LAYOUT_TO_START_OF = "layout_toStartOf"; // $NON-NLS-1$ + public static final String ATTR_LAYOUT_TO_END_OF = "layout_toEndOf"; // $NON-NLS-1$ + public static final String ATTR_LAYOUT_BELOW = "layout_below"; // $NON-NLS-1$ + public static final String ATTR_LAYOUT_ABOVE = "layout_above"; // $NON-NLS-1$ + // Margins + public static final String ATTR_LAYOUT_MARGIN = "layout_margin"; // $NON-NLS-1$ + public static final String ATTR_LAYOUT_MARGIN_LEFT = "layout_marginLeft"; // $NON-NLS-1$ + public static final String ATTR_LAYOUT_MARGIN_RIGHT = "layout_marginRight"; // $NON-NLS-1$ + public static final String ATTR_LAYOUT_MARGIN_START = "layout_marginStart"; // $NON-NLS-1$ + public static final String ATTR_LAYOUT_MARGIN_END = "layout_marginEnd"; // $NON-NLS-1$ + public static final String ATTR_LAYOUT_MARGIN_TOP = "layout_marginTop"; // $NON-NLS-1$ + public static final String ATTR_LAYOUT_MARGIN_BOTTOM = "layout_marginBottom"; // $NON-NLS-1$ + // Attributes: Drawables + public static final String ATTR_TILE_MODE = "tileMode"; // $NON-NLS-1$ + // Attributes: CoordinatorLayout + public static final String ATTR_LAYOUT_ANCHOR = "layout_anchor"; // $NON-NLS-1$ + public static final String ATTR_LAYOUT_ANCHOR_GRAVITY = "layout_anchorGravity"; // $NON-NLS-1$ + public static final String ATTR_LAYOUT_BEHAVIOR = "layout_behavior"; // $NON-NLS-1$ + public static final String ATTR_LAYOUT_KEYLINE = "layout_keyline"; // $NON-NLS-1$ + // Values: Manifest + public static final String VALUE_SPLIT_ACTION_BAR_WHEN_NARROW = + "splitActionBarWhenNarrow"; // NON-NLS-$1 + // Values: Layouts + public static final String VALUE_FILL_PARENT = "fill_parent"; // $NON-NLS-1$ + public static final String VALUE_MATCH_PARENT = "match_parent"; // $NON-NLS-1$ + public static final String VALUE_VERTICAL = "vertical"; // $NON-NLS-1$ + public static final String VALUE_TRUE = "true"; // $NON-NLS-1$ + public static final String VALUE_EDITABLE = "editable"; // $NON-NLS-1$ + public static final String VALUE_AUTO_FIT = "auto_fit"; // $NON-NLS-1$ + public static final String VALUE_SELECTABLE_ITEM_BACKGROUND = + "?android:attr/selectableItemBackground"; //$NON-NLS-1$ + // Values: Resources + public static final String VALUE_ID = "id"; // $NON-NLS-1$ + // Values: Drawables + public static final String VALUE_DISABLED = "disabled"; // $NON-NLS-1$ + public static final String VALUE_CLAMP = "clamp"; // $NON-NLS-1$ + // Menus + public static final String ATTR_SHOW_AS_ACTION = "showAsAction"; // $NON-NLS-1$ + public static final String ATTR_TITLE = "title"; // $NON-NLS-1$ + public static final String ATTR_VISIBLE = "visible"; // $NON-NLS-1$ + public static final String VALUE_IF_ROOM = "ifRoom"; // $NON-NLS-1$ + public static final String VALUE_ALWAYS = "always"; // $NON-NLS-1$ + // Units + public static final String UNIT_DP = "dp"; // $NON-NLS-1$ + public static final String UNIT_DIP = "dip"; // $NON-NLS-1$ + public static final String UNIT_SP = "sp"; // $NON-NLS-1$ + public static final String UNIT_PX = "px"; // $NON-NLS-1$ + public static final String UNIT_IN = "in"; // $NON-NLS-1$ + public static final String UNIT_MM = "mm"; // $NON-NLS-1$ + public static final String UNIT_PT = "pt"; // $NON-NLS-1$ + // Filenames and folder names + public static final String ANDROID_MANIFEST_XML = "AndroidManifest.xml"; // $NON-NLS-1$ + public static final String OLD_PROGUARD_FILE = "proguard.cfg"; // $NON-NLS-1$ + public static final String CLASS_FOLDER = + "bin" + File.separator + "classes"; // $NON-NLS-1$ //$NON-NLS-2$ + public static final String GEN_FOLDER = "gen"; // $NON-NLS-1$ + public static final String SRC_FOLDER = "src"; // $NON-NLS-1$ + public static final String LIBS_FOLDER = "libs"; // $NON-NLS-1$ + public static final String BIN_FOLDER = "bin"; // $NON-NLS-1$ + public static final String RES_FOLDER = "res"; // $NON-NLS-1$ + public static final String DOT_XML = ".xml"; // $NON-NLS-1$ + public static final String DOT_XSD = ".xsd"; // $NON-NLS-1$ + public static final String DOT_GIF = ".gif"; // $NON-NLS-1$ + public static final String DOT_JPG = ".jpg"; // $NON-NLS-1$ + public static final String DOT_JPEG = ".jpeg"; // $NON-NLS-1$ + public static final String DOT_WEBP = ".webp"; // $NON-NLS-1$ + public static final String DOT_PNG = ".png"; // $NON-NLS-1$ + public static final String DOT_9PNG = ".9.png"; // $NON-NLS-1$ + public static final String DOT_JAVA = ".java"; // $NON-NLS-1$ + public static final String DOT_CLASS = ".class"; // $NON-NLS-1$ + public static final String DOT_JAR = ".jar"; // $NON-NLS-1$ + public static final String DOT_GRADLE = ".gradle"; // $NON-NLS-1$ + public static final String DOT_PROPERTIES = ".properties"; // $NON-NLS-1$ + /** Extension of the Application package Files, i.e. "apk". */ + public static final String EXT_ANDROID_PACKAGE = "apk"; // $NON-NLS-1$ + /** Extension of java files, i.e. "java" */ + public static final String EXT_JAVA = "java"; // $NON-NLS-1$ + /** Extension of compiled java files, i.e. "class" */ + public static final String EXT_CLASS = "class"; // $NON-NLS-1$ + /** Extension of xml files, i.e. "xml" */ + public static final String EXT_XML = "xml"; // $NON-NLS-1$ + /** Extension of gradle files, i.e. "gradle" */ + public static final String EXT_GRADLE = "gradle"; // $NON-NLS-1$ + /** Extension of jar files, i.e. "jar" */ + public static final String EXT_JAR = "jar"; // $NON-NLS-1$ + /** Extension of ZIP files, i.e. "zip" */ + public static final String EXT_ZIP = "zip"; // $NON-NLS-1$ + /** Extension of aidl files, i.e. "aidl" */ + public static final String EXT_AIDL = "aidl"; // $NON-NLS-1$ + /** Extension of Renderscript files, i.e. "rs" */ + public static final String EXT_RS = "rs"; // $NON-NLS-1$ + /** Extension of Renderscript files, i.e. "rsh" */ + public static final String EXT_RSH = "rsh"; // $NON-NLS-1$ + /** Extension of FilterScript files, i.e. "fs" */ + public static final String EXT_FS = "fs"; // $NON-NLS-1$ + /** Extension of Renderscript bitcode files, i.e. "bc" */ + public static final String EXT_BC = "bc"; // $NON-NLS-1$ + /** Extension of dependency files, i.e. "d" */ + public static final String EXT_DEP = "d"; // $NON-NLS-1$ + /** Extension of native libraries, i.e. "so" */ + public static final String EXT_NATIVE_LIB = "so"; // $NON-NLS-1$ + /** Extension of dex files, i.e. "dex" */ + public static final String EXT_DEX = "dex"; // $NON-NLS-1$ + /** Extension for temporary resource files, ie "ap_ */ + public static final String EXT_RES = "ap_"; // $NON-NLS-1$ + /** Extension for pre-processable images. Right now pngs */ + public static final String EXT_PNG = "png"; // $NON-NLS-1$ + /** Extension for Android archive files */ + public static final String EXT_AAR = "aar"; // $NON-NLS-1$ + /** Extension for Java heap dumps. */ + public static final String EXT_HPROF = "hprof"; // $NON-NLS-1$ + + private static final String DOT = "."; // $NON-NLS-1$ + /** Dot-Extension of the Application package Files, i.e. ".apk". */ + public static final String DOT_ANDROID_PACKAGE = DOT + EXT_ANDROID_PACKAGE; + /** Dot-Extension of aidl files, i.e. ".aidl" */ + public static final String DOT_AIDL = DOT + EXT_AIDL; + /** Dot-Extension of renderscript files, i.e. ".rs" */ + public static final String DOT_RS = DOT + EXT_RS; + /** Dot-Extension of renderscript header files, i.e. ".rsh" */ + public static final String DOT_RSH = DOT + EXT_RSH; + /** Dot-Extension of FilterScript files, i.e. ".fs" */ + public static final String DOT_FS = DOT + EXT_FS; + /** Dot-Extension of renderscript bitcode files, i.e. ".bc" */ + public static final String DOT_BC = DOT + EXT_BC; + /** Dot-Extension of dependency files, i.e. ".d" */ + public static final String DOT_DEP = DOT + EXT_DEP; + /** Dot-Extension of dex files, i.e. ".dex" */ + public static final String DOT_DEX = DOT + EXT_DEX; + /** Dot-Extension for temporary resource files, ie "ap_ */ + public static final String DOT_RES = DOT + EXT_RES; + /** Dot-Extension for BMP files, i.e. ".bmp" */ + public static final String DOT_BMP = ".bmp"; // $NON-NLS-1$ + /** Dot-Extension for SVG files, i.e. ".svg" */ + public static final String DOT_SVG = ".svg"; // $NON-NLS-1$ + /** Dot-Extension for template files */ + public static final String DOT_FTL = ".ftl"; // $NON-NLS-1$ + /** Dot-Extension of text files, i.e. ".txt" */ + public static final String DOT_TXT = ".txt"; // $NON-NLS-1$ + /** Dot-Extension for Android archive files */ + public static final String DOT_AAR = DOT + EXT_AAR; // $NON-NLS-1$ + /** Dot-Extension for Java heap dumps. */ + public static final String DOT_HPROF = DOT + EXT_HPROF; // $NON-NLS-1$ + /** Resource base name for java files and classes */ + public static final String FN_RESOURCE_BASE = "R"; // $NON-NLS-1$ + /** Resource java class filename, i.e. "R.java" */ + public static final String FN_RESOURCE_CLASS = FN_RESOURCE_BASE + DOT_JAVA; + /** Resource class file filename, i.e. "R.class" */ + public static final String FN_COMPILED_RESOURCE_CLASS = FN_RESOURCE_BASE + DOT_CLASS; + /** Resource text filename, i.e. "R.txt" */ + public static final String FN_RESOURCE_TEXT = FN_RESOURCE_BASE + DOT_TXT; + /** Filename for public resources in AAR archives */ + public static final String FN_PUBLIC_TXT = "public.txt"; + /** Generated manifest class name */ + public static final String FN_MANIFEST_BASE = "Manifest"; // $NON-NLS-1$ + /** Generated BuildConfig class name */ + public static final String FN_BUILD_CONFIG_BASE = "BuildConfig"; // $NON-NLS-1$ + /** Manifest java class filename, i.e. "Manifest.java" */ + public static final String FN_MANIFEST_CLASS = FN_MANIFEST_BASE + DOT_JAVA; + /** BuildConfig java class filename, i.e. "BuildConfig.java" */ + public static final String FN_BUILD_CONFIG = FN_BUILD_CONFIG_BASE + DOT_JAVA; + + public static final String DRAWABLE_FOLDER = "drawable"; // $NON-NLS-1$ + public static final String DRAWABLE_XHDPI = "drawable-xhdpi"; // $NON-NLS-1$ + public static final String DRAWABLE_XXHDPI = "drawable-xxhdpi"; // $NON-NLS-1$ + public static final String DRAWABLE_XXXHDPI = "drawable-xxxhdpi"; // $NON-NLS-1$ + public static final String DRAWABLE_HDPI = "drawable-hdpi"; // $NON-NLS-1$ + public static final String DRAWABLE_MDPI = "drawable-mdpi"; // $NON-NLS-1$ + public static final String DRAWABLE_LDPI = "drawable-ldpi"; // $NON-NLS-1$ + // Resources + public static final String PREFIX_RESOURCE_REF = "@"; // $NON-NLS-1$ + public static final String PREFIX_THEME_REF = "?"; // $NON-NLS-1$ + public static final String PREFIX_BINDING_EXPR = "@{"; // $NON-NLS-1$ + public static final String ANDROID_PREFIX = "@android:"; // $NON-NLS-1$ + public static final String ANDROID_THEME_PREFIX = "?android:"; // $NON-NLS-1$ + public static final String LAYOUT_RESOURCE_PREFIX = "@layout/"; // $NON-NLS-1$ + public static final String STYLE_RESOURCE_PREFIX = "@style/"; // $NON-NLS-1$ + public static final String COLOR_RESOURCE_PREFIX = "@color/"; // $NON-NLS-1$ + public static final String NEW_ID_PREFIX = "@+id/"; // $NON-NLS-1$ + public static final String ID_PREFIX = "@id/"; // $NON-NLS-1$ + public static final String DRAWABLE_PREFIX = "@drawable/"; // $NON-NLS-1$ + public static final String STRING_PREFIX = "@string/"; // $NON-NLS-1$ + public static final String DIMEN_PREFIX = "@dimen/"; // $NON-NLS-1$ + public static final String MIPMAP_PREFIX = "@mipmap/"; // $NON-NLS-1$ + public static final String ANDROID_LAYOUT_RESOURCE_PREFIX = "@android:layout/"; // $NON-NLS-1$ + public static final String ANDROID_STYLE_RESOURCE_PREFIX = "@android:style/"; // $NON-NLS-1$ + public static final String ANDROID_COLOR_RESOURCE_PREFIX = "@android:color/"; // $NON-NLS-1$ + public static final String ANDROID_NEW_ID_PREFIX = "@android:+id/"; // $NON-NLS-1$ + public static final String ANDROID_ID_PREFIX = "@android:id/"; // $NON-NLS-1$ + public static final String ANDROID_DRAWABLE_PREFIX = "@android:drawable/"; // $NON-NLS-1$ + public static final String ANDROID_STRING_PREFIX = "@android:string/"; // $NON-NLS-1$ + public static final String RESOURCE_CLZ_ID = "id"; // $NON-NLS-1$ + public static final String RESOURCE_CLZ_COLOR = "color"; // $NON-NLS-1$ + public static final String RESOURCE_CLZ_ARRAY = "array"; // $NON-NLS-1$ + public static final String RESOURCE_CLZ_ATTR = "attr"; // $NON-NLS-1$ + public static final String RESOURCE_CLR_STYLEABLE = "styleable"; // $NON-NLS-1$ + public static final String NULL_RESOURCE = "@null"; // $NON-NLS-1$ + public static final String TRANSPARENT_COLOR = "@android:color/transparent"; // $NON-NLS-1$ + public static final String REFERENCE_STYLE = "style/"; // $NON-NLS-1$ + public static final String PREFIX_ANDROID = "android:"; // $NON-NLS-1$ + // Resource Types + public static final String DRAWABLE_TYPE = "drawable"; // $NON-NLS-1$ + public static final String MENU_TYPE = "menu"; // $NON-NLS-1$ + // Packages + public static final String ANDROID_PKG_PREFIX = "android."; // $NON-NLS-1$ + public static final String WIDGET_PKG_PREFIX = "android.widget."; // $NON-NLS-1$ + public static final String VIEW_PKG_PREFIX = "android.view."; // $NON-NLS-1$ + // Project properties + public static final String ANDROID_LIBRARY = "android.library"; // $NON-NLS-1$ + public static final String PROGUARD_CONFIG = "proguard.config"; // $NON-NLS-1$ + public static final String ANDROID_LIBRARY_REFERENCE_FORMAT = + "android.library.reference.%1$d"; //$NON-NLS-1$ + public static final String PROJECT_PROPERTIES = "project.properties"; // $NON-NLS-1$ + // Java References + public static final String ATTR_REF_PREFIX = "?attr/"; // $NON-NLS-1$ + public static final String R_PREFIX = "R."; // $NON-NLS-1$ + public static final String R_ID_PREFIX = "R.id."; // $NON-NLS-1$ + public static final String R_LAYOUT_RESOURCE_PREFIX = "R.layout."; // $NON-NLS-1$ + public static final String R_DRAWABLE_PREFIX = "R.drawable."; // $NON-NLS-1$ + public static final String R_STYLEABLE_PREFIX = "R.styleable."; // $NON-NLS-1$ + public static final String R_ATTR_PREFIX = "R.attr."; // $NON-NLS-1$ + // Attributes related to tools + public static final String ATTR_IGNORE = "ignore"; // $NON-NLS-1$ + public static final String ATTR_LOCALE = "locale"; // $NON-NLS-1$ + // SuppressLint + public static final String SUPPRESS_ALL = "all"; // $NON-NLS-1$ + public static final String SUPPRESS_LINT = "SuppressLint"; // $NON-NLS-1$ + public static final String TARGET_API = "TargetApi"; // $NON-NLS-1$ + public static final String ATTR_TARGET_API = "targetApi"; // $NON-NLS-1$ + public static final String FQCN_SUPPRESS_LINT = + "android.annotation." + SUPPRESS_LINT; // $NON-NLS-1$ + public static final String FQCN_TARGET_API = "android.annotation." + TARGET_API; // $NON-NLS-1$ + // Class Names + public static final String CONSTRUCTOR_NAME = ""; // $NON-NLS-1$ + public static final String CLASS_CONSTRUCTOR = ""; // $NON-NLS-1$ + public static final String FRAGMENT = "android/app/Fragment"; // $NON-NLS-1$ + public static final String FRAGMENT_V4 = "android/support/v4/app/Fragment"; // $NON-NLS-1$ + public static final String ANDROID_APP_ACTIVITY = "android/app/Activity"; // $NON-NLS-1$ + public static final String ANDROID_APP_SERVICE = "android/app/Service"; // $NON-NLS-1$ + public static final String ANDROID_CONTENT_CONTENT_PROVIDER = + "android/content/ContentProvider"; //$NON-NLS-1$ + public static final String ANDROID_CONTENT_BROADCAST_RECEIVER = + "android/content/BroadcastReceiver"; //$NON-NLS-1$ + public static final String ANDROID_VIEW_VIEW = "android/view/View"; // $NON-NLS-1$ + // Method Names + public static final String FORMAT_METHOD = "format"; // $NON-NLS-1$ + public static final String GET_STRING_METHOD = "getString"; // $NON-NLS-1$ + public static final String ATTR_TAG = "tag"; // $NON-NLS-1$ + public static final String ATTR_NUM_COLUMNS = "numColumns"; // $NON-NLS-1$ + // Some common layout element names + public static final String CALENDAR_VIEW = "CalendarView"; // $NON-NLS-1$ + public static final String SPACE = "Space"; // $NON-NLS-1$ + public static final String GESTURE_OVERLAY_VIEW = "GestureOverlayView"; // $NON-NLS-1$ + public static final String ATTR_HANDLE = "handle"; // $NON-NLS-1$ + public static final String ATTR_CONTENT = "content"; // $NON-NLS-1$ + public static final String ATTR_CHECKED = "checked"; // $NON-NLS-1$ + // TextView + public static final String ATTR_DRAWABLE_RIGHT = "drawableRight"; // $NON-NLS-1$ + public static final String ATTR_DRAWABLE_LEFT = "drawableLeft"; // $NON-NLS-1$ + public static final String ATTR_DRAWABLE_START = "drawableStart"; // $NON-NLS-1$ + public static final String ATTR_DRAWABLE_END = "drawableEnd"; // $NON-NLS-1$ + public static final String ATTR_DRAWABLE_BOTTOM = "drawableBottom"; // $NON-NLS-1$ + public static final String ATTR_DRAWABLE_TOP = "drawableTop"; // $NON-NLS-1$ + public static final String ATTR_DRAWABLE_PADDING = "drawablePadding"; // $NON-NLS-1$ + public static final String ATTR_USE_DEFAULT_MARGINS = "useDefaultMargins"; // $NON-NLS-1$ + public static final String ATTR_MARGINS_INCLUDED_IN_ALIGNMENT = + "marginsIncludedInAlignment"; //$NON-NLS-1$ + public static final String VALUE_WRAP_CONTENT = "wrap_content"; // $NON-NLS-1$ + public static final String VALUE_FALSE = "false"; // $NON-NLS-1$ + public static final String VALUE_N_DP = "%ddp"; // $NON-NLS-1$ + public static final String VALUE_ZERO_DP = "0dp"; // $NON-NLS-1$ + public static final String VALUE_ONE_DP = "1dp"; // $NON-NLS-1$ + public static final String VALUE_TOP = "top"; // $NON-NLS-1$ + public static final String VALUE_BOTTOM = "bottom"; // $NON-NLS-1$ + public static final String VALUE_CENTER_VERTICAL = "center_vertical"; // $NON-NLS-1$ + public static final String VALUE_CENTER_HORIZONTAL = "center_horizontal"; // $NON-NLS-1$ + public static final String VALUE_FILL_HORIZONTAL = "fill_horizontal"; // $NON-NLS-1$ + public static final String VALUE_FILL_VERTICAL = "fill_vertical"; // $NON-NLS-1$ + public static final String VALUE_0 = "0"; // $NON-NLS-1$ + public static final String VALUE_1 = "1"; // $NON-NLS-1$ + // Gravity values. These have the GRAVITY_ prefix in front of value because we already + // have VALUE_CENTER_HORIZONTAL defined for layouts, and its definition conflicts + // (centerHorizontal versus center_horizontal) + public static final String GRAVITY_VALUE_ = "center"; // $NON-NLS-1$ + public static final String GRAVITY_VALUE_CENTER = "center"; // $NON-NLS-1$ + public static final String GRAVITY_VALUE_LEFT = "left"; // $NON-NLS-1$ + public static final String GRAVITY_VALUE_RIGHT = "right"; // $NON-NLS-1$ + public static final String GRAVITY_VALUE_START = "start"; // $NON-NLS-1$ + public static final String GRAVITY_VALUE_END = "end"; // $NON-NLS-1$ + public static final String GRAVITY_VALUE_BOTTOM = "bottom"; // $NON-NLS-1$ + public static final String GRAVITY_VALUE_TOP = "top"; // $NON-NLS-1$ + public static final String GRAVITY_VALUE_FILL_HORIZONTAL = "fill_horizontal"; // $NON-NLS-1$ + public static final String GRAVITY_VALUE_FILL_VERTICAL = "fill_vertical"; // $NON-NLS-1$ + public static final String GRAVITY_VALUE_CENTER_HORIZONTAL = "center_horizontal"; // $NON-NLS-1$ + public static final String GRAVITY_VALUE_CENTER_VERTICAL = "center_vertical"; // $NON-NLS-1$ + public static final String GRAVITY_VALUE_FILL = "fill"; // $NON-NLS-1$ + /** The top level android package as a prefix, "android.". */ + public static final String ANDROID_SUPPORT_PKG_PREFIX = + ANDROID_PKG_PREFIX + "support."; // $NON-NLS-1$ + /** The android.view. package prefix */ + public static final String ANDROID_VIEW_PKG = ANDROID_PKG_PREFIX + "view."; // $NON-NLS-1$ + /** The android.widget. package prefix */ + public static final String ANDROID_WIDGET_PREFIX = ANDROID_PKG_PREFIX + "widget."; // $NON-NLS-1$ + /** The android.webkit. package prefix */ + public static final String ANDROID_WEBKIT_PKG = ANDROID_PKG_PREFIX + "webkit."; // $NON-NLS-1$ + /** The LayoutParams inner-class name suffix, .LayoutParams */ + public static final String DOT_LAYOUT_PARAMS = ".LayoutParams"; // $NON-NLS-1$ + /** The fully qualified class name of an EditText view */ + public static final String FQCN_EDIT_TEXT = "android.widget.EditText"; // $NON-NLS-1$ + /** The fully qualified class name of a LinearLayout view */ + public static final String FQCN_LINEAR_LAYOUT = "android.widget.LinearLayout"; // $NON-NLS-1$ + /** The fully qualified class name of a RelativeLayout view */ + public static final String FQCN_RELATIVE_LAYOUT = "android.widget.RelativeLayout"; // $NON-NLS-1$ + /** The fully qualified class name of a RelativeLayout view */ + public static final String FQCN_GRID_LAYOUT = "android.widget.GridLayout"; // $NON-NLS-1$ + + public static final String FQCN_GRID_LAYOUT_V7 = + "android.support.v7.widget.GridLayout"; //$NON-NLS-1$ + /** The fully qualified class name of a FrameLayout view */ + public static final String FQCN_FRAME_LAYOUT = "android.widget.FrameLayout"; // $NON-NLS-1$ + /** The fully qualified class name of a TableRow view */ + public static final String FQCN_TABLE_ROW = "android.widget.TableRow"; // $NON-NLS-1$ + /** The fully qualified class name of a TableLayout view */ + public static final String FQCN_TABLE_LAYOUT = "android.widget.TableLayout"; // $NON-NLS-1$ + /** The fully qualified class name of a GridView view */ + public static final String FQCN_GRID_VIEW = "android.widget.GridView"; // $NON-NLS-1$ + /** The fully qualified class name of a TabWidget view */ + public static final String FQCN_TAB_WIDGET = "android.widget.TabWidget"; // $NON-NLS-1$ + /** The fully qualified class name of a Button view */ + public static final String FQCN_BUTTON = "android.widget.Button"; // $NON-NLS-1$ + /** The fully qualified class name of a RadioButton view */ + public static final String FQCN_RADIO_BUTTON = "android.widget.RadioButton"; // $NON-NLS-1$ + /** The fully qualified class name of a ToggleButton view */ + public static final String FQCN_TOGGLE_BUTTON = "android.widget.ToggleButton"; // $NON-NLS-1$ + /** The fully qualified class name of a Spinner view */ + public static final String FQCN_SPINNER = "android.widget.Spinner"; // $NON-NLS-1$ + /** The fully qualified class name of an AdapterView */ + public static final String FQCN_ADAPTER_VIEW = "android.widget.AdapterView"; // $NON-NLS-1$ + /** The fully qualified class name of a ListView */ + public static final String FQCN_LIST_VIEW = "android.widget.ListView"; // $NON-NLS-1$ + /** The fully qualified class name of an ExpandableListView */ + public static final String FQCN_EXPANDABLE_LIST_VIEW = + "android.widget.ExpandableListView"; //$NON-NLS-1$ + /** The fully qualified class name of a GestureOverlayView */ + public static final String FQCN_GESTURE_OVERLAY_VIEW = + "android.gesture.GestureOverlayView"; //$NON-NLS-1$ + /** The fully qualified class name of a DatePicker */ + public static final String FQCN_DATE_PICKER = "android.widget.DatePicker"; // $NON-NLS-1$ + /** The fully qualified class name of a TimePicker */ + public static final String FQCN_TIME_PICKER = "android.widget.TimePicker"; // $NON-NLS-1$ + /** The fully qualified class name of a RadioGroup */ + public static final String FQCN_RADIO_GROUP = "android.widgets.RadioGroup"; // $NON-NLS-1$ + /** The fully qualified class name of a Space */ + public static final String FQCN_SPACE = "android.widget.Space"; // $NON-NLS-1$ + + public static final String FQCN_SPACE_V7 = "android.support.v7.widget.Space"; // $NON-NLS-1$ + /** The fully qualified class name of a TextView view */ + public static final String FQCN_TEXT_VIEW = "android.widget.TextView"; // $NON-NLS-1$ + /** The fully qualified class name of an ImageView view */ + public static final String FQCN_IMAGE_VIEW = "android.widget.ImageView"; // $NON-NLS-1$ + + public static final String ATTR_SRC = "src"; // $NON-NLS-1$ + public static final String ATTR_GRAVITY = "gravity"; // $NON-NLS-1$ + public static final String ATTR_WEIGHT_SUM = "weightSum"; // $NON-NLS-1$ + public static final String ATTR_EMS = "ems"; // $NON-NLS-1$ + public static final String VALUE_HORIZONTAL = "horizontal"; // $NON-NLS-1$ + public static final String GRADLE_PLUGIN_NAME = "com.android.tools.build:gradle:"; + public static final String GRADLE_MINIMUM_VERSION = "2.2.1"; + public static final String GRADLE_LATEST_VERSION = "2.4"; + public static final String GRADLE_PLUGIN_MINIMUM_VERSION = "1.0.0"; + public static final String GRADLE_PLUGIN_RECOMMENDED_VERSION = "1.3.0"; + public static final String GRADLE_PLUGIN_LATEST_VERSION = GRADLE_PLUGIN_RECOMMENDED_VERSION; + public static final String MIN_BUILD_TOOLS_VERSION = "19.1.0"; + public static final String SUPPORT_LIB_ARTIFACT = "com.android.support:support-v4"; + public static final String APPCOMPAT_LIB_ARTIFACT = "com.android.support:appcompat-v7"; + // Annotations + public static final String SUPPORT_ANNOTATIONS_PREFIX = "android.support.annotation."; + public static final String INT_DEF_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "IntDef"; + public static final String STRING_DEF_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "StringDef"; + public static final String TYPE_DEF_VALUE_ATTRIBUTE = "value"; + public static final String TYPE_DEF_FLAG_ATTRIBUTE = "flag"; + public static final String FN_ANNOTATIONS_ZIP = "annotations.zip"; + // Data Binding MISC + public static final String DATA_BINDING_LIB_ARTIFACT = "com.android.databinding:library"; + public static final String[] TAGS_DATA_BINDING = + new String[] {TAG_VARIABLE, TAG_IMPORT, TAG_LAYOUT, TAG_DATA}; + public static final String[] ATTRS_DATA_BINDING = + new String[] {ATTR_NAME, ATTR_TYPE, ATTR_CLASS, ATTR_ALIAS}; +} diff --git a/build-tools/xml-formatter/src/main/java/com/android/ide/common/xml/XmlAttributeSortOrder.java b/build-tools/xml-formatter/src/main/java/com/android/ide/common/xml/XmlAttributeSortOrder.java index a70b7e9c1..0aae9c670 100644 --- a/build-tools/xml-formatter/src/main/java/com/android/ide/common/xml/XmlAttributeSortOrder.java +++ b/build-tools/xml-formatter/src/main/java/com/android/ide/common/xml/XmlAttributeSortOrder.java @@ -35,174 +35,178 @@ import static com.google.common.base.Strings.nullToEmpty; /** Order to use when sorting attributes */ public enum XmlAttributeSortOrder { - NO_SORTING("none"), //$NON-NLS-1$ - ALPHABETICAL("alpha"), //$NON-NLS-1$ - LOGICAL("logical"); //$NON-NLS-1$ + NO_SORTING("none"), // $NON-NLS-1$ + ALPHABETICAL("alpha"), // $NON-NLS-1$ + LOGICAL("logical"); // $NON-NLS-1$ - XmlAttributeSortOrder(String key) { - this.key = key; + XmlAttributeSortOrder(String key) { + this.key = key; + } + + public final String key; + + /** + * @return a comparator for use by this attribute sort order + */ + @Nullable + public Comparator getAttributeComparator() { + switch (this) { + case NO_SORTING: + return null; + case ALPHABETICAL: + return ALPHABETICAL_COMPARATOR; + case LOGICAL: + default: + return SORTED_ORDER_COMPARATOR; } + } - public final String key; - - /** - * @return a comparator for use by this attribute sort order - */ - @Nullable - public Comparator getAttributeComparator() { - switch (this) { - case NO_SORTING: - return null; - case ALPHABETICAL: - return ALPHABETICAL_COMPARATOR; - case LOGICAL: - default: - return SORTED_ORDER_COMPARATOR; - } - } - - /** Comparator which can be used to sort attributes in the coding style priority order */ - private static final Comparator SORTED_ORDER_COMPARATOR = new Comparator() { + /** Comparator which can be used to sort attributes in the coding style priority order */ + private static final Comparator SORTED_ORDER_COMPARATOR = + new Comparator() { @Override public int compare(Attr attr1, Attr attr2) { - // Namespace declarations should always go first - String prefix1 = attr1.getPrefix(); - String prefix2 = attr2.getPrefix(); - if (XMLNS.equals(prefix1)) { - if (XMLNS.equals(prefix2)) { - return 0; - } - return -1; - } else if (XMLNS.equals(attr2.getPrefix())) { - return 1; + // Namespace declarations should always go first + String prefix1 = attr1.getPrefix(); + String prefix2 = attr2.getPrefix(); + if (XMLNS.equals(prefix1)) { + if (XMLNS.equals(prefix2)) { + return 0; } + return -1; + } else if (XMLNS.equals(attr2.getPrefix())) { + return 1; + } - // Sort by preferred attribute order - String name1 = prefix1 != null ? attr1.getLocalName() : attr1.getName(); - String name2 = prefix2 != null ? attr2.getLocalName() : attr2.getName(); - return compareAttributes(prefix1, name1, prefix2, name2); + // Sort by preferred attribute order + String name1 = prefix1 != null ? attr1.getLocalName() : attr1.getName(); + String name2 = prefix2 != null ? attr2.getLocalName() : attr2.getName(); + return compareAttributes(prefix1, name1, prefix2, name2); } - }; + }; - /** - * Comparator which can be used to sort attributes into alphabetical order (but xmlns - * is always first) - */ - private static final Comparator ALPHABETICAL_COMPARATOR = new Comparator() { + /** + * Comparator which can be used to sort attributes into alphabetical order (but xmlns is always + * first) + */ + private static final Comparator ALPHABETICAL_COMPARATOR = + new Comparator() { @Override public int compare(Attr attr1, Attr attr2) { - // Namespace declarations should always go first - if (XMLNS.equals(attr1.getPrefix())) { - if (XMLNS.equals(attr2.getPrefix())) { - return 0; - } - return -1; - } else if (XMLNS.equals(attr2.getPrefix())) { - return 1; + // Namespace declarations should always go first + if (XMLNS.equals(attr1.getPrefix())) { + if (XMLNS.equals(attr2.getPrefix())) { + return 0; } + return -1; + } else if (XMLNS.equals(attr2.getPrefix())) { + return 1; + } - // Sort by name rather than local name to ensure we sort by namespaces first, - // then by names. - return attr1.getName().compareTo(attr2.getName()); + // Sort by name rather than local name to ensure we sort by namespaces first, + // then by names. + return attr1.getName().compareTo(attr2.getName()); } - }; + }; - /** - * Returns {@link Comparator} values for ordering attributes in the following - * order: - *
      - *
    • id - *
    • style - *
    • layout_width - *
    • layout_height - *
    • other layout params, sorted alphabetically - *
    • other attributes, sorted alphabetically - *
    - * - * @param name1 the first attribute name to compare - * @param name2 the second attribute name to compare - * @return a negative number if name1 should be ordered before name2 - */ - public static int compareAttributes(String name1, String name2) { - int priority1 = getAttributePriority(name1); - int priority2 = getAttributePriority(name2); - if (priority1 != priority2) { - return priority1 - priority2; - } - - // Sort remaining attributes alphabetically - return name1.compareTo(name2); + /** + * Returns {@link Comparator} values for ordering attributes in the following order: + * + *
      + *
    • id + *
    • style + *
    • layout_width + *
    • layout_height + *
    • other layout params, sorted alphabetically + *
    • other attributes, sorted alphabetically + *
    + * + * @param name1 the first attribute name to compare + * @param name2 the second attribute name to compare + * @return a negative number if name1 should be ordered before name2 + */ + public static int compareAttributes(String name1, String name2) { + int priority1 = getAttributePriority(name1); + int priority2 = getAttributePriority(name2); + if (priority1 != priority2) { + return priority1 - priority2; } - /** - * Returns {@link Comparator} values for ordering attributes in the following - * order: - *
      - *
    • id - *
    • style - *
    • layout_width - *
    • layout_height - *
    • other layout params, sorted alphabetically - *
    • other attributes, sorted alphabetically, first by namespace, then by name - *
    - * @param prefix1 the namespace prefix, if any, of {@code name1} - * @param name1 the first attribute name to compare - * @param prefix2 the namespace prefix, if any, of {@code name2} - * @param name2 the second attribute name to compare - * @return a negative number if name1 should be ordered before name2 - */ - public static int compareAttributes( - @Nullable String prefix1, @NotNull String name1, - @Nullable String prefix2, @NotNull String name2) { - int priority1 = getAttributePriority(name1); - int priority2 = getAttributePriority(name2); - if (priority1 != priority2) { - return priority1 - priority2; - } + // Sort remaining attributes alphabetically + return name1.compareTo(name2); + } - int namespaceDelta = nullToEmpty(prefix1).compareTo(nullToEmpty(prefix2)); - if (namespaceDelta != 0) { - return namespaceDelta; - } - - // Sort remaining attributes alphabetically - return name1.compareTo(name2); + /** + * Returns {@link Comparator} values for ordering attributes in the following order: + * + *
      + *
    • id + *
    • style + *
    • layout_width + *
    • layout_height + *
    • other layout params, sorted alphabetically + *
    • other attributes, sorted alphabetically, first by namespace, then by name + *
    + * + * @param prefix1 the namespace prefix, if any, of {@code name1} + * @param name1 the first attribute name to compare + * @param prefix2 the namespace prefix, if any, of {@code name2} + * @param name2 the second attribute name to compare + * @return a negative number if name1 should be ordered before name2 + */ + public static int compareAttributes( + @Nullable String prefix1, + @NotNull String name1, + @Nullable String prefix2, + @NotNull String name2) { + int priority1 = getAttributePriority(name1); + int priority2 = getAttributePriority(name2); + if (priority1 != priority2) { + return priority1 - priority2; } - - /** Returns a sorting priority for the given attribute name */ - private static int getAttributePriority(String name) { - if (ATTR_ID.equals(name)) { - return 10; - } - - if (ATTR_NAME.equals(name)) { - return 15; - } - - if (ATTR_STYLE.equals(name)) { - return 20; - } - - if (name.startsWith(ATTR_LAYOUT_RESOURCE_PREFIX)) { - // Width and height are special cased because we (a) want width and height - // before the other layout attributes, and (b) we want width to sort before height - // even though it comes after it alphabetically. - if (name.equals(ATTR_LAYOUT_WIDTH)) { - return 30; - } - if (name.equals(ATTR_LAYOUT_HEIGHT)) { - return 40; - } - - return 50; - } - - // "color" sorts to the end - if (ATTR_COLOR.equals(name)) { - return 100; - } - - return 60; + int namespaceDelta = nullToEmpty(prefix1).compareTo(nullToEmpty(prefix2)); + if (namespaceDelta != 0) { + return namespaceDelta; } -} \ No newline at end of file + + // Sort remaining attributes alphabetically + return name1.compareTo(name2); + } + + /** Returns a sorting priority for the given attribute name */ + private static int getAttributePriority(String name) { + if (ATTR_ID.equals(name)) { + return 10; + } + + if (ATTR_NAME.equals(name)) { + return 15; + } + + if (ATTR_STYLE.equals(name)) { + return 20; + } + + if (name.startsWith(ATTR_LAYOUT_RESOURCE_PREFIX)) { + // Width and height are special cased because we (a) want width and height + // before the other layout attributes, and (b) we want width to sort before height + // even though it comes after it alphabetically. + if (name.equals(ATTR_LAYOUT_WIDTH)) { + return 30; + } + if (name.equals(ATTR_LAYOUT_HEIGHT)) { + return 40; + } + + return 50; + } + + // "color" sorts to the end + if (ATTR_COLOR.equals(name)) { + return 100; + } + + return 60; + } +} diff --git a/build-tools/xml-formatter/src/main/java/com/android/ide/common/xml/XmlFormatPreferences.java b/build-tools/xml-formatter/src/main/java/com/android/ide/common/xml/XmlFormatPreferences.java index 3422a3299..caf130475 100644 --- a/build-tools/xml-formatter/src/main/java/com/android/ide/common/xml/XmlFormatPreferences.java +++ b/build-tools/xml-formatter/src/main/java/com/android/ide/common/xml/XmlFormatPreferences.java @@ -23,72 +23,68 @@ import org.w3c.dom.Attr; import java.util.Comparator; -/** - * Formatting preferences used by the Android XML formatter. - */ +/** Formatting preferences used by the Android XML formatter. */ public class XmlFormatPreferences { - /** Use the Eclipse indent (tab/space, indent size) settings? */ - public boolean useEclipseIndent = false; + /** Use the Eclipse indent (tab/space, indent size) settings? */ + public boolean useEclipseIndent = false; - /** Remove empty lines in all cases? */ - public boolean removeEmptyLines = false; + /** Remove empty lines in all cases? */ + public boolean removeEmptyLines = false; - /** Reformat the text and comment blocks? */ - public boolean reflowText = false; + /** Reformat the text and comment blocks? */ + public boolean reflowText = false; - /** Join lines when reformatting text and comment blocks? */ - public boolean joinLines = false; + /** Join lines when reformatting text and comment blocks? */ + public boolean joinLines = false; - /** Can attributes appear on the same line as the opening line if there is just one of them? */ - public boolean oneAttributeOnFirstLine = true; + /** Can attributes appear on the same line as the opening line if there is just one of them? */ + public boolean oneAttributeOnFirstLine = true; - /** The sorting order to use when formatting */ - public XmlAttributeSortOrder sortAttributes = XmlAttributeSortOrder.LOGICAL; + /** The sorting order to use when formatting */ + public XmlAttributeSortOrder sortAttributes = XmlAttributeSortOrder.LOGICAL; - /** Returns the comparator to use when formatting, or null for no sorting */ - @Nullable - public Comparator getAttributeComparator() { - return sortAttributes.getAttributeComparator(); + /** Returns the comparator to use when formatting, or null for no sorting */ + @Nullable + public Comparator getAttributeComparator() { + return sortAttributes.getAttributeComparator(); + } + + /** Should there be a space before the closing > or /> ? */ + public boolean spaceBeforeClose = true; + + /** The string to insert for each indentation level */ + protected String mOneIndentUnit = " "; // $NON-NLS-1$ + + /** Tab width (number of spaces to display for a tab) */ + protected int mTabWidth = -1; // -1: uninitialized + + protected XmlFormatPreferences() {} + + /** + * Returns a new preferences object initialized with the defaults + * + * @return an {@link XmlFormatPreferences} object + */ + @NotNull + public static XmlFormatPreferences defaults() { + return new XmlFormatPreferences(); + } + + public String getOneIndentUnit() { + return mOneIndentUnit; + } + + /** + * Returns the number of spaces used to display a single tab character + * + * @return the number of spaces used to display a single tab character + */ + @SuppressWarnings("restriction") // Editor settings + public int getTabWidth() { + if (mTabWidth == -1) { + mTabWidth = 4; } - /** Should there be a space before the closing > or /> ? */ - public boolean spaceBeforeClose = true; - - /** The string to insert for each indentation level */ - protected String mOneIndentUnit = " "; //$NON-NLS-1$ - - /** Tab width (number of spaces to display for a tab) */ - protected int mTabWidth = -1; // -1: uninitialized - - - protected XmlFormatPreferences() { - } - - /** - * Returns a new preferences object initialized with the defaults - * - * @return an {@link XmlFormatPreferences} object - */ - @NotNull - public static XmlFormatPreferences defaults() { - return new XmlFormatPreferences(); - } - - public String getOneIndentUnit() { - return mOneIndentUnit; - } - - /** - * Returns the number of spaces used to display a single tab character - * - * @return the number of spaces used to display a single tab character - */ - @SuppressWarnings("restriction") // Editor settings - public int getTabWidth() { - if (mTabWidth == -1) { - mTabWidth = 4; - } - - return mTabWidth; - } + return mTabWidth; + } } diff --git a/build-tools/xml-formatter/src/main/java/com/android/ide/common/xml/XmlFormatStyle.java b/build-tools/xml-formatter/src/main/java/com/android/ide/common/xml/XmlFormatStyle.java index 851359c01..0609b1327 100644 --- a/build-tools/xml-formatter/src/main/java/com/android/ide/common/xml/XmlFormatStyle.java +++ b/build-tools/xml-formatter/src/main/java/com/android/ide/common/xml/XmlFormatStyle.java @@ -26,61 +26,59 @@ import org.w3c.dom.Element; import org.w3c.dom.Node; /** - * Style to use when printing the XML. Different types of Android XML files use slightly - * different preferred formats. For example, in layout files there is typically always a - * newline between successive elements, whereas in a manifest file there is typically only - * newlines between different types of elements. As another example, in resource files, - * the format is typically much more compact: the text content of {@code } tags is - * included on the same line whereas for other layout styles the children are typically - * placed on a line of their own. + * Style to use when printing the XML. Different types of Android XML files use slightly different + * preferred formats. For example, in layout files there is typically always a newline between + * successive elements, whereas in a manifest file there is typically only newlines between + * different types of elements. As another example, in resource files, the format is typically much + * more compact: the text content of {@code } tags is included on the same line whereas for + * other layout styles the children are typically placed on a line of their own. */ public enum XmlFormatStyle { - /** Layout formatting style: blank lines between elements, attributes on separate lines */ - LAYOUT, + /** Layout formatting style: blank lines between elements, attributes on separate lines */ + LAYOUT, - /** Similar to layout formatting style, but no blank lines inside opening elements */ - FILE, + /** Similar to layout formatting style, but no blank lines inside opening elements */ + FILE, - /** Resource style: one line per complete element including text child content */ - RESOURCE, + /** Resource style: one line per complete element including text child content */ + RESOURCE, - /** - * Similar to layout style, but no newlines between related elements such as - * successive {@code } declarations, and no newlines inside - * the second level elements (so an {@code } declaration appears as a - * single block with no whitespace within it) - */ - MANIFEST; + /** + * Similar to layout style, but no newlines between related elements such as successive {@code + * } declarations, and no newlines inside the second level elements (so an {@code + * } declaration appears as a single block with no whitespace within it) + */ + MANIFEST; - @NotNull - public static XmlFormatStyle get(@Nullable Node node) { - if (node != null) { - Document doc = (node.getNodeType() == Node.DOCUMENT_NODE) - ? (Document) node : node.getOwnerDocument(); - if (doc != null) { - Element root = doc.getDocumentElement(); - if (root != null) { - String tag = root.getTagName(); - if (tag.equals(SdkConstants.TAG_RESOURCES)) { - return RESOURCE; - } else if (tag.equals(AndroidManifest.NODE_MANIFEST)) { - return MANIFEST; - } + @NotNull + public static XmlFormatStyle get(@Nullable Node node) { + if (node != null) { + Document doc = + (node.getNodeType() == Node.DOCUMENT_NODE) ? (Document) node : node.getOwnerDocument(); + if (doc != null) { + Element root = doc.getDocumentElement(); + if (root != null) { + String tag = root.getTagName(); + if (tag.equals(SdkConstants.TAG_RESOURCES)) { + return RESOURCE; + } else if (tag.equals(AndroidManifest.NODE_MANIFEST)) { + return MANIFEST; + } - // How do we detect a layout vs other files such as drawables?? - // For now, assume that capitalized tags are view names, or names - // with package components are custom views - if (Character.isUpperCase(tag.charAt(0)) - || SdkConstants.VIEW_TAG.equals(tag) - || SdkConstants.VIEW_INCLUDE.equals(tag) - || SdkConstants.VIEW_MERGE.equals(tag) - || tag.indexOf('.') != -1) { - return LAYOUT; - } - } - } + // How do we detect a layout vs other files such as drawables?? + // For now, assume that capitalized tags are view names, or names + // with package components are custom views + if (Character.isUpperCase(tag.charAt(0)) + || SdkConstants.VIEW_TAG.equals(tag) + || SdkConstants.VIEW_INCLUDE.equals(tag) + || SdkConstants.VIEW_MERGE.equals(tag) + || tag.indexOf('.') != -1) { + return LAYOUT; + } } - - return FILE; + } } -} \ No newline at end of file + + return FILE; + } +} diff --git a/build-tools/xml-formatter/src/main/java/com/android/ide/common/xml/XmlPrettyPrinter.java b/build-tools/xml-formatter/src/main/java/com/android/ide/common/xml/XmlPrettyPrinter.java index 85559cd03..f184f0f04 100644 --- a/build-tools/xml-formatter/src/main/java/com/android/ide/common/xml/XmlPrettyPrinter.java +++ b/build-tools/xml-formatter/src/main/java/com/android/ide/common/xml/XmlPrettyPrinter.java @@ -52,1207 +52,1217 @@ import static com.android.utils.XmlUtils.XML_COMMENT_END; import static com.android.utils.XmlUtils.XML_PROLOG; /** - * Visitor which walks over the subtree of the DOM to be formatted and pretty prints - * the DOM into the given {@link StringBuilder} + * Visitor which walks over the subtree of the DOM to be formatted and pretty prints the DOM into + * the given {@link StringBuilder} */ public class XmlPrettyPrinter { - /** The style to print the XML in */ - private final XmlFormatStyle mStyle; + /** The style to print the XML in */ + private final XmlFormatStyle mStyle; - /** Formatting preferences to use when formatting the XML */ - private final XmlFormatPreferences mPrefs; - /** Start node to start formatting at */ - private Node mStartNode; - /** Start node to stop formatting after */ - private Node mEndNode; - /** Whether the visitor is currently in range */ - private boolean mInRange; - /** Output builder */ - @SuppressWarnings("StringBufferField") - private StringBuilder mOut; - /** String to insert for a single indentation level */ - private String mIndentString; - /** Line separator to use */ - private String mLineSeparator; - /** If true, we're only formatting an open tag */ - private boolean mOpenTagOnly; - /** List of indentation to use for each given depth */ - private String[] mIndentationLevels; - /** Whether the formatter should end the document with a newline */ - private boolean mEndWithNewline; + /** Formatting preferences to use when formatting the XML */ + private final XmlFormatPreferences mPrefs; + /** Start node to start formatting at */ + private Node mStartNode; + /** Start node to stop formatting after */ + private Node mEndNode; + /** Whether the visitor is currently in range */ + private boolean mInRange; + /** Output builder */ + @SuppressWarnings("StringBufferField") + private StringBuilder mOut; + /** String to insert for a single indentation level */ + private String mIndentString; + /** Line separator to use */ + private String mLineSeparator; + /** If true, we're only formatting an open tag */ + private boolean mOpenTagOnly; + /** List of indentation to use for each given depth */ + private String[] mIndentationLevels; + /** Whether the formatter should end the document with a newline */ + private boolean mEndWithNewline; - /** - * Creates a new {@link XmlPrettyPrinter} - * - * @param prefs the preferences to format with - * @param style the style to format with - * @param lineSeparator the line separator to use, such as "\n" (can be null, in which - * case the system default is looked up via the line.separator property) - */ - public XmlPrettyPrinter(XmlFormatPreferences prefs, XmlFormatStyle style, - String lineSeparator) { - mPrefs = prefs; - mStyle = style; - if (lineSeparator == null) { - lineSeparator = SdkUtils.getLineSeparator(); - } - mLineSeparator = lineSeparator; + /** + * Creates a new {@link XmlPrettyPrinter} + * + * @param prefs the preferences to format with + * @param style the style to format with + * @param lineSeparator the line separator to use, such as "\n" (can be null, in which case the + * system default is looked up via the line.separator property) + */ + public XmlPrettyPrinter(XmlFormatPreferences prefs, XmlFormatStyle style, String lineSeparator) { + mPrefs = prefs; + mStyle = style; + if (lineSeparator == null) { + lineSeparator = SdkUtils.getLineSeparator(); + } + mLineSeparator = lineSeparator; + } + + /** + * Sets whether the document should end with a newline/ line separator + * + * @param endWithNewline if true, ensure that the document ends with a newline + * @return this, for constructor chaining + */ + public XmlPrettyPrinter setEndWithNewline(boolean endWithNewline) { + mEndWithNewline = endWithNewline; + return this; + } + + /** + * Sets the indentation levels to use (indentation string to use for each depth, indexed by depth + * + * @param indentationLevels an array of strings to use for the various indentation levels + */ + public void setIndentationLevels(String[] indentationLevels) { + mIndentationLevels = indentationLevels; + } + + @NotNull + private String getLineSeparator() { + return mLineSeparator; + } + + /** + * Pretty-prints the given XML document, which must be well-formed. If it is not, the original + * unformatted XML document is returned + * + * @param xml the XML content to format + * @param prefs the preferences to format with + * @param style the style to format with + * @param lineSeparator the line separator to use, such as "\n" (can be null, in which case the + * system default is looked up via the line.separator property) + * @return the formatted document (or if a parsing error occurred, returns the unformatted + * document) + */ + @NotNull + public static String prettyPrint( + @NotNull String xml, + @NotNull XmlFormatPreferences prefs, + @NotNull XmlFormatStyle style, + @Nullable String lineSeparator) { + Document document = XmlUtils.parseDocumentSilently(xml, true); + if (document != null) { + XmlPrettyPrinter printer = new XmlPrettyPrinter(prefs, style, lineSeparator); + printer.setEndWithNewline(xml.endsWith(printer.getLineSeparator())); + StringBuilder sb = new StringBuilder(3 * xml.length() / 2); + printer.prettyPrint(-1, document, null, null, sb, false /*openTagOnly*/); + return sb.toString(); + } else { + // Parser error: just return the unformatted content + return xml; + } + } + + /** + * Pretty prints the given node + * + * @param node the node, usually a document, to be printed + * @param prefs the formatting preferences + * @param style the formatting style to use + * @param lineSeparator the line separator to use, or null to use the default + * @return a formatted string + * @deprecated Use {@link #prettyPrint(org.w3c.dom.Node, XmlFormatPreferences, XmlFormatStyle, + * String, boolean)} instead + */ + @NotNull + @Deprecated + public static String prettyPrint( + @NotNull Node node, + @NotNull XmlFormatPreferences prefs, + @NotNull XmlFormatStyle style, + @Nullable String lineSeparator) { + return prettyPrint(node, prefs, style, lineSeparator, false); + } + + /** + * Pretty prints the given node + * + * @param node the node, usually a document, to be printed + * @param prefs the formatting preferences + * @param style the formatting style to use + * @param lineSeparator the line separator to use, or null to use the default + * @param endWithNewline if true, ensure that the printed output ends with a newline + * @return a formatted string + */ + @NotNull + public static String prettyPrint( + @NotNull Node node, + @NotNull XmlFormatPreferences prefs, + @NotNull XmlFormatStyle style, + @Nullable String lineSeparator, + boolean endWithNewline) { + XmlPrettyPrinter printer = new XmlPrettyPrinter(prefs, style, lineSeparator); + printer.setEndWithNewline(endWithNewline); + StringBuilder sb = new StringBuilder(1000); + printer.prettyPrint(-1, node, null, null, sb, false /*openTagOnly*/); + String xml = sb.toString(); + if (node.getNodeType() == Node.DOCUMENT_NODE && !xml.startsWith("').append(mLineSeparator); - } + private void visitBeforeChildren(int depth, Node node) { + short type = node.getNodeType(); + switch (type) { + case Node.DOCUMENT_NODE: + case Node.DOCUMENT_FRAGMENT_NODE: + // Nothing to do + break; - @Nullable - @SuppressWarnings("MethodMayBeStatic") // Intentionally instance method so it can be overridden - protected String getSource(@NotNull Node node) { - return null; - } + case Node.ATTRIBUTE_NODE: + // Handled as part of processing elements + break; - private void printDocType(Node node) { - String content = getSource(node); - if (content != null) { - mOut.append(content); - mOut.append(mLineSeparator); - } - } - - private void printCharacterData(Node node) { - String nodeValue = node.getNodeValue(); - boolean separateLine = nodeValue.indexOf('\n') != -1; - if (separateLine && !endsWithLineSeparator()) { - mOut.append(mLineSeparator); - } - mOut.append(""); //$NON-NLS-1$ - if (separateLine) { - mOut.append(mLineSeparator); - } - } - - private void printText(Node node) { - boolean escape = true; - String text = node.getNodeValue(); - - String source = getSource(node); - if (source != null) { - // Get the original source string. This will contain the actual entities - // such as ">" instead of ">" which it gets turned into for the DOM nodes. - // By operating on source we can preserve the user's entities rather than - // having > for example always turned into >. - text = source; - escape = false; + case Node.ELEMENT_NODE: + { + printOpenElementTag(depth, node); + break; } - // Most text nodes are just whitespace for formatting (which we're replacing) - // so look for actual text content and extract that part out - String trimmed = text.trim(); - if (!trimmed.isEmpty()) { - // TODO: Reformat the contents if it is too wide? + case Node.TEXT_NODE: + { + printText(node); + break; + } - // Note that we append the actual text content, NOT the trimmed content, - // since the whitespace may be significant, e.g. - // Sync error: %1$s... + case Node.CDATA_SECTION_NODE: + printCharacterData(node); + break; - // However, we should remove all blank lines in the prefix and suffix of the - // text node, or we will end up inserting additional blank lines each time you're - // formatting a text node within an outer element (which also adds spacing lines) - int lastPrefixNewline = -1; - for (int i = 0, n = text.length(); i < n; i++) { - char c = text.charAt(i); - if (c == '\n') { - lastPrefixNewline = i; - } else if (!Character.isWhitespace(c)) { - break; - } - } - int firstSuffixNewline = -1; - for (int i = text.length() - 1; i >= 0; i--) { - char c = text.charAt(i); - if (c == '\n') { - firstSuffixNewline = i; - } else if (!Character.isWhitespace(c)) { - break; - } - } - if (lastPrefixNewline != -1 || firstSuffixNewline != -1) { - boolean stripSuffix; - if (firstSuffixNewline == -1) { - firstSuffixNewline = text.length(); - stripSuffix = false; - } else { - stripSuffix = true; - } + case Node.PROCESSING_INSTRUCTION_NODE: + printProcessingInstruction(node); + break; - int stripFrom = lastPrefixNewline + 1; - if (firstSuffixNewline >= stripFrom) { - text = text.substring(stripFrom, firstSuffixNewline); + case Node.COMMENT_NODE: + { + printComment(depth, node); + break; + } - // In markup strings we may need to preserve spacing on the left and/or - // right if we're next to a markup string on the given side - if (lastPrefixNewline != -1) { - Node left = node.getPreviousSibling(); - if (left != null && left.getNodeType() == Node.ELEMENT_NODE - && isMarkupElement((Element) left)) { - text = ' ' + text; - } - } - if (stripSuffix) { - Node right = node.getNextSibling(); - if (right != null && right.getNodeType() == Node.ELEMENT_NODE - && isMarkupElement((Element) right)) { - text += ' '; - } - } - } - } + case Node.DOCUMENT_TYPE_NODE: + printDocType(node); + break; - if (escape) { - XmlUtils.appendXmlTextValue(mOut, text); - } else { - // Text is already escaped - mOut.append(text); - } + case Node.ENTITY_REFERENCE_NODE: + case Node.ENTITY_NODE: + case Node.NOTATION_NODE: + break; + default: + assert false : type; + } + } - if (mStyle != XmlFormatStyle.RESOURCE) { - mOut.append(mLineSeparator); - } + private void visitAfterChildren(int depth, Node node) { + short type = node.getNodeType(); + switch (type) { + case Node.ATTRIBUTE_NODE: + // Handled as part of processing elements + break; + case Node.ELEMENT_NODE: + { + printCloseElementTag(depth, node); + break; + } + } + } + + private void printProcessingInstruction(Node node) { + mOut.append("').append(mLineSeparator); + } + + @Nullable + @SuppressWarnings("MethodMayBeStatic") // Intentionally instance method so it can be overridden + protected String getSource(@NotNull Node node) { + return null; + } + + private void printDocType(Node node) { + String content = getSource(node); + if (content != null) { + mOut.append(content); + mOut.append(mLineSeparator); + } + } + + private void printCharacterData(Node node) { + String nodeValue = node.getNodeValue(); + boolean separateLine = nodeValue.indexOf('\n') != -1; + if (separateLine && !endsWithLineSeparator()) { + mOut.append(mLineSeparator); + } + mOut.append(""); // $NON-NLS-1$ + if (separateLine) { + mOut.append(mLineSeparator); + } + } + + private void printText(Node node) { + boolean escape = true; + String text = node.getNodeValue(); + + String source = getSource(node); + if (source != null) { + // Get the original source string. This will contain the actual entities + // such as ">" instead of ">" which it gets turned into for the DOM nodes. + // By operating on source we can preserve the user's entities rather than + // having > for example always turned into >. + text = source; + escape = false; + } + + // Most text nodes are just whitespace for formatting (which we're replacing) + // so look for actual text content and extract that part out + String trimmed = text.trim(); + if (!trimmed.isEmpty()) { + // TODO: Reformat the contents if it is too wide? + + // Note that we append the actual text content, NOT the trimmed content, + // since the whitespace may be significant, e.g. + // Sync error: %1$s... + + // However, we should remove all blank lines in the prefix and suffix of the + // text node, or we will end up inserting additional blank lines each time you're + // formatting a text node within an outer element (which also adds spacing lines) + int lastPrefixNewline = -1; + for (int i = 0, n = text.length(); i < n; i++) { + char c = text.charAt(i); + if (c == '\n') { + lastPrefixNewline = i; + } else if (!Character.isWhitespace(c)) { + break; + } + } + int firstSuffixNewline = -1; + for (int i = text.length() - 1; i >= 0; i--) { + char c = text.charAt(i); + if (c == '\n') { + firstSuffixNewline = i; + } else if (!Character.isWhitespace(c)) { + break; + } + } + if (lastPrefixNewline != -1 || firstSuffixNewline != -1) { + boolean stripSuffix; + if (firstSuffixNewline == -1) { + firstSuffixNewline = text.length(); + stripSuffix = false; } else { - // Ensure that if we're in the middle of a markup string, we preserve spacing. - // In other words, "first second" - we don't want that middle - // space to disappear, but we do want repeated spaces to collapse into one. + stripSuffix = true; + } + + int stripFrom = lastPrefixNewline + 1; + if (firstSuffixNewline >= stripFrom) { + text = text.substring(stripFrom, firstSuffixNewline); + + // In markup strings we may need to preserve spacing on the left and/or + // right if we're next to a markup string on the given side + if (lastPrefixNewline != -1) { Node left = node.getPreviousSibling(); + if (left != null + && left.getNodeType() == Node.ELEMENT_NODE + && isMarkupElement((Element) left)) { + text = ' ' + text; + } + } + if (stripSuffix) { Node right = node.getNextSibling(); - if (left != null && right != null - && left.getNodeType() == Node.ELEMENT_NODE - && right.getNodeType() == Node.ELEMENT_NODE - && isMarkupElement((Element)left)) { - mOut.append(' '); + if (right != null + && right.getNodeType() == Node.ELEMENT_NODE + && isMarkupElement((Element) right)) { + text += ' '; } + } } + } + + if (escape) { + XmlUtils.appendXmlTextValue(mOut, text); + } else { + // Text is already escaped + mOut.append(text); + } + + if (mStyle != XmlFormatStyle.RESOURCE) { + mOut.append(mLineSeparator); + } + } else { + // Ensure that if we're in the middle of a markup string, we preserve spacing. + // In other words, "first second" - we don't want that middle + // space to disappear, but we do want repeated spaces to collapse into one. + Node left = node.getPreviousSibling(); + Node right = node.getNextSibling(); + if (left != null + && right != null + && left.getNodeType() == Node.ELEMENT_NODE + && right.getNodeType() == Node.ELEMENT_NODE + && isMarkupElement((Element) left)) { + mOut.append(' '); + } } + } - private void printComment(int depth, Node node) { - String comment = node.getNodeValue(); - boolean multiLine = comment.indexOf('\n') != -1; - String trimmed = comment.trim(); + private void printComment(int depth, Node node) { + String comment = node.getNodeValue(); + boolean multiLine = comment.indexOf('\n') != -1; + String trimmed = comment.trim(); - // See if this is an "end-of-the-line" comment, e.g. it is not a multi-line - // comment and it appears on the same line as an opening or closing element tag; - // if so, continue to place it as a suffix comment - boolean isSuffixComment = false; - if (!multiLine) { - Node previous = node.getPreviousSibling(); - isSuffixComment = true; - if (previous == null && node.getParentNode().getNodeType() == Node.DOCUMENT_NODE) { - isSuffixComment = false; - } - while (previous != null) { - short type = previous.getNodeType(); - if (type == Node.COMMENT_NODE) { - isSuffixComment = false; - break; - } else if (type == Node.TEXT_NODE) { - if (previous.getNodeValue().indexOf('\n') != -1) { - isSuffixComment = false; - break; - } - } else { - break; - } - previous = previous.getPreviousSibling(); - } - if (isSuffixComment) { - // Remove newline added by element open tag or element close tag - if (endsWithLineSeparator()) { - removeLastLineSeparator(); - } - mOut.append(' '); - } - } - - // Put the comment on a line on its own? Only if it was separated by a blank line - // in the previous version of the document. In other words, if the document - // adds blank lines between comments this formatter will preserve that fact, and vice - // versa for a tightly formatted document it will preserve that convention as well. - if (!mPrefs.removeEmptyLines && !isSuffixComment) { - Node curr = node.getPreviousSibling(); - if (curr == null) { - if (mOut.length() > 0 && !endsWithLineSeparator()) { - mOut.append(mLineSeparator); - } - } else if (curr.getNodeType() == Node.TEXT_NODE) { - String text = curr.getNodeValue(); - // Count how many newlines we find in the trailing whitespace of the - // text node - int newLines = 0; - for (int i = text.length() - 1; i >= 0; i--) { - char c = text.charAt(i); - if (Character.isWhitespace(c)) { - if (c == '\n') { - newLines++; - if (newLines == 2) { - break; - } - } - } else { - break; - } - } - if (newLines >= 2) { - mOut.append(mLineSeparator); - } else if (text.trim().isEmpty() && curr.getPreviousSibling() == null) { - // Comment before first child in node - mOut.append(mLineSeparator); - } - } - } - - - // TODO: Reformat the comment text? - if (!multiLine) { - if (!isSuffixComment) { - indent(depth); - } - mOut.append(XML_COMMENT_BEGIN).append(' '); - mOut.append(trimmed); - mOut.append(' ').append(XML_COMMENT_END); - mOut.append(mLineSeparator); + // See if this is an "end-of-the-line" comment, e.g. it is not a multi-line + // comment and it appears on the same line as an opening or closing element tag; + // if so, continue to place it as a suffix comment + boolean isSuffixComment = false; + if (!multiLine) { + Node previous = node.getPreviousSibling(); + isSuffixComment = true; + if (previous == null && node.getParentNode().getNodeType() == Node.DOCUMENT_NODE) { + isSuffixComment = false; + } + while (previous != null) { + short type = previous.getNodeType(); + if (type == Node.COMMENT_NODE) { + isSuffixComment = false; + break; + } else if (type == Node.TEXT_NODE) { + if (previous.getNodeValue().indexOf('\n') != -1) { + isSuffixComment = false; + break; + } } else { - // Strip off blank lines at the beginning and end of the comment text. - // Find last newline at the beginning of the text: - int index = 0; - int end = comment.length(); - int recentNewline = -1; - while (index < end) { - char c = comment.charAt(index); - if (c == '\n') { - recentNewline = index; - } + break; + } + previous = previous.getPreviousSibling(); + } + if (isSuffixComment) { + // Remove newline added by element open tag or element close tag + if (endsWithLineSeparator()) { + removeLastLineSeparator(); + } + mOut.append(' '); + } + } + + // Put the comment on a line on its own? Only if it was separated by a blank line + // in the previous version of the document. In other words, if the document + // adds blank lines between comments this formatter will preserve that fact, and vice + // versa for a tightly formatted document it will preserve that convention as well. + if (!mPrefs.removeEmptyLines && !isSuffixComment) { + Node curr = node.getPreviousSibling(); + if (curr == null) { + if (mOut.length() > 0 && !endsWithLineSeparator()) { + mOut.append(mLineSeparator); + } + } else if (curr.getNodeType() == Node.TEXT_NODE) { + String text = curr.getNodeValue(); + // Count how many newlines we find in the trailing whitespace of the + // text node + int newLines = 0; + for (int i = text.length() - 1; i >= 0; i--) { + char c = text.charAt(i); + if (Character.isWhitespace(c)) { + if (c == '\n') { + newLines++; + if (newLines == 2) { + break; + } + } + } else { + break; + } + } + if (newLines >= 2) { + mOut.append(mLineSeparator); + } else if (text.trim().isEmpty() && curr.getPreviousSibling() == null) { + // Comment before first child in node + mOut.append(mLineSeparator); + } + } + } + + // TODO: Reformat the comment text? + if (!multiLine) { + if (!isSuffixComment) { + indent(depth); + } + mOut.append(XML_COMMENT_BEGIN).append(' '); + mOut.append(trimmed); + mOut.append(' ').append(XML_COMMENT_END); + mOut.append(mLineSeparator); + } else { + // Strip off blank lines at the beginning and end of the comment text. + // Find last newline at the beginning of the text: + int index = 0; + int end = comment.length(); + int recentNewline = -1; + while (index < end) { + char c = comment.charAt(index); + if (c == '\n') { + recentNewline = index; + } + if (!Character.isWhitespace(c)) { + break; + } + index++; + } + + int start = recentNewline + 1; + + // Find last newline at the end of the text + index = end - 1; + recentNewline = -1; + while (index > start) { + char c = comment.charAt(index); + if (c == '\n') { + recentNewline = index; + } + if (!Character.isWhitespace(c)) { + break; + } + index--; + } + + end = recentNewline == -1 ? index + 1 : recentNewline; + if (start >= end) { + // It's a blank comment like - just clean it up + if (!isSuffixComment) { + indent(depth); + } + mOut.append(XML_COMMENT_BEGIN).append(' ').append(XML_COMMENT_END); + mOut.append(mLineSeparator); + return; + } + + trimmed = comment.substring(start, end); + + // When stripping out prefix and suffix blank lines we might have ended up + // with a single line comment again so check and format single line comments + // without newlines inside the delimiters + multiLine = trimmed.indexOf('\n') != -1; + if (multiLine) { + indent(depth); + mOut.append(XML_COMMENT_BEGIN); + mOut.append(mLineSeparator); + + // See if we need to add extra spacing to keep alignment. Consider a comment + // like this: + // + // This String will be + // " Deprecated strings - Move the identifiers to this section,\n" + + // " and remove the actual text. -->" + // where the left side column no longer lines up. + // To fix this, we need to insert some extra whitespace into the first line + // of the string; in particular, the exact number of characters that the + // first line of the comment was indented with! + + // However, if the comment started like this: + // + // then obviously the align-indent is 0, so we only want to compute an + // align indent when we don't find a newline before the content + boolean startsWithNewline = false; + for (int i = 0; i < start; i++) { + if (comment.charAt(i) == '\n') { + startsWithNewline = true; + break; + } + } + if (!startsWithNewline) { + Node previous = node.getPreviousSibling(); + if (previous != null && previous.getNodeType() == Node.TEXT_NODE) { + String prevText = previous.getNodeValue(); + int indentation = XML_COMMENT_BEGIN.length(); + for (int i = prevText.length() - 1; i >= 0; i--) { + char c = prevText.charAt(i); + if (c == '\n') { + break; + } else { + indentation += (c == '\t') ? mPrefs.getTabWidth() : 1; + } + } + + // See if the next line after the newline has indentation; if it doesn't, + // leave things alone. This fixes a case like this: + // + // such that it doesn't turn it into + // + // In this case we instead want + // + int minIndent = Integer.MAX_VALUE; + String[] lines = trimmed.split("\n"); // $NON-NLS-1$ + // Skip line 0 since we know that it doesn't start with a newline + for (int i = 1; i < lines.length; i++) { + int indent = 0; + String line = lines[i]; + for (int j = 0; j < line.length(); j++) { + char c = line.charAt(j); if (!Character.isWhitespace(c)) { - break; + // Only set minIndent if there's text content on the line; + // blank lines can exist in the comment without affecting + // the overall minimum indentation boundary. + if (indent < minIndent) { + minIndent = indent; + } + break; + } else { + indent += (c == '\t') ? mPrefs.getTabWidth() : 1; } - index++; + } } - int start = recentNewline + 1; + if (minIndent < indentation) { + indentation = minIndent; - // Find last newline at the end of the text - index = end - 1; - recentNewline = -1; - while (index > start) { - char c = comment.charAt(index); - if (c == '\n') { - recentNewline = index; - } + // Subtract any indentation that is already present on the line + String line = lines[0]; + for (int j = 0; j < line.length(); j++) { + char c = line.charAt(j); if (!Character.isWhitespace(c)) { - break; + break; + } else { + indentation -= (c == '\t') ? mPrefs.getTabWidth() : 1; } - index--; + } } - end = recentNewline == -1 ? index + 1 : recentNewline; - if (start >= end) { - // It's a blank comment like - just clean it up - if (!isSuffixComment) { - indent(depth); - } - mOut.append(XML_COMMENT_BEGIN).append(' ').append(XML_COMMENT_END); - mOut.append(mLineSeparator); - return; + for (int i = 0; i < indentation; i++) { + mOut.append(' '); } - trimmed = comment.substring(start, end); - - // When stripping out prefix and suffix blank lines we might have ended up - // with a single line comment again so check and format single line comments - // without newlines inside the delimiters - multiLine = trimmed.indexOf('\n') != -1; - if (multiLine) { - indent(depth); - mOut.append(XML_COMMENT_BEGIN); - mOut.append(mLineSeparator); - - // See if we need to add extra spacing to keep alignment. Consider a comment - // like this: - // - // This String will be - // " Deprecated strings - Move the identifiers to this section,\n" + - // " and remove the actual text. -->" - // where the left side column no longer lines up. - // To fix this, we need to insert some extra whitespace into the first line - // of the string; in particular, the exact number of characters that the - // first line of the comment was indented with! - - // However, if the comment started like this: - // - // then obviously the align-indent is 0, so we only want to compute an - // align indent when we don't find a newline before the content - boolean startsWithNewline = false; - for (int i = 0; i < start; i++) { - if (comment.charAt(i) == '\n') { - startsWithNewline = true; - break; - } + if (indentation < 0) { + boolean prefixIsSpace = true; + for (int i = 0; i < -indentation && i < trimmed.length(); i++) { + if (!Character.isWhitespace(trimmed.charAt(i))) { + prefixIsSpace = false; + break; } - if (!startsWithNewline) { - Node previous = node.getPreviousSibling(); - if (previous != null && previous.getNodeType() == Node.TEXT_NODE) { - String prevText = previous.getNodeValue(); - int indentation = XML_COMMENT_BEGIN.length(); - for (int i = prevText.length() - 1; i >= 0; i--) { - char c = prevText.charAt(i); - if (c == '\n') { - break; - } else { - indentation += (c == '\t') ? mPrefs.getTabWidth() : 1; - } - } - - // See if the next line after the newline has indentation; if it doesn't, - // leave things alone. This fixes a case like this: - // - // such that it doesn't turn it into - // - // In this case we instead want - // - int minIndent = Integer.MAX_VALUE; - String[] lines = trimmed.split("\n"); //$NON-NLS-1$ - // Skip line 0 since we know that it doesn't start with a newline - for (int i = 1; i < lines.length; i++) { - int indent = 0; - String line = lines[i]; - for (int j = 0; j < line.length(); j++) { - char c = line.charAt(j); - if (!Character.isWhitespace(c)) { - // Only set minIndent if there's text content on the line; - // blank lines can exist in the comment without affecting - // the overall minimum indentation boundary. - if (indent < minIndent) { - minIndent = indent; - } - break; - } else { - indent += (c == '\t') ? mPrefs.getTabWidth() : 1; - } - } - } - - if (minIndent < indentation) { - indentation = minIndent; - - // Subtract any indentation that is already present on the line - String line = lines[0]; - for (int j = 0; j < line.length(); j++) { - char c = line.charAt(j); - if (!Character.isWhitespace(c)) { - break; - } else { - indentation -= (c == '\t') ? mPrefs.getTabWidth() : 1; - } - } - } - - for (int i = 0; i < indentation; i++) { - mOut.append(' '); - } - - if (indentation < 0) { - boolean prefixIsSpace = true; - for (int i = 0; i < -indentation && i < trimmed.length(); i++) { - if (!Character.isWhitespace(trimmed.charAt(i))) { - prefixIsSpace = false; - break; - } - } - if (prefixIsSpace) { - trimmed = trimmed.substring(-indentation); - } - } - } - } - - mOut.append(trimmed); - mOut.append(mLineSeparator); - indent(depth); - mOut.append(XML_COMMENT_END); - mOut.append(mLineSeparator); - } else { - mOut.append(XML_COMMENT_BEGIN).append(' '); - mOut.append(trimmed); - mOut.append(' ').append(XML_COMMENT_END); - mOut.append(mLineSeparator); + } + if (prefixIsSpace) { + trimmed = trimmed.substring(-indentation); + } } + } } - // Preserve whitespace after comment: See if the original document had two or - // more newlines after the comment, and if so have a blank line between this - // comment and the next - Node next = node.getNextSibling(); - if (!mPrefs.removeEmptyLines && (next != null) - && (next.getNodeType() == Node.TEXT_NODE)) { - String text = next.getNodeValue(); - int newLinesBeforeText = 0; - for (int i = 0, n = text.length(); i < n; i++) { - char c = text.charAt(i); - if (c == '\n') { - newLinesBeforeText++; - if (newLinesBeforeText == 2) { - // Yes - mOut.append(mLineSeparator); - break; - } - } else if (!Character.isWhitespace(c)) { - break; - } - } - } + mOut.append(trimmed); + mOut.append(mLineSeparator); + indent(depth); + mOut.append(XML_COMMENT_END); + mOut.append(mLineSeparator); + } else { + mOut.append(XML_COMMENT_BEGIN).append(' '); + mOut.append(trimmed); + mOut.append(' ').append(XML_COMMENT_END); + mOut.append(mLineSeparator); + } } - private boolean endsWithLineSeparator() { - int separatorLength = mLineSeparator.length(); - if (mOut.length() >= separatorLength) { - for (int i = 0, j = mOut.length() - separatorLength; i < separatorLength; i++) { - if (mOut.charAt(j) != mLineSeparator.charAt(i)) { - return false; - } - } - } - - return true; - } - - private void removeLastLineSeparator() { - int newLength = mOut.length() - mLineSeparator.length(); - if (newLength >= 0) { - mOut.setLength(newLength); - } - } - - private void printOpenElementTag(int depth, Node node) { - Element element = (Element) node; - if (newlineBeforeElementOpen(element, depth)) { + // Preserve whitespace after comment: See if the original document had two or + // more newlines after the comment, and if so have a blank line between this + // comment and the next + Node next = node.getNextSibling(); + if (!mPrefs.removeEmptyLines && (next != null) && (next.getNodeType() == Node.TEXT_NODE)) { + String text = next.getNodeValue(); + int newLinesBeforeText = 0; + for (int i = 0, n = text.length(); i < n; i++) { + char c = text.charAt(i); + if (c == '\n') { + newLinesBeforeText++; + if (newLinesBeforeText == 2) { + // Yes mOut.append(mLineSeparator); + break; + } + } else if (!Character.isWhitespace(c)) { + break; } - if (indentBeforeElementOpen(element, depth)) { - indent(depth); - } - mOut.append('<').append(element.getTagName()); - - NamedNodeMap attributes = element.getAttributes(); - int attributeCount = attributes.getLength(); - if (attributeCount > 0) { - // Sort the attributes - List attributeList = new ArrayList(); - for (int i = 0; i < attributeCount; i++) { - attributeList.add((Attr) attributes.item(i)); - } - Comparator comparator = mPrefs.getAttributeComparator(); - if (comparator != null) { - Collections.sort(attributeList, comparator); - } - - // Put the single attribute on the same line as the element tag? - boolean singleLine = mPrefs.oneAttributeOnFirstLine && attributeCount == 1 - // In resource files we always put all the attributes (which is - // usually just zero, one or two) on the same line - || mStyle == XmlFormatStyle.RESOURCE; - - // We also place the namespace declaration on the same line as the root element, - // but this doesn't also imply singleLine handling; subsequent attributes end up - // on their own lines - boolean indentNextAttribute; - if (singleLine || (depth == 0 && XMLNS.equals(attributeList.get(0).getPrefix()))) { - mOut.append(' '); - indentNextAttribute = false; - } else { - mOut.append(mLineSeparator); - indentNextAttribute = true; - } - - Attr last = attributeList.get(attributeCount - 1); - for (Attr attribute : attributeList) { - if (indentNextAttribute) { - indent(depth + 1); - } - mOut.append(attribute.getName()); - mOut.append('=').append('"'); - XmlUtils.appendXmlAttributeValue(mOut, attribute.getValue()); - mOut.append('"'); - - // Don't add a newline at the last attribute line; the > should - // immediately follow the last attribute - if (attribute != last) { - mOut.append(singleLine ? " " : mLineSeparator); //$NON-NLS-1$ - indentNextAttribute = !singleLine; - } - } - } - - boolean isClosed = isEmptyTag(element); - - // Add a space before the > or /> ? In resource files, only do this when closing the - // element - if (mPrefs.spaceBeforeClose && (mStyle != XmlFormatStyle.RESOURCE || isClosed) - // in files etc still treat the entries as in resource files - && !TAG_ITEM.equals(element.getTagName()) - && (isClosed || element.getAttributes().getLength() > 0)) { - mOut.append(' '); - } - - if (isClosed) { - mOut.append('/'); - } - - mOut.append('>'); - - if (newlineAfterElementOpen(element, depth, isClosed)) { - mOut.append(mLineSeparator); + } + } + } + + private boolean endsWithLineSeparator() { + int separatorLength = mLineSeparator.length(); + if (mOut.length() >= separatorLength) { + for (int i = 0, j = mOut.length() - separatorLength; i < separatorLength; i++) { + if (mOut.charAt(j) != mLineSeparator.charAt(i)) { + return false; } + } } - private void printCloseElementTag(int depth, Node node) { - Element element = (Element) node; - if (isEmptyTag(element)) { - // Empty tag: Already handled as part of opening tag - return; - } + return true; + } - // Put the closing declaration on its own line - unless it's a compact - // resource file format - // If the element had element children, separate the end tag from them - if (newlineBeforeElementClose(element, depth)) { - mOut.append(mLineSeparator); - } - if (indentBeforeElementClose(element, depth)) { - indent(depth); - } - mOut.append('<').append('/'); - mOut.append(node.getNodeName()); - mOut.append('>'); + private void removeLastLineSeparator() { + int newLength = mOut.length() - mLineSeparator.length(); + if (newLength >= 0) { + mOut.setLength(newLength); + } + } - if (newlineAfterElementClose(element, depth)) { - mOut.append(mLineSeparator); + private void printOpenElementTag(int depth, Node node) { + Element element = (Element) node; + if (newlineBeforeElementOpen(element, depth)) { + mOut.append(mLineSeparator); + } + if (indentBeforeElementOpen(element, depth)) { + indent(depth); + } + mOut.append('<').append(element.getTagName()); + + NamedNodeMap attributes = element.getAttributes(); + int attributeCount = attributes.getLength(); + if (attributeCount > 0) { + // Sort the attributes + List attributeList = new ArrayList(); + for (int i = 0; i < attributeCount; i++) { + attributeList.add((Attr) attributes.item(i)); + } + Comparator comparator = mPrefs.getAttributeComparator(); + if (comparator != null) { + Collections.sort(attributeList, comparator); + } + + // Put the single attribute on the same line as the element tag? + boolean singleLine = + mPrefs.oneAttributeOnFirstLine && attributeCount == 1 + // In resource files we always put all the attributes (which is + // usually just zero, one or two) on the same line + || mStyle == XmlFormatStyle.RESOURCE; + + // We also place the namespace declaration on the same line as the root element, + // but this doesn't also imply singleLine handling; subsequent attributes end up + // on their own lines + boolean indentNextAttribute; + if (singleLine || (depth == 0 && XMLNS.equals(attributeList.get(0).getPrefix()))) { + mOut.append(' '); + indentNextAttribute = false; + } else { + mOut.append(mLineSeparator); + indentNextAttribute = true; + } + + Attr last = attributeList.get(attributeCount - 1); + for (Attr attribute : attributeList) { + if (indentNextAttribute) { + indent(depth + 1); } + mOut.append(attribute.getName()); + mOut.append('=').append('"'); + XmlUtils.appendXmlAttributeValue(mOut, attribute.getValue()); + mOut.append('"'); + + // Don't add a newline at the last attribute line; the > should + // immediately follow the last attribute + if (attribute != last) { + mOut.append(singleLine ? " " : mLineSeparator); // $NON-NLS-1$ + indentNextAttribute = !singleLine; + } + } } - private boolean newlineBeforeElementOpen(Element element, int depth) { - if (hasBlankLineAbove()) { - return false; - } + boolean isClosed = isEmptyTag(element); - if (mPrefs.removeEmptyLines || depth <= 0) { - return false; - } + // Add a space before the > or /> ? In resource files, only do this when closing the + // element + if (mPrefs.spaceBeforeClose + && (mStyle != XmlFormatStyle.RESOURCE || isClosed) + // in files etc still treat the entries as in resource files + && !TAG_ITEM.equals(element.getTagName()) + && (isClosed || element.getAttributes().getLength() > 0)) { + mOut.append(' '); + } - if (isMarkupElement(element)) { - return false; - } + if (isClosed) { + mOut.append('/'); + } - // See if this element should be separated from the previous element. - // This is the case if we are not compressing whitespace (checked above), - // or if we are not immediately following a comment (in which case the - // newline would have been added above it), or if we are not in a formatting - // style where - if (mStyle == XmlFormatStyle.LAYOUT) { - // In layouts we always separate elements + mOut.append('>'); + + if (newlineAfterElementOpen(element, depth, isClosed)) { + mOut.append(mLineSeparator); + } + } + + private void printCloseElementTag(int depth, Node node) { + Element element = (Element) node; + if (isEmptyTag(element)) { + // Empty tag: Already handled as part of opening tag + return; + } + + // Put the closing declaration on its own line - unless it's a compact + // resource file format + // If the element had element children, separate the end tag from them + if (newlineBeforeElementClose(element, depth)) { + mOut.append(mLineSeparator); + } + if (indentBeforeElementClose(element, depth)) { + indent(depth); + } + mOut.append('<').append('/'); + mOut.append(node.getNodeName()); + mOut.append('>'); + + if (newlineAfterElementClose(element, depth)) { + mOut.append(mLineSeparator); + } + } + + private boolean newlineBeforeElementOpen(Element element, int depth) { + if (hasBlankLineAbove()) { + return false; + } + + if (mPrefs.removeEmptyLines || depth <= 0) { + return false; + } + + if (isMarkupElement(element)) { + return false; + } + + // See if this element should be separated from the previous element. + // This is the case if we are not compressing whitespace (checked above), + // or if we are not immediately following a comment (in which case the + // newline would have been added above it), or if we are not in a formatting + // style where + if (mStyle == XmlFormatStyle.LAYOUT) { + // In layouts we always separate elements + return true; + } + + if (mStyle == XmlFormatStyle.MANIFEST + || mStyle == XmlFormatStyle.RESOURCE + || mStyle == XmlFormatStyle.FILE) { + Node curr = element.getPreviousSibling(); + + //