From a6db8d0c869d9c946465474f7471bbb7ff0a4ceb Mon Sep 17 00:00:00 2001 From: itsaky Date: Thu, 29 Jul 2021 13:56:28 +0530 Subject: [PATCH] - Implmented Diagnostics --- app/build.gradle | 7 +- app/dictionary.txt | 4 + app/libs/{ecj.jar => ecj-4.6.1.jar} | Bin 2440899 -> 2408044 bytes app/proguard-rules.pro | 18 +- .../com/itsaky/androidide/EditorActivity.java | 162 +- .../adapters/DiagnosticItemAdpater.java | 65 + .../adapters/DiagnosticsAdapter.java | 63 + .../com/itsaky/androidide/app/StudioApp.java | 76 +- .../androidide/fragments/EditorFragment.java | 55 +- .../fragments/FileTreeFragment.java | 11 +- .../fragments/preferences/IDEPreferences.java | 2 +- .../androidide/fragments/sheets/LogSheet.java | 1 - .../interfaces/CompileListener.java | 2 +- .../interfaces/DexResultListener.java | 7 + .../interfaces/DiagnosticClickListener.java | 9 + .../language/groovy/GroovyAutoComplete.java | 3 +- .../language/java/JavaAutoComplete.java | 13 +- .../language/java/JavaLanguage.java | 6 +- .../expression/CompleteThisKeyword.java | 2 +- .../parser/internal/CompiledClassLoader.java | 55 - .../parser/internal/IJavaDocCommentable.java | 0 .../parser/internal/JavaClassManager.java | 246 +- .../java/parser/internal/SuggestItem.java | 0 .../java/parser/model/ClassDescription.java | 30 +- .../parser/model/ConstructorDescription.java | 19 +- .../java/parser/model/MethodDescription.java | 4 +- .../java/provider/JavaCompletionProvider.java | 4 +- .../language/logs/LogLanguageImpl.java | 3 +- .../language/xml/XMLAutoComplete.java | 8 +- .../androidide/language/xml/XMLLanguage.java | 4 +- .../androidide/models/CompletionEdit.java | 0 .../androidide/models/CompletionListItem.java | 0 .../androidide/models/DiagnosticGroup.java | 18 + .../androidide/receivers/LogReceiver.java | 8 +- .../androidide/services/IDEService.java | 2 + .../androidide/services/Jar2DexService.java | 6 + .../services/compiler/CompilerProgress.java | 33 + .../services/compiler/CompilerRequestor.java | 101 + .../services/compiler/CompilerWrapper.java | 138 + .../compiler/JavaCompilerService.java | 438 ++- .../compiler/model/CompilerDiagnostic.java | 16 +- .../model/CompilerDiagnosticImpl.java | 35 +- .../compiler/request/CompilationRequest.java | 71 + .../itsaky/androidide/shell/ShellServer.java | 12 +- .../tools/SourceJavaFileObject.java | 39 + .../com/itsaky/androidide/utils/Either.java | 0 .../com/itsaky/androidide/utils/Logger.java | 119 + .../editor/annotations/Experimental.java | 0 .../interfaces/AutoCompleteProvider.java | 3 +- .../editor/interfaces/CodeAnalyzer.java | 0 .../interfaces/EditorEventListener.java | 0 .../editor/interfaces/EditorLanguage.java | 0 .../editor/interfaces/NewlineHandler.java | 0 .../rosemoe/editor/langs/EmptyLanguage.java | 3 +- .../rosemoe/editor/struct/BlockLine.java | 0 .../rosemoe/editor/struct/CompletionItem.java | 0 .../rosemoe/editor/struct/ErrorOrWarning.java | 0 .../rosemoe/editor/struct/HexColor.java | 0 .../rosemoe/editor/struct/NavigationItem.java | 0 .../io/github/rosemoe/editor/struct/Span.java | 0 .../rosemoe/editor/text/CachedIndexer.java | 0 .../rosemoe/editor/text/CharPosition.java | 0 .../github/rosemoe/editor/text/Content.java | 0 .../rosemoe/editor/text/ContentLine.java | 0 .../rosemoe/editor/text/ContentListener.java | 0 .../io/github/rosemoe/editor/text/Cursor.java | 0 .../github/rosemoe/editor/text/FontCache.java | 0 .../rosemoe/editor/text/FormatThread.java | 0 .../github/rosemoe/editor/text/Indexer.java | 0 .../editor/text/LineNumberCalculator.java | 0 .../editor/text/LineRemoveListener.java | 0 .../rosemoe/editor/text/NoCacheIndexer.java | 0 .../rosemoe/editor/text/ObjectAllocator.java | 0 .../rosemoe/editor/text/SpanMapUpdater.java | 0 .../rosemoe/editor/text/SpanRecycler.java | 0 .../editor/text/TextAnalyzeResult.java | 0 .../rosemoe/editor/text/TextAnalyzer.java | 0 .../github/rosemoe/editor/text/TextUtils.java | 0 .../rosemoe/editor/text/UndoManager.java | 0 .../rosemoe/editor/util/BinaryHeap.java | 0 .../github/rosemoe/editor/util/IntPair.java | 0 .../rosemoe/editor/util/LongArrayList.java | 0 .../github/rosemoe/editor/util/Objects.java | 0 .../rosemoe/editor/widget/AbstractLayout.java | 0 .../rosemoe/editor/widget/CodeEditor.java | 168 +- .../rosemoe/editor/widget/CursorBlink.java | 0 .../widget/DefaultCompletionItemAdapter.java | 2 +- .../editor/widget/DiagnosticWindow.java | 79 + .../widget/EditorAutoCompleteWindow.java | 8 +- .../editor/widget/EditorBasePopupWindow.java | 2 +- .../editor/widget/EditorColorScheme.java | 0 .../widget/EditorCompletionAdapter.java | 0 .../editor/widget/EditorInputConnection.java | 0 .../rosemoe/editor/widget/EditorSearcher.java | 0 .../widget/EditorTextActionModeStarter.java | 0 .../editor/widget/EditorTextActionWindow.java | 2 +- .../widget/EditorTouchEventHandler.java | 0 .../rosemoe/editor/widget/KeyMetaStates.java | 0 .../github/rosemoe/editor/widget/Layout.java | 0 .../editor/widget/LineBreakLayout.java | 0 .../editor/widget/MaterialEdgeEffect.java | 0 .../io/github/rosemoe/editor/widget/Row.java | 0 .../rosemoe/editor/widget/RowIterator.java | 0 .../rosemoe/editor/widget/SymbolChannel.java | 0 .../editor/widget/SymbolPairMatch.java | 0 .../editor/widget/TextActionPopupWindow.java | 4 +- .../editor/widget/TextComposeBasePopup.java | 0 .../rosemoe/editor/widget/WordwrapLayout.java | 0 .../editor/widget/schemes/SchemeDarcula.java | 0 .../editor/widget/schemes/SchemeEclipse.java | 0 .../editor/widget/schemes/SchemeGitHub.java | 0 .../widget/schemes/SchemeNotepadXX.java | 0 .../editor/widget/schemes/SchemeVS2019.java | 0 .../java/org/eclipse/jdt/core/Signature.java | 2927 +++++++++++++++++ .../compiler/tool/EclipseCompiler.java | 253 ++ .../compiler/tool/EclipseCompilerImpl.java | 594 ++++ .../src/main/res/anim/popup_in.xml | 0 .../src/main/res/anim/popup_out.xml | 0 .../res/drawable/ic_compilation_error.xml | 30 + app/src/main/res/drawable/ic_diagnostics.xml | 11 + app/src/main/res/drawable/ic_info.xml | 4 +- .../res/drawable/ic_more_vert_black_20dp.xml | 0 .../res/drawable/round_content_copy_20.xml | 0 .../res/drawable/round_content_cut_20.xml | 0 .../res/drawable/round_content_paste_20.xml | 0 .../src/main/res/drawable/round_save_20.xml | 0 .../main/res/drawable/round_select_all_20.xml | 0 app/src/main/res/layout/activity_editor.xml | 47 + .../layout/default_completion_result_item.xml | 0 .../res/layout/layout_completion_window.xml | 0 .../res/layout/layout_diagnostic_group.xml | 20 + .../res/layout/layout_diagnostic_info.xml | 69 + .../res/layout/layout_diagnostic_item.xml | 34 + .../src/main/res/layout/problem_view.xml | 0 .../main/res/layout/text_compose_panel.xml | 0 .../res/layout/text_compose_popup_window.xml | 0 app/src/main/res/menu/menu_editor_nav.xml | 4 +- .../src/main/res/mipmap/box_red.png | Bin app/src/main/res/values/colors.xml | 7 + app/src/main/res/values/strings.xml | 19 + app/src/main/res/values/styles.xml | 67 + build.gradle | 4 +- editor/.gitignore | 1 - editor/build.gradle | 58 - editor/consumer-rules.pro | 0 editor/proguard-rules.pro | 21 - editor/src/main/AndroidManifest.xml | 21 - editor/src/main/res/drawable/bg_ripple.xml | 24 - .../main/res/drawable/bg_ripple_disabled.xml | 12 - .../main/res/drawable/bg_ripple_pressed.xml | 12 - editor/src/main/res/drawable/ic_info.xml | 11 - editor/src/main/res/values-zh/strings.xml | 29 - editor/src/main/res/values/attrs.xml | 33 - editor/src/main/res/values/colors.xml | 22 - editor/src/main/res/values/dimens.xml | 3 - editor/src/main/res/values/strings.xml | 33 - editor/src/main/res/values/styles.xml | 86 - settings.gradle | 1 - 158 files changed, 5911 insertions(+), 732 deletions(-) create mode 100644 app/dictionary.txt rename app/libs/{ecj.jar => ecj-4.6.1.jar} (87%) create mode 100644 app/src/main/java/com/itsaky/androidide/adapters/DiagnosticItemAdpater.java create mode 100644 app/src/main/java/com/itsaky/androidide/adapters/DiagnosticsAdapter.java create mode 100644 app/src/main/java/com/itsaky/androidide/interfaces/DiagnosticClickListener.java delete mode 100644 app/src/main/java/com/itsaky/androidide/language/java/parser/internal/CompiledClassLoader.java rename {editor => app}/src/main/java/com/itsaky/androidide/language/java/parser/internal/IJavaDocCommentable.java (100%) rename {editor => app}/src/main/java/com/itsaky/androidide/language/java/parser/internal/SuggestItem.java (100%) rename {editor => app}/src/main/java/com/itsaky/androidide/models/CompletionEdit.java (100%) rename {editor => app}/src/main/java/com/itsaky/androidide/models/CompletionListItem.java (100%) create mode 100644 app/src/main/java/com/itsaky/androidide/models/DiagnosticGroup.java create mode 100644 app/src/main/java/com/itsaky/androidide/services/compiler/CompilerProgress.java create mode 100644 app/src/main/java/com/itsaky/androidide/services/compiler/CompilerRequestor.java create mode 100644 app/src/main/java/com/itsaky/androidide/services/compiler/CompilerWrapper.java create mode 100644 app/src/main/java/com/itsaky/androidide/services/compiler/request/CompilationRequest.java create mode 100644 app/src/main/java/com/itsaky/androidide/tools/SourceJavaFileObject.java rename {editor => app}/src/main/java/com/itsaky/androidide/utils/Either.java (100%) create mode 100644 app/src/main/java/com/itsaky/androidide/utils/Logger.java rename {editor => app}/src/main/java/io/github/rosemoe/editor/annotations/Experimental.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/interfaces/AutoCompleteProvider.java (90%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/interfaces/CodeAnalyzer.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/interfaces/EditorEventListener.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/interfaces/EditorLanguage.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/interfaces/NewlineHandler.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/langs/EmptyLanguage.java (94%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/struct/BlockLine.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/struct/CompletionItem.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/struct/ErrorOrWarning.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/struct/HexColor.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/struct/NavigationItem.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/struct/Span.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/text/CachedIndexer.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/text/CharPosition.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/text/Content.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/text/ContentLine.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/text/ContentListener.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/text/Cursor.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/text/FontCache.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/text/FormatThread.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/text/Indexer.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/text/LineNumberCalculator.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/text/LineRemoveListener.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/text/NoCacheIndexer.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/text/ObjectAllocator.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/text/SpanMapUpdater.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/text/SpanRecycler.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/text/TextAnalyzeResult.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/text/TextAnalyzer.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/text/TextUtils.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/text/UndoManager.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/util/BinaryHeap.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/util/IntPair.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/util/LongArrayList.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/util/Objects.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/widget/AbstractLayout.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/widget/CodeEditor.java (95%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/widget/CursorBlink.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/widget/DefaultCompletionItemAdapter.java (98%) create mode 100644 app/src/main/java/io/github/rosemoe/editor/widget/DiagnosticWindow.java rename {editor => app}/src/main/java/io/github/rosemoe/editor/widget/EditorAutoCompleteWindow.java (97%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/widget/EditorBasePopupWindow.java (98%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/widget/EditorColorScheme.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/widget/EditorCompletionAdapter.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/widget/EditorInputConnection.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/widget/EditorSearcher.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/widget/EditorTextActionModeStarter.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/widget/EditorTextActionWindow.java (99%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/widget/EditorTouchEventHandler.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/widget/KeyMetaStates.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/widget/Layout.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/widget/LineBreakLayout.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/widget/MaterialEdgeEffect.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/widget/Row.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/widget/RowIterator.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/widget/SymbolChannel.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/widget/SymbolPairMatch.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/widget/TextActionPopupWindow.java (99%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/widget/TextComposeBasePopup.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/widget/WordwrapLayout.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeDarcula.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeEclipse.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeGitHub.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeNotepadXX.java (100%) rename {editor => app}/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeVS2019.java (100%) create mode 100644 app/src/main/java/org/eclipse/jdt/core/Signature.java create mode 100644 app/src/main/java/org/eclipse/jdt/internal/compiler/tool/EclipseCompiler.java create mode 100644 app/src/main/java/org/eclipse/jdt/internal/compiler/tool/EclipseCompilerImpl.java rename {editor => app}/src/main/res/anim/popup_in.xml (100%) rename {editor => app}/src/main/res/anim/popup_out.xml (100%) create mode 100644 app/src/main/res/drawable/ic_compilation_error.xml create mode 100644 app/src/main/res/drawable/ic_diagnostics.xml rename {editor => app}/src/main/res/drawable/ic_more_vert_black_20dp.xml (100%) rename {editor => app}/src/main/res/drawable/round_content_copy_20.xml (100%) rename {editor => app}/src/main/res/drawable/round_content_cut_20.xml (100%) rename {editor => app}/src/main/res/drawable/round_content_paste_20.xml (100%) rename {editor => app}/src/main/res/drawable/round_save_20.xml (100%) rename {editor => app}/src/main/res/drawable/round_select_all_20.xml (100%) rename {editor => app}/src/main/res/layout/default_completion_result_item.xml (100%) rename {editor => app}/src/main/res/layout/layout_completion_window.xml (100%) create mode 100644 app/src/main/res/layout/layout_diagnostic_group.xml create mode 100644 app/src/main/res/layout/layout_diagnostic_info.xml create mode 100644 app/src/main/res/layout/layout_diagnostic_item.xml rename {editor => app}/src/main/res/layout/problem_view.xml (100%) rename {editor => app}/src/main/res/layout/text_compose_panel.xml (100%) rename {editor => app}/src/main/res/layout/text_compose_popup_window.xml (100%) rename {editor => app}/src/main/res/mipmap/box_red.png (100%) delete mode 100644 editor/.gitignore delete mode 100644 editor/build.gradle delete mode 100644 editor/consumer-rules.pro delete mode 100644 editor/proguard-rules.pro delete mode 100644 editor/src/main/AndroidManifest.xml delete mode 100644 editor/src/main/res/drawable/bg_ripple.xml delete mode 100644 editor/src/main/res/drawable/bg_ripple_disabled.xml delete mode 100644 editor/src/main/res/drawable/bg_ripple_pressed.xml delete mode 100644 editor/src/main/res/drawable/ic_info.xml delete mode 100644 editor/src/main/res/values-zh/strings.xml delete mode 100644 editor/src/main/res/values/attrs.xml delete mode 100644 editor/src/main/res/values/colors.xml delete mode 100644 editor/src/main/res/values/dimens.xml delete mode 100644 editor/src/main/res/values/strings.xml delete mode 100644 editor/src/main/res/values/styles.xml diff --git a/app/build.gradle b/app/build.gradle index 6f5179476..31508fb9e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -59,7 +59,7 @@ android { stringfog.enable false } release { - minifyEnabled true + minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' signingConfig signingConfigs.release stringfog.enable true @@ -75,8 +75,9 @@ android { } stringfog { + def isRelease = getGradle().getStartParameter().getTaskRequests().toString().contains("release") key 'WVV0aGMwaEFZVzVFVW05SlJFbGtaUT09' - enable true + enable false implementation 'com.github.megatronking.stringfog.xor.StringFogImpl' } @@ -90,6 +91,7 @@ dependencies { implementation 'com.github.bumptech.glide:glide:4.12.0' implementation 'com.github.megatronking.stringfog:xor:1.1.0' implementation 'com.github.piruin:quickaction:2.4.3' + implementation 'com.github.Rosemoe:BlockLinkedList:1.0.0' implementation 'com.github.smart-fun:XmlToJson:1.5.1' // Dependencies for java completion @@ -119,7 +121,6 @@ dependencies { // Local projects implementation project(':treeview') - implementation project(':editor') implementation project(':spinkit') implementation project(':slidr') implementation project(':javadocparser') diff --git a/app/dictionary.txt b/app/dictionary.txt new file mode 100644 index 000000000..8c2a35a51 --- /dev/null +++ b/app/dictionary.txt @@ -0,0 +1,4 @@ +o0OoO0 +0oO00Oo0 +oO0o0O0O +0O0o00 diff --git a/app/libs/ecj.jar b/app/libs/ecj-4.6.1.jar similarity index 87% rename from app/libs/ecj.jar rename to app/libs/ecj-4.6.1.jar index 1c7e9cad3046d6dcb9b2a6dfb78abd68eae98b2c..d0323c672d21645b239ca153b572921a8fe8f9c0 100644 GIT binary patch delta 31437 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 N%D&jT#tj*o{|_`->JtC} delta 64131 zcmaI9c|4Te|39AH*vC5diLqzR5|KTH?6i=Q$etuoYFeb-61j>*yEe2LqO@p1;?_nrn*0l;avx$bVmj(dVU4TvQQ^B2rGMOOUV+yy$BtH^A)03b+AgOd>L==$nK5 zfH0eST}MX%VB$Fz#_A+$ zkt+IjhZvw186OwJDB|fUb+o224Siig0hps*9Rw1+M39odfFLZd6OARkr*nZFfRzo5E#x<+0G?0VTTwIl);5kqF)867YICNC1r4}6~?|4BUJOj=fA*? z<=776e6CFsOcx_|R;UY;OCOzsB*sCKu?7!w?V8LoVz5G8o&?@hK)|I!=#y1j&6O&I zmlh%-?{Xpjo&>-t96jb;E)ldeL9YaoZ-zXbha-2Vfh!VOJPwi|^4R|&*ZlzNUIB|( z39VfJp#C(Dq!971A;^pbq}d)5&~V3Vt`ZYcLqJwRbCHGM{e+6fN|fZ5L$)B~n<`k{ zLK(?w#SXu(#65@{PS{-qV=5F8=oJ{n8q((ej;aq5S8l^CNqAml@RJrrfz6gUHU^g5 z{*w-(5N!BV5?Ku7vc|}NY`7Qv2gyquu2x5auK! zXb`NoZrpBUBiADyU{Dzlu^odpcyOl)>Pk;%Ki6Zn;XHefxwD;p{S4;K9`8zu z@CP+e(phrzxu0`!NfSd0xKS4gK|Y!Sb!ZB~w~*VD4cJhpj=CTaaf%rLiA@W+z3I|Z zLYA#ulV}i;7#lOb&;d}t5^MHqA@^f$Md>MFp-~3L;|Ts>Bw!RXx-y7cN}0Wq-5qru z(yfO<#v?4_YYO)#ZiJ8}&fS4VzYd0qR-BbG8@LDg*)XrW>e|G#G;Tps+G^l=7dG8A zjr-5I>1>z*iP&#}G|~Ywu|m?hok1g6qL#`-AsX&N`E`ZGjB)mK9OItHgLNaZ2KS%? z90E%RFyVtwaSKlW7k2MGNMGD*&9Ih5ZQS1DTC%+YI!L_a79;hoK7{nGf>^OsUUEPA zw_Nx8>WU=R2z(ffW4QGlt9rJ%@TE{lqSTZ6hbshu9I z)rYk^WX!`4lI~9DK^^oautpe1=Gk-}E!33=Mx#0cd4nWdtkZN0oTQIH=JgO#ru7vvfTeEDGx!QAZl=JCT;K>U_=_dYQQ%bsok|`+=)b|2r?7R! z%DlJHyhX4$0lki-Tl6GqMI0wi)H zAPX_lAe^^~1F1NDa>m#U*aD0-jNoN)VofKpX@H$@ph47x16Q_ynoIy4!2&H~c&|+g zR3iSiK$(6A;cvxA>p0#)KzhXUqP8aoU33qCS;6tVB~bcx*YKjT<^yuul;iM8IIQ8l zf!wFus*dWeEv)haV`e7tenl9+wY+Go7=rIu7!$Xa_YuMjU^xtk_8*L0m&~gGT34ok zLbG72V1#sJS7#i%y zSLY+l4f)Wh<-Q5YOw$JyJPrAZ^^wd?&(*~U%V&^f*|7h;fX5T7#e7XLo{-4$XX;91 zZUn&4F)@|-NAleUSB@h&ihx!MTDHp=miLO~`(r$@yap-H0J`fz97Ljs?+OGl{tO?Q z{9ARAgiUxb+H!{P5DZ3a5I?c_g}O2+j$0EU<#A}juJV~dXh>ws3w3D{DFn4b6w7>m zpHGKVrrvWtRTAk3?N$;y$Mrd%0XT=0S=I*U1Dk+4mtZ5#y4=mzEQ%~9k>@}2q2cx~ z$Q;HpZ-3?!hiQ}y;?LiFNY79`WKkB@Q~VF#Ina|t_P0Z&@3TTkHOLFruqgj5sR;`4 z5*{yM&}4=orLUrgO0;f<<4&n;bE`NODNChNA;l#a?BqW z8k70_tDw-3$W5=n^SO}R-FRky9mxL|W_GUxegUHY6?BM17>MgZHcNgve-An`AlOJr zVsVE$8lIA2s(%dClVu&pKLoU7BT<>_^haht0k*Ru68Jx>BD2{bK1M`Hf_r3QSV9QL zCh`kJNDrsmzJ2^IXeT$W10S*dHI#)+h-uG+wg>pxyZrHUG7_m86B!qoIN1*BVM2|- zP155ie;PE&8*kW01HW1Ps5Euak>X1*t7(<|>w(p5q$WwzTrj;Ew}V%A`TL=;vOy{w znRye6O}!S9%aW+)@6ke+#ZHLTRLG}&n9*jR`AdP(Y$QJ+`j;OSo9$3Od(}av z=3jnpC}1RVcNfgVyudzxyn2kfz^cjB`H7!h;L%1Hwk%+lW2v|a_@d1*LCP%f6+rBx zgAadWPL=x#$ip@|Mu1=4H!&nJEH*5T!~JSd0IuW2;tmpMLy46aBY>*%D46X7^ASrf zMj#GqZTTAkKB6>108LO?K)_r~|CMNzJqCL45V(wQnTL*6md%Bn1C>L3&yVM6@a7Uaf@U)IBEuw z)W8|-@m|0lGMWwLC#w1d5cBLHL*HX$Yrg=yGNHU17BC=wzE>9^Q=|pL_!3W?rb7Z_ zFmZYf3z(9iVA%aWz*I^b7I+WkDeI>I>IrR2k-yC_hs%Bncms#olK2U)KB!?HK#UAb z3@dfPGng2CkP;VckvIdqyHL{>bcNkT)+|9(zS=?%C{F`@vPxzNUeuVdlU<4WVfXMF zXzPw?o4Z^v0%%K*7DO$`z#NIO!)uyH3kJcOY>)tPZL=WaHsOqrp_qp)n+2J`L$FK^%L2KTamLO( zBWMY&Fl$hqk0`7XL^;L;!`}kWPF<)HggCQ-sASBBW?g`#^;HY9IHff-g0y>3(8F;E zI~oPIKnTfoceV(kXOix%eB*`I`Z0 zijfJ)6faKX=MCVXTo@vE;njsUQch0pGI)tuqcBW%%AshAVFwMSQFOsURIqngBU!)jeBb?^LX+l@ss0;CrUNCz z1Bb-)HzkTQB$Ypb5Z_@{MeK#w{6ZGs1*1Rgl4?s4Ld3NJgfn)47ey!p96%!X{DMF* zak65WpM-=SP0HjantrQ`Dlv>FhmCZgqZR%OE-|5i|NaHB@;40o#lYeYT#xf4gnmFh zW~)LjfP(x%4Y_P7R0md*j8H0Uv)D+~2;7Si=q&h`wM9+no;C6-DXst`>)-%Z1W)Wc zmI!I1i9PQx6yY35Y%ZK){fR=#D8<+qJ|gBXEc+FJ+88){osbg;@J70jtRjb#9)nw- zU?;UOg+3!Ek%HNHIwxKeLYs^OM%^luivl)*CkE4m(3#gYK&NB$*EFG{|3+((;7Vf>U6U<@ zj{WvRwOxv3H)IQ?{#!QZM47t~#d)zh`eg0eFO;HA#+ej8tx5=Wac^i2_;#0NT_uFC zcgI6W;bow6i7_aSUYLkI*Mwpa5%Zo3@e%RtLr>CaD>TrOMR)_3c}u7oZQ$5IS)!kp zflOKnCK+If`~DGfosftc!!iaEb_~}6rKdu*XrIp(CPE1J2~o&Zc|iytW{h;7&{RmP z8$!ZVLXV$;MEZLokw2i>vC;>GWK~cKu#xDhQrH=R%;nK1t4~PSM;?KeGKA4q)e=v0 z*nss6;U36Mwj@4cr;af4Njva)f*FT(gn=33=UBYN9(`eS)pR2qEw~m8XO-y-=P=O1 z7)c?b1Q{sl_v}RQ7#MI_={~|c%>N6w-6@P3d@l6vuef*n?i8j%??%$Ig^`ym;nws4 zE~_Qk!tXiD>Yi+2A)OPY(@?ObSbwEQ9laPMtVj zpUw&YhIq0609khi*t-^&H1%@f94KjQkSH-E1nXyjgFSJ2e6JCXhR%YK!bI^6VN?NL zLLJ(KS3h+__|W9)q+lUp<85JdNqF-zvM$^aeX`PS3vY2mULLoOFcJ_!8uvOOgc%HT zEJpzmZyNAss|X(<;m7{ZEmaZJ=B%JDreFp=R~6Y03|cw?FJOT5MX5--vLWbE$`G+M z{I_(pe-fXH5Nq(ztL-O}4MQ(mFhAig#(rw$lPClEBMNf$Ja*rgB_j3UJ`9J4M!<9S z*mEPHB8$OuByxSY2x|8F;H+4@=h_i2vJ>`PY$TOX6^F~+XF!_AAe-gBL1YLfH8v7m zX;^?IHP}^YTScCOt2XTrLA(B5pg|J0qi~1FKCpug;wOS6fH3QUQWN`}=shBMpTl00 zk0{F(5hEDcBFLZhKvx4S*g9Lpb<#m39HrP4IP!>AgBab;71^%F5u;O725JBo0C5(x zdoN4m9cKz`SC;q-;=OG{KSJc5!4;-Y(z#h&j4dywu$u5K*q$&uo*NgC5lF-Kc^9*2cpj6 zl@{f4;y@}%GZ2H`0X1c?*>(#=jlpcTOe#?rD=JJ77`_P3MfX5xSvQu8mO#nEaJ0`U z0s8F0)U1dVEdgq>k?54G#{=oP6vVO)#*3yZB0cS7*|yv65k;w72o+cW@0oq}h^oV$ znT>>v3y^y@*6vDy=rYcNS}w~#yeQK{3vEdq-3;pV2R*8tchdl#{d@Ux1l8O#8 z^Kb}0iHTi=5U`QvO6)4!g}hc~4=Wsl zwI0~fh_%$#5_<A*_24aZQ z2~a=pVxX9TSl7RS{2V6DfksEi+Y~)xF>BbSkZLNaGSCcm5ZvyD1z(62TQMnEh(l4@=YvR2!0A3=A?;l1LB*cbkbJhAuR$PF66tpNzz zX{@A8miQe|l9VZ;$v`J^34q*>t&b+eFHBl*O9r?f#GJt@G7H7;a;j*g1=P9#5&MKA zcBWWdcrs!P;`b4Vtprd(9IGgQR9uf!#XTi}^a9l!@#-Zd;!8MJ_dF|(&Z5eJh-Wc! zpoa(#$G?Wqgtt7&ceI)vW z5#&(~gh2_1A=E}<1_}cS3^rz<>D3<2b0Vk#A-#pm4!pbR8s(Wg=^tuRoKXqyfOo5SJLJxsg2Kx#K4JX@k5 zaUz6@3iT|=*8^C+su1es3H1btGE>M%A&^*xC7zC;HgZTTj-#TP!3=;c7qh~ zf+9#*ZKfhVNCEH+mgu&bdY3~-o9$Gz$vp*Q>|0>kDc9{(NQF(749*idCAb2gc&=w> z78O6&!-mp{$YLrQOxJ>|y|I=Xi>a|twn(JmaVpxPsDXzxF&iC@QyrjBu#p;sswKo5 zKWjXKQ>NntRbo=6FtrpA=XHNobGrzxg?JF_d986zvE>| zsgmHIab%ti3=Tg4=ztx$?Hx53fJ64|oqfCw1MM#qAaXZQ-cBLZ`l&C`kcEN#g#G{( zq2B|l45J+fsEz+c+kAxlF$V|XHCS^#Qq6%KlfusrQqcs5pMt>SBCBeUy8nORs0Q)d7>N~ZR132dg zcr;QI^kxnEJTiCmJ6LfHYGXNo+w zL!C>~Y*LbnUrKx!$EhxaHyxw1xh3Idg&?DGO+ZqbeN`Zd4kNmO6;m)Pl6fVSI9b7l zqe&tkGRhMt{WAf{=*gt#CCZ&4_e5Y@Ux;OH6Oyb#GV@&6sYjp9KvTpFDBTnH?VH3T z;l7;>6eDcqBoT0tG4hTn-d`_}lk|c+2p1UmZbF5r#)gmRN7Xu47 zNpi!GH7+sBl_5#ArAeZl5)XuY;vnE>nj{?!RkGCE@;? z1O~bT*A{IrWejP?2BN7Wg^8p%Yk{-~52+A%9*k`ZA2GudtS-m-jr%Wul9Uqk zUlQrIP72B724oR7J$Ri|^`w8;_mEy7b1P`}1uZ^>6|hlC3|a#zb1zd0*>V#q!6B#} ztiw#HN;Jd~B$BxeklL`1-49e^Wo?tX08g`$$XVY1xo5(!qp(u5q;$ZdVsD@YcAfz) z25)nvTsc*g@Bvp|gE+3lW=G{o;Rhnfg;nyU5P4OBKoh4!Cizn6bV!eAxi2L^^!b2; z7FiCmBQ{QByn(e%^=8?!7NnN=93csC^)X_6b99=O7Gw zGuA&3q_pJ5Njr@=;19-j!d^5A_`{mbPw0~iv-j{zxE`0)J!7v`e;M=518 zawHNdHr9qb61fb0vXVbbZCi%c)bW!>O%uOtya9s8GV_x@NZzr`B+JK_H1x;8_~eWd zB)yJ2Ih#h5pOzN**W)1gP-x;<=6a4aI+j6T!YfZ2?d|61Be8jVo;r+&L61-ggdIcqD)eOnHg%)7Oa_=bR4F4w91MgGHBPko;x|8)= zNcJH4WQHDL5&|XjBp5$&4E9(@79E4>5r;yc0uMt}(y=?Y>B}AkcZw|oTI7Q}-Er-R zFp`ZRYlj~3FqEBIVwSS#$oL)9tFyRX$ymzX1JaVnzsukveGID0#D&OI8(DNA0y5)6 z0l5iQ#oI1covp0gq)fCUKLp}ru^kqUvL;{$dtphAL%cQp$a$KJ&?oDhv#i4+6biP< z6yo7>u$v2n&A`#0Q7r2R(I=5Nj>sYkR6-_u<4V_hL^hVJbb17dtO$c=d5*!ICH}P# z*5=c)=;uQ8i1@KeC7S#`3t;5;@+jL2F6*K%9IgZQ^SSl0JtwZVg#4YDY)+;q{Jwa`siCmLki z$-7xSf(_y$%3sQ&vl(vCQ`8^oA0feFL&+juU-}KxTN{x-=l`?TbQTXeWn2 zo8j2uC@vtg?c}aQ0Wq8{hsGk0y+~3q^mLZPY`Gfp#X3EL4dN#ZH_4$LU#BxdPW&c; z)h0Rgn*@5qr$cgRh z_z^I60L$GLNE0L{5`QsyKs$2b*cKYfl7C>qd@OTw3#|f(wkeTaNmUZzm}Mbw zn}LIp)Jn61pscE;~qP;n9c^Ms}kMKs_QtQiMvc?4RrqF&KHkq?{b5lhxUmW04E z6Yo?Pe59e5(n*j6@oEh;mt}w~!6p=cqSb*3nV)H>mf_*T| z3c!ZC^{~A?2wzT|LHb@&7)8=DHn5q$@v;Kq&R$^t4;;Rn%L=l<9TNG9rGQTElAuXU zJhwN>Qb5n`=@BGSV*@N62lQ#e8p>Q#_y8KRk!W;X<%C?;4X1Z3=URoo3nYa0k+h9IREK}%pc-BU~=2M7ky@ktR4hTY(7N4(nS zPl`)OByb(oSvvK7Cc}GVHaqKz*Ov#5&qFqO+f&(~f zJG9a?FN83`8aC@Hd4PuG;%uY<;pwY{b`e344~f`)A-+mk@GLS3S6ZTkPFA9TI^VH> zw3jH!kWB$}eY1moHyt0Mghs6~NSmj)2qlFmefbX@ZN%q;3OqQ`N|!6Cl8MG4I4?#C z9UL}66TXL?ON~+L=5#Jw4FRHlC%7XBTG_;A({hy(+H6h==X|(r41TBRMJzINyJVsg zOEOXUYb=Me*-P*fhjsx2dB8E}v12^Cl<_aauw5uYxV=?Ir$m=v@4N=eE-Ub@ayGnk z$3`M|*h39_2Pw$X`=ZQ49uklmOAsG-!w|Mp9fcadxXN0|uW|q$(n&*-|Rb@VG3AtW{G%QSN{s%mJfVjcO{($wC3Ehia;zK)7onnT>dwPt;U-2M-OA zGBeFoP(DP%J+l%nQ%B8IX23-}i4?Pi)%hHdOfLLvpMtH5GCbQyBBKLU&|?KJA;%^P z#JWHgR3I3{n+zBYF1jLd@@nXlHG8Rwnc74kC5W;uD(Eb2(Nu&;#F44mqLKxXp=U8< zIp%}aP;jPT=*}z^rAa6n_MM<^+F>_-C{zgoH)dtQC6^HR!vaseTaKvsl2wU8utDO4 z;3*X}^}Yq<42)DdrQ*klT%QdE$_Ef{aN1R#Q~3pH_a+;fV8J_NZ2}%l2Fg@2$iakM zSd2KQsfy^Z530xsd?Qw=sd^Khqomub3KA9jAkjkLBuX1+aiyiI6J#+PiRO*Ra2)7^ zA5E~bSB--=Fxc@GAnbxvX+-}*RW!9d0Pl_B%;H|8D#ckt*l>NazbycB@sSrRHb^y^ zEZ&gjq~LS8;Q31c--rd92D5)@aDo^}5A<6NbK}BSPPVzl@P`w>4n|+9y6!)JD91sT zb32izFSq~$yFyf(T>iZfg@`++%6?ii51zgr1FJQ02**yVI{e3K@^4Tk`~+X(XRBEo z&Z%CTtO5eWjq9qC#M&xVRPga@oes1Lch{ayFQB4Ez0Rw`wi8k3<^(7kLIF z%X?Jsaw1Lsi;Tj^^Y2vCCy}TG;e}N(DC&di%t?@#yeSqZ38^-OGxuemD*m-cQbjY2 zECs(LVNa@lQr!ogh(wxWNQoN!(^g|WDhA(qEO=OuA7)*-+Ik$8FNm`LNm z$uR0#BV;rbVb+*9eLGyqk;sWK3c@&;hS#o8pchZBEk6;(R$zuR2JTg)hfDxb5aIB2 ziWbIbDA64!FdQ+&!k{EgA&QjfG)&;J94Fap6;@znF}RC?T!}Nc=OZ zQ!!Fnjc&t%l%HS)mZ^^Uvt5&J3zdI-arp^WhyZ^k?(Kf*(9yjeQj>T+4OSlngO6jw z-%g{$&!Tx1!v+o8ER3s0n?79&s>b-bL~Swf!33gr5Dyp@M)WIE$d3|EbX2L&nV_`} zKsvB=o#>aLXTR~LOHGnt3LPOg;e{1E==gO#Hd1<`OkiLsw&IHyy#X!D#-Sr4jF|{= zQ6SxnFz}(nV6oJPj*1fo>S1BYK6I$7Y+>Sg#~FP8t0F!xVg2%8)WIEbOYQp~T2JSXjlIbs@+mM3UaI|&058b8+P7hem zx6-R&6e5weS#&f|uTnrnXvZ=iWzk1DWwMd%-#sg!qbIoH;c_tr?+S$v(OJTWrtXJb zAr>w`oH!5H1n{k&=g66CM|rtBDX-%V3va1{)lG zOc(t?wV8Zk_HMK?8vB3R$Q}=8L2gR0|B&gpGB!RkCM<#T2Q|h)5G|g*9>mQjbsrBOf_Tp=LjI)j7}qXv^ixKB?`R&k`GxEC%C>i@(?oi4V!h zFXER-Oq{L!_;05GbL-g40@Dp)b^8u3|70^-WBu;Xisq(nCUel1dx*bOu|#S{@Ao@< z9?VSA$vRl&aNxo#uOFp7%fED_)+Ze=qS!Roi_G^+KXWU?k^d-n)9R;Tdff}+j8}(; zd-OW;7;iSvSlp6i-)s8J>}8DT$s!PTOm?>~UPWedi3xs4I`0 z)F1GwJr6rdInSM4IyiUDmn{??QSSED6?*p?hNrg{m4_?oWIaepX*_s2HSpuvTP3HOg-_4$)0g@IG=%#Ctu{5U;JGGa;g3S}2r{T0@i9V}~@S%k)&9sC0_ z&!^jKpU!$aCtE?M@$(7)%KZH7)_LE|0br!KxWX`kqbydT+WA170)a$>k`P%%YfQ5PX+aA~K%4~OjCe%1n!uRF4F?er77q64gbceh&DG5Pry z?vSzDdVS^MrWf`HZcqlI>U8$dmWu9E>^_hz{z=&Md6eq9>vsJn#O1Rw{vFSDJi5=6 zA6xpOG2d6}7%kK-s7fM1;ao^)m{azi6?|4jI$GO)9NM|cJxe6uNO?x0|Mpjpgj$d9 z)NxkWTB97A7`FaqovDGO$I>M^`JM|k{?yf-8ryT`$SL8v>=$cRN!1qI?>fFIY#D#b zW~WI1?PtaQRL?ut5FX^^Yxj^i5E7U3?emOa=5EdMPX|NIN|(N+#Vww>p)@Z~tL4Tp zwXAoke&3(JopkR4$IVuRBwB|VGoeBFL3 z=Ir}*_7Ti?{s)g&r(VyHYS!)VkoX>HF-tf$eDPhygmd()XiCCs`W=hXUUH@IEVa*hEN1TLw4;`TcfP}e%@=QeY>csjor^}Z_lp%+$Ju~ zAclU3)~!7DPIDP+l{S%K;@7O-=aP6XVDFI1KdPj+E>EO?I_!tKHEobG#(|^87U-wL_!x1BNXUhf=0w9&G-^`&qznhppLT_5E+m zN^UEq@Nvh|y>=Q3#)&V}UY2KO<9fsVOHk93eSSZPs2^fsXST#HD_Os#u*8Bca>!)H zE>Vv-lZdP)#1P1>H0&-FG!@ zNoT==punMFLoYWTUUpMr|38?RhilChZtGdRoZYr7)Td0b{=bUt9 zUCusPbfr(XwC(C+ne$iomR|8;P-ubePfk?#T@S0S9+V0^`!j*{WP|KycD1!ODYJ9< z{X6~T>e}JLPrt2*Pk+8BHuFT}R-4V>o!5E4ym>@`C)E29i`nvp&mm8qB5z-0Yi%Vf zm`^Q|5fj+(aaC>wub1PI;$^j~YCDA$j)W{3y|MAp%1CY5Y2Gc4-%gi&GB3?p!Ml5X zXlQ8Xjxe!r>7mrxs(X8!csjk9eqJ4sk9IBGTwM0-@*-Z1YnwZ>Lhh)YT)Lq*E_%hG zZA~er))A8V9%W&|&DH0#cSMR*$p4}GEI2g#NVBQffYDL)wY1PW#fP<0@2D#-G@g9i zni6)hkESf#Ch0H6zo*k{Mik$#!p$XN>A7=D``j&_>08pQ*FFmKTGT)D(!A@1%st%( z7c6H6>iB3cjipK_M~?~$wMSf$^b5I=9(A+9t=;#YWb*s$!jFX?1J}2YF1AqrcFw@7 zj`*EW6IHBFbDh#Qmz6^DW*+nT1zk5P*+5`17#a6>DofqET+*|d2^{($-0qu@|3k9JPP}L3KA=iOxHY@J!Sf;M>6S_FAQns=LhP7-!z#YFE&;* z*Xm)Oky+CyD5{WcFY|gWZEEO(6JC$kS1Gx6hZnqbw{zHB-fX+f>FCLx=-swb8$Bi6 zOx!j7x9HsYKGstiY@pBl?Vz(V?vZzTf+$PSytz3$GB+sVc9M)8ODMG?h0@UFqb!=4 zU-fNiGXKYJ-N1x7CV`TddEHi=@-$7qX`sH&&WM?vwI^aj_NM(Kt1E8iJUtkg^?d4c z`(?UP??lcvbsloxoj-QV>xs+GS85jrp6QOhyfw}7?$%Zd)k~ zF2CZQe`>q+`O3YnExTVSB!#&vU)vtJ)mwRe%e{x;vuUCJh4R~VDkAq>)E_Bc!Z#AU zdBxpduZ*LoG4tw9zdBYv7_eZ}D_u$J^zRKurZwu0>0|N5p;I>6pSG3Vd0?uZnDjK) zfU6^x4*tQu_axGFM#_c8qFdK5z7=z&iZ04!KfK>_s==N&n_3OEBZiWCFC@4HzX?9^ zgFjwOFm#W}qwoQ#l;wwWWLmF3jVXg^u=C9y-}0?(#r> z_wj8DJsH+Y#>c#WTuR&bvaFjf_Rq0D^0}|1cb$*+zV1GMMa5}b@%PCRpWYs_%{{G+|jm@)h>$=%#U z{ZinOy6NG<*DY`F3Q${_LsRdD^4n-_bo5#snc!Xh$?CKB&GbfN71QlaUR%SQXmvKG zue@fMoaqd$=dE}Vx7TUU?K1&Y@zQ(aQn@oD#~6WjrLk+QGdx!<@E0>bXLNpHp|N%% zsGsk&8m+c)qQbqO)6!{$!?cD5rn?xsV~kuU(soK6Xf>KUueH<4 zL+@tL=J!uGN6esIau`ppMrmG_x)hL&_^7`2-S;JLvMtAQ%kP!7L%8U`yo_c6rm-C(BYOCwp zKjmZ;eSNm<#6p{yg^4`BFJ#zyU(741&v|y=yZ26!@#WlG21@sqydT^ACE!BJk_Upb z&6vN!S`VEIXpcRVSt!#|mWk^L{*+N;afkCq;Obx0$ixA1Jv ziUkoJHwvr_-VllWHn}e)F0V86e;@559X0$_B7e)i9VPCk-}5$WfB2703@>YTdg!r38ExX5)5uW z6Un>vReeD4{(wKeHsu;-e#qranD?&7rYz{9?es|A=-%Y7 zN&LPk@0)U#q?GAzxvN)MRetH+aQ8H&nv~R;!hQKKw#_vcS1ij77*c&R5YCiuts?}} z4+j0Sd_x~kN6cC$=DdJQTzp4QH<#5{X{;<7jGvnm)B2XbL$ddP`p)uaVV7cx(?UPf z=W00GGiHZxm_EG0C8D=u|6lVw)%-I5#E67rOJDV9C>u@tEBNk>-JF~PNp(N-?T^EJ zEBbu4%pzj!Yuy(0$UeENf2FOo@%%+c|B@MjLvVBk%CtUG4oI-F;=pzAbbRF3)2&j`1qYU9awG9-{nO zTHsEVifHO^m|baiLCL4n6qWXvg`0O>)d^Z+u~+oBZ$$nL(dIVEruYc^sf%>__v(mQ zZ*7XG_;4k@IlHB6c0nA^5usB%wZGl`EgNK7F`qT0mL!srdP+w}UM` zyE}UKUG=TJ>1(p{rkqjhz?aJ4TmFxjv=5^ZDl2YEf3v9W5>$D{nEmsv_u#4*foeIG zhCKg#T(y3emc(#nzgB$mtA+1XMz2zoyjQ$j^R@3r^;qAkqFdE^s;5)`%3CkmbbYLU z`}8HY`%(+QL_?k|1FE}HSne5v9hQ(|6rgG5+kHUAuIya@{ znweVJjU_dwrtN$d)c23giJY-lv-mCzivkU3Q_9J)cu<%$=oR7X8ptT;n(#Ir-r^T{>=WY=pw)H zUi0x)%$L52vTN6mG}QVobxsxf+Y+>>#jjUqmzy8cI3wCdMu)HAY(vJ)k2>cUSnOmT zzrO43+8-)9QrkjvtWWt_?^S(~Ix@HB+si#2e;tI+zH7eQ8DlVh7dv|g-&AhEq zZYX_AQJvw#H-D8YI#%qzV$)M$N-=);!gcW>Sr=yEJsXW}TJ#M5d$aFp{zH53K1Jtl z0Du2tk$2Vd%#u-~se{gD-$KkfHk%pI-txGo+xO>I325v{}1<4y8CHf6eH$e3|*Fo4#?!?c{CGZSU}m@u^5Fi_bCZ&=5_YlRs7? z|7m}cwc6;LttM&Wa|KoN#NViYnVaf&_?Gu~lUbUlD*sxmFmqORullYsOT)G}&#?Qz zFA>lC&7UJvq>5HacU3yxHonK5`tX9%lK7ylhE@tgxuNQJm7B))bS~@ zy=!Cnw=<GO#9Tgnaiur(FejVH@c=MHbm^yNRaW& z_5HNrqHDmh+(mNtpZIJw=^NN@mLNKRbXir9+uX*FY86`(?wXyQ6Q$X~JRTmTVVbij zN|v}}pz&@|Liy=>s|G*M2iF#)az&et46%l|%_E+l3aR_8S?ZrD{JgYN+s1DL)xYm= zxKj?lsAp6{VY(-SY4gpq@DkHS=Z*2wPG^O(#8Q{|?7jz?H=QeP?6N%EG9=O*abQZo z;^TVn8Y4I6KVEwCP>+@RTopHF?;6+JpX)52$*c-AHQbwbB&O8*?YeCxG8GQTz0DcD z8BM8Yl7cROyBw2h6>XRN!sC7442LrXaxXqGk~2-++z#!rZg@PcH~87*%iqqPoT(r9 zrE zq0QyO6;{6m^%ig!4C%(*$h<%)^DZ4$S|1!rnU!h&t1d6aEhs?b1}{;!?v`2S@h=h5LfaQq*Bit&VBpr@3* z^q-UGrsc}$O7l89pKi-*Ky9b9WRLx+MqXLg%r$Fuhq`)HBh@5K|5?k- z{rsR+pg}_-vh~}*LH16yVD$@ICi53}ys+oII+`YPd3lWRz159J%I6cgAAh*=&Kiwz z$!jRp;trhq;l-ossa0oIpY;3Yb6Zm+z53n??S!6l&#M=d27RaT~uN57YHwRo9t#?Uc2_>z}FzMcAIT)Nucu=BA)2fBm?p*zU+t7dh_G?=Fb~33Yu|slWge`;s|r$?fJ=G^KQl)|F*ic z-M>ht?&q!{W#f*t{>sQMe#d*ARN2=1MyA?TXY-~We5h38sI-A*^iOlvb7pvm1o1w= zwXeq4{)R;t?bZ&8u1lcwN7wb?aqC`D!>&ono_`zL7j&@YOmXFK+^jcZW~U7b7aFbF z-jl=25N{^0Ad$l5Y^}Zvow=$>R+I8y8?SQjCcTkoeule1Q zod4zhx=S@d%D0p41Grt@A2vx;F9Ci@ z(fYHeW#+x%o%e>aEAjo=y@L@=W7h=}RKy*rN4J|_&FHv#!O6jJMpnd=e&%_bkyxig z4-4+E6Lu_8JNU6ZZU0BBOFMgG{@M{2_SUhqukPL0O?6d&n#Is_)4o-#tZcWu&}D^m zj+2~$(}TG|j7GYjg@ONzr*n2^^fhgCxpZM`qwxU!PWiThXQ{7c2a0GL?)-ReKKMsd zu*G%6F}GCzqS*(Ri?-fhnw`cpB%K~zD7dJrxl?ve$e|Xg5kK(&qhliCM;qO{rv6q; zPP=y?!{Dx(%8y?>|4t_t&5$ei*%t@nKe+M#PA85Bs_`##jQ`R2zY{)-quJ?Xb4l@% zW~t`+C10yuRa$jR{UlDGP@8(k&Xn(j`1RPN?H|+XADGOP`K7o;eAbwF>Sudi)n8nH zv_C&ImCfFCY`U%J162;+1>Mn|=4Y z9oe;5-}zg{k+@OkCnu-y9bLfugx^+UEi(Q&5fAEibfy(s=?(TRWL-+iC+ zT{PhOUW?;SVQVaXX5R99>Z0v0?W*|4y}|Z$)BGPdmZ!Q@W(4&9>bm|Y=fH;ja~Efu z7*@V|mXf9@QgH8+^k1)k~;l%-dClZ}}D^9LSvUZT++ynVgR;!L$9NHSM)}KXRD-GzfHIMzyB`E z$qWgtxtVS&Tv_bA#ETTx7%;(dSU9 zs@@JIr4gzXUb=g|k8zpOWiPa8oNMd~C*oxLj$QHm8CUz$B{98?-^!|cds5%X=Oe0Z zAGU9dnEAV<_UNMzRn>ZwaORVw`wtQx+Ke7vz46`Qdp$pyaUJ~S;zfcbAv7a>f1fW` z&FA|ae-RnjDiU}>WFhe-JjCOXjN8R|zC`O_cCOX~>E4mC>&a!CrZ~m~1ZQb1HusBM z9u-jE&g~$gSUGa^*y3EVsEbhtoC6!Drwz!Tr+w0R{o=}uPoX;u?ypOIXu>>|cp~dy z_)>Z9+hHlbMTWH7!`pQ3EL(8Td6)5qm(!vu!WBf+26HZFPaus-=-#jm zK^3Ogzol^nbBhQ(l}@c843>Dw6bQ_izB8(dt?CYNpB z9N1xReNV#uU4pu0ddBvAYi+eAvoznWRhk=f)#6-nZm6R0g=N&rx+(x(S zKzHc!fjRl9PA;*Q%#b;UhkayDWL=awPd&o?n&mUCK{s~Lz4Dpa%IK?a8H+v)H{GJ_ z5u*41j-Fe&!}!p0K{-lx_wE%B1a8b2UF%vme4zNS|FuVLVPWg|lGQq&nO(0J|8ThM zWYm0-ugk9=`fGH4c2kO;=RJ$pzFaOl--X^#&fdTseBAQwg2(PVR|+0jWahS*mwNv| zugz@DtxQVCQ|h6u4I0yX#^yVB9y%biip%Qee1jP@i}0y20cZdt3IT zI@Sr#a8vFM;APoueSYfV+SO}x4|~r$@oZVg(NLE&^EytA{#5l_&XbyaVsDK{gy@<% z7go4m{Wdx@>+6~hpXGs;gR>GueEkA9+H{Uq4lP;xWZ+>zo{zy1Y5Dmd_>Pwjr%K2g zhthWKZ|zy;ePka~WXM!->_G5|p8}ip6NxJ$eAJ9PW7GNT^2gMBTh?XnQrWxi^37u- ziI$iD34C+_aU3hVQ{ zD1kY#4wUvB{WTf`b1JsEy>~S@_H%mcdA2YM2kGT&RA=2Y=yGCq)f*%^CcItW(*OOX zTkfYP8nt%q#PjOPW$JJ56S^b)0VAW`I-X4N!rt{$TUXY8`(V>%p?BF)ug&vk)LOsD z*WaEDI@X?wUEj9&_$&F!b;`FytuNh|yP!X)du2g?i{oyKtj;4(ta>ece{3q#PFdo6 z^t!#?V6pP+qf)O!W^OED#<%xWHMPn)#hU77CEu6(`&9Rr?T;?iyE{(lODP>bHP2?P zm#6Ha^?C7!d=Cr%s4xtv5xKfqdq|3V>|o5FuIC+Z`qo!iu2slcZqpxOJosae;d5pG zrOdBmf@xx)f6?TJ z<}J}*v;F_hH1850dDFa0Feyds*`p4<Ay zLwz;}f^PMX%Fg`%`1%fTtlt0s?7jCM&tvbAvPqOJLMc0YL`I9A`jG6AILgilkrA0G zp&>*nLXoVD%v6N`eV+4p@V&0z-}SjZIj`62{l4$}ea~~B<30g*lyeSz4XmqOe43_-SLQ)H~Cj-O(it1!)bXx{vj8i#($_YN&N$US^`?tkXo9YjROKOoSoS-z+a1 z$9q;~B^0Jwr<4jkf7jDh(C;&Ez8Wq*6_j_b_%!LWn)|5{w8;zO!5OCV;h)a=6uEQ0 zy=Q)Woc+{)&oO?dKlOBwgg!cZ%E~B$=WPCTeSn^R%}E_M`uhC*Qn5nMFAnY#hQ=RL zL^v}KM!0o`)aOiFMddn+ou&!;DsQQ7Dmv)xoa$|3Nv2xgDf_XhCTTD*LGUkD?RhBs$8;u z0bBWpx$fw+$C-ul55A8pDxQDy+3WFz-Y(lnC!t=(dpz~RP5qo@-FR1~%H)8kj+?IB{`s7p0T zZU_Gy?6{+92^6_>hwghG{Tp$><*i>3 zJ#msw)}b$y6ByAK8)}g*r5E1Rc>X(czrvLH=(H?(^95U}B@exWAt#=1P`}!;r!4`K z!fkRYm;anNruss*G(2={EAOLotNtB0M11x^)}`Q8-E+{${RPXMgyK~OsO$_fodmHQ_$LZo}<3O zVTy{HY)ic7X4oGH){)%nA0FpDw`=IsTUQdGr(L;4LteY$)>SAa&0@=W!Q-Lf z54p!4>5YZAVz=mPdTN<$&();YJ#-&!JS-e%Sz$5rFz47zxkK%TiHAA%GoB?5S~JVm zvZBApS>~XIdlKxaR|r|S6P0AwseUMp3a!&HlAKom@0pxQ#&I>w=XUL*K7R$cgF{W| zC$4tAF9+*crrC6*e^37T$6Z?~+>&dWoZ-9Q7xK<@y>Vfy$uj-m2ltIbHTJ(h{4DQV zyXtA`)bX)-%9BrMo?v8tgPwOZE~wdkrRDU?IhRZ2k5%fwy7#rM6QM8X$33ZK=nyRGbj0Vc^fX6p@B{rbmC$4=RWgbxdzR<5}5kjpGN{0ycsq5GJqwES2X z*(qnPKYp2(y|t~!*7SN?(5qE)>lTOXm|E&)D~O)fg(Z9;zt^(WlBTtApjBjBF1GcT zW4N1Wtf}q1V=dji{9AwKeh51}ktZ8(wN2k3v>#lR2=Osg_(M9vP@C|_gOg!|Zy}d$ znk?OQG$c)Div*b0xR-}jSy{ziD`SYg-*{wYYAH{m+Q95F)h zy{6yrNRFX%PPmeT~6VX~=NL#s|=gbZ?>KTbWvd|Y_h{poph^bL+97a1o3d;9Y@{Ek1l==Zjh>AVP^WUOAIr?qJUJD5;eGOhGv81A z*1hW8oJ*|K@ql$$pv-*x->6w#mAPeHr9}Nkm+e<$wT5z!SOuS{IclO=wz9Wpgg?nX zjQ?#B)b#L9~(HXOxPP*y)TD1Ipv;k5@hOrnevx(>g+8$Y_!eq?QLWmm;d3lR>a(C^M~yB zWgJpjuiejfK4t2z82+}S(2f1+Z=RROPv@unqI?M^8ug`Rk*7a1{hw9Z>x@HkNv8OAC)o+N5 zZ;+jRks$hwjz_z;){yarxo+odQAxF{_Nz1P$3$l(AJ%D$7o_MsM%jEpr+o5Ex|91+ z%ABJt^|;HA#@GZo-Fj(}%fBVF6J2gf9$ip2Ahi{Y#=P)MAT;bXh;j9j5glJv))n|o z?G^o>{^i1X@6Sq+M+!_^OeQv%y;s<|$}jwhY1%gC-qMbe@}f<+y0NPKVV&DEl(t6W z7Ux7wj`~N%B!;q#a2;bNxoR7u!ygorO0A+J^{?nMy?J`{9Q^+y=h)L5{`0IWTS;qk z8xTIIs~}AM^j+Zc*;)0i|J{J{{VhZYwrJsVM*i;sW6vj7cMc?hjj#g-*kkx$#k~Dx zF|sk-g(N>Z=TxF}nqjSmOBTe#SbS;)BhcV%Q``#?`*wk|ofabZg8$2*UV z2TIhG=3d|XR+O4MeW8OpyC?ADZJxdl6LnLJD{(&GnXYtcGt^UmlXEdh3Ti+8_KS|Q zX9q*lJF}N%eLwBaXcUNfv{v&-wI?pbdIoo0J7S`y5qKxp!JyYmSTf+a^(zkAQu0)J zsUR_onUVj8Y7dY858obt<)us-$>+G&*Xeg~8GV+Qr{Phyz#&)KhXk_B`t+4i&wNtp zwPYi??;ID^64NmWZ)6ox)S17>&1YU_cau<-S)}vM{ofL)FQ0`vOP5RPtT2t62PAd~ z7Fym)b{38MAe8!H-e0ooc(P8?Y>sI>7q1xP)`*GZp+3eNKkp@+ZGP;I{+A^$MlGQd z^xTWSe8Uc!?&pKcTPxdg%xi}YW(*$4e|x|s$m;L>r_{qM-j!3El0M1o))msI)GM9( z5(T%z-ahIwe5mt%M0K;;>WLf8#Yoahx((ge)Ej)3HWcs14mf%Q4_6tL%TEySG6js-$xLs%jDjD#Xx3r+t$antVvn~ zUG4WSq{~-9&*&#>vR`!LL~zjWm-XMO!>!n!HU2irSfnaGYZ&?}ui9{W^rO_9lNUWQ z1&)6*`S>J7(ZhmkY2EdGZ{X*7%YzLD5=)W|LTED+%y3|T-0!vMGR1jj^)o;0C_*y~ zMO$w+v|cD;=OAj0cTuVB+wfrQRxP|QNLp-pR<`TT-~S@1=rY`RjpKjc4>!CN6@QR& z#K*&<%Z%G*EWP25zeY$>163t|x6Zdb)uTbNXCpuUBJ-zh9X=v`Mo>ax^m4vXY{Pm{ z-@W+(f>><~oBz#k1K$&>!u@;5Sz;4&E#!`~dR?}V`qXM)_@9w1cc|j)(oKT23iJR! zN85)Yr_bU~Q?BZBopc%ze$^!MM)RfzgN%Z=?b~B7B8{?=^_KH&AA3`6bC#jp zcN%Szb*rE4Y+}Dq(~U#=M_b7{hWdN^nHS+xQn`&CY@Vv26-kEe$g}?d%qU= zTgcfz!gjWLe9YVamGM^vKH0N21#Z0OmV5lX`lCjqyu(Yh@5==>+s{;wjnFyrW;zn z&BvD4qeOas=qUCkA9^D!^YPKd@}Y(s9cp}<^cd9`w&B}ChmJmRv#h!mHOC-$)jPCG z@wY~s{n6+{|0by0p4(<{HN~uiaTLW+)iqr|>GV!b&($w-E?3$8H^#9$r)|5AEP&P; zqrG)LZLTS1HY_&7zA>(rlXY=RGTp_{{L}kUZFk}OuTDs1Xg%#Y!kBebRb4flU7FJW zEThdA;u1CAYlI)sKX3hsOuOiy@T|5-O|4k=!nXFq$N&*tBcH7ysj*jg1v{=WtjtU; zy(x3C2|V{v>|Gc|t=VM_^OZvjYw2m{w?ghFu1W+*X-PK+jx@jM|GB~yGHdf{k;nBc z#f53pCu;xvoVaX{YBrcocEoQJ?BB0A{iu_I68teokm`X0TL0G;_U@~%unhizP2iuJ zGvL+NkGK<->&%%mHJ?h#e0+S$bTKQNQ^Q}ngioKlu|Ra~ zx7<{Qa=>KgKex%@iF#AMYj+|iy<&pir>W)oT`c%P^=CuSbt0nMy+D)wV5|1|ocC-q z^DAVXelCj4lqaPhH+*;*`Ri#YL8iA6%Ld=bx488y;#^O+_$}5t3fEFoetF%KTUn0? zd}Z_aO5T=Ph0t_@6-Hs={G#vjr(8peSEPMxKC5eT#-8*}E|?E|MQ4A;tzM7JT64u* z?BSZHOE=;YUMo^|7ho=zGEhzGU~z6MwhNE4ZLTB|G)gY}OZ3&@qKmrgQZTX6YDV^d zXDymFx0G!!onIw=`o%Tv$KstY&Rx^<(^R)YT+xh_I`OxERet@7agb%UWVCuYxS8l^ z*(C9jC2;-emliYh%m;E`u8CZO`@!2nzeg})3T3sU!p#rbF z-Zi-<79xlGqR8sqf}QTUnhCqeO&t6aHrcWDl~$oik2$_TDLmxsbDQ%6G7EA}R>npV z&IYBn%eQj)I{P`-oVzhIciMsqym;M!OR~09YCWJVQ@Fl#oP5kPyE5@!}rJ*=4r!Kng~h>F;^DiK18gCrKQgn zyjvp8dVm6_j;I__0&RveB<>-2+pS9?4dVa8@xmS<54XHClctgsetVE>} za>;H_v(^%xK=F^W)!dj!Kl)Z08|?>j9EpyfS+hTjypS^%(5{s6hk50Q^!W(w>j*KZ6^#yY~T9P82 z2X+TK+zynRDl%U&Ro)YiK4fwI5l1Y|_$zl(sR>^KYK+bCVTTf?fQm8=x^+~=w>#3fbft4m~?e(!9 z2d85KDU8tWzBdYt;seYS+OOL^c#WmJ)M(%Mw z(;feVwt0Qf-S`1h>##=!v!hL2#!?TxzO^6w3Dt(#csz~_4%0sLej9V@e%b2Uz3@cF*u%S;ulKt}-}myesOb6ORS_R{Y~}PTWSu1vo(ehAq~ABv|-J<7X{TWsa6)>6k_UXrR2KEm2dGRjC5Kf^Jkn8 z#<}WnShMj6L7qmGHI=z6#-QbjpnmM%MpAXHiGf7k^XG)-tj0rna(t?JBDj~b-Adf8 zWX5?&)sKcvbJB=yAF*RGEPZKm=ReYOKWe#~D+F#r_rEABwZyf4(O4BTDeNqF7}MbY z$N@#kOR0r_P^Hq>!yGV*emQA4wbmX`Xj%Lza53r$I_$4s#l%+!5qZpupO(4TN637x zj(ToPoi4AsS?T=E*p%U)AdA6WF~%Heo5Qf zaV^kYB7Gre>IVli_3hv5PYGO>5AlVNo=K*)l2oZvv%WpYMzc=ABQ#pU(ay_gWk{`t zQjgPA)k{99Rb8&hNb^hjAf~aY;=3?Q0?M2|wyTLjE0oDC#dH3-Zc6N;L-l{>+yl8- zMWSnt-e5IUI7;{I;IySUW8J?@?Pm}AlL*8vMxU29_@&q>_Lb|~it!c02b!`e1)3c> zn(v1qr@n?7bex1Dm}680du6Gwj7aDm554uZ?}u$+!8Y4J>UD)p67d*OjHVHDhprd_ z&)=W+cjI||+$r?dH%gati%l=^btWGFRrO=x^(oI36Ibvhy1NQ`EQ-$6KZAQVR_Sis z!NgNV$OX{n__aJ|7}twoX8HO4i@?F}o+Kk3S2gY@1g)FeOuQr}CEc_h%jfZbU-GJL zRYPvtLToXklCWH?Xi%1klF1eGDaYx})#Nr!rKhonYek2h{S6`clt zW*k(iG~Ic}u8U>5qG#xAgs0-xrZec0#)`zKx##Xyh+!s*Qt0HFq4lT8x|XQkPUv&@Z>@jicv;eL~B4Vl)Rw8Ko_^ z`ll>wbbJ!n99n8;ns{@N zw5w1#*skrtbcno#%f0pU#3j#_S}s(kIbAcC^%{}%KT&P&wP;<$+;T zYH`MAT15{fce--@^A9>l3p>`A^o1Bn?Y#UXvTua*CblVxSZ*fWcS;E;yMIpd_#-Q# zkir(XrKv=h>mJ-fGe^D&-yAaPFTg~h$WEG`w)D{P%q`J;X!VG7TV=>W=Hh{+(d6EB zhX?h#)B+FlS+mS#tloQ6yy5?h+GNxhhzozY{vl#)jMx95rrpql|r9lrS2s4*4Dw6`ji)6 ze+9#2CS{J~sTbB-T;TWFWX!eqTy(jx=6-6m@R4*r--|UvQu`L`BOD#3Eb&s6T6JmV zN`KzIkQ`$BuYl;(%}Zr1{S+c@8EK;!U7ictY4u4)nYqPpiiv~FR7boQt^cw*$d)X* z<}2K;>rnog#?;nUf476rWa11>bKEo159Hwu))9>UMXW}7@^!*eOvgoKeuPUZwH%^u z&V1&<<0U*`SnyO@edq}9+Y$LcCe7d*U*tEH)_zM-`7Ynm(N;D!S_mKJwE8T2O%db0 z%-d`|b)#A%Z$2ib<@Zvpz}W%nG&^Qz{rX;iA+l6X{fOzru$vz%8PzibrlX1l(*E4e zKRo$7Tx)AY%KnnWf#?CU+P2`;jneaey3JdD>~HNV+bByUcvxNcRqQPpCP6eIfphOqOgSgHz3o4u5CRb&)hf__@o54O(mskT%r zHcHV$wZDpQer~uEu3IcX$on~o&~~hNrn%>V%HKw#s-fwB*QKMZg4>Q9^&b5tBu004 zZ9+?|kw?(K)MDnJ&Nv-e@bRROlJ0kT_YG2BCr~{IzLtvdUNj}<1QYw$pOHiqAE_{; z&t7Y`ZrQXCR5^&0usyb@@@0Ng9AtU#_DqiGvvZ4Y|52S{a0sXjnY$noP((vQNdCC8 z<3P$=8Jew>4?@XS2PN$+`l}l%zlfVzN!dMCyOsUjnr4e;o3)aiz~=*t=le1Z^H7J> z#DA(`7#V+gI`0!Sw+J^+Sd{88b{?X>mW;8}zC&?ZxhQO8tTt#1)z=i5Fn2U6`%c+Z_CMqJcd@d6IPIx#RCIB)+-B=2CR%RdRJQaT z3G%^vx8t@FTKHBl1z&J1h3h-Eouuu1?z6 z?D+5XKi&o50G9u%Ly{9nR3G$wxZ)H(r$wkY|7P-%`&##h3-0#H<`+_!Mm}$B9W)bU zyN$V)Jn@Qj;P|Na0N3{C@G$vm6wN$&5MybVV)f~2rq_Q}1O=4FIh#a%8cQ;8Z`a|@E!UdZPUe-N+v9yot0|J0j^ zLraGYB9u(i%QY^&`7GU6U74n#q9yb-*&>v$7ISS@kO_5AbG>247Rk;bimcC`G5uvG90wA+=z~{o>_3aW#+S z0@jm_SAs|H>FABAvzeN7v_3YzW_&|6Mq6l5B=5Wui#da6d(NFvX-t*ETF;$@z7IYn z{f?d(J&)5`wC5fkI&~DC12c4fX^^Z)SvkKg%~DKuExyZr z;Zxxy)oFJ%kD20dgGv*9`13 zFVRfb=7%KC_?;gxP+-$;$mSpzEvKS4_LE#&X4uHttiOqK&pf5rMO{ldf@~WZ8YDAAu!> zA4mK2YDOz#ZDc*Au2kO`r0n3xss6U0Vw2#d`p7m=_*c(q1B`;CRNJaG;oy~OL0aeR z{ye%q%#@bqe^m`Zo?i^x>*AlEJavpp+D9VD^PPN7r)8A!Gy3yY1VZ=kXfNB;cFSLj zY*?D(6NZL^d-Vzls%m_77WC;IXi1zY)8Y%5F8j}4XV<8{C)*>v`s2pBl6VV^SA5#2 zV7`eBM9b?wofqJ=(Zq4Ca`vH|%O8&SwS6DRE?Qu!eIk|K6yChZFw>ku^g;e2o9*+} z!?uH`Dr2PG22HHL7%Mjxv)GnnsH@9mFMIo@&l*`z@P8SvoG%<2>=^L*9xbrmAo6%P zQ~CIaH_5xk;kzthfu<6NzrG8cef?B6cA;g9>#g?IHLv!tj?i#3O~yT?rj}NRw>E(vPwRw!DZ5Ti??0Fj__hAHR@pL@cZI}%TWU_W%$o@lMfFT zzV$tp+esCrC+irS(^nw({$FLN>=xPTxlX}Ji78YSW})!G;3!KgU3}{euG>#O@SY*; zqx(@GMV-Y)s>%Cq^bs?)_p&X=Q%U#rVy8fw^_Z+^5}RSAi<9(E&7P!_R=m-?8u@cD zby54lACi7E+U%pcO``hk?G)fehwN7sg=u!tj>d9NODpuUu4$KxX3G@Xzc0_3d&MMZ zFjU^_nrw<`t~_YReC9i0>B1q|y#C-Gu2w}F(=V-x%6f^l+UtGS1+Ql{rhh#z@n?uW zba9|)&O+D3?ugQ(f+KbY3YR&$CR9|CAdzAZ?c1{h$J4(P{?b{8gp4Pb23`_Qmo2n~{)gQq>b5!e-e>yTP4&9uq zPY-^az)P}8LEFuCm4VkYTtBo(?D21TnEKyJo$d zS`O66e*1??En8;vW{5!fXv`%R`-qQcBUtRD$+kjR>~GZAMfF@{@f?0wqt>jK-q!D# z{8yzcvb?`=G?Wvgo8DDsBQ#QU`H%2KY&pS!QCEL6?XllH&Gs2)>2F3R$Z`r#vu~@D zh`Zd2;X6sLVf}Uj*=r=v+Eo|P&(=3NG~2?SuLb>6qg(QJa8RXl zCYu!CnE9sOL{-f_J$vOE`#7^M_)KV8ZLR_@gNif0j#^(}meEvHZ4g%o)Z|nb|Mq zT{CTuW+!G|CYF=A7{c|qyIn;yZm2JZ9<9psZL*F*C602f@AS=r;J>d9&4gqH`fjIP zs3si_2+$PXVEiR3C^o+}NuoT>ohx=nz48HZbIAKr(v}mVe>teMA7`~G8NZ0obN&$_ zlCB=Sc6cB;`Gm{4lTjG^zuBX1wYLRK6ILGcHtJrH*1pn>%9&*g8nXJ~;qeKjjtdY`QQ609 zIb*|C_moPm)4ddRxU==`aJxhL-#j<>;9P%+i74ru*r)enUknp#qY5(TGHoKF<5$%w z1kV&Vl**n@NZq8z+&O`nAhZp*L`mv?ukpFfoz(d^Y(H;4GPIrRKF``<^L%z_OicKy z83jFt$u5|T!_`ZMJBb;Co9{{gRGA9ap$Of7+A zFuy4#a{YkFR3399JAV~R&JxY@_WA4oHC|%-^6Kg|!GSh@O})mREi{IE`|`DDyNL%s zjCl(+mmjb4ZYOGtPq`CcA0N&3A}7Lz9R;o;VW<%B=# zvFi#pU#Bq3_LpV0E`0;)@Chc=Cm_2dY(jh}_LQdT4A&av8qmLcdZO0a+mKX-=g+7s&QU0A*x)D5o zvcycFy&#i>LaO_ABdfl=RF&5)onWCTC7#;{#{Rm9b8Gw0$3i5B9(s*sUzu)FsekGKMcO1i1tTwHZ^)e_Nn|@7{Re0`b@c4m3jvM8h&&pk7c=EhH z^Ywjj>L;fh(f6Rc+59oKy-L!0y_NXrI9lE+XP6)$^+jQ!_wn=-Pjpl%FMW{J3g~cl z2XCg`2ws(C>F}ww8nm*K%THN&AKzP~Mru?Z%PLm#vcF1JN6Dz$#J5XZ!D;mD%A#Fa znSn}1QIvAFc1mD#uv)%<-fs%oQ^qcA!SC0i1!ay_KFAa`DUs@79cw21G?RPXDp_b7 z8aJQMw-~ljdR8FSQRM{|j4yjE#B`GT@`qlpubiCQY6|KzDBIX3glrrHcTU`ekAo7g zz@274j61}V3HQQJ?NbLq8Qj5!;QS}#bg5a~nSv8LCs0YA6C}W%ix19Wh27bD3nIq{ ztNSC*YRGX14c7X43!>$b1Ff*ii}fhv)MQEA`CmReXLY^L5{yF5>H?=)kw8P0xbu)7 z;ZElLwsXpHQnBDBa>_AQ$p098zR|Q{6LQqvY!<{F1-WB_ocNcCJ2ST%XI1;_h2RV>7Nb^#BoO8??gTI=oFZZ; z7yO-q_i?97VwH1`aYv^q;0~pKxpOSzdXL~3ax7!&kRX!Fn>a5=amTFH@(c+&AV=c? z&EC%*GvSWncihQg(-*-|Tn-)RQ52A54ek>{JD;S+e?U5LQV{n6X$UCkdGvh)cb~_7 zz~#;#_v&zM$edst?w~nX4XG~+B8S&@<6bgO+-WaH%Yvmlr_nsm6@zq{j8 zXIu~Gy=4bV3TUPg_l+AWTt)fs$X}j9#qE`w zHQ^lJ$0hw|=WxCl2NW~z^hj*6fRYS)=Y&E|ApC&K$K{>2_t6QJf@^zNIqOs0@!L0X zv0vYDT;z^o-Rt=JQ`~Wh!Z>B+j&j8l)rC7B5bF~tNuZzJC}g;lz{%Q?QS$(_?Tz|| zJN^$IOMsdfGHb>SYszkj?4Ba>Z!MEADoc@&nJCGO6?$_Y#fkfM<336d`zt4;oQM0B z6$;2h$>0PHd8os{uM~C`7TtU>FRxiXq@|A&Lip$#b%txYl>zK>iIQ zIheJ9)u9i7+C~e$R)Ri1KrzAU1^vD1iw0OWS>1pUmvFL_2`3a%grX#TV}e*V7ooUd zOS4mZEf1WA2#Qgza5^5vAf0*JWNq8CJGozip;yiT(uK~J0B68n)oZW4h&MJ2&q`3- zu#Yb#z@XL#VDiwxhoCJ01HPcW1`5HjL9%-g%T6NC9s-*|3>MzFwo?F5b@YPD~-0DnM+VM2N5|6eFyLVDQr^1e~oxLP_u# zfE5l17;i_wz7vuFm^kl&5Xx=@Y&(RRHvuQK^%yjgQD2yqht#T3(r^Oa)xbpf5(0Jx z5isnS48cT56in#BLj4+W{WTF0o{cBq+!40GL`?=lfTQd21(JILh`3yYhpvOyso!W*63#|?UVq7nTo19G-tP+LZ)}kn2&!1q^ zJRBHZhoXjL>#)57QmX@u%|}EI7=Bn@3WH%|2S_SaMYQ+O{Ea9fm|-}w&&4<0JsRMG7|LqImJ^QA^G47IxJ+S%#7=<-KtTwdu$VMt z310xGSK)u@%weH9d~I(<$O=(CMbR?3TM`~H);*~$rEj7KjsV1tlrSrfTSJ0=4~IWt zuPngjLa>LkOTdoa`zi4L%n}l4M&a9Fiv=MoJ?^A>NU zQh)j!q|R^(>mU9y`2V4>LKN7KPVbY@wtyU@<5>9P6gw=47@BZw4*})0plIM|VWW-v z#04(e@~4pk!iSRYIKawqw>>!74V(-0?e zIle{uEERN~oro!wAC0Y*+&7>s()=)shGyU3Cbtd%mMS5jVFyYQ&PlWa5hw132F?j1 z>g4cpj zJPcNNd$-YC% z{LXEk=9urUh6eXHQ@BP}`3@vuhOJEmPcn1N%=)b+_Z5Jg7hzBdvi*vphC}EDgJ(h4_=X&WbpRz^}y|h;d*sLE%3`b3jCV4Ach;Y zq&)aGcJ(Eo6Jc7U2cer@k_zkuqvG|xr2XFGB^*%67BB(@arg$(uUto%@^46{I$`iJ zIpjD7;y}9%Zn}3GH9d-KZxv8W!J6Z+qlaU_=Hp4IcMQ43Xa5eaLv7(}Nhs5Tj0s=g zm1oE}q0H|f{U0u{xhT~89l1lBhHdDB5m0FynJ!Jo0oWCSfSKb+P1Rz-8why!A{h%l zhx`*jK9Y+tz7t4`PlCbuW(2I51UH343rP8Y!3=s9&oI)+?VH=3CIQbmkLTeH2Iw6O z(l6jaMo4A~8PC#{rG|27b zI>L_p0HdBC{J)K96g`|DnjgT}Y->M_9s81CCZ81Q{RzSh3}G;J24F_g2xc;abmLAD za!%;_3`pjv0>V&>lC$E+l^)n;R3Bk@XK|zcEC9#OBVfcV;wTFS(}EFj4hLSj0lf3h z0q;lR5T@Y<@V+ww_|5?vrV@mCI*($8dghRpI1e+am3tV|d1Tr4t(bgoOPr{|Yp|=j zFY~}KupI`)pv-!5W_$zD`w5u#R|qrMNKTK>#zUBS(+@M+a9Eiaz;(tjL%Dx2eH2csb`i+qw%}_CNOGAR zf14$+wa?@;B}A|S>M~>r8D8FQgUFUH0rwC!%*gC-R%fIrI3b#4K&z_2v@E2*j4U(V zmqCJL$6-(odbW&=utPAoUngm`F4fp5n(FhTEy5ZksC__8-hCBBl>{{cH)4upvPhtxm`%|1I%ZkQH_x;9ZXv^$4{ z;pQ<-dP+`6auXDbiEmd0o!>;J)6h-e-T{qguseBjf|Q)8TPP~9H;nbGw1q6v0g7JLhOfd-A;5%@;~5(x{!GuV4{M_K?%T|&UCuGBR6`MV8f z2ohlCAjC(9L|1|ifLbXqh;5|V@O9ul_*xA5Nr%Sn{ZrEe@Jtp0o}owDMIa1H2eXrTxUjOH*n2-_F4K}-{sk$A|D!Bh@Sx55suGHr@@DKI}gwyFnzw32J)|= z5rwq!X?AZUI#@vnhF?RAtZ3}bu2%EDED!Y3KmwaIVo)~&5e<~kOoNYI78}SC@h2=T z5B+9CW1kPvvIF$#01iFgzN_aS+~;_89;fjdq`_}Kcg}#!Zn1;RR&U{Gbq+Lkd2Y&q z79ya61h_yIW^$OZYHGN+jquTv6@sOTKr?a6i#3} z$PMGt&<{=|5nJkgX2tw4jlDDT;sWv?f>06{8vF2I0>0h^dtzK@4QPac7Qf)~;s%7T z2$aN)G^t+rI#?B(K2*VrCi$OvPk{&U55R62Tril2G{k7}BT)*>{s5b0cy@*d>DIqt zCg}*Cq59v|f&(uQwCUmn*y=I$*;hc*|L`;k#Lb7s-r4x=lQ7@JONjT+fSG)tsw+A>Db`eI+`di9mi8g(Bao0 z4bl7ZS(1a(Y)8}K_nI~0K%^d_L8Ol4?b2LORvaC^jnD~!6b1|S(xF1ws)Vv&inRsJ)%SJ6e?Q` zE>!m}%?VM9BTb5398g+caFmxgGE7{D(WfiBC4zSG@8$BmvD-BA0O$G)}LQmlfz>Es) zvk6p2{3b}y(?a4hX!u0}NJR#uxO@c9;2UchOr)B?gb?&xo1P5XmO%z#W0iewdCd1~ zD4}!}di*UfBn#Y3p4=n(A&ayqYB|7MJ&kAZ-I>7z6evLsl!3<;$I2ll-(i#2Zg`La z`e{m!f1FTmu`gfWy$~J!8G3RkQyv+tp2!0aTmg6n|Kyfe0aUE8G6P((;N8hH^b`=i z00aF_L(@LPuy+`$48RGoff-alrotuI$He8mRDmM{eqC&<2!O~_9TjeU3| zJ_VBOR0CPpAb?tqfl7d%zNe}qqrHGSXw56c2(hRQV&kWd-08)_j3DWLh7^*TV_=0w zH<4TSO<1Ex_P-kS`Q6s#tN|n)CqcxG? z=iBPOlHZ{FU*Uo#GQ?770Y7dGd+1YI$Y>e>GtG>9m_vVd+kQXHt}x+QYRLXCzI>_c z`%+(I-XlA{&Oi<2Xd`nv{|2ar=h~ne(5!e3_Lf@e-#!<*YzY1Q2$~rhc1N0A@DY%Q zcy>G^0uQ`DV6dG73#J}LQ$ZR!XzbfVraFM&=E4&xbd1zcg$_0&DLsswP>&99dzgDi zfL$1C5i;&=K}|e6EEWC9%YH8V);C2_X`=c|sMsNc-&71w~mFgBdZ1Uk@43H1z=ZQ62%~_0ZTS ze2L<4!SsNma6LRj0nr{qdY&|B`Gk$fVMZLfb_|(^(qZt~85qRg%Mlz0*Sg{GwG?FI z&WK-qo`Z?bB*gEVAVxl@LJz4|xsZKfi%LT@BKlw~1a)Mh4-Bg^5HM38>00$LNS2F$ zi3Vs!h|>U(Ya0OObUvQJ-XuSQLH;%b+%!P4&S?n1JFgMY!w_lW29H1tG7UitINu^n z%p*oc{JN6b2-xs+Apmj6esfo{U$BcWt^|Y@x1q#m?5(hNG04h2g!Li0xFY9cV|u`wg8|} z4;GYz?5cKim;M6eBh~`sV`>yfFSIjJLJKdLxS>T0#OE4pPBOmB?^1pz0YxyiOHn|h zubJ@s3148Ecos`bK+ijw7$7Z6#OdgFkg|g%2y6)U)oDp6%@V1rDofDhtiWEFHChsi zu|m^B>vK%_N8G$i`$9g3{nHPu4m&bFumZ+UFCfN$fic{psH_2a{|XFh!q;BO%%IfP zAZ@QIL|Flj!-n2kqp=T+Kf`j4nmvqJDl@*$y^ERmwiVEyuo(p&@GF$-L;EC6>##%u z+B$_4ko`39{};?1XF&nCPoZg`^%Z9PxN!?+`&o8bTDimfM$%PSAT5pv)+t#)7;TW4 z($j*A>o%ZV8Bz#cXM@~<2%KQqo9Pxmn3jV0&menh8fSpZBJhmlEZB{^dj=VDs1ty< zcV~dN504Or{wy+(NSy`p;P(g^c^2uiWp(?K$Q*!a1328iJ^Ppoi!dVrJ?mqkhkR|3 zMRt-caCrwjG{G^u3x+M2Xh?%a z^Cn{j8|e0MDMVmK5~?6$-Fbln$|eWw9L#cw!K^eiOv%cXO2NtpIXECeb#(wd zz;nQkvjt`pq3&}?RnNd+=L-aUp392gZ#?UQMiV4MEp8xgv`*mfFrK&|d*o7&_m!*c z18ge=@wg&Yq3H@7UjKxEw_TAsu7Sa869~xF#){e*pXA&Ck^2KrP(z7sNPIG2hI;Vwlrp+dD!0ab$x> zNO4F;i;WHMb555HjLIHhG^BRl;q};q>#*YHvCOdX~w=JR-19c zg$y?Q-Iz%aNTJLVq)@WFOVdKxJ#0HGpj0oAimWg@l0PD1c6NOJ&coDYNgVau3wb#8 z6GqR<;80C(q>rBU256Nk4lVOWmaVU0bVVJYf$WkHmk-iM?Eha~=O33-mIiPQD$)-h zrg?kc*ZW@8&X`gv`Bf&vEHU{JGchI!gIyGrSSm&NolY*%soJd_l`y`NFf5LtHvXVvDcM_)0Y>QdejB7bNnEbMPNP!PTTz&~B(KM+MKmih>J^ zUP~>6+X_IRCIkA^BcQ{icQUdwD>xfstEo3oSdvBtY`{t?*hwovx*=Vb-ka_rCLspQ z^HA6Rft$HbhrJt8kY3%)51q!^)1b+m8>AOidrJy5Se3B^no=lhUZHU6cS6{WMWxa! z`PajGn6XTSo1Aik@cC4#nYUBHrs#VV2D9`dN(z(H065=4Fo-pMtrxZIGJ^2?2>46s zgH>?7`5}2=tEhWfgTjZMSaSvho^VSq#@Z##5Q%>}M6%EU=>WDMoq84N>0lDojf5v3 z=$%FNihIYPw`73HtPhBWW>DWJ&TN3!F_PrbMD}OU2(&%}oH&T=Ugq_d2 z8w>1Qyjat7gV+%6vK~O3!N?~6+aQJot5J9Y;b=k9I9VoP8>og%+W^vK zDT>sASvOD%cL7n%dKnpGjiUDWYy{GqijkGeEnd!SA*i+I?_P0Y&o%f=G$AR_(PM|1=V5KQzZ(2~uYYReXog@%$Ww3kUJ zFG6trV+4G2*+1|)dm>&BVsqa$iE+o+8~~SN$ivn_CNX%eM=%rSr*p`IIohO-{c0AO zEQ&U{igoX{Sd*UBunGdqUMv_&T z+*tJi6Ko&Ir>@v8E5@G>{(iV2{5h~zlt3V_(D<84ti6y@a$pDEHH#`adze|z(zk)? z_j5?)=r(Gdu5JUngoPxO3aBRKO*gCaGaw7`N9@^jvuKS@7l2*uIs*L)>0vw*1Ife` zLNXgSAo(pjSV(RA&~owr*~kF zzySZGrC-SJ?Aedt?iOwbGg$2+9H_!G{|WdC;?|xVv+g@63B$jD1mMN#UPR($lJis( z;OMsrUz72*Rt?_eAtyUXmF$QjhCF3C~T=NpJc_-jHZ%TFs zUO?e&wUgAvU)`VZX&GPM2`N7ILm}VT;I|8|TSD+U$Vr`Hc?eFACZNu$FoOg$p2d*b z{xO`gh+=R4;pNm~Fqs-oLdhs~we?{ljhdJql8RHX>V}LZU>iFYZ38WR6w`E+XHMX9 z=IW_{dkDHs;lN3qr-$B$_-5>e__jq6Rp)EPZZOOJjQrj0FLkw)(j&UX56q-eFoW?e zM!{lA>9&4fiYd(?;b~cT35AXcDD-89GJ5oa%RuOo!iAQzUXCoM%$t9NR}!hofLCdX z%Ja!m*Y2u}=<+4_=$)=n5UpC+RLR{fM0^;DpFr(ZsVr92~y?L=QAL}tCbL8IZ#U#=?A`+jcF7gQ#Yye@C$!tAP)C-#*V{qkdS(@8*K+0%jTj*~gINxM zgNwCV4e{1U%>ySn2f)eQQ!16yd6F2V-bDvKm$cM@z1}H_`T6k&L8bRl)_f2K7hvna zDi6|9-9#r(b@SQ^+XW8NUMSx~U{e*!CLi+VBTNfkw?y2J&6BFQzt;V z6aMc}$VK|~sM<$?RSCo9K zIJFXI3II0fNJ4I>)CrR;gIDja(Oh$NWlr7|JKTSwK?v-`$yeOMa z&T+7IKPZ#i$0=vqP&%Scmcm(3e4J+Yk;Y%eDVf6wP`vW}`2<+SUy!AE+LJ96|9T+D z{vs1Nvx(P=MW1=WMxlC`75w<`3gX8(@hvlUC&9}9wv6CFCT=wkrMXRtl#Xe#C3OLD z{6iVTu}u66hrkAqHa_N3zYY_D+mJnb!daNj&1)2zpL((8c&{M$$cV1|Xsyb(D&NXC M+jW9%s_^XpAGyq=Gynhq diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 385351e30..0945229ca 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,5 +1,8 @@ -keepattributes SourceFile, LineNumberTable -renamesourcefileattribute FuckYou +-obfuscationdictionary dictionary.txt +-packageobfuscationdictionary dictionary.txt +-classobfuscationdictionary dictionary.txt -ignorewarnings -dontwarn @@ -14,17 +17,14 @@ -dontwarn androidx.lifecycle.** -keep class androidx.arch.** { *; } -keep class androidx.lifecycle.** { *; } --keepclasseswithmembernames,includedescriptorclasses class * { - native ; -} +-keep class org.eclipse.jdt.** { (...); } +-keep class org.eclipse.jdt.** { static *; } + +-keepclasseswithmembernames,includedescriptorclasses class * { native ; } -keep public class * implements com.bumptech.glide.module.GlideModule --keep class * extends com.bumptech.glide.module.AppGlideModule { - (...); -} +-keep class * extends com.bumptech.glide.module.AppGlideModule { (...); } -keep public enum com.bumptech.glide.load.ImageHeaderParser$** { **[] $VALUES; public *; } --keep class com.bumptech.glide.load.data.ParcelFileDescriptorRewinder$InternalRewinder { - *** rewind(); -} +-keep class com.bumptech.glide.load.data.ParcelFileDescriptorRewinder$InternalRewinder { *** rewind(); } diff --git a/app/src/main/java/com/itsaky/androidide/EditorActivity.java b/app/src/main/java/com/itsaky/androidide/EditorActivity.java index 24b1824cb..55507826f 100644 --- a/app/src/main/java/com/itsaky/androidide/EditorActivity.java +++ b/app/src/main/java/com/itsaky/androidide/EditorActivity.java @@ -17,6 +17,8 @@ import androidx.appcompat.app.ActionBarDrawerToggle; import androidx.core.content.ContextCompat; import androidx.core.view.GravityCompat; import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import androidx.transition.Slide; import androidx.transition.TransitionManager; import com.blankj.utilcode.util.ClipboardUtils; @@ -29,6 +31,7 @@ import com.google.android.material.navigation.NavigationView; import com.google.android.material.tabs.TabLayout; import com.google.android.material.transition.MaterialArcMotion; import com.google.android.material.transition.MaterialContainerTransform; +import com.itsaky.androidide.adapters.DiagnosticsAdapter; import com.itsaky.androidide.adapters.EditorPagerAdapter; import com.itsaky.androidide.adapters.viewholders.FileTreeViewHolder; import com.itsaky.androidide.app.StudioActivity; @@ -41,9 +44,11 @@ import com.itsaky.androidide.fragments.sheets.OptionsListFragment; import com.itsaky.androidide.fragments.sheets.TextSheetFragment; import com.itsaky.androidide.interfaces.CompileListener; import com.itsaky.androidide.interfaces.DexResultListener; +import com.itsaky.androidide.interfaces.DiagnosticClickListener; import com.itsaky.androidide.language.logs.LogLanguageImpl; import com.itsaky.androidide.models.AndroidProject; import com.itsaky.androidide.models.ConstantsBridge; +import com.itsaky.androidide.models.DiagnosticGroup; import com.itsaky.androidide.models.LogLine; import com.itsaky.androidide.models.SheetOption; import com.itsaky.androidide.receivers.LogReceiver; @@ -64,6 +69,7 @@ import com.unnamed.b.atv.model.TreeNode; import io.github.rosemoe.editor.langs.EmptyLanguage; import io.github.rosemoe.editor.widget.CodeEditor; import java.io.File; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -71,14 +77,14 @@ import java.util.Map; import java.util.regex.Pattern; import me.piruin.quickaction.ActionItem; import me.piruin.quickaction.QuickAction; -import com.elvishew.xlog.XLog; public class EditorActivity extends StudioActivity implements FileTreeFragment.FileActionListener, IDEService.BuildListener, TabLayout.OnTabSelectedListener, NavigationView.OnNavigationItemSelectedListener, DexResultListener, - CompileListener { + CompileListener, + DiagnosticClickListener { private ActivityEditorBinding mBinding; private EditorPagerAdapter mPagerAdapter; @@ -89,6 +95,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F private TreeNode mLastHolded; private CodeEditor buildView; private CodeEditor logView; + private RecyclerView diagnosticList; private LogLanguageImpl mLogLanguageImpl; @@ -97,6 +104,8 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F private OptionsListFragment mFileOptionsFragment; private boolean recreateCompletionServices = false; + + private final Map> diagnostics = new HashMap<>(); private final String RES_PATH_REGEX = "/.*/src/.*/res"; private final String LAYOUTRES_PATH_REGEX = "/.*/src/.*/res/layout"; @@ -111,9 +120,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F private static final int ACTIONID_CLOSE = 100; private static final int ACTIONID_OTHERS = 101; private static final int ACTIONID_ALL = 102; - - private static final HashMap> mDiagnosticsMap = new HashMap<>(); - + private LogReceiver mLogReceiver = new LogReceiver().setLogListener(line -> appendApkLog(line)); private BroadcastReceiver mServiceStartReceiver = new BroadcastReceiver(){ @@ -186,7 +193,6 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F KeyboardUtils.registerSoftInputChangedListener(this, __ -> onSoftInputChanged()); registerLogReceiver(); - setupContainers(); } @@ -198,6 +204,10 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F if(getLogView().getParent() != null && getLogView().getParent() instanceof ViewGroup) { ((ViewGroup) getLogView().getParent()).removeView(getLogView()); } + + if(getDiagnosticsList().getParent() != null && getDiagnosticsList().getParent() instanceof ViewGroup) { + ((ViewGroup) getDiagnosticsList().getParent()).removeView(getDiagnosticsList()); + } mBinding.buildOutcontainer.addView(getBuildView(), new ViewGroup.LayoutParams(-1, -1)); mBinding.buildContainer.setVisibility(View.GONE); @@ -208,12 +218,18 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F mBinding.logContainer.setVisibility(View.GONE); mBinding.logOutToolbar.setNavigationOnClickListener(v -> hideLogResult()); mBinding.logOutToolbar.setOnClickListener(v -> hideLogResult()); + + mBinding.diagListContainer.addView(getDiagnosticsList(), new ViewGroup.LayoutParams(-1, -1)); + mBinding.diagContainer.setVisibility(View.GONE); + mBinding.diagToolbar.setNavigationOnClickListener(v -> hideDiagnostics()); + mBinding.diagToolbar.setOnClickListener(v -> hideDiagnostics()); mBinding.viewOptionsCard.setVisibility(View.GONE); mBinding.transformScrim.setVisibility(View.GONE); mBinding.transformScrim.setOnClickListener(v -> hideViewOptions()); mBinding.viewBuildOut.setOnClickListener(v -> showBuildResult()); + mBinding.viewDiags.setOnClickListener(v -> showDiagnostics()); mBinding.viewLogs.setOnClickListener(v -> showLogResult()); mBinding.viewFiles.setOnClickListener(v -> showFiles()); mBinding.viewDaemonStatus.setOnClickListener(v -> showDaemonStatus()); @@ -250,18 +266,20 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F @Override public void onBackPressed() { - if(mBinding.buildContainer.getVisibility() == View.VISIBLE) { - hideBuildResult(); - } else if(mBinding.logContainer.getVisibility() == View.VISIBLE) { - hideLogResult(); - } else if(mBinding.viewOptionsCard.getVisibility() == View.VISIBLE) { - hideViewOptions(); - } else if(mBinding.getRoot().isDrawerOpen(GravityCompat.END)) { + if(mBinding.getRoot().isDrawerOpen(GravityCompat.END)) { mBinding.getRoot().closeDrawer(GravityCompat.END); } else if(mBinding.getRoot().isDrawerOpen(GravityCompat.START)) { mBinding.getRoot().closeDrawer(GravityCompat.START); } else if(getDaemonStatusFragment().isShowing()) { getDaemonStatusFragment().dismiss(); + } else if(mBinding.buildContainer.getVisibility() == View.VISIBLE) { + hideBuildResult(); + } else if(mBinding.logContainer.getVisibility() == View.VISIBLE) { + hideLogResult(); + } else if(mBinding.viewOptionsCard.getVisibility() == View.VISIBLE) { + hideViewOptions(); + } else if(mBinding.diagContainer.getVisibility() == View.VISIBLE) { + hideDiagnostics(); } else if(mFileOptionsFragment != null && mFileOptionsFragment.isShowing()) { mFileOptionsFragment.dismiss(); } else { @@ -411,6 +429,8 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F if(recreateCompletionServices) { getApp().createCompletionService(mProject); } else if(task.getType() == GradleTask.Type.BUILD) { + getApp().createJavaCompiler(mProject); + getApp().getJavaCompiler().compileAllAsync(this); if(task.getTaskID() == IDEService.TASK_ASSEMBLE_DEBUG) { if(task.buildsApk()) { install(task.getApk(new File(mProject.getMainModulePath(), "build").getAbsolutePath(), mProject.getMainModule())); @@ -429,6 +449,11 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F if(task.canOutput()) { setStatus(msg); } + + if(task.getType() == GradleTask.Type.BUILD) { + getApp().createJavaCompiler(mProject); + getApp().getJavaCompiler().compileAllAsync(this); + } showBuildResult(); @@ -441,9 +466,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F public void onGetDependencies(List dependencies) { setStatus(getString(R.string.msg_starting_completion)); mProject.setClassPaths(dependencies); - getApp().createJavaCompiler(mProject, this); - getApp().getJavaCompiler().compileAll(); - new Jar2DexService(getApp().getIndexedDir(), dependencies, this).startDexing(); + createCompletionServices(); } @Override @@ -464,19 +487,31 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F public void saveFiles() { saveAll(); } - + + /** + * @deprecated This is replaced by the ClassFileReader. There is no need to dex the JAR files in order to load them. + */ + @Deprecated @Override public void onDex(String name) { setStatus(getString(R.string.msg_indexing, name)); } - + + /** + * @deprecated This is replaced by the ClassFileReader. There is no need to dex the JAR files in order to load them. + */ + @Deprecated @Override public void onDexSuccess(List dexes) { ConstantsBridge.PROJECT_DEXES = dexes; setStatus(getString(R.string.msg_indexing_success)); createCompletionServices(); } - + + /** + * @deprecated This is replaced by the ClassFileReader. There is no need to dex the JAR files in order to load them. + */ + @Deprecated @Override public void onDexFailed(String message) { appendBuildOut(message); @@ -484,14 +519,20 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F showBuildResult(); } - private void appendBuildOut(String str) { - str = str.endsWith("\n") ? str : str.concat("\n"); - getBuildView(true).getText().append(str); + // Can be called from a different different, better to run on UI thread + private void appendBuildOut(final String str) { + runOnUiThread(() -> { + String strFinal = str.endsWith("\n") ? str : str.concat("\n"); + getBuildView(true).getText().append(strFinal); + }); } - private void appendLogOut(String str) { - str = str.endsWith("\n") ? str : str.concat("\n"); - getLogView(true).getText().append(str); + // Can be called from a different different, better to run on UI thread + private void appendLogOut(final String str) { + runOnUiThread(() -> { + String strFinal = str.endsWith("\n") ? str : str.concat("\n"); + getLogView(true).getText().append(strFinal); + }); } private void showFiles() { @@ -563,6 +604,20 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F mBinding.logContainer.setVisibility(View.GONE); mBinding.viewOptionsCard.setVisibility(View.VISIBLE); } + + private void showDiagnostics() { + TransitionManager.beginDelayedTransition(mBinding.getRoot(), createContainerTransformFor(mBinding.viewDiags, mBinding.diagContainer)); + mBinding.diagContainer.setVisibility(View.VISIBLE); + mBinding.viewOptionsCard.setVisibility(View.GONE); + hideToolbarAndTabs(); + } + + private void hideDiagnostics() { + showToolbarAndTabs(); + TransitionManager.beginDelayedTransition(mBinding.getRoot(), createContainerTransformFor(mBinding.diagContainer, mBinding.viewDiags)); + mBinding.diagContainer.setVisibility(View.GONE); + mBinding.viewOptionsCard.setVisibility(View.VISIBLE); + } private MaterialContainerTransform createContainerTransformFor(View start, View end) { return createContainerTransformFor(start, end, mBinding.realContainer); @@ -632,7 +687,12 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F } @Override - public void onCompilationResult(boolean success, Map> diagnostics) { + public void onCompilationResult(boolean singleFile, Map> diagnostics) { + if(getDiagnosticsList().getParent() == null || !(getDiagnosticsList().getParent() instanceof ViewGroup)) + setupContainers(); + getDiagnosticsList().setAdapter(new DiagnosticsAdapter(mapAsGroup(diagnostics), this)); + this.diagnostics.clear(); + this.diagnostics.putAll(diagnostics); provideDiagnosticsToEditors(diagnostics); } @@ -650,6 +710,21 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F } } } + + @Override + public void onGroupClick(DiagnosticGroup group) { + if(group != null + && group.file != null + && group.file.exists() + && FileUtils.isUtf8(group.file)) + { + openFile(group.file); + } + } + + @Override + public void onDiagnosticClick(CompilerDiagnostic diagnostic) { + } /***************************** **** Tab Related ************ @@ -690,8 +765,8 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F final int id = p1.getItemId(); if(id == R.id.editornav_discuss) { getApp().openTelegramGroup(); -// } else if(id == R.id.editornav_suggest) { -// getApp().openIssueTracker(); + } else if(id == R.id.editornav_suggest) { + getApp().openIssueTracker(); } else if(id == R.id.editornav_needHelp) { showNeedHelpDialog(); } else if(id == R.id.editornav_settings) { @@ -796,7 +871,32 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F logView.setTextSize(12); return logView; } - + + private RecyclerView getDiagnosticsList() { + return diagnosticList == null ? createDiagnosticsList() : diagnosticList; + } + + private RecyclerView createDiagnosticsList() { + diagnosticList = new RecyclerView(this); + diagnosticList.setLayoutManager(new LinearLayoutManager(this)); + diagnosticList.setAdapter(new DiagnosticsAdapter(mapAsGroup(this.diagnostics), this)); + return diagnosticList; + } + + private List mapAsGroup(Map> diags) { + List groups = new ArrayList<>(); + if(diags == null || diags.size() <= 0) + return groups; + for(File file : diags.keySet()) { + List 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 LogLanguageImpl getLogLanguage() { return mLogLanguageImpl == null ? mLogLanguageImpl = new LogLanguageImpl() : mLogLanguageImpl; } @@ -823,6 +923,8 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F boolean isGradleSaved = mPagerAdapter.saveAll(); getApp().toast(R.string.all_saved, Toaster.Type.SUCCESS); if(isGradleSaved) notifySyncNeeded(); + if(getApp().getJavaCompiler() != null) + getApp().getJavaCompiler().compileAllAsync(this); return isGradleSaved; } @@ -1148,7 +1250,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F mBinding.tabs.setupWithViewPager(mBinding.editorViewPager); mBinding.tabs.addOnTabSelectedListener(this); } - + private void closeFile(int index) { closeFile(index, true); } diff --git a/app/src/main/java/com/itsaky/androidide/adapters/DiagnosticItemAdpater.java b/app/src/main/java/com/itsaky/androidide/adapters/DiagnosticItemAdpater.java new file mode 100644 index 000000000..ec1cebd0e --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/adapters/DiagnosticItemAdpater.java @@ -0,0 +1,65 @@ +package com.itsaky.androidide.adapters; + +import android.graphics.PorterDuff; +import android.view.LayoutInflater; +import android.view.ViewGroup; +import androidx.core.content.ContextCompat; +import androidx.recyclerview.widget.RecyclerView; +import com.itsaky.androidide.R; +import com.itsaky.androidide.databinding.LayoutDiagnosticItemBinding; +import com.itsaky.androidide.services.compiler.model.CompilerDiagnostic; +import java.util.List; +import javax.tools.Diagnostic; +import com.itsaky.androidide.interfaces.DiagnosticClickListener; + +public class DiagnosticItemAdpater extends RecyclerView.Adapter { + + private List diags; + private DiagnosticClickListener listener; + + public DiagnosticItemAdpater(List diags, DiagnosticClickListener listener) { + this.diags = diags; + this.listener = listener; + } + + @Override + public DiagnosticItemAdpater.VH onCreateViewHolder(ViewGroup p1, int p2) { + return new VH(LayoutDiagnosticItemBinding.inflate(LayoutInflater.from(p1.getContext()), p1, false)); + } + + @Override + public void onBindViewHolder(DiagnosticItemAdpater.VH p1, int p2) { + final CompilerDiagnostic diagnostic = diags.get(p2); + final LayoutDiagnosticItemBinding binding = p1.binding; + + binding.icon.setImageResource(getDiagnosticIconId(diagnostic)); + binding.icon.setColorFilter(ContextCompat.getColor(binding.icon.getContext(), diagnostic.kind() == Diagnostic.Kind.ERROR ? R.color.diagnostic_error : R.color.diagnostic_warning), PorterDuff.Mode.SRC_ATOP); + binding.title.setText(diagnostic.message()); + + binding.getRoot().setOnClickListener(v -> { + if(listener != null) { + listener.onDiagnosticClick(diagnostic); + } + }); + } + + @Override + public int getItemCount() { + return diags.size(); + } + + private int getDiagnosticIconId(CompilerDiagnostic diagnostic) { + if(diagnostic.kind() == Diagnostic.Kind.ERROR) + return R.drawable.ic_compilation_error; + return R.drawable.ic_info; + } + + public 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 new file mode 100644 index 000000000..d5d912e17 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/adapters/DiagnosticsAdapter.java @@ -0,0 +1,63 @@ +package com.itsaky.androidide.adapters; + +import android.graphics.PorterDuff; +import android.view.LayoutInflater; +import android.view.ViewGroup; +import androidx.core.content.ContextCompat; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import com.itsaky.androidide.R; +import com.itsaky.androidide.databinding.LayoutDiagnosticGroupBinding; +import com.itsaky.androidide.interfaces.DiagnosticClickListener; +import com.itsaky.androidide.models.DiagnosticGroup; +import java.util.List; + +public class DiagnosticsAdapter extends RecyclerView.Adapter { + + 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 DiagnosticItemAdpater(group.diagnostics, 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/app/StudioApp.java b/app/src/main/java/com/itsaky/androidide/app/StudioApp.java index bdae075e6..bf6931e33 100644 --- a/app/src/main/java/com/itsaky/androidide/app/StudioApp.java +++ b/app/src/main/java/com/itsaky/androidide/app/StudioApp.java @@ -11,15 +11,10 @@ import android.text.TextUtils; import androidx.core.app.NotificationManagerCompat; import androidx.multidex.MultiDexApplication; import com.blankj.utilcode.util.EncodeUtils; +import com.blankj.utilcode.util.FileUtils; import com.blankj.utilcode.util.ResourceUtils; import com.blankj.utilcode.util.SPStaticUtils; import com.blankj.utilcode.util.ThrowableUtils; -import com.elvishew.xlog.LogConfiguration; -import com.elvishew.xlog.LogLevel; -import com.elvishew.xlog.XLog; -import com.elvishew.xlog.printer.Printer; -import com.elvishew.xlog.printer.file.FilePrinter; -import com.elvishew.xlog.printer.file.naming.DateFileNameGenerator; import com.google.firebase.messaging.FirebaseMessaging; import com.itsaky.androidide.R; import com.itsaky.androidide.app.StudioApp; @@ -28,8 +23,10 @@ import com.itsaky.androidide.language.java.manager.JavaCharacter; import com.itsaky.androidide.language.java.provider.JavaCompletionProvider; import com.itsaky.androidide.language.xml.completion.XMLCompletionService; import com.itsaky.androidide.models.AndroidProject; +import com.itsaky.androidide.models.ConstantsBridge; import com.itsaky.androidide.services.IDEService; import com.itsaky.androidide.services.MessagingService; +import com.itsaky.androidide.services.compiler.JavaCompilerService; import com.itsaky.androidide.shell.ShellServer; import com.itsaky.androidide.utils.Environment; import com.itsaky.androidide.utils.FileUtil; @@ -37,16 +34,11 @@ import com.itsaky.androidide.utils.PreferenceManager; import com.itsaky.androidide.utils.StudioUtils; import com.itsaky.toaster.Toaster; import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Locale; -import com.itsaky.androidide.models.ConstantsBridge; -import androidx.localbroadcastmanager.content.LocalBroadcastManager; -import java.io.FileOutputStream; -import java.io.IOException; -import com.itsaky.androidide.services.compiler.JavaCompilerService; -import com.itsaky.androidide.interfaces.CompileListener; -import com.blankj.utilcode.util.FileUtils; public class StudioApp extends MultiDexApplication { @@ -93,10 +85,6 @@ public class StudioApp extends MultiDexApplication newShell(line -> handleLog(line)).bgAppend("logcat -v threadtime"); setupLibsIfNeeded(); - if(isFrameworkInstalled()) { - newShell(t -> XLog.i(t)).bgAppend("printenv > /sdcard/androidideenv.txt && java -version"); - } - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { createNotificationChannels(); } @@ -153,41 +141,28 @@ public class StudioApp extends MultiDexApplication } private void initLogger() { - LogConfiguration config = new LogConfiguration.Builder() - .logLevel(DEBUG ? LogLevel.ALL : LogLevel.NONE) - .tag("Android IDE") - .enableThreadInfo() - .enableStackTrace(2) - .enableBorder() - .build(); - Printer filePrinter = new FilePrinter - .Builder(FileUtil.getExternalStorageDir() + "/ide_xlog") - .fileNameGenerator(new DateFileNameGenerator()) - .build(); - XLog.init( - config, - filePrinter); + } private boolean setupLibsIfNeeded() { File lib = Environment.LIBDIR; - return extractLibs(lib); -// if(lib != null && lib.exists() && lib.isDirectory()) { -// boolean libhook, librt, libgcc, libgcc_real, libpthread, libz; -// libhook = librt = libgcc = libgcc_real = libpthread = libz = false; -// File[] list = lib.listFiles(); -// if(list != null) { -// for(File f : list) { -// libhook = libhook == false && f.getName().equals("libhook.so"); -// librt = librt == false && f.getName().equals("librt.so"); -// libpthread = libpthread == false && f.getName().equals("libpthread.so"); -// libgcc = libgcc == false && f.getName().equals("libgcc.so"); -// libgcc_real = libgcc_real = false & f.getName().equals("libgcc_real.so"); -// libz = libz == false && f.getName().equals("libz.so.1"); -// } -// } else return extractLibs(lib); -// return !(libhook && librt && libgcc && libgcc_real && libpthread && libz) ? extractLibs(lib) : true; -// } else return extractLibs(lib); + if(lib != null && lib.exists() && lib.isDirectory()) { + boolean libhook, librt, libgcc, libgcc_real, libpthread, libz, libcpp_shared; + libhook = librt = libgcc = libgcc_real = libpthread = libz = libcpp_shared = false; + File[] list = lib.listFiles(); + if(list != null) { + for(File f : list) { + libhook = libhook == false ? f.getName().equals("libhook.so") : libhook; + librt = librt == false ? f.getName().equals("librt.so") : librt; + libpthread = libpthread == false ? f.getName().equals("libpthread.so") : libpthread; + libgcc = libgcc == false ? f.getName().equals("libgcc.so") : libgcc; + libgcc_real = libgcc_real = false ? f.getName().equals("libgcc_real.so") : libgcc_real; + libz = libz == false ? f.getName().equals("libz.so.1") : libz; + libcpp_shared = libcpp_shared == false ? f.getName().equals("libc++_shared.so") : libcpp_shared; + } + } else return extractLibs(lib); + return !(libhook && librt && libgcc && libgcc_real && libpthread && libz && libcpp_shared) ? extractLibs(lib) : true; + } else return extractLibs(lib); } private boolean extractLibs(File lib) { @@ -248,10 +223,9 @@ public class StudioApp extends MultiDexApplication this.mCompletionProvider.loadProject(project); } - public void createJavaCompiler(AndroidProject project, CompileListener listener) { + public void createJavaCompiler(AndroidProject project) { this.mJavaCompilerService = - new JavaCompilerService(project) - .setListener(listener); + new JavaCompilerService(project); } public JavaCompilerService getJavaCompiler() { diff --git a/app/src/main/java/com/itsaky/androidide/fragments/EditorFragment.java b/app/src/main/java/com/itsaky/androidide/fragments/EditorFragment.java index 300c4b616..b71c30e4e 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/EditorFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/EditorFragment.java @@ -5,21 +5,26 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.itsaky.androidide.adapters.CompletionListAdapter; +import com.itsaky.androidide.app.StudioApp; import com.itsaky.androidide.databinding.FragmentEditorBinding; import com.itsaky.androidide.fragments.preferences.EditorPreferences; +import com.itsaky.androidide.interfaces.CompileListener; import com.itsaky.androidide.language.groovy.GroovyLanguage; import com.itsaky.androidide.language.java.JavaLanguage; import com.itsaky.androidide.language.xml.XMLLanguage; import com.itsaky.androidide.language.xml.lexer.XMLLexer; import com.itsaky.androidide.models.AndroidProject; import com.itsaky.androidide.models.ConstantsBridge; +import com.itsaky.androidide.services.compiler.JavaCompilerService; import com.itsaky.androidide.services.compiler.model.CompilerDiagnostic; import com.itsaky.androidide.syntax.colorschemes.SchemeWombat; import com.itsaky.androidide.tasks.TaskExecutor; import com.itsaky.androidide.tasks.callables.ReadFileTask; +import com.itsaky.androidide.tools.SourceJavaFileObject; import com.itsaky.androidide.utils.FileUtil; import com.itsaky.androidide.utils.PreferenceManager; import com.itsaky.androidide.utils.TypefaceUtils; +import com.sun.tools.javac.resources.compiler; import io.github.rosemoe.editor.interfaces.EditorEventListener; import io.github.rosemoe.editor.langs.EmptyLanguage; import io.github.rosemoe.editor.widget.CodeEditor; @@ -28,13 +33,11 @@ import java.io.File; import java.io.StringReader; import java.util.ArrayList; import java.util.List; +import java.util.Map; import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.Token; -import com.elvishew.xlog.XLog; -import android.text.TextUtils; -import com.itsaky.androidide.services.compiler.JavaCompilerService; -public class EditorFragment extends BaseFragment implements EditorEventListener { +public class EditorFragment extends BaseFragment implements EditorEventListener, CompileListener { public FragmentEditorBinding binding; private File mFile; @@ -43,9 +46,7 @@ public class EditorFragment extends BaseFragment implements EditorEventListener private boolean isFirstCreate = false; private static AndroidProject project; - - private List diags; - + public static final String KEY_FILE_PATH = "file_path"; public static final String KEY_PROJECT = "project"; public static final String EXT_JAVA = ".java"; @@ -55,7 +56,7 @@ public class EditorFragment extends BaseFragment implements EditorEventListener public static final String EXT_GROOVY = ".groovy"; public static final String EXT_KOTLIN = ".kt"; public static final String EXT_JSON = ".json"; - + public static EditorFragment newInstance(File file, AndroidProject project) { Bundle bundle = new Bundle(); bundle.putString(KEY_FILE_PATH, file.getAbsolutePath()); @@ -100,11 +101,30 @@ public class EditorFragment extends BaseFragment implements EditorEventListener postRead(); }); } + + @Override + public void onCompilationResult(boolean singleFile, Map> diagnostics) { + if(diagnostics != null + && getFile() != null + && diagnostics.containsKey(getFile())) + { + binding.editorCodeEditor.setDiagnostics(diagnostics.get(getFile())); + } + } + + private void requestCompilation() { + final long s = System.currentTimeMillis(); + final SourceJavaFileObject source = new SourceJavaFileObject(getFile(), binding.editorCodeEditor.getText().toString()); + JavaCompilerService compiler = StudioApp.getInstance().getJavaCompiler(); + if(compiler != null) { + compiler.compileSingleAsync(source, this); + } + } public void setDiagnostics(List diags) { - this.diags = diags; - XLog.i("[EditorFragment] Diagnostics for file: " + getFile().getAbsolutePath() - + "\ndiagnostics: " + TextUtils.join("\n", diags)); + if(binding.editorCodeEditor != null) { + binding.editorCodeEditor.setDiagnostics(diags); + } } private void configureEditorIfNeeded() { @@ -176,10 +196,10 @@ public class EditorFragment extends BaseFragment implements EditorEventListener private void postRead() { if (mFile.isFile() && mFile.getName().endsWith(EXT_JAVA)) { - binding.editorCodeEditor.setEditorLanguage(new JavaLanguage(binding.editorCodeEditor, project)); + binding.editorCodeEditor.setEditorLanguage(new JavaLanguage(project)); binding.editorCodeEditor.setColorScheme(new SchemeVS2019()); } else if (mFile.isFile() && mFile.getName().endsWith(EXT_XML)) { - binding.editorCodeEditor.setEditorLanguage(new XMLLanguage(binding.editorCodeEditor)); + binding.editorCodeEditor.setEditorLanguage(new XMLLanguage()); binding.editorCodeEditor.setColorScheme(new SchemeWombat()); } else if (mFile.isFile() && mFile.getName().endsWith(EXT_GRADLE)) { binding.editorCodeEditor.setEditorLanguage(new GroovyLanguage()); @@ -251,6 +271,8 @@ public class EditorFragment extends BaseFragment implements EditorEventListener @Override public void afterDelete(CodeEditor editor, CharSequence content, int startLine, int startColumn, int endLine, int endColumn, CharSequence deletedContent) { isModified = true; + + requestCompilation(); } @Override @@ -263,11 +285,8 @@ public class EditorFragment extends BaseFragment implements EditorEventListener if(isOpen && insertedContent.toString().equals("/")) { closeCurrentTag(editor.getText().toString(), endLine, endColumn); } - - JavaCompilerService compiler = getStudioActivity().getApp().getJavaCompiler(); - if(compiler != null) { - compiler.compileAll(); - } + + requestCompilation(); } @Override 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 406a877f9..d91f160c1 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/FileTreeFragment.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/FileTreeFragment.java @@ -26,6 +26,7 @@ import com.unnamed.b.atv.view.AndroidTreeView; import java.io.File; import java.util.ArrayList; import java.util.Arrays; +import com.blankj.utilcode.util.FileUtils; public class FileTreeFragment extends BottomSheetDialogFragment implements TreeNode.TreeNodeClickListener, TreeNode.TreeNodeLongClickListener { @@ -86,13 +87,8 @@ public class FileTreeFragment extends BottomSheetDialogFragment implements TreeN public void onClick(TreeNode node, Object p2) { final File f = (File) p2; if(f.isFile()) { - if (mFileActionListener != null && f.isFile()) { - for(String ext : compatibleExtensions) { - if(f.getName().endsWith(ext)) { - mFileActionListener.openFile(f); - break; - } - } + if (mFileActionListener != null && FileUtils.isUtf8(f)) { + mFileActionListener.openFile(f); } } else if(f.isDirectory() && f.exists()) { if(node.isExpanded()) { @@ -181,7 +177,6 @@ public class FileTreeFragment extends BottomSheetDialogFragment implements TreeN final File gradleHome = Environment.GRADLE_PROPS_DIR; final File root = StudioApp.getInstance().getRootDir().getParentFile(); File projectDir = new File(mProject.getProjectPath()); - projectDir = root; mRoot = TreeNode.root(projectDir); if(gradleHome.exists() && gradleHome.isDirectory()) { if(!gradleProps.exists()) 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 f55a045ed..85b931607 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/preferences/IDEPreferences.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/preferences/IDEPreferences.java @@ -84,7 +84,7 @@ public class IDEPreferences extends BasePreferenceFragment { screen.addPreference(editor); screen.addPreference(build); -// screen.addPreference(issueTracker); + screen.addPreference(issueTracker); screen.addPreference(telegram); screen.addPreference(changelog); diff --git a/app/src/main/java/com/itsaky/androidide/fragments/sheets/LogSheet.java b/app/src/main/java/com/itsaky/androidide/fragments/sheets/LogSheet.java index 219c4dc3e..cc7bd9173 100644 --- a/app/src/main/java/com/itsaky/androidide/fragments/sheets/LogSheet.java +++ b/app/src/main/java/com/itsaky/androidide/fragments/sheets/LogSheet.java @@ -8,7 +8,6 @@ import android.widget.LinearLayout; import android.widget.ScrollView; import android.widget.TextView; import com.blankj.utilcode.util.ThrowableUtils; -import com.elvishew.xlog.XLog; import com.itsaky.androidide.R; import com.itsaky.androidide.models.LogLine; diff --git a/app/src/main/java/com/itsaky/androidide/interfaces/CompileListener.java b/app/src/main/java/com/itsaky/androidide/interfaces/CompileListener.java index 3919e7aa7..28c164a6e 100644 --- a/app/src/main/java/com/itsaky/androidide/interfaces/CompileListener.java +++ b/app/src/main/java/com/itsaky/androidide/interfaces/CompileListener.java @@ -6,5 +6,5 @@ import java.util.List; import java.util.Map; public interface CompileListener { - void onCompilationResult(boolean success, Map> diagnostics); + void onCompilationResult(boolean singleFile, Map> diagnostics); } diff --git a/app/src/main/java/com/itsaky/androidide/interfaces/DexResultListener.java b/app/src/main/java/com/itsaky/androidide/interfaces/DexResultListener.java index bd0fb564d..a2ce1ef51 100644 --- a/app/src/main/java/com/itsaky/androidide/interfaces/DexResultListener.java +++ b/app/src/main/java/com/itsaky/androidide/interfaces/DexResultListener.java @@ -3,6 +3,13 @@ package com.itsaky.androidide.interfaces; import androidx.core.util.Pair; import java.util.List; +/** + * A callback to listen for updates while dexing JAR files + * + * @deprecated This is replaced by the ClassFileReader. There is no need to dex the JAR files in order to load them. + */ + +@Deprecated public interface DexResultListener { public void onDex(String name); public void onDexSuccess(List dexes); diff --git a/app/src/main/java/com/itsaky/androidide/interfaces/DiagnosticClickListener.java b/app/src/main/java/com/itsaky/androidide/interfaces/DiagnosticClickListener.java new file mode 100644 index 000000000..2d8b9dc54 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/interfaces/DiagnosticClickListener.java @@ -0,0 +1,9 @@ +package com.itsaky.androidide.interfaces; + +import com.itsaky.androidide.models.DiagnosticGroup; +import com.itsaky.androidide.services.compiler.model.CompilerDiagnostic; + +public interface DiagnosticClickListener { + void onGroupClick(DiagnosticGroup group); + void onDiagnosticClick(CompilerDiagnostic diagnostic); +} 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 c70ba0d5d..5fce68f9b 100644 --- a/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyAutoComplete.java +++ b/app/src/main/java/com/itsaky/androidide/language/groovy/GroovyAutoComplete.java @@ -6,6 +6,7 @@ import com.itsaky.androidide.utils.Either; import io.github.rosemoe.editor.interfaces.AutoCompleteProvider; import io.github.rosemoe.editor.struct.CompletionItem; import io.github.rosemoe.editor.text.TextAnalyzeResult; +import io.github.rosemoe.editor.widget.CodeEditor; import java.util.ArrayList; import java.util.List; import java.util.Locale; @@ -17,7 +18,7 @@ public class GroovyAutoComplete implements AutoCompleteProvider { private static final List OTHERS = createOtherCompletions(); @Override - public List> getAutoCompleteItems(String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line) { + public List> getAutoCompleteItems(CodeEditor editor, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line) { List> result = new ArrayList<>(); for(String artifact : ANDROIDX_ARTIFACTS) { diff --git a/app/src/main/java/com/itsaky/androidide/language/java/JavaAutoComplete.java b/app/src/main/java/com/itsaky/androidide/language/java/JavaAutoComplete.java index ff330bb8a..1bbe9c2f9 100644 --- a/app/src/main/java/com/itsaky/androidide/language/java/JavaAutoComplete.java +++ b/app/src/main/java/com/itsaky/androidide/language/java/JavaAutoComplete.java @@ -15,22 +15,23 @@ import java.util.ArrayList; import java.util.List; import javax.tools.Diagnostic; import javax.tools.JavaFileObject; +import com.itsaky.androidide.utils.Logger; +import java.util.stream.Collectors; +import com.itsaky.androidide.services.compiler.model.CompilerDiagnosticImpl; public class JavaAutoComplete implements AutoCompleteProvider { - - private CodeEditor editor; private JavaLanguageAnalyzer analyzer; - public JavaAutoComplete(CodeEditor editor, JavaLanguageAnalyzer analyzer) { - this.editor = editor; + public JavaAutoComplete(JavaLanguageAnalyzer analyzer) { this.analyzer = analyzer; } @Override - public List> getAutoCompleteItems(String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line) { + public List> getAutoCompleteItems(CodeEditor editor, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line) { Pair, List>> pair = StudioApp.getInstance().getCompletionProvider().getSuggestions(editor); final ArrayList suggestions = pair.first; - final List> diags = pair.second; +// final List> diags = pair.second; +// editor.setDiagnostics(diags.stream().map(CompilerDiagnosticImpl::fromDiagnostic).collect(Collectors.toList())); if(suggestions != null) { List> result = new ArrayList<>(); for(SuggestItem item : suggestions) { 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 cef9260b5..0f9ff2ad5 100644 --- a/app/src/main/java/com/itsaky/androidide/language/java/JavaLanguage.java +++ b/app/src/main/java/com/itsaky/androidide/language/java/JavaLanguage.java @@ -21,14 +21,12 @@ public class JavaLanguage implements EditorLanguage private JavaLanguageAnalyzer analyzer; private JavaAutoComplete complete; private AndroidProject project; - private CodeEditor editor; - public JavaLanguage(CodeEditor editor, AndroidProject project) + public JavaLanguage(AndroidProject project) { this.analyzer = new JavaLanguageAnalyzer(); - this.complete = new JavaAutoComplete(editor, analyzer); + this.complete = new JavaAutoComplete(analyzer); this.project = project; - this.editor = editor; } @Override diff --git a/app/src/main/java/com/itsaky/androidide/language/java/parser/expression/CompleteThisKeyword.java b/app/src/main/java/com/itsaky/androidide/language/java/parser/expression/CompleteThisKeyword.java index 95a760d7d..4accb5aaa 100644 --- a/app/src/main/java/com/itsaky/androidide/language/java/parser/expression/CompleteThisKeyword.java +++ b/app/src/main/java/com/itsaky/androidide/language/java/parser/expression/CompleteThisKeyword.java @@ -49,7 +49,7 @@ public class CompleteThisKeyword extends JavaCompleteMatcherImpl { MethodDescription desc = new MethodDescription( method.getName().toString(), returnType, - method.getModifiers().flags, + (int) method.getModifiers().flags, paramsStr); setInfo(desc, editor, incomplete); result.add(desc); diff --git a/app/src/main/java/com/itsaky/androidide/language/java/parser/internal/CompiledClassLoader.java b/app/src/main/java/com/itsaky/androidide/language/java/parser/internal/CompiledClassLoader.java deleted file mode 100644 index 416fd807a..000000000 --- a/app/src/main/java/com/itsaky/androidide/language/java/parser/internal/CompiledClassLoader.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.itsaky.androidide.language.java.parser.internal; - -import android.text.TextUtils; -import androidx.annotation.NonNull; -import com.elvishew.xlog.XLog; -import com.itsaky.androidide.models.AndroidProject; -import com.itsaky.androidide.models.ConstantsBridge; -import dalvik.system.DexFile; -import dalvik.system.PathClassLoader; -import java.io.File; -import java.util.ArrayList; -import java.util.Enumeration; -import java.util.List; - -class CompiledClassLoader { - - @NonNull - public ArrayList getCompiledClassesFromProject(@NonNull AndroidProject project) { - ArrayList classes = new ArrayList<>(); - try { - final List dexes = ConstantsBridge.PROJECT_DEXES; - final PathClassLoader loader = new PathClassLoader(TextUtils.join(File.pathSeparator, dexes), ClassLoader.getSystemClassLoader()); - if (dexes == null || dexes.size() <= 0) return classes; - for (String dex : dexes) { - final DexFile d = new DexFile(new File(dex)); - Enumeration entries = d.entries(); - String name = null; - while (entries.hasMoreElements() && (name = entries.nextElement()) != null) { - while(name.startsWith(File.separator)) - name = name.substring(1); - if(name.contains(File.separator)) - name = name.replace(File.separator, "."); - Class load = tryLoad(loader, name); - if (load != null) - classes.add(load); - } - d.close(); - } - - ConstantsBridge.CLASS_LOAD_SUCCESS = true; - return classes; - } catch (Throwable th) { - ConstantsBridge.CLASS_LOAD_SUCCESS = false; - return classes; - } - } - - private Class tryLoad(ClassLoader loader, String name) { - try { - return loader.loadClass(name); - } catch (ClassNotFoundException e) { - return null; - } - } -} diff --git a/editor/src/main/java/com/itsaky/androidide/language/java/parser/internal/IJavaDocCommentable.java b/app/src/main/java/com/itsaky/androidide/language/java/parser/internal/IJavaDocCommentable.java similarity index 100% rename from editor/src/main/java/com/itsaky/androidide/language/java/parser/internal/IJavaDocCommentable.java rename to app/src/main/java/com/itsaky/androidide/language/java/parser/internal/IJavaDocCommentable.java diff --git a/app/src/main/java/com/itsaky/androidide/language/java/parser/internal/JavaClassManager.java b/app/src/main/java/com/itsaky/androidide/language/java/parser/internal/JavaClassManager.java index cc5120073..c9837f6d4 100644 --- a/app/src/main/java/com/itsaky/androidide/language/java/parser/internal/JavaClassManager.java +++ b/app/src/main/java/com/itsaky/androidide/language/java/parser/internal/JavaClassManager.java @@ -1,26 +1,47 @@ package com.itsaky.androidide.language.java.parser.internal; -import androidx.annotation.Nullable; +import android.text.TextUtils; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.core.util.Pair; import com.blankj.utilcode.util.FileIOUtils; import com.blankj.utilcode.util.FileUtils; import com.itsaky.androidide.interfaces.Filter; import com.itsaky.androidide.language.java.parser.JavacParser; import com.itsaky.androidide.language.java.parser.model.ClassDescription; +import com.itsaky.androidide.language.java.parser.model.FieldDescription; +import com.itsaky.androidide.language.java.parser.model.MethodDescription; import com.itsaky.androidide.models.AndroidProject; +import com.itsaky.androidide.models.ConstantsBridge; +import com.itsaky.androidide.utils.Logger; import com.sun.tools.javac.tree.JCTree; +import dalvik.system.DexFile; +import dalvik.system.PathClassLoader; import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Locale; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import org.eclipse.jdt.core.Signature; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException; +import org.eclipse.jdt.internal.compiler.classfmt.FieldInfo; +import org.eclipse.jdt.internal.compiler.env.IBinaryField; +import org.eclipse.jdt.internal.compiler.env.IBinaryMethod; +import org.eclipse.jdt.internal.compiler.env.IBinaryType; +import com.itsaky.androidide.language.java.parser.model.ConstructorDescription; +import com.itsaky.androidide.utils.Either; +import com.blankj.utilcode.util.ThrowableUtils; +import com.itsaky.androidide.utils.Environment; public class JavaClassManager implements IClassManager { - private static final String TAG = "JavaClassReader"; private static final String JAVA_DOT_LANG_DOT = "java.lang."; private static JavaClassManager INSTANCE; @@ -65,11 +86,9 @@ public class JavaClassManager implements IClassManager { public void loadFromProject(AndroidProject project) { try { - CompiledClassLoader classLoader = new CompiledClassLoader(); - ArrayList classes = classLoader.getCompiledClassesFromProject(project); - for (Class clazz : classes) { - getClassWrapper(clazz); - } + try { + loadClasspaths(project); + } catch (Throwable e) {} JavacParser parser = new JavacParser(); List javaSrcDirs = project.getSourcePaths(); for (String dir : javaSrcDirs) { @@ -84,9 +103,7 @@ public class JavaClassManager implements IClassManager { } } } - } catch (Throwable e) { - e.printStackTrace(); - } + } catch (Throwable e) {} } /** @@ -249,5 +266,214 @@ public class JavaClassManager implements IClassManager { } return result; } + + private void loadClasspaths(AndroidProject project) throws IOException, ClassFormatException, ClassNotFoundException { + final List classpaths = project.getClassPaths(); + if(!classpaths.contains(Environment.BOOTCLASSPATH)) + classpaths.add(Environment.BOOTCLASSPATH.getAbsolutePath()); + for(int i=0;i entries = zip.entries(); + + ZipEntry entry = null; + while(entries.hasMoreElements() && ((entry = entries.nextElement()) != null)) { + if(!entry.getName().endsWith(".class")) + continue; + try { + ClassFileReader reader = ClassFileReader.read(zip, entry.getName()); + describe(zip, reader); + } catch (Throwable th) {} + } + } + } + + private ClassDescription describe(ZipFile zip, IBinaryType type) throws IOException, ClassFormatException, ClassNotFoundException { + if(type == null) + return null; + final char[] name = type.getName(); + final char[] superClassName = type.getSuperclassName(); + if(name == null) return null; + final int mods = type.getModifiers(); + final boolean isPremitive = false; + final boolean isAnnotation = false; + final boolean isEnum = false; + final boolean isMember = type.getEnclosingTypeName() != null; + final IBinaryField[] fields = type.getFields(); + final IBinaryMethod[] methods = type.getMethods(); + final char[][] interfaces = type.getInterfaceNames(); + ClassDescription clazz = new ClassDescription(bytecodeToSourceType(new String(name)), mods, isPremitive, isAnnotation, isEnum, isMember); + + int index = 0; + while(fields != null && index < fields.length) { + IBinaryField f = fields[index]; + if(f == null) continue; + FieldDescription field = describe(zip, f); + if(field == null) continue; + clazz.addField(field); + ++index; + } + + index = 0; + while(methods != null && index < methods.length) { + IBinaryMethod m = methods[index]; + if(m == null) continue; + Either result = describe(zip, m); + if(result == null) continue; + if(result.isLeft()) + clazz.addMethod(result.getLeft()); + else clazz.addConstructor(result.getRight()); + ++index; + } + + // TODO: Add interfaces + + clazz.setSuperclass(superClassName == null ? ClassDescription.ofObject() : findType(zip, new String(superClassName))); + + update(clazz); + return clazz; + } + + private Either describe(ZipFile zip, IBinaryMethod m) throws IOException, IllegalArgumentException, ClassFormatException, ClassNotFoundException { + if(zip == null || m == null) return null; + final char[] descriptor = m.getMethodDescriptor(); + final char[] signature = m.getGenericSignature(); + final char[] name = m.getSelector(); + final int mods = m.getModifiers(); + if(name == null || descriptor == null) return null; + IClass returns = findType(zip, Signature.getReturnType(new String(descriptor))); + String[] paramTypes = Signature.getParameterTypes(new String(descriptor)); + ArrayList params = new ArrayList<>(); + for(String type : paramTypes) { + params.add(bytecodeToSourceType(type)); + } + return m.isConstructor() ? Either.forRight(new ConstructorDescription(new String(name).replace("/", "."), params)) : Either.forLeft(new MethodDescription(new String(name).replace("/", "."), returns, mods, params)); + } + + private FieldDescription describe(ZipFile zip, IBinaryField field) throws IOException, ClassFormatException, ClassNotFoundException { + if(field == null) + return null; + final char[] n = field.getName(); + final char[] t = field.getTypeName(); + if(n == null || t == null) return null; + final String name = new String(n); + final String type = new String(t); + final int mods = field.getModifiers(); + final String value = field instanceof FieldInfo && ((FieldInfo) field).hasConstant() ? field.getConstant().stringValue() : ""; + IClass typeClass = findType(zip, type); + if(typeClass == null) + typeClass = new ClassDescription(Object.class); + FieldDescription f = new FieldDescription(mods, typeClass, name.replace("/", "."), value); + return f; + } + + private IClass findType(ZipFile zip, String name) throws IOException, ClassFormatException, ClassNotFoundException { + if(zip == null || name == null) + return null; + final int arrayCount = Signature.getArrayCount(name); + if(arrayCount == 0) { + if(name.length() == 1) { + // A primitive type + final Class load = primitiveType(name.charAt(0)); + if(load != null) { + ClassDescription desc = new ClassDescription(load); + desc.initMembers(load); + update(desc); + return desc; + } + } + IClass found = getParsedClass(bytecodeToSourceType(name)); + if(found != null) { + return found; + } else { + ClassFileReader reader = ClassFileReader.read(zip, name); + if(reader == null) + return null; + else { + // This class will be updated in describe() method + return describe(zip, reader); + } + } + } else { + if(Signature.getElementType(name).length() == 1) { + // This type may be an array of primitive + Class load = Class.forName(name); + if(load != null) { + ClassDescription desc = new ClassDescription(load); + desc.initMembers(load); + update(desc); + return desc; + } + } + // TODO: Load array type classes of objects + // Return without loading members of this class + return new ClassDescription(Void.TYPE); + } + } + + private String bytecodeToSourceType(String type) { + type = Signature.getElementType(type); + if(type.startsWith("L") && type.endsWith(";")) { + type = type.substring(1, type.lastIndexOf(";")); + } + return type.replace("/", "."); + } + + public Class primitiveType(char typeId) { + switch (typeId) { + case 'I' : return int.class; + case 'B' : return byte.class; + case 'S' : return short.class; + case 'C' : return char.class; + case 'F' : return float.class; + case 'D' : return double.class; + case 'Z' : return boolean.class; + case 'J' : return long.class; + default : return null; + } + } + + + /** + * Kept as a backup if loading jar files fails for some reasons + */ + private ArrayList loadFromDexes(AndroidProject project) { + ArrayList classes = new ArrayList<>(); + try { + final List dexes = ConstantsBridge.PROJECT_DEXES; + final PathClassLoader loader = new PathClassLoader(TextUtils.join(File.pathSeparator, dexes), ClassLoader.getSystemClassLoader()); + if (dexes == null || dexes.size() <= 0) return classes; + for (String dex : dexes) { + final DexFile d = new DexFile(new File(dex)); + Enumeration entries = d.entries(); + String name = null; + while (entries.hasMoreElements() && (name = entries.nextElement()) != null) { + while(name.startsWith(File.separator)) + name = name.substring(1); + if(name.contains(File.separator)) + name = name.replace(File.separator, "."); + Class load = tryLoad(loader, name); + if (load != null) + classes.add(load); + } + d.close(); + } + + ConstantsBridge.CLASS_LOAD_SUCCESS = true; + return classes; + } catch (Throwable th) { + ConstantsBridge.CLASS_LOAD_SUCCESS = false; + return classes; + } + } + + private Class tryLoad(ClassLoader loader, String name) { + try { + return loader.loadClass(name); + } catch (ClassNotFoundException e) { + return null; + } + } } diff --git a/editor/src/main/java/com/itsaky/androidide/language/java/parser/internal/SuggestItem.java b/app/src/main/java/com/itsaky/androidide/language/java/parser/internal/SuggestItem.java similarity index 100% rename from editor/src/main/java/com/itsaky/androidide/language/java/parser/internal/SuggestItem.java rename to app/src/main/java/com/itsaky/androidide/language/java/parser/internal/SuggestItem.java diff --git a/app/src/main/java/com/itsaky/androidide/language/java/parser/model/ClassDescription.java b/app/src/main/java/com/itsaky/androidide/language/java/parser/model/ClassDescription.java index c5fc18eb3..0d9c3b8ef 100644 --- a/app/src/main/java/com/itsaky/androidide/language/java/parser/model/ClassDescription.java +++ b/app/src/main/java/com/itsaky/androidide/language/java/parser/model/ClassDescription.java @@ -56,6 +56,12 @@ public class ClassDescription extends JavaSuggestItemImpl implements IClass { this.isMemberClass = isMember; this.mEum = isEnum; } + + public static ClassDescription ofObject() { + ClassDescription desc = new ClassDescription(Object.class); + desc.initMembers(Object.class); + return desc; + } @Override public boolean isMemberClass() { @@ -251,7 +257,6 @@ public class ClassDescription extends JavaSuggestItemImpl implements IClass { for (Class anInterface : interfaces) { IClass implement = JavaClassManager.getInstance().getClassWrapper(anInterface); mImplements.add(implement); -// initClassMembers(implement); } if (c.getSuperclass() != null) { @@ -262,8 +267,6 @@ public class ClassDescription extends JavaSuggestItemImpl implements IClass { mSuperClass = JavaClassManager.getInstance().getParsedClass(Object.class.getName()); } } - -// initClassMembers(mSuperClass); for (Constructor constructor : c.getConstructors()) { if (Modifier.isPublic(constructor.getModifiers())) { @@ -288,26 +291,5 @@ public class ClassDescription extends JavaSuggestItemImpl implements IClass { public void setSuperclass(IClass superclass) { this.mSuperClass = superclass; -// initClassMembers(mSuperClass); } - -// private void initClassMembers(IClass clazz) { -// if(clazz.getFields() != null) { -// for(IField f : clazz.getFields()) { -// addField(f); -// } -// } -// -// if(clazz.getMethods() != null) { -// for(IMethod method : clazz.getMethods()) { -// addMethod(method); -// } -// } -// -// if(clazz.getConstructors() != null) { -// for(ConstructorDescription desc : clazz.getConstructors()) { -// addConstructor(desc); -// } -// } -// } } diff --git a/app/src/main/java/com/itsaky/androidide/language/java/parser/model/ConstructorDescription.java b/app/src/main/java/com/itsaky/androidide/language/java/parser/model/ConstructorDescription.java index 1deccde11..784f6c106 100644 --- a/app/src/main/java/com/itsaky/androidide/language/java/parser/model/ConstructorDescription.java +++ b/app/src/main/java/com/itsaky/androidide/language/java/parser/model/ConstructorDescription.java @@ -13,21 +13,26 @@ import io.github.rosemoe.editor.widget.CodeEditor; import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; public class ConstructorDescription extends JavaSuggestItemImpl implements IJavaDocCommentable { - private ArrayList mParameterTypes = new ArrayList<>(); + private ArrayList mParameterTypes = new ArrayList<>(); private String mConstructorName; public ConstructorDescription(Constructor constructor) { mConstructorName = constructor.getName(); Class[] parameterTypes = constructor.getParameterTypes(); for (Class parameterType : parameterTypes) { - IClass type = JavaClassManager.getInstance().getClassWrapper(parameterType); - mParameterTypes.add(type); + mParameterTypes.add(parameterType.getName()); } } public ConstructorDescription(String name, List paramTypes) { + mConstructorName = name; + mParameterTypes.addAll(paramTypes.stream().filter(c -> c != null).map(c -> c.getFullClassName()).collect(Collectors.toList())); + } + + public ConstructorDescription(String name, ArrayList paramTypes) { mConstructorName = name; mParameterTypes.addAll(paramTypes); } @@ -95,21 +100,21 @@ public class ConstructorDescription extends JavaSuggestItemImpl implements IJava return getSimpleName() + "()"; } - private String paramsToString(@NonNull ArrayList parameterTypes) { + private String paramsToString(@NonNull ArrayList parameterTypes) { StringBuilder result = new StringBuilder(); boolean firstTime = true; - for (IClass parameterType : parameterTypes) { + for (String parameterType : parameterTypes) { if (firstTime) { firstTime = false; } else { result.append(","); } - result.append(parameterType.getSimpleName()); + result.append(parameterType); } return result.toString(); } - public ArrayList getParameterTypes() { + public ArrayList getParameterTypes() { return mParameterTypes; } diff --git a/app/src/main/java/com/itsaky/androidide/language/java/parser/model/MethodDescription.java b/app/src/main/java/com/itsaky/androidide/language/java/parser/model/MethodDescription.java index 2080ca446..e7658e479 100644 --- a/app/src/main/java/com/itsaky/androidide/language/java/parser/model/MethodDescription.java +++ b/app/src/main/java/com/itsaky/androidide/language/java/parser/model/MethodDescription.java @@ -28,10 +28,10 @@ public class MethodDescription extends JavaSuggestItemImpl implements Member, Su private IClass mReturnType; public MethodDescription(String name, IClass returnType, - long modifiers, ArrayList parameterTypes) { + int modifiers, ArrayList parameterTypes) { mMethodName = name; mReturnType = returnType; - mModifiers = (int) modifiers; + mModifiers = modifiers; mParameterTypes = parameterTypes; } diff --git a/app/src/main/java/com/itsaky/androidide/language/java/provider/JavaCompletionProvider.java b/app/src/main/java/com/itsaky/androidide/language/java/provider/JavaCompletionProvider.java index 330ed5bd8..e52afeb8c 100644 --- a/app/src/main/java/com/itsaky/androidide/language/java/provider/JavaCompletionProvider.java +++ b/app/src/main/java/com/itsaky/androidide/language/java/provider/JavaCompletionProvider.java @@ -44,7 +44,7 @@ public class JavaCompletionProvider { this.mPackageManager = new PackageManager(); this.mJavaParser = new JavacParser(); - addAutoComplete(); + addMatchers(); } public boolean loadProject(AndroidProject project) { @@ -57,7 +57,7 @@ public class JavaCompletionProvider { return initiated; } - private void addAutoComplete() { + private void addMatchers() { mCompletors.add(new CompleteExpression(mClassLoader)); mCompletors.add(new CompleteNewKeyword(mClassLoader)); mCompletors.add(new CompletePackage(mPackageManager)); 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 cb8268950..2f5a19cd7 100644 --- a/app/src/main/java/com/itsaky/androidide/language/logs/LogLanguageImpl.java +++ b/app/src/main/java/com/itsaky/androidide/language/logs/LogLanguageImpl.java @@ -10,6 +10,7 @@ import io.github.rosemoe.editor.interfaces.NewlineHandler; import io.github.rosemoe.editor.struct.CompletionItem; import io.github.rosemoe.editor.text.TextAnalyzeResult; import io.github.rosemoe.editor.text.TextAnalyzer; +import io.github.rosemoe.editor.widget.CodeEditor; import io.github.rosemoe.editor.widget.SymbolPairMatch; import java.util.ArrayList; import java.util.List; @@ -90,7 +91,7 @@ public class LogLanguageImpl implements EditorLanguage { private static class LogCompletor implements AutoCompleteProvider { @Override - public List> getAutoCompleteItems(String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line) { + public List> getAutoCompleteItems(CodeEditor editor, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line) { return new ArrayList>(); } } diff --git a/app/src/main/java/com/itsaky/androidide/language/xml/XMLAutoComplete.java b/app/src/main/java/com/itsaky/androidide/language/xml/XMLAutoComplete.java index 541bda0fa..d8131c66b 100644 --- a/app/src/main/java/com/itsaky/androidide/language/xml/XMLAutoComplete.java +++ b/app/src/main/java/com/itsaky/androidide/language/xml/XMLAutoComplete.java @@ -18,8 +18,6 @@ import com.itsaky.androidide.utils.EitherOfResult; public class XMLAutoComplete implements AutoCompleteProvider { - private CodeEditor editor; - private Comparator RESULTS_SORTER = new Comparator(){ @Override @@ -27,13 +25,9 @@ public class XMLAutoComplete implements AutoCompleteProvider { return ((CompletionListItem) p1).getSortText().compareTo(((CompletionListItem) p2).getSortText()); } }; - - public XMLAutoComplete(CodeEditor editor) { - this.editor = editor; - } @Override - public List> getAutoCompleteItems(String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line) { + public List> getAutoCompleteItems(CodeEditor editor, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line) { // We do not store the completion service here in this class // This is because, if we modify the instance of the service in app, // it'll be updated here 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 ca753a41d..e4aad94d9 100644 --- a/app/src/main/java/com/itsaky/androidide/language/xml/XMLLanguage.java +++ b/app/src/main/java/com/itsaky/androidide/language/xml/XMLLanguage.java @@ -19,8 +19,8 @@ public class XMLLanguage implements EditorLanguage { private XMLAutoComplete completer; private NewlineHandler[] newlineHandlers = new NewlineHandler[] {}; - public XMLLanguage(CodeEditor editor) { - this.completer = new XMLAutoComplete(editor); + public XMLLanguage() { + this.completer = new XMLAutoComplete(); this.analyzer = new XMLAnalyzer(); this.newlineHandlers = new NewlineHandler[]{new IndentHandler()}; } diff --git a/editor/src/main/java/com/itsaky/androidide/models/CompletionEdit.java b/app/src/main/java/com/itsaky/androidide/models/CompletionEdit.java similarity index 100% rename from editor/src/main/java/com/itsaky/androidide/models/CompletionEdit.java rename to app/src/main/java/com/itsaky/androidide/models/CompletionEdit.java diff --git a/editor/src/main/java/com/itsaky/androidide/models/CompletionListItem.java b/app/src/main/java/com/itsaky/androidide/models/CompletionListItem.java similarity index 100% rename from editor/src/main/java/com/itsaky/androidide/models/CompletionListItem.java rename to app/src/main/java/com/itsaky/androidide/models/CompletionListItem.java diff --git a/app/src/main/java/com/itsaky/androidide/models/DiagnosticGroup.java b/app/src/main/java/com/itsaky/androidide/models/DiagnosticGroup.java new file mode 100644 index 000000000..89acdd8dc --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/models/DiagnosticGroup.java @@ -0,0 +1,18 @@ +package com.itsaky.androidide.models; +import com.itsaky.androidide.services.compiler.model.CompilerDiagnostic; +import java.io.File; +import java.util.List; + +public class DiagnosticGroup { + 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; + } +} diff --git a/app/src/main/java/com/itsaky/androidide/receivers/LogReceiver.java b/app/src/main/java/com/itsaky/androidide/receivers/LogReceiver.java index 4f8bbe6d9..134b1bdd7 100644 --- a/app/src/main/java/com/itsaky/androidide/receivers/LogReceiver.java +++ b/app/src/main/java/com/itsaky/androidide/receivers/LogReceiver.java @@ -4,9 +4,9 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import com.itsaky.androidide.models.LogLine; +import com.itsaky.androidide.utils.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; -import com.elvishew.xlog.XLog; public class LogReceiver extends BroadcastReceiver { @@ -37,7 +37,7 @@ public class LogReceiver extends BroadcastReceiver { Matcher m = LOG_HEAD_PATTERN.matcher(line); LogLine log = null; final boolean find = line != null && line.trim().length() > 2 /*if length() == 2 then only [] is available*/ && m.find(); - XLog.i("line -> " + line + "\nlogTag find() -> " + find); + Logger.instance().i("line -> " + line + "\nlogTag find() -> " + find); if(find) { String date = ""; String time = ""; @@ -84,9 +84,9 @@ public class LogReceiver extends BroadcastReceiver { log = new LogLine("", "", "", "", "", "", line); } - XLog.i("log = " + log + "\nlogListener = " + listener); + Logger.instance().i("log = " + log + "\nlogListener = " + listener); if(log != null && listener != null) { - XLog.i("listener.appendLogLine(...) log ->\n" + log); + Logger.instance().i("listener.appendLogLine(...) log ->\n" + log); listener.appendLogLine(log); logTag = null; } diff --git a/app/src/main/java/com/itsaky/androidide/services/IDEService.java b/app/src/main/java/com/itsaky/androidide/services/IDEService.java index 2d4fb802a..393cffd4c 100644 --- a/app/src/main/java/com/itsaky/androidide/services/IDEService.java +++ b/app/src/main/java/com/itsaky/androidide/services/IDEService.java @@ -207,6 +207,8 @@ public class IDEService extends Service implements ShellServer.Callback { @Override public void onDestroy() { super.onDestroy(); + + // Delete the tmp directory. This directory is not needed anymore... FileUtils.delete(Environment.TMP_DIR); isRunning = false; } diff --git a/app/src/main/java/com/itsaky/androidide/services/Jar2DexService.java b/app/src/main/java/com/itsaky/androidide/services/Jar2DexService.java index 1073cd147..7e6643bf1 100644 --- a/app/src/main/java/com/itsaky/androidide/services/Jar2DexService.java +++ b/app/src/main/java/com/itsaky/androidide/services/Jar2DexService.java @@ -12,6 +12,12 @@ import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; +/** + * A utility class to convert JAR files to DEX + * + * @deprecated This is replaced by the ClassFileReader. There is no need to dex the JAR files in order to load them. + */ +@Deprecated public class Jar2DexService implements ShellServer.Callback { private final List mClasspaths; diff --git a/app/src/main/java/com/itsaky/androidide/services/compiler/CompilerProgress.java b/app/src/main/java/com/itsaky/androidide/services/compiler/CompilerProgress.java new file mode 100644 index 000000000..99f9a8e92 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/services/compiler/CompilerProgress.java @@ -0,0 +1,33 @@ +package com.itsaky.androidide.services.compiler; + +import org.eclipse.jdt.core.compiler.CompilationProgress; + +public class CompilerProgress extends CompilationProgress { + + private boolean canceled = false; + + public void setCanceled(boolean canceled) { + this.canceled = canceled; + } + + @Override + public void begin(int p1) { + } + + @Override + public void done() { + } + + @Override + public boolean isCanceled() { + return canceled; + } + + @Override + public void setTaskName(String p1) { + } + + @Override + public void worked(int p1, int p2) { + } +} diff --git a/app/src/main/java/com/itsaky/androidide/services/compiler/CompilerRequestor.java b/app/src/main/java/com/itsaky/androidide/services/compiler/CompilerRequestor.java new file mode 100644 index 000000000..f644b19b0 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/services/compiler/CompilerRequestor.java @@ -0,0 +1,101 @@ +package com.itsaky.androidide.services.compiler; + +import java.io.File; +import java.nio.charset.Charset; +import java.util.Locale; +import javax.tools.Diagnostic; +import javax.tools.DiagnosticListener; +import javax.tools.JavaFileObject; +import org.eclipse.jdt.core.compiler.CategorizedProblem; +import org.eclipse.jdt.internal.compiler.CompilationResult; +import org.eclipse.jdt.internal.compiler.ICompilerRequestor; +import org.eclipse.jdt.internal.compiler.problem.DefaultProblem; +import org.eclipse.jdt.internal.compiler.tool.EclipseCompilerImpl; +import org.eclipse.jdt.internal.compiler.tool.EclipseFileManager; +import org.eclipse.jdt.internal.compiler.tool.EclipseFileObject; + +public class CompilerRequestor implements ICompilerRequestor { + + private DiagnosticListener listener; + + public CompilerRequestor(DiagnosticListener listener) { + this.listener = listener; + } + + @Override + public void acceptResult(CompilationResult p1) { + if (p1 != null && p1.hasProblems()) { + for (CategorizedProblem problem : p1.getAllProblems()) { + report(problem); + } + } + } + + private void report(final CategorizedProblem problem) { + this.listener.report(new Diagnostic() { + @Override + public String getCode() { + return null; + } + @Override + public long getColumnNumber() { + if (problem instanceof DefaultProblem) { + return ((DefaultProblem) problem).column; + } + return Diagnostic.NOPOS; + } + @Override + public long getEndPosition() { + if (problem instanceof DefaultProblem) { + return ((DefaultProblem) problem).getSourceEnd(); + } + return Diagnostic.NOPOS; + } + @Override + public Kind getKind() { + if (problem.isError()) { + return Diagnostic.Kind.ERROR; + } + if (problem.isWarning()) { + return Diagnostic.Kind.WARNING; + } else if (problem instanceof DefaultProblem && ((DefaultProblem) problem).isInfo()) { + return Diagnostic.Kind.NOTE; + } + return Diagnostic.Kind.OTHER; + } + @Override + public long getLineNumber() { + if (problem instanceof DefaultProblem) { + return ((DefaultProblem) problem).getSourceLineNumber(); + } + return Diagnostic.NOPOS; + } + @Override + public String getMessage(Locale locale) { + return problem.getMessage(); + } + @Override + public long getPosition() { + if (problem instanceof DefaultProblem) { + return ((DefaultProblem) problem).getSourceStart(); + } + return Diagnostic.NOPOS; + } + @Override + public JavaFileObject getSource() { + if (problem instanceof DefaultProblem) { + File f = new File(new String(problem.getOriginatingFileName())); + if (f.exists()) { + return new EclipseFileObject(null, f.toURI(), JavaFileObject.Kind.SOURCE, null); + } + return null; + } + return null; + } + @Override + public long getStartPosition() { + return getPosition(); + } + }); + } +} diff --git a/app/src/main/java/com/itsaky/androidide/services/compiler/CompilerWrapper.java b/app/src/main/java/com/itsaky/androidide/services/compiler/CompilerWrapper.java new file mode 100644 index 000000000..fcdf35c50 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/services/compiler/CompilerWrapper.java @@ -0,0 +1,138 @@ +package com.itsaky.androidide.services.compiler; + +import java.io.PrintWriter; +import org.eclipse.jdt.core.compiler.CompilationProgress; +import org.eclipse.jdt.internal.compiler.CompilationResult; +import org.eclipse.jdt.internal.compiler.Compiler; +import org.eclipse.jdt.internal.compiler.ICompilerRequestor; +import org.eclipse.jdt.internal.compiler.IErrorHandlingPolicy; +import org.eclipse.jdt.internal.compiler.IProblemFactory; +import org.eclipse.jdt.internal.compiler.ReadManager; +import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; +import org.eclipse.jdt.internal.compiler.ast.ImportReference; +import org.eclipse.jdt.internal.compiler.env.ICompilationUnit; +import org.eclipse.jdt.internal.compiler.env.INameEnvironment; +import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; +import org.eclipse.jdt.internal.compiler.lookup.SourceTypeCollisionException; +import org.eclipse.jdt.internal.compiler.problem.AbortCompilation; +import org.eclipse.jdt.internal.compiler.util.Messages; + +public class CompilerWrapper extends Compiler { + + public CompilerWrapper(INameEnvironment env, + IErrorHandlingPolicy errorHandlingPolicy, + CompilerOptions options, + ICompilerRequestor compilerRequestor, + IProblemFactory problemFactory, + PrintWriter out, + CompilationProgress progress) { + super(env, errorHandlingPolicy, options, compilerRequestor, problemFactory, out, progress); + this.useSingleThread = true; + } + + public CompilerWrapper setProgress(CompilationProgress progress) { + this.progress = progress; + return this; + } + + @Override + public void compile(ICompilationUnit[] sourceUnits) { + compileInternal(sourceUnits, false); + } + + private void compileInternal(ICompilationUnit[] sourceUnits, boolean lastRound) { + this.stats.startTime = System.currentTimeMillis(); + try { + reportProgress(Messages.compilation_beginningToCompile); + if (this.annotationProcessorManager == null) { + beginToCompile(sourceUnits); + } else { + ICompilationUnit[] originalUnits = sourceUnits.clone(); // remember source units in case a source type collision occurs + try { + beginToCompile(sourceUnits); + if (!lastRound) processAnnotations(); + if (!this.options.generateClassFiles) return; + } catch (SourceTypeCollisionException e) { + backupAptProblems(); + reset(); + int originalLength = originalUnits.length; + int newProcessedLength = e.newAnnotationProcessorUnits.length; + ICompilationUnit[] combinedUnits = new ICompilationUnit[originalLength + newProcessedLength]; + System.arraycopy(originalUnits, 0, combinedUnits, 0, originalLength); + System.arraycopy(e.newAnnotationProcessorUnits, 0, combinedUnits, originalLength, newProcessedLength); + this.annotationProcessorStartIndex = originalLength; + compileInternal(combinedUnits, e.isLastRound); + return; + } + } + restoreAptProblems(); + processCompiledUnits(0, lastRound); + } catch (AbortCompilation e) { + this.handleInternalException(e, null); + } catch (Throwable th) { + AbortCompilation a = new AbortCompilation(); + a.isSilent = true; + handleInternalException(a, null); + } + if (this.options.verbose) { + if (this.totalUnits > 1) { + this.out.println(Messages.bind(Messages.compilation_units, String.valueOf(this.totalUnits))); + } else { + this.out.println(Messages.bind(Messages.compilation_unit, String.valueOf(this.totalUnits))); + } + } + } + + @Override + protected void internalBeginToCompile(ICompilationUnit[] sourceUnits, int maxUnits) { + if (!this.useSingleThread && maxUnits >= ReadManager.THRESHOLD) + this.parser.readManager = new ReadManager(sourceUnits, maxUnits); + + for (int i = 0; i < maxUnits; i++) { + CompilationResult unitResult = null; + try { + if (this.options.verbose) { + this.out.println( + Messages.bind(Messages.compilation_request, + new String[] { + String.valueOf(i + 1), + String.valueOf(maxUnits), + new String(sourceUnits[i].getFileName()) + })); + } + CompilationUnitDeclaration parsedUnit; + unitResult = new CompilationResult(sourceUnits[i], i, maxUnits, this.options.maxProblemsPerUnit); + long parseStart = System.currentTimeMillis(); + if (this.totalUnits < this.parseThreshold) { + parsedUnit = this.parser.parse(sourceUnits[i], unitResult); + } else { + parsedUnit = this.parser.dietParse(sourceUnits[i], unitResult); + } + long resolveStart = System.currentTimeMillis(); + this.stats.parseTime += resolveStart - parseStart; + this.lookupEnvironment.buildTypeBindings(parsedUnit, null); + this.stats.resolveTime += System.currentTimeMillis() - resolveStart; + addCompilationUnit(sourceUnits[i], parsedUnit); + ImportReference currentPackage = parsedUnit.currentPackage; + if (currentPackage != null) { + unitResult.recordPackageName(currentPackage.tokens); + } + if (this.parser.readManager != null) { + this.parser.readManager.shutdown(); + this.parser.readManager = null; + } + this.lookupEnvironment.completeTypeBindings(); + } catch (AbortCompilation a) { + if (a.compilationResult == null) + a.compilationResult = unitResult; + throw a; + } catch (Throwable th) { + AbortCompilation a = new AbortCompilation(); + a.isSilent = true; + throw a; + } finally { + sourceUnits[i] = null; + } + } + } +} diff --git a/app/src/main/java/com/itsaky/androidide/services/compiler/JavaCompilerService.java b/app/src/main/java/com/itsaky/androidide/services/compiler/JavaCompilerService.java index 1a965dbb7..e4318d42a 100644 --- a/app/src/main/java/com/itsaky/androidide/services/compiler/JavaCompilerService.java +++ b/app/src/main/java/com/itsaky/androidide/services/compiler/JavaCompilerService.java @@ -2,13 +2,18 @@ package com.itsaky.androidide.services.compiler; import android.text.TextUtils; import com.blankj.utilcode.util.FileUtils; +import com.blankj.utilcode.util.ThreadUtils; import com.itsaky.androidide.interfaces.CompileListener; import com.itsaky.androidide.models.AndroidProject; import com.itsaky.androidide.services.compiler.model.CompilerDiagnostic; import com.itsaky.androidide.services.compiler.model.CompilerDiagnosticImpl; +import com.itsaky.androidide.services.compiler.request.CompilationRequest; +import com.itsaky.androidide.tools.SourceJavaFileObject; import com.itsaky.androidide.utils.Environment; +import com.itsaky.androidide.utils.Logger; import java.io.File; import java.io.FileFilter; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -18,20 +23,41 @@ import javax.tools.Diagnostic; import javax.tools.DiagnosticListener; import javax.tools.JavaCompiler; import javax.tools.JavaFileObject; +import org.eclipse.jdt.core.compiler.CategorizedProblem; +import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies; +import org.eclipse.jdt.internal.compiler.ICompilerRequestor; +import org.eclipse.jdt.internal.compiler.IErrorHandlingPolicy; +import org.eclipse.jdt.internal.compiler.IProblemFactory; +import org.eclipse.jdt.internal.compiler.batch.CompilationUnit; +import org.eclipse.jdt.internal.compiler.batch.FileSystem; +import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; +import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory; +import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities; import org.eclipse.jdt.internal.compiler.tool.EclipseCompiler; import org.eclipse.jdt.internal.compiler.tool.EclipseFileManager; -import com.elvishew.xlog.XLog; +import org.eclipse.jdt.internal.compiler.tool.EclipseFileObject; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; public class JavaCompilerService implements DiagnosticListener { - private AndroidProject project; - - private CompileListener listener; - private EclipseCompiler compiler; - private EclipseFileManager fileManager; - - private List options; - private final Map> diagnostics = new HashMap<>(); + private AndroidProject mProject; + + private EclipseCompiler mCompiler; + private CompilerWrapper mSingleFileCompiler; + private EclipseFileManager mFileManager; + + private FileSystem sf_nameEnvironment; + private CompilerOptions sf_options; + private IProblemFactory sf_problemFactory; + private ICompilerRequestor sf_compilerRequest; + private CompilerProgress sf_progress; + private IErrorHandlingPolicy sf_policy; + private PrintWriter sf_outWriter; + + private List mOptions; + private List mSources; + + private static final Map> mDiagnostics = new HashMap<>(); private FileFilter JAVA_FILTER = new FileFilter(){ @@ -42,90 +68,344 @@ public class JavaCompilerService implements DiagnosticListener { }; public JavaCompilerService(AndroidProject project) { - this.project = project; - - getCompiler(); + this.mProject = project; + this.createOptions(); } - - public JavaCompilerService setListener(CompileListener listener) { - this.listener = listener; - return this; + + private void createOptions() { + this.mOptions = new ArrayList<>(); + + mOptions.add("-1.8"); + mOptions.add("-d"); + mOptions.add(Environment.TMP_DIR.getAbsolutePath()); + mOptions.add("-proc:none"); + mOptions.add("-warn:all"); + mOptions.add("-warn:-nls"); + mOptions.add("-g"); + mOptions.add("-bootclasspath"); + mOptions.add(Environment.BOOTCLASSPATH.getAbsolutePath()); + mOptions.add("-cp"); + mOptions.add(TextUtils.join(File.pathSeparator, mProject.getClassPaths())); + } + + private EclipseCompiler getCompiler() { + return getCompiler(false); } - - private EclipseCompiler getCompiler() { - if(compiler == null) { - this.compiler = new EclipseCompiler(); - this.fileManager = (EclipseFileManager) compiler.getStandardFileManager(this, Locale.US, null); - this.options = new ArrayList<>(); - - options.add("-1.8"); - options.add("-d"); - options.add("none"); - options.add("-proc:none"); - options.add("-warn:all"); - options.add("-g"); - options.add("-bootclasspath"); - options.add(Environment.BOOTCLASSPATH.getAbsolutePath()); - options.add("-cp"); - options.add(TextUtils.join(File.pathSeparator, project.getClassPaths())); - } - - return compiler; + + private EclipseCompiler getCompiler(boolean forceCreate) { + if(mCompiler == null || forceCreate) { + this.mCompiler = new EclipseCompiler(); + this.mFileManager = (EclipseFileManager) mCompiler.getStandardFileManager(this, Locale.US, null); + } + return mCompiler; } - - public boolean compileSingle(File file) { - List ops = new ArrayList(options); - ops.add("-sourcepath"); - ops.add(TextUtils.join(File.pathSeparator, project.getSourcePaths())); - - JavaCompiler.CompilationTask task = getCompiler().getTask(null, fileManager, this, ops, null, fileManager.getJavaFileObjects(file)); - diagnostics.clear(); - boolean success = task.call(); - if(this.listener != null) { - this.listener.onCompilationResult(success, diagnostics); - } + + private EclipseFileManager getFileManager() { + if(mFileManager == null) + getCompiler(true); + + return mFileManager; + } + + private CompilerWrapper getSingleFileCompiler() { + if(this.sf_nameEnvironment == null) + this.sf_nameEnvironment = new FileSystem(createClasspaths(mProject.getClassPaths()), null, null); + + if(this.sf_compilerRequest == null) + this.sf_compilerRequest = new CompilerRequestor(this); + + if(this.sf_problemFactory == null) + this.sf_problemFactory = getProblemFactory(); + + if(this.sf_options == null) { + this.sf_options = new CompilerOptions(); + putOptions(sf_options); + } + + if(sf_policy == null) + this.sf_policy = DefaultErrorHandlingPolicies.exitAfterAllProblems(); + + if(sf_outWriter == null) + sf_outWriter = new PrintWriter(System.out); + + if(mSingleFileCompiler == null) { + mSingleFileCompiler = new CompilerWrapper( + sf_nameEnvironment, + sf_policy, + sf_options, + sf_compilerRequest, + sf_problemFactory, + sf_outWriter, + sf_progress = new CompilerProgress() + ); + } + return mSingleFileCompiler; + } + + private void putOptions(CompilerOptions options) { + final long languageLevel = ClassFileConstants.JDK1_8; + options.sourceLevel = options.targetJDK = options.complianceLevel = languageLevel; + options.processAnnotations = false; + Map map = options.getMap(); + for(String warn : CompilerOptions.warningTokens) { + map.put(warn, CompilerOptions.ENABLED); + } + map.put(CompilerOptions.OPTION_ReportNonExternalizedStringLiteral, CompilerOptions.DISABLED); + map.put(CompilerOptions.OPTION_LocalVariableAttribute, CompilerOptions.DO_NOT_GENERATE); + map.put(CompilerOptions.OPTION_LineNumberAttribute, CompilerOptions.DO_NOT_GENERATE); + map.put(CompilerOptions.OPTION_SourceFileAttribute, CompilerOptions.DO_NOT_GENERATE); + options.set(map); + } + + private String[] createClasspaths(List classpaths) { + if (classpaths == null) { + classpaths = new ArrayList<>(); + } + + if(!classpaths.contains(Environment.BOOTCLASSPATH.getAbsolutePath())) { + classpaths.add(Environment.BOOTCLASSPATH.getAbsolutePath()); + } + + String[] result = new String[classpaths.size()]; + return classpaths.toArray(result); + } + + private CompilationRequest createCompilationRequestForAll(CompileListener listener) { + if(mSources == null || mSources.size() <= 0) { + loadSources(); + } + return CompilationRequest.Builder + .newInstance() + .withCallback(listener) + .withFiles(mSources) + .build(); + } + + private void notifyCompiled(CompileListener listener, Map> diags) { + ThreadUtils.runOnUiThread(() -> { + listener.onCompilationResult(true, diags); + }); + } + + protected void cancelCompilation() { + getCompiler().setCompilationCancelled(true); + if(sf_progress != null) { + sf_progress.setCanceled(true); + } + } + + public List getClasspaths() { + return mProject.getClassPaths(); + } + + public void loadSources() { + mSources = new ArrayList(); + for(String path : mProject.getSourcePaths()) { + if(path == null || path.trim().length() <= 0) continue; + File p = new File(path); + if(!p.exists() || !p.isDirectory()) continue; + mSources.addAll(FileUtils.listFilesInDirWithFilter(p, JAVA_FILTER, true)); + } + } + + public void compileAllAsync(CompileListener listener) { + new Thread(() -> { + compile(createCompilationRequestForAll(listener)); + }).start(); + } + + public void compileAsync(CompilationRequest request) { + new Thread(() -> { + compile(request); + }).start(); + } + + public void compileSingleAsync(SourceJavaFileObject source, CompileListener listener) { + new Thread(() -> { + compileSingle(source, listener); + }).start(); + } + + public boolean compile(final CompilationRequest request) { + this.mDiagnostics.clear(); + final List fileObjects = (ArrayList) getFileManager().getJavaFileObjectsFromFiles(request.files()); + JavaCompiler.CompilationTask task = getCompiler() + .setCompilationCancelled() + .newProgress() + .getTask(null, getFileManager(), this, mOptions, null, fileObjects); + boolean success = task.call(); + notifyCompiled(request.callback(), mDiagnostics); return success; - } - - public boolean compileAll() { - List files = new ArrayList(); - for(String path : project.getSourcePaths()) { - if(path == null || path.trim().length() <= 0) continue; - File p = new File(path); - if(!p.exists() || !p.isDirectory()) continue; - files.addAll(FileUtils.listFilesInDirWithFilter(p, JAVA_FILTER, true)); - } - return compile(files); - } - - public boolean compile(List files) { - JavaCompiler.CompilationTask task = getCompiler().getTask(null, fileManager, this, options, null, fileManager.getJavaFileObjectsFromFiles(files)); - diagnostics.clear(); - final long s = System.nanoTime(); - boolean success = task.call(); - XLog.i("[JavaCompiler]" - + "\ncompiled: " + files.size() + " files" - + "\ntimeElapsed: " + (System.nanoTime() - s) + "ns"); - return success; - } - - public Map> getDiagnostics() { - return diagnostics; - } + } + + public void compileSingle(final SourceJavaFileObject source, final CompileListener listener) { + mDiagnostics.clear(); + cancelCompilation(); + if(mDiagnostics != null) mDiagnostics.clear(); + getSingleFileCompiler() + .setProgress(this.sf_progress = new CompilerProgress()) + .compile(new CompilationUnit[]{source.asUnit()}); + notifyCompiled(listener, mDiagnostics); + } @Override public void report(Diagnostic diagnostic) { CompilerDiagnostic d = CompilerDiagnosticImpl.fromDiagnostic(diagnostic); - if(diagnostics.containsKey(d.file())) { - List diags = diagnostics.get(d.file()); + if(mDiagnostics.containsKey(d.file())) { + List diags = mDiagnostics.get(d.file()); if(diags == null) diags = new ArrayList<>(); diags.add(d); - diagnostics.put(d.file(), diags); + mDiagnostics.put(d.file(), diags); } else { List diags = new ArrayList<>(); diags.add(d); - diagnostics.put(d.file(), diags); + mDiagnostics.put(d.file(), diags); } } + + private IProblemFactory getProblemFactory() { + return new DefaultProblemFactory() { + @Override + public CategorizedProblem createProblem( + final char[] originatingFileName, + final int problemId, + final String[] problemArguments, + final String[] messageArguments, + final int severity, + final int startPosition, + final int endPosition, + final int lineNumber, + final int columnNumber) { + + report(new Diagnostic() { + @Override + public String getCode() { + return Integer.toString(problemId); + } + @Override + public long getColumnNumber() { + return columnNumber; + } + @Override + public long getEndPosition() { + return endPosition; + } + @Override + public Kind getKind() { + if ((severity & ProblemSeverities.Error) != 0) { + return Diagnostic.Kind.ERROR; + } + if ((severity & ProblemSeverities.Optional) != 0) { + return Diagnostic.Kind.WARNING; + } + if ((severity & ProblemSeverities.Warning) != 0) { + return Diagnostic.Kind.MANDATORY_WARNING; + } + return Diagnostic.Kind.OTHER; + } + @Override + public long getLineNumber() { + return lineNumber; + } + @Override + public String getMessage(Locale locale) { + if (locale != null) { + setLocale(locale); + } + return getLocalizedMessage(problemId, problemArguments); + } + @Override + public long getPosition() { + return startPosition; + } + @Override + public JavaFileObject getSource() { + File f = new File(new String(originatingFileName)); + if (f.exists()) { + return new EclipseFileObject(null, f.toURI(), JavaFileObject.Kind.SOURCE, null); + } + return null; + } + @Override + public long getStartPosition() { + return startPosition; + } + }); + return super.createProblem(originatingFileName, problemId, problemArguments, messageArguments, severity, startPosition, endPosition, lineNumber, columnNumber); + } + @Override + public CategorizedProblem createProblem( + final char[] originatingFileName, + final int problemId, + final String[] problemArguments, + final int elaborationID, + final String[] messageArguments, + final int severity, + final int startPosition, + final int endPosition, + final int lineNumber, + final int columnNumber) { + + report(new Diagnostic() { + @Override + public String getCode() { + return Integer.toString(problemId); + } + @Override + public long getColumnNumber() { + return columnNumber; + } + @Override + public long getEndPosition() { + return endPosition; + } + @Override + public Kind getKind() { + if ((severity & ProblemSeverities.Error) != 0) { + return Diagnostic.Kind.ERROR; + } + if ((severity & ProblemSeverities.Info) != 0) { + return Diagnostic.Kind.NOTE; + } + if ((severity & ProblemSeverities.Optional) != 0) { + return Diagnostic.Kind.WARNING; + } + if ((severity & ProblemSeverities.Warning) != 0) { + return Diagnostic.Kind.MANDATORY_WARNING; + } + return Diagnostic.Kind.OTHER; + } + @Override + public long getLineNumber() { + return lineNumber; + } + @Override + public String getMessage(Locale locale) { + if (locale != null) { + setLocale(locale); + } + return getLocalizedMessage(problemId, problemArguments); + } + @Override + public long getPosition() { + return startPosition; + } + @Override + public JavaFileObject getSource() { + File f = new File(new String(originatingFileName)); + if (f.exists()) { + return new EclipseFileObject(null, f.toURI(), JavaFileObject.Kind.SOURCE, null); + } + return null; + } + @Override + public long getStartPosition() { + return startPosition; + } + }); + return super.createProblem(originatingFileName, problemId, problemArguments, elaborationID, messageArguments, severity, startPosition, endPosition, lineNumber, columnNumber); + } + }; + } } diff --git a/app/src/main/java/com/itsaky/androidide/services/compiler/model/CompilerDiagnostic.java b/app/src/main/java/com/itsaky/androidide/services/compiler/model/CompilerDiagnostic.java index 1c7437096..5c3ae0004 100644 --- a/app/src/main/java/com/itsaky/androidide/services/compiler/model/CompilerDiagnostic.java +++ b/app/src/main/java/com/itsaky/androidide/services/compiler/model/CompilerDiagnostic.java @@ -4,11 +4,13 @@ import java.io.File; import javax.tools.Diagnostic; public interface CompilerDiagnostic { - File file(); - long line(); - long column(); - long start(); - long end(); - String message(); - Diagnostic.Kind kind(); + File file(); + long line(); + long column(); + long start(); + long end(); + String message(); + Diagnostic.Kind kind(); + + int color(); } diff --git a/app/src/main/java/com/itsaky/androidide/services/compiler/model/CompilerDiagnosticImpl.java b/app/src/main/java/com/itsaky/androidide/services/compiler/model/CompilerDiagnosticImpl.java index e4f18610d..5ad0c28b1 100644 --- a/app/src/main/java/com/itsaky/androidide/services/compiler/model/CompilerDiagnosticImpl.java +++ b/app/src/main/java/com/itsaky/androidide/services/compiler/model/CompilerDiagnosticImpl.java @@ -15,16 +15,20 @@ public class CompilerDiagnosticImpl implements CompilerDiagnostic { protected long end = 0; protected Diagnostic.Kind kind; + + public static final int COLOR_ERROR = 0xfff44336; + public static final int COLOR_WARNING = 0xffffb300; + public static final int COLOR_NOTE = 0xffffffff; private CompilerDiagnosticImpl(){} private CompilerDiagnosticImpl(Diagnostic diagnostic) { this.file = new File(diagnostic.getSource().toUri()); this.message = diagnostic.getMessage(Locale.US); - this.line = diagnostic.getLineNumber(); + this.line = diagnostic.getLineNumber() - 1; this.column = diagnostic.getColumnNumber(); this.start = diagnostic.getStartPosition(); - this.end = diagnostic.getEndPosition(); + this.end = diagnostic.getEndPosition() + 1; this.kind = diagnostic.getKind(); } @@ -80,9 +84,34 @@ public class CompilerDiagnosticImpl implements CompilerDiagnostic { public String message() { return message; } + + @Override + public int color() { + if(kind() == Diagnostic.Kind.ERROR) { + return COLOR_ERROR; + } else if(kind() == Diagnostic.Kind.WARNING || kind() == Diagnostic.Kind.MANDATORY_WARNING) { + return COLOR_WARNING; + } + return COLOR_NOTE; + } @Override public String toString() { - return "CompilerDiagnosticImpl(file=" + file() + ", line=" + line() + ", column=" + column() + ", start=" + start() + ", end=" + end() + ", message=" + message() + ")"; + return "CompilerDiagnosticImpl(file=" + file() + ", line=" + line() + ", column=" + column() + ", start=" + start() + ", end=" + end() + ", message=" + message() + ", kind=" + kind().toString() + ")"; } + + @Override + public boolean equals(Object obj) { + if(obj instanceof CompilerDiagnosticImpl) { + CompilerDiagnosticImpl that = (CompilerDiagnosticImpl) obj; + return this.file().equals(that.file()) && + this.line() == that.line() && + this.column() == that.column() && + this.start() == that.start() && + this.end() == that.end() && + this.message().equals(that.message()) && + this.kind() == that.kind(); + } + return false; + } } diff --git a/app/src/main/java/com/itsaky/androidide/services/compiler/request/CompilationRequest.java b/app/src/main/java/com/itsaky/androidide/services/compiler/request/CompilationRequest.java new file mode 100644 index 000000000..8d6ccb737 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/services/compiler/request/CompilationRequest.java @@ -0,0 +1,71 @@ +package com.itsaky.androidide.services.compiler.request; + +import com.itsaky.androidide.interfaces.CompileListener; +import com.itsaky.androidide.services.compiler.model.CompilerDiagnostic; +import com.itsaky.androidide.tools.SourceJavaFileObject; +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public interface CompilationRequest { + + List files(); + CompileListener callback(); + + public static class Builder { + + private List files; + private CompileListener callback; + + private Builder(){ + files = new ArrayList<>(); + } + + public static Builder newInstance() { + return new Builder(); + } + + public Builder withFiles(File... files) { + if(files == null) { + this.files = new ArrayList<>(); + return this; + } + this.files = new ArrayList<>(); + for(File file : files) { + this.files.add(file); + } + return this; + } + + public Builder withFiles(List files) { + if(files == null) { + this.files = new ArrayList<>(); + return this; + } + this.files = files; + return this; + } + + public Builder withCallback(CompileListener callback) { + this.callback = callback; + return this; + } + + public CompilationRequest build() { + return new CompilationRequest(){ + + @Override + public List files() { + return files; + } + + @Override + public CompileListener callback() { + return callback; + } + }; + } + } +} diff --git a/app/src/main/java/com/itsaky/androidide/shell/ShellServer.java b/app/src/main/java/com/itsaky/androidide/shell/ShellServer.java index 828f99a08..9d7ee42ab 100644 --- a/app/src/main/java/com/itsaky/androidide/shell/ShellServer.java +++ b/app/src/main/java/com/itsaky/androidide/shell/ShellServer.java @@ -2,7 +2,6 @@ package com.itsaky.androidide.shell; import com.blankj.utilcode.util.CloseUtils; import com.blankj.utilcode.util.ThrowableUtils; -import com.elvishew.xlog.XLog; import com.itsaky.androidide.utils.Environment; import java.io.BufferedReader; import java.io.Closeable; @@ -38,25 +37,30 @@ public class ShellServer extends Thread { stringBuilder.append("\nexport PATH=$PATH:" + Environment.path(Environment.ANDROID_HOME) + "/cmdline-tools/latest/bin"); stringBuilder.append("\nexport PATH=$PATH:" + Environment.path(Environment.ANDROID_HOME) + "/cmake/bin"); + // Provide paths to .so files needed to execute JDK final String lib = Environment.LIBDIR.getAbsolutePath(); stringBuilder.append("\nexport LD_LIBRARY_PATH=" + lib); stringBuilder.append(System.getenv().containsKey("LD_LIBRARY_PATH") ? ":" + System.getenv().get("LD_LIBRARY_PATH") : ""); if(android.os.Build.VERSION.SDK_INT >= 30) { + // Hook calls to malloc() in native binaries + // Required only in Android 11 and up stringBuilder.append("\nexport LD_PRELOAD=" + Environment.LIBHOOKSO.getAbsolutePath()); } + // Monotonic clock fix, needed for JDK stringBuilder.append("\nls -l /system/lib64/libc.so && " + "ln -sf /apex/com.android.runtime/lib64/bionic/libc.so " + lib + "/librt.so && " + "ln -sf /apex/com.android.runtime/lib64/bionic/libc.so " + lib + "/libpthread.so"); - + + // Make sure there are proper permissions set to the $HOME directory stringBuilder.append("\nchmod -R 777 $HOME"); - stringBuilder.append("\njava -XshowSettings:all > /sdcard/androidide_jdk_settings.txt"); + + // Execute all commands... append(stringBuilder.toString(), false); } catch (Throwable th) { if (callback != null) { String out = ThrowableUtils.getFullStackTrace(th).concat("\n"); - XLog.e("[ShellServer] error setting up server ->\n" + out); callback.output(out); } } diff --git a/app/src/main/java/com/itsaky/androidide/tools/SourceJavaFileObject.java b/app/src/main/java/com/itsaky/androidide/tools/SourceJavaFileObject.java new file mode 100644 index 000000000..c6ff217a4 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/tools/SourceJavaFileObject.java @@ -0,0 +1,39 @@ +package com.itsaky.androidide.tools; + +import java.io.File; +import java.net.URI; +import javax.tools.SimpleJavaFileObject; +import org.eclipse.jdt.internal.compiler.batch.CompilationUnit; + +public class SourceJavaFileObject extends SimpleJavaFileObject { + + private File file; + private String source; + + public SourceJavaFileObject(File file, String source) { + super(URI.create("source"), Kind.SOURCE); + this.file = file; + this.source = source; + } + + public File getFile() { + return this.file; + } + + @Override + public String getName() { + return file.getAbsolutePath(); + } + + @Override + public CharSequence getCharContent(boolean p1) { + return source; + } + + public CompilationUnit asUnit() { + return new CompilationUnit( + getCharContent(true).toString().toCharArray(), + getFile().getAbsolutePath(), + "UTF-8"); + } +} diff --git a/editor/src/main/java/com/itsaky/androidide/utils/Either.java b/app/src/main/java/com/itsaky/androidide/utils/Either.java similarity index 100% rename from editor/src/main/java/com/itsaky/androidide/utils/Either.java rename to app/src/main/java/com/itsaky/androidide/utils/Either.java diff --git a/app/src/main/java/com/itsaky/androidide/utils/Logger.java b/app/src/main/java/com/itsaky/androidide/utils/Logger.java new file mode 100644 index 000000000..0520f9286 --- /dev/null +++ b/app/src/main/java/com/itsaky/androidide/utils/Logger.java @@ -0,0 +1,119 @@ +package com.itsaky.androidide.utils; + +import com.elvishew.xlog.LogConfiguration; +import com.elvishew.xlog.LogLevel; +import com.elvishew.xlog.XLog; +import com.elvishew.xlog.printer.Printer; +import com.elvishew.xlog.printer.file.FilePrinter; +import com.elvishew.xlog.printer.file.naming.LevelFileNameGenerator; + +public class Logger { + + private static Logger instance; + private static com.elvishew.xlog.Logger xLogger; + private static String TAG = "AndroidIDE"; + + public static Logger instance() { + return instance == null ? createInstance(TAG) : instance; + } + + public static Logger instance(String tag) { + return instance == null ? createInstance(tag) : instance; + } + + private Logger(String tag) { + this.TAG = tag; + } + + private static Logger createInstance(String tag) { + LogConfiguration config = new LogConfiguration.Builder() + .logLevel(com.itsaky.androidide.BuildConfig.DEBUG ? LogLevel.ALL : LogLevel.NONE) + .tag("AndroidIDE") + .enableThreadInfo() + .enableStackTrace(10) + .disableBorder() + .build(); + Printer filePrinter = new FilePrinter + .Builder(FileUtil.getExternalStorageDir() + "/ide_xlog") + .fileNameGenerator(new LevelFileNameGenerator()) + .cleanStrategy(f -> false) + .build(); + XLog.init(config, filePrinter); + xLogger = XLog.tag("AndroidIDE").build(); + + instance = new Logger(tag); + return instance; + } + + public Logger d(Object... messages) { + StringBuilder sb = new StringBuilder(); + sb.append("["); + sb.append(TAG); + sb.append("]"); + sb.append("\n"); + if(messages == null) { + xLogger.d("null"); + return this; + } + for(Object msg : messages) { + sb.append(msg); + sb.append("\n"); + } + xLogger.d(sb.toString()); + return this; + } + + public Logger e(Object... messages) { + StringBuilder sb = new StringBuilder(); + sb.append("["); + sb.append(TAG); + sb.append("]"); + sb.append("\n"); + if(messages == null) { + xLogger.e("null"); + return this; + } + for(Object msg : messages) { + sb.append(msg); + sb.append("\n"); + } + xLogger.e(sb.toString()); + return this; + } + + public Logger v(Object... messages) { + StringBuilder sb = new StringBuilder(); + sb.append("["); + sb.append(TAG); + sb.append("]"); + sb.append("\n"); + if(messages == null) { + xLogger.v("null"); + return this; + } + for(Object msg : messages) { + sb.append(msg); + sb.append("\n"); + } + xLogger.v(sb.toString()); + return this; + } + + public Logger i(Object... messages) { + StringBuilder sb = new StringBuilder(); + sb.append("["); + sb.append(TAG); + sb.append("]"); + sb.append("\n"); + if(messages == null) { + xLogger.i("null"); + return this; + } + for(Object msg : messages) { + sb.append(msg); + sb.append("\n"); + } + xLogger.i(sb.toString()); + return this; + } +} diff --git a/editor/src/main/java/io/github/rosemoe/editor/annotations/Experimental.java b/app/src/main/java/io/github/rosemoe/editor/annotations/Experimental.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/annotations/Experimental.java rename to app/src/main/java/io/github/rosemoe/editor/annotations/Experimental.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/interfaces/AutoCompleteProvider.java b/app/src/main/java/io/github/rosemoe/editor/interfaces/AutoCompleteProvider.java similarity index 90% rename from editor/src/main/java/io/github/rosemoe/editor/interfaces/AutoCompleteProvider.java rename to app/src/main/java/io/github/rosemoe/editor/interfaces/AutoCompleteProvider.java index 784ba9db0..88ba413d2 100644 --- a/editor/src/main/java/io/github/rosemoe/editor/interfaces/AutoCompleteProvider.java +++ b/app/src/main/java/io/github/rosemoe/editor/interfaces/AutoCompleteProvider.java @@ -19,6 +19,7 @@ import com.itsaky.androidide.language.java.parser.internal.SuggestItem; import com.itsaky.androidide.utils.Either; import io.github.rosemoe.editor.struct.CompletionItem; import io.github.rosemoe.editor.text.TextAnalyzeResult; +import io.github.rosemoe.editor.widget.CodeEditor; import java.util.List; /** @@ -37,7 +38,7 @@ public interface AutoCompleteProvider { * @param line The line of cursor * @return Analyzed items */ - List> getAutoCompleteItems(String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line); + List> getAutoCompleteItems(CodeEditor editor, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line); } diff --git a/editor/src/main/java/io/github/rosemoe/editor/interfaces/CodeAnalyzer.java b/app/src/main/java/io/github/rosemoe/editor/interfaces/CodeAnalyzer.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/interfaces/CodeAnalyzer.java rename to app/src/main/java/io/github/rosemoe/editor/interfaces/CodeAnalyzer.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/interfaces/EditorEventListener.java b/app/src/main/java/io/github/rosemoe/editor/interfaces/EditorEventListener.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/interfaces/EditorEventListener.java rename to app/src/main/java/io/github/rosemoe/editor/interfaces/EditorEventListener.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/interfaces/EditorLanguage.java b/app/src/main/java/io/github/rosemoe/editor/interfaces/EditorLanguage.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/interfaces/EditorLanguage.java rename to app/src/main/java/io/github/rosemoe/editor/interfaces/EditorLanguage.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/interfaces/NewlineHandler.java b/app/src/main/java/io/github/rosemoe/editor/interfaces/NewlineHandler.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/interfaces/NewlineHandler.java rename to app/src/main/java/io/github/rosemoe/editor/interfaces/NewlineHandler.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/langs/EmptyLanguage.java b/app/src/main/java/io/github/rosemoe/editor/langs/EmptyLanguage.java similarity index 94% rename from editor/src/main/java/io/github/rosemoe/editor/langs/EmptyLanguage.java rename to app/src/main/java/io/github/rosemoe/editor/langs/EmptyLanguage.java index 032d4cb98..0dfcee871 100644 --- a/editor/src/main/java/io/github/rosemoe/editor/langs/EmptyLanguage.java +++ b/app/src/main/java/io/github/rosemoe/editor/langs/EmptyLanguage.java @@ -24,6 +24,7 @@ import io.github.rosemoe.editor.interfaces.NewlineHandler; import io.github.rosemoe.editor.struct.CompletionItem; import io.github.rosemoe.editor.text.TextAnalyzeResult; import io.github.rosemoe.editor.text.TextAnalyzer.AnalyzeThread.Delegate; +import io.github.rosemoe.editor.widget.CodeEditor; import io.github.rosemoe.editor.widget.SymbolPairMatch; import java.util.ArrayList; import java.util.List; @@ -79,7 +80,7 @@ public class EmptyLanguage implements EditorLanguage { public static class EmptyAutoCompleteProvider implements AutoCompleteProvider { @Override - public List> getAutoCompleteItems(String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line) { + public List> getAutoCompleteItems(CodeEditor editor, String prefix, boolean isInCodeBlock, TextAnalyzeResult colors, int line) { return new ArrayList>(); } diff --git a/editor/src/main/java/io/github/rosemoe/editor/struct/BlockLine.java b/app/src/main/java/io/github/rosemoe/editor/struct/BlockLine.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/struct/BlockLine.java rename to app/src/main/java/io/github/rosemoe/editor/struct/BlockLine.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/struct/CompletionItem.java b/app/src/main/java/io/github/rosemoe/editor/struct/CompletionItem.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/struct/CompletionItem.java rename to app/src/main/java/io/github/rosemoe/editor/struct/CompletionItem.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/struct/ErrorOrWarning.java b/app/src/main/java/io/github/rosemoe/editor/struct/ErrorOrWarning.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/struct/ErrorOrWarning.java rename to app/src/main/java/io/github/rosemoe/editor/struct/ErrorOrWarning.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/struct/HexColor.java b/app/src/main/java/io/github/rosemoe/editor/struct/HexColor.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/struct/HexColor.java rename to app/src/main/java/io/github/rosemoe/editor/struct/HexColor.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/struct/NavigationItem.java b/app/src/main/java/io/github/rosemoe/editor/struct/NavigationItem.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/struct/NavigationItem.java rename to app/src/main/java/io/github/rosemoe/editor/struct/NavigationItem.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/struct/Span.java b/app/src/main/java/io/github/rosemoe/editor/struct/Span.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/struct/Span.java rename to app/src/main/java/io/github/rosemoe/editor/struct/Span.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/text/CachedIndexer.java b/app/src/main/java/io/github/rosemoe/editor/text/CachedIndexer.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/text/CachedIndexer.java rename to app/src/main/java/io/github/rosemoe/editor/text/CachedIndexer.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/text/CharPosition.java b/app/src/main/java/io/github/rosemoe/editor/text/CharPosition.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/text/CharPosition.java rename to app/src/main/java/io/github/rosemoe/editor/text/CharPosition.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/text/Content.java b/app/src/main/java/io/github/rosemoe/editor/text/Content.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/text/Content.java rename to app/src/main/java/io/github/rosemoe/editor/text/Content.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/text/ContentLine.java b/app/src/main/java/io/github/rosemoe/editor/text/ContentLine.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/text/ContentLine.java rename to app/src/main/java/io/github/rosemoe/editor/text/ContentLine.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/text/ContentListener.java b/app/src/main/java/io/github/rosemoe/editor/text/ContentListener.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/text/ContentListener.java rename to app/src/main/java/io/github/rosemoe/editor/text/ContentListener.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/text/Cursor.java b/app/src/main/java/io/github/rosemoe/editor/text/Cursor.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/text/Cursor.java rename to app/src/main/java/io/github/rosemoe/editor/text/Cursor.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/text/FontCache.java b/app/src/main/java/io/github/rosemoe/editor/text/FontCache.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/text/FontCache.java rename to app/src/main/java/io/github/rosemoe/editor/text/FontCache.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/text/FormatThread.java b/app/src/main/java/io/github/rosemoe/editor/text/FormatThread.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/text/FormatThread.java rename to app/src/main/java/io/github/rosemoe/editor/text/FormatThread.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/text/Indexer.java b/app/src/main/java/io/github/rosemoe/editor/text/Indexer.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/text/Indexer.java rename to app/src/main/java/io/github/rosemoe/editor/text/Indexer.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/text/LineNumberCalculator.java b/app/src/main/java/io/github/rosemoe/editor/text/LineNumberCalculator.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/text/LineNumberCalculator.java rename to app/src/main/java/io/github/rosemoe/editor/text/LineNumberCalculator.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/text/LineRemoveListener.java b/app/src/main/java/io/github/rosemoe/editor/text/LineRemoveListener.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/text/LineRemoveListener.java rename to app/src/main/java/io/github/rosemoe/editor/text/LineRemoveListener.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/text/NoCacheIndexer.java b/app/src/main/java/io/github/rosemoe/editor/text/NoCacheIndexer.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/text/NoCacheIndexer.java rename to app/src/main/java/io/github/rosemoe/editor/text/NoCacheIndexer.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/text/ObjectAllocator.java b/app/src/main/java/io/github/rosemoe/editor/text/ObjectAllocator.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/text/ObjectAllocator.java rename to app/src/main/java/io/github/rosemoe/editor/text/ObjectAllocator.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/text/SpanMapUpdater.java b/app/src/main/java/io/github/rosemoe/editor/text/SpanMapUpdater.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/text/SpanMapUpdater.java rename to app/src/main/java/io/github/rosemoe/editor/text/SpanMapUpdater.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/text/SpanRecycler.java b/app/src/main/java/io/github/rosemoe/editor/text/SpanRecycler.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/text/SpanRecycler.java rename to app/src/main/java/io/github/rosemoe/editor/text/SpanRecycler.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/text/TextAnalyzeResult.java b/app/src/main/java/io/github/rosemoe/editor/text/TextAnalyzeResult.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/text/TextAnalyzeResult.java rename to app/src/main/java/io/github/rosemoe/editor/text/TextAnalyzeResult.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/text/TextAnalyzer.java b/app/src/main/java/io/github/rosemoe/editor/text/TextAnalyzer.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/text/TextAnalyzer.java rename to app/src/main/java/io/github/rosemoe/editor/text/TextAnalyzer.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/text/TextUtils.java b/app/src/main/java/io/github/rosemoe/editor/text/TextUtils.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/text/TextUtils.java rename to app/src/main/java/io/github/rosemoe/editor/text/TextUtils.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/text/UndoManager.java b/app/src/main/java/io/github/rosemoe/editor/text/UndoManager.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/text/UndoManager.java rename to app/src/main/java/io/github/rosemoe/editor/text/UndoManager.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/util/BinaryHeap.java b/app/src/main/java/io/github/rosemoe/editor/util/BinaryHeap.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/util/BinaryHeap.java rename to app/src/main/java/io/github/rosemoe/editor/util/BinaryHeap.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/util/IntPair.java b/app/src/main/java/io/github/rosemoe/editor/util/IntPair.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/util/IntPair.java rename to app/src/main/java/io/github/rosemoe/editor/util/IntPair.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/util/LongArrayList.java b/app/src/main/java/io/github/rosemoe/editor/util/LongArrayList.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/util/LongArrayList.java rename to app/src/main/java/io/github/rosemoe/editor/util/LongArrayList.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/util/Objects.java b/app/src/main/java/io/github/rosemoe/editor/util/Objects.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/util/Objects.java rename to app/src/main/java/io/github/rosemoe/editor/util/Objects.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/AbstractLayout.java b/app/src/main/java/io/github/rosemoe/editor/widget/AbstractLayout.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/AbstractLayout.java rename to app/src/main/java/io/github/rosemoe/editor/widget/AbstractLayout.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/CodeEditor.java b/app/src/main/java/io/github/rosemoe/editor/widget/CodeEditor.java similarity index 95% rename from editor/src/main/java/io/github/rosemoe/editor/widget/CodeEditor.java rename to app/src/main/java/io/github/rosemoe/editor/widget/CodeEditor.java index 97f5fe4d7..ffd1315ca 100644 --- a/editor/src/main/java/io/github/rosemoe/editor/widget/CodeEditor.java +++ b/app/src/main/java/io/github/rosemoe/editor/widget/CodeEditor.java @@ -56,7 +56,7 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.Px; -import io.github.rosemoe.editor.R; +import com.itsaky.androidide.R; import io.github.rosemoe.editor.interfaces.EditorEventListener; import io.github.rosemoe.editor.interfaces.EditorLanguage; import io.github.rosemoe.editor.interfaces.NewlineHandler; @@ -82,6 +82,13 @@ import java.util.LinkedList; import java.util.List; import com.blankj.utilcode.util.DeviceUtils; import io.github.rosemoe.editor.struct.HexColor; +import android.graphics.Path; +import android.graphics.Point; +import android.graphics.PointF; +import com.itsaky.androidide.services.compiler.model.CompilerDiagnostic; +import javax.tools.Diagnostic; +import java.util.stream.Collectors; +import com.itsaky.androidide.utils.Logger; /** * CodeEditor is a editor that can highlight text regions by doing basic syntax analyzing @@ -219,6 +226,7 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca private EditorLanguage mLanguage; private long mLastMakeVisible = 0; private EditorAutoCompleteWindow mCompletionWindow; + private DiagnosticWindow mDiagnosticWindow; private EditorTouchEventHandler mEventHandler; private Paint.Align mLineNumberAlign; private GestureDetector mBasicDetector; @@ -243,7 +251,8 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca KeyMetaStates mKeyMetaStates = new KeyMetaStates(this); private HashMap mLineColors; - + private HashMap> mDiagnostics; + public CodeEditor(Context context) { this(context, null); } @@ -431,6 +440,7 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca setFocusableInTouchMode(true); mConnection = new EditorInputConnection(this); mCompletionWindow = new EditorAutoCompleteWindow(this); + mDiagnosticWindow = new DiagnosticWindow(this); mVerticalEdgeGlow = new MaterialEdgeEffect(); mHorizontalGlow = new MaterialEdgeEffect(); mOverrideSymbolPairs = new SymbolPairMatch(); @@ -462,6 +472,57 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca mErrorColor = Color.parseColor("#FF5252"); mWarningColor = Color.parseColor("#FFD740"); mRect2.set(-1, -1, -1, -1); + + mDiagnostics = new HashMap<>(); + } + + public void showOrHideDiagnosticWindow() { + int line = getCursor().getLeftLine(); + int index = getCursor().getLeft(); + boolean hide = false; + boolean has = mDiagnostics.containsKey(Long.valueOf(line)); + if(has) { + List diags = mDiagnostics.get(Long.valueOf(line)); + if(diags != null) { + diags = diags.stream().filter(d -> d.start() <= index && d.end() >= index).collect(Collectors.toList()); + if(diags != null && diags.size() > 0){ + CompilerDiagnostic d = diags.get(0); + mDiagnosticWindow.setDiagnostic(d); + if(!mDiagnosticWindow.isShowing()) + mDiagnosticWindow.show(); + else updateDiagnosticWindowPosition(); + } else hide = true; + } else hide = true; + } else hide = true; + if(hide && mDiagnosticWindow.isShowing()) { + mDiagnosticWindow.hide(); + } + } + + public void setDiagnostics(List diags) { + this.mDiagnostics = new HashMap<>(); + mapDiagnostics(diags); + } + + private void mapDiagnostics(List diags) { + if(diags == null) + return; + for(int i=0;i ds = mDiagnostics.get(d.line()); + if(ds == null) + ds = new ArrayList<>(); + if(!ds.contains(d)) + ds.add(d); + } else { + List ds = new ArrayList<>(); + ds.add(d); + mDiagnostics.put(Long.valueOf(d.line()), ds); + } + } } public CodeEditor setLineColors(HashMap map) { @@ -1141,7 +1202,7 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca } } - // Draw hex color values + // Draw hex colors if(isLineColorsEnabled() && mLineColors != null && mLineColors.size() > 0) { for(HexColor c : mLineColors.keySet()) { if(c != null && c.isSameLine(line) && mLineColors.get(c) != null) { @@ -1159,7 +1220,35 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca } } } - + + // Draw errors and warnings + if(mDiagnostics != null && mDiagnostics.containsKey(Long.valueOf(line))) { + List diags = mDiagnostics.get(Long.valueOf(line)); + if(diags != null && diags.size() > 0) { + for(int i=0;i=1;i--) { + float startSeed = i * seed + sideDiff + left; + float endSeed = startSeed - seed; + if (i == 1) { + endSeed -= sideDiff; + } + path.lineTo(startSeed - halfSeed, innerHeight); + path.lineTo(endSeed, y); + } + + canvas.drawPath(path, paint); + } /** * Draw code block lines on screen @@ -2141,6 +2258,36 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca mTextActionPresenter.onExit(); } } + + private void updateDiagnosticWindowPosition() { + float panelX = updateCursorAnchor() + mDpUnit * 20; + float[] rightLayoutOffset = mLayout.getCharLayoutOffset(mCursor.getRightLine(), mCursor.getRightColumn()); + float panelY = rightLayoutOffset[0] - getOffsetY() + getRowHeight() / 2f; + float restY = getHeight() - panelY; + if (restY > mDpUnit * 120) { + restY = mDpUnit * 120; + } else if (restY < mDpUnit * 100) { + float offset = 0; + while (restY < mDpUnit * 100) { + restY += getRowHeight(); + panelY -= getRowHeight(); + offset += getRowHeight(); + } + getScroller().startScroll(getOffsetX(), getOffsetY(), 0, (int) offset, 0); + } + mDiagnosticWindow.setExtendedX(panelX); + mDiagnosticWindow.setExtendedY(panelY); + if (getWidth() < 500 * mDpUnit) { + mDiagnosticWindow.setWidth(getWidth() * 7 / 8); + mDiagnosticWindow.setExtendedX(getWidth() / 8f / 2f); + } else { + int w = getWidth() / 3; + mDiagnosticWindow.setWidth(w * 2); + } + + mDiagnosticWindow.setHeight((int) restY); + mDiagnosticWindow.updatePosition(); + } /** * Apply new position of auto completion window @@ -3227,6 +3374,8 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca if (mTextActionPresenter != null) { mTextActionPresenter.onExit(); } + + showOrHideDiagnosticWindow(); } /** @@ -3302,6 +3451,7 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca mTextActionPresenter.onBeginTextSelect(); } mEventHandler.notifyTouchedSelectionHandlerLater(); + showOrHideDiagnosticWindow(); } /** @@ -3767,7 +3917,9 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE | EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD; - + + outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI|EditorInfo.IME_FLAG_NO_FULLSCREEN; + outAttrs.initialSelStart = getCursor() != null ? getCursor().getLeft() : 0; outAttrs.initialSelEnd = getCursor() != null ? getCursor().getRight() : 0; outAttrs.initialCapsMode = mConnection.getCursorCapsMode(0); @@ -4075,6 +4227,9 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca @Override public void afterInsert(Content content, int startLine, int startColumn, int endLine, int endColumn, CharSequence insertedContent) { + if(mDiagnosticWindow != null) + mDiagnosticWindow.hide(); + // Update spans if (isSpanMapPrepared(true, endLine - startLine)) { if (startLine == endLine) { @@ -4135,6 +4290,9 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca @Override public void afterDelete(Content content, int startLine, int startColumn, int endLine, int endColumn, CharSequence deletedContent) { + if(mDiagnosticWindow != null) + mDiagnosticWindow.hide(); + if (isSpanMapPrepared(false, endLine - startLine)) { if (startLine == endLine) { SpanMapUpdater.shiftSpansOnSingleLineDelete(mSpanner.getResult().getSpanMap(), startLine, startColumn, endColumn); diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/CursorBlink.java b/app/src/main/java/io/github/rosemoe/editor/widget/CursorBlink.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/CursorBlink.java rename to app/src/main/java/io/github/rosemoe/editor/widget/CursorBlink.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/DefaultCompletionItemAdapter.java b/app/src/main/java/io/github/rosemoe/editor/widget/DefaultCompletionItemAdapter.java similarity index 98% rename from editor/src/main/java/io/github/rosemoe/editor/widget/DefaultCompletionItemAdapter.java rename to app/src/main/java/io/github/rosemoe/editor/widget/DefaultCompletionItemAdapter.java index 6f995e5f5..ec5279058 100644 --- a/editor/src/main/java/io/github/rosemoe/editor/widget/DefaultCompletionItemAdapter.java +++ b/app/src/main/java/io/github/rosemoe/editor/widget/DefaultCompletionItemAdapter.java @@ -24,7 +24,7 @@ import android.widget.ImageView; import android.widget.TextView; import com.itsaky.androidide.language.java.parser.internal.SuggestItem; import com.itsaky.androidide.utils.Either; -import io.github.rosemoe.editor.R; +import com.itsaky.androidide.R; import io.github.rosemoe.editor.struct.CompletionItem; import android.graphics.drawable.Drawable; import androidx.core.content.ContextCompat; diff --git a/app/src/main/java/io/github/rosemoe/editor/widget/DiagnosticWindow.java b/app/src/main/java/io/github/rosemoe/editor/widget/DiagnosticWindow.java new file mode 100644 index 000000000..c1d47315b --- /dev/null +++ b/app/src/main/java/io/github/rosemoe/editor/widget/DiagnosticWindow.java @@ -0,0 +1,79 @@ +package io.github.rosemoe.editor.widget; + +import android.graphics.PorterDuff; +import android.view.LayoutInflater; +import androidx.core.content.ContextCompat; +import com.itsaky.androidide.R; +import com.itsaky.androidide.databinding.LayoutDiagnosticInfoBinding; +import com.itsaky.androidide.services.compiler.model.CompilerDiagnostic; +import javax.tools.Diagnostic; + +public class DiagnosticWindow extends EditorBasePopupWindow { + + private CodeEditor editor; + private CompilerDiagnostic diagnostic; + private LayoutDiagnosticInfoBinding binding; + + public DiagnosticWindow(CodeEditor editor) { + super(editor); + this.editor = editor; + this.binding = LayoutDiagnosticInfoBinding.inflate(LayoutInflater.from(editor.getContext())); + + setContentView(this.binding.getRoot()); + } + + @Override + public void show() { + super.show(); + setupData(); + } + + public void setMaxHeight(int height) { + + } + + @Override + public void updatePosition() { + super.updatePosition(); + setupData(); + } + + private void setupData() { + if(diagnostic != null) { + binding.icon.setImageResource(getDiagnosticIconId()); + binding.icon.setColorFilter(ContextCompat.getColor(binding.icon.getContext(), R.color.primaryIconColor), PorterDuff.Mode.SRC_ATOP); + binding.errorType.setText(getDiagnosticTypeString()); + binding.errorPosition.setText( + editor.getContext().getString( + com.itsaky.androidide.R.string.diagnostic_position, + ""+ diagnostic.line(), + ""+ diagnostic.column() + ) + ); + + binding.msg.setText(diagnostic.message()); + binding.msg.setTextIsSelectable(true); + } + } + + public DiagnosticWindow setDiagnostic(CompilerDiagnostic diag) { + this.diagnostic = diag; + return this; + } + + private int getDiagnosticIconId() { + if(diagnostic.kind() == Diagnostic.Kind.ERROR) + return R.drawable.ic_compilation_error; + return R.drawable.ic_info; + } + + private String getDiagnosticTypeString() { + if(diagnostic.kind() == Diagnostic.Kind.ERROR) { + return editor.getContext().getString(com.itsaky.androidide.R.string.diagnostic_error); + } else if(diagnostic.kind() == Diagnostic.Kind.WARNING || diagnostic.kind() == Diagnostic.Kind.MANDATORY_WARNING) { + return editor.getContext().getString(com.itsaky.androidide.R.string.diagnostic_warning); + } + return editor.getContext().getString(com.itsaky.androidide.R.string.diagnostic_note); + } + +} diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/EditorAutoCompleteWindow.java b/app/src/main/java/io/github/rosemoe/editor/widget/EditorAutoCompleteWindow.java similarity index 97% rename from editor/src/main/java/io/github/rosemoe/editor/widget/EditorAutoCompleteWindow.java rename to app/src/main/java/io/github/rosemoe/editor/widget/EditorAutoCompleteWindow.java index 991008d4a..d7c2b7884 100644 --- a/editor/src/main/java/io/github/rosemoe/editor/widget/EditorAutoCompleteWindow.java +++ b/app/src/main/java/io/github/rosemoe/editor/widget/EditorAutoCompleteWindow.java @@ -19,14 +19,12 @@ import android.content.Context; import android.graphics.drawable.GradientDrawable; import android.view.LayoutInflater; import android.view.View; -import android.view.ViewGroup; import android.widget.Toast; -import com.blankj.utilcode.util.DeviceUtils; +import com.itsaky.androidide.databinding.LayoutCompletionWindowBinding; import com.itsaky.androidide.language.java.parser.internal.SuggestItem; import com.itsaky.androidide.models.CompletionEdit; import com.itsaky.androidide.models.CompletionListItem; import com.itsaky.androidide.utils.Either; -import io.github.rosemoe.editor.databinding.LayoutCompletionWindowBinding; import io.github.rosemoe.editor.interfaces.AutoCompleteProvider; import io.github.rosemoe.editor.struct.CompletionItem; import io.github.rosemoe.editor.text.CharPosition; @@ -276,7 +274,7 @@ public class EditorAutoCompleteWindow extends EditorBasePopupWindow { mAdapter.attachAttributes(this, results); mBinding.list.setAdapter(mAdapter); mCurrent = 0; - float newHeight = mEditor.getDpUnit() * 40 * results.size(); + float newHeight = mEditor.getDpUnit() * mAdapter.getItemHeight() * results.size(); if (isShowing()) { update(getWidth(), (int) Math.min(newHeight, mMaxHeight)); } @@ -307,7 +305,7 @@ public class EditorAutoCompleteWindow extends EditorBasePopupWindow { @Override public void run() { try { - displayResults(mLocalProvider.getAutoCompleteItems(mPrefix, mInner, mColors, mLine), mTime); + displayResults(mLocalProvider.getAutoCompleteItems(mEditor, mPrefix, mInner, mColors, mLine), mTime); } catch (Exception e) { e.printStackTrace(); displayResults(new ArrayList<>(), mTime); diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/EditorBasePopupWindow.java b/app/src/main/java/io/github/rosemoe/editor/widget/EditorBasePopupWindow.java similarity index 98% rename from editor/src/main/java/io/github/rosemoe/editor/widget/EditorBasePopupWindow.java rename to app/src/main/java/io/github/rosemoe/editor/widget/EditorBasePopupWindow.java index d0084ae16..2c6542660 100644 --- a/editor/src/main/java/io/github/rosemoe/editor/widget/EditorBasePopupWindow.java +++ b/app/src/main/java/io/github/rosemoe/editor/widget/EditorBasePopupWindow.java @@ -17,7 +17,7 @@ package io.github.rosemoe.editor.widget; import android.view.Gravity; import android.widget.PopupWindow; -import io.github.rosemoe.editor.R; +import com.itsaky.androidide.R; /** * Editor base panel class diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/EditorColorScheme.java b/app/src/main/java/io/github/rosemoe/editor/widget/EditorColorScheme.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/EditorColorScheme.java rename to app/src/main/java/io/github/rosemoe/editor/widget/EditorColorScheme.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/EditorCompletionAdapter.java b/app/src/main/java/io/github/rosemoe/editor/widget/EditorCompletionAdapter.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/EditorCompletionAdapter.java rename to app/src/main/java/io/github/rosemoe/editor/widget/EditorCompletionAdapter.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/EditorInputConnection.java b/app/src/main/java/io/github/rosemoe/editor/widget/EditorInputConnection.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/EditorInputConnection.java rename to app/src/main/java/io/github/rosemoe/editor/widget/EditorInputConnection.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/EditorSearcher.java b/app/src/main/java/io/github/rosemoe/editor/widget/EditorSearcher.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/EditorSearcher.java rename to app/src/main/java/io/github/rosemoe/editor/widget/EditorSearcher.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/EditorTextActionModeStarter.java b/app/src/main/java/io/github/rosemoe/editor/widget/EditorTextActionModeStarter.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/EditorTextActionModeStarter.java rename to app/src/main/java/io/github/rosemoe/editor/widget/EditorTextActionModeStarter.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/EditorTextActionWindow.java b/app/src/main/java/io/github/rosemoe/editor/widget/EditorTextActionWindow.java similarity index 99% rename from editor/src/main/java/io/github/rosemoe/editor/widget/EditorTextActionWindow.java rename to app/src/main/java/io/github/rosemoe/editor/widget/EditorTextActionWindow.java index cf150bc5f..e62dfa213 100644 --- a/editor/src/main/java/io/github/rosemoe/editor/widget/EditorTextActionWindow.java +++ b/app/src/main/java/io/github/rosemoe/editor/widget/EditorTextActionWindow.java @@ -24,7 +24,7 @@ import android.view.MotionEvent; import android.view.View; import android.widget.Button; -import io.github.rosemoe.editor.R; +import com.itsaky.androidide.R; /** * This will show when selecting text diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/EditorTouchEventHandler.java b/app/src/main/java/io/github/rosemoe/editor/widget/EditorTouchEventHandler.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/EditorTouchEventHandler.java rename to app/src/main/java/io/github/rosemoe/editor/widget/EditorTouchEventHandler.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/KeyMetaStates.java b/app/src/main/java/io/github/rosemoe/editor/widget/KeyMetaStates.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/KeyMetaStates.java rename to app/src/main/java/io/github/rosemoe/editor/widget/KeyMetaStates.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/Layout.java b/app/src/main/java/io/github/rosemoe/editor/widget/Layout.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/Layout.java rename to app/src/main/java/io/github/rosemoe/editor/widget/Layout.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/LineBreakLayout.java b/app/src/main/java/io/github/rosemoe/editor/widget/LineBreakLayout.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/LineBreakLayout.java rename to app/src/main/java/io/github/rosemoe/editor/widget/LineBreakLayout.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/MaterialEdgeEffect.java b/app/src/main/java/io/github/rosemoe/editor/widget/MaterialEdgeEffect.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/MaterialEdgeEffect.java rename to app/src/main/java/io/github/rosemoe/editor/widget/MaterialEdgeEffect.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/Row.java b/app/src/main/java/io/github/rosemoe/editor/widget/Row.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/Row.java rename to app/src/main/java/io/github/rosemoe/editor/widget/Row.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/RowIterator.java b/app/src/main/java/io/github/rosemoe/editor/widget/RowIterator.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/RowIterator.java rename to app/src/main/java/io/github/rosemoe/editor/widget/RowIterator.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/SymbolChannel.java b/app/src/main/java/io/github/rosemoe/editor/widget/SymbolChannel.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/SymbolChannel.java rename to app/src/main/java/io/github/rosemoe/editor/widget/SymbolChannel.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/SymbolPairMatch.java b/app/src/main/java/io/github/rosemoe/editor/widget/SymbolPairMatch.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/SymbolPairMatch.java rename to app/src/main/java/io/github/rosemoe/editor/widget/SymbolPairMatch.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/TextActionPopupWindow.java b/app/src/main/java/io/github/rosemoe/editor/widget/TextActionPopupWindow.java similarity index 99% rename from editor/src/main/java/io/github/rosemoe/editor/widget/TextActionPopupWindow.java rename to app/src/main/java/io/github/rosemoe/editor/widget/TextActionPopupWindow.java index 68925021c..63579251c 100644 --- a/editor/src/main/java/io/github/rosemoe/editor/widget/TextActionPopupWindow.java +++ b/app/src/main/java/io/github/rosemoe/editor/widget/TextActionPopupWindow.java @@ -25,10 +25,8 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.widget.LinearLayout; - import com.google.android.material.button.MaterialButton; - -import io.github.rosemoe.editor.R; +import com.itsaky.androidide.R; public class TextActionPopupWindow extends TextComposeBasePopup implements View.OnClickListener, CodeEditor.EditorTextActionPresenter { private final CodeEditor mEditor; diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/TextComposeBasePopup.java b/app/src/main/java/io/github/rosemoe/editor/widget/TextComposeBasePopup.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/TextComposeBasePopup.java rename to app/src/main/java/io/github/rosemoe/editor/widget/TextComposeBasePopup.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/WordwrapLayout.java b/app/src/main/java/io/github/rosemoe/editor/widget/WordwrapLayout.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/WordwrapLayout.java rename to app/src/main/java/io/github/rosemoe/editor/widget/WordwrapLayout.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeDarcula.java b/app/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeDarcula.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeDarcula.java rename to app/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeDarcula.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeEclipse.java b/app/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeEclipse.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeEclipse.java rename to app/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeEclipse.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeGitHub.java b/app/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeGitHub.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeGitHub.java rename to app/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeGitHub.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeNotepadXX.java b/app/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeNotepadXX.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeNotepadXX.java rename to app/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeNotepadXX.java diff --git a/editor/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeVS2019.java b/app/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeVS2019.java similarity index 100% rename from editor/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeVS2019.java rename to app/src/main/java/io/github/rosemoe/editor/widget/schemes/SchemeVS2019.java diff --git a/app/src/main/java/org/eclipse/jdt/core/Signature.java b/app/src/main/java/org/eclipse/jdt/core/Signature.java new file mode 100644 index 000000000..1846d3ff8 --- /dev/null +++ b/app/src/main/java/org/eclipse/jdt/core/Signature.java @@ -0,0 +1,2927 @@ +/******************************************************************************* + * Copyright (c) 2000, 2015 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * IBM Corporation - added J2SE 1.5 support + * Stephan Herrmann - Contribution for + * Bug 463533 - Signature.getSignatureSimpleName() returns different results for resolved and unresolved extends + *******************************************************************************/ +package org.eclipse.jdt.core; + +import java.util.ArrayList; + +import org.eclipse.jdt.core.compiler.CharOperation; +import org.eclipse.jdt.internal.compiler.parser.ScannerHelper; +import org.eclipse.jdt.internal.compiler.util.Util; + +/** + * Provides methods for encoding and decoding type and method signature strings. + *

+ * Signatures obtained from parsing source files (i.e. files with one of the + * {@link JavaCore#getJavaLikeExtensions() Java-like extensions}) differ subtly + * from ones obtained from pre-compiled binary (".class") files in class names are + * usually left unresolved in the former. For example, the normal resolved form + * of the type "String" embeds the class's package name ("Ljava.lang.String;" + * or "Ljava/lang/String;"), whereas the unresolved form contains only what is + * written "QString;". + *

+ *

+ * Generic types introduce to the Java language in J2SE 1.5 add three new + * facets to signatures: type variables, parameterized types with type arguments, + * and formal type parameters. Rich signatures containing these facets + * only occur when dealing with code that makes overt use of the new language + * features. All other code, and certainly all Java code written or compiled + * with J2SE 1.4 or earlier, involved only simple signatures. + *

+ *

+ * Note that the "Q", "!" and "|" formats are specific to Eclipse; the remainder + * are specified in the JVM spec. + *

+ *

+ * The syntax for a type signature is: + *

+ * TypeSignature ::=
+ *     "B"  // byte
+ *   | "C"  // char
+ *   | "D"  // double
+ *   | "F"  // float
+ *   | "I"  // int
+ *   | "J"  // long
+ *   | "S"  // short
+ *   | "V"  // void
+ *   | "Z"  // boolean
+ *   | "T" + Identifier + ";" // type variable
+ *   | "[" + TypeSignature  // array X[]
+ *   | "!" + TypeSignature  // capture-of ?
+ *   | "|" + TypeSignature + (":" + TypeSignature)+ // intersection type
+ *   | ResolvedClassTypeSignature
+ *   | UnresolvedClassTypeSignature
+ *
+ * ResolvedClassTypeSignature ::= // resolved named type (in compiled code)
+ *     "L" + Identifier + OptionalTypeArguments
+ *           ( ( "." | "/" ) + Identifier + OptionalTypeArguments )* + ";"
+ *     | OptionalTypeParameters + "L" + Identifier +
+ *           ( ( "." | "/" ) + Identifier )* + ";"
+ *
+ * UnresolvedClassTypeSignature ::= // unresolved named type (in source code)
+ *     "Q" + Identifier + OptionalTypeArguments
+ *           ( ( "." | "/" ) + Identifier + OptionalTypeArguments )* + ";"
+ *     | OptionalTypeParameters "Q" + Identifier +
+ *           ( ( "." | "/" ) + Identifier )* + ";"
+ *
+ * OptionalTypeArguments ::=
+ *     "<" + TypeArgument+ + ">"
+ *   |
+ *
+ * TypeArgument ::=
+ *   | TypeSignature
+ *   | "*" // wildcard ?
+ *   | "+" TypeSignature // wildcard ? extends X
+ *   | "-" TypeSignature // wildcard ? super X
+ *
+ * OptionalTypeParameters ::=
+ *     "<" + FormalTypeParameterSignature+ + ">"
+ *   |
+ * 
+ *

+ *

+ * Examples: + *

    + *
  • "[[I" denotes int[][]
  • + *
  • "Ljava.lang.String;" denotes java.lang.String in compiled code
  • + *
  • "QString;" denotes String in source code
  • + *
  • "Qjava.lang.String;" denotes java.lang.String in source code
  • + *
  • "[QString;" denotes String[] in source code
  • + *
  • "QMap<QString;*>;" denotes Map<String,?> in source code
  • + *
  • "Qjava.util.List<TV;>;" denotes java.util.List<V> in source code
  • + *
  • "<E;>Ljava.util.List;" denotes <E>java.util.List in source code
  • + *
+ *

+ *

+ * The syntax for a method signature is: + *

+ * MethodSignature ::= OptionalTypeParameters + "(" + ParamTypeSignature* + ")" + ReturnTypeSignature
+ * ParamTypeSignature ::= TypeSignature
+ * ReturnTypeSignature ::= TypeSignature
+ * 
+ *

+ * Examples: + *

    + *
  • "()I" denotes int foo()
  • + *
  • "([Ljava.lang.String;)V" denotes void foo(java.lang.String[]) in compiled code
  • + *
  • "(QString;)QObject;" denotes Object foo(String) in source code
  • + *
+ *

+ *

+ * The syntax for a formal type parameter signature is: + *

+ * FormalTypeParameterSignature ::=
+ *     TypeVariableName + OptionalClassBound + InterfaceBound*
+ * TypeVariableName ::= Identifier
+ * OptionalClassBound ::=
+ *     ":"
+ *   | ":" + TypeSignature
+ * InterfaceBound ::=
+ *     ":" + TypeSignature
+ * 
+ *

+ * Examples: + *

    + *
  • "X:" denotes X
  • + *
  • "X:QReader;" denotes X extends Reader in source code
  • + *
  • "X:QReader;:QSerializable;" denotes X extends Reader & Serializable in source code
  • + *
+ *

+ *

+ * This class provides static methods and constants only. + *

+ *

Note: An empty signature is considered to be syntactically incorrect. So most methods will throw + * an IllegalArgumentException if an empty signature is provided.

+ * + * @noinstantiate This class is not intended to be instantiated by clients. + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +public final class Signature { + + /** + * Kind constant for an array type signature. + * @see #getTypeSignatureKind(String) + * @since 3.0 + */ + public static final int ARRAY_TYPE_SIGNATURE = 4; + + /** + * Kind constant for a base (primitive or void) type signature. + * @see #getTypeSignatureKind(String) + * @since 3.0 + */ + public static final int BASE_TYPE_SIGNATURE = 2; + + private static final char[] BOOLEAN = "boolean".toCharArray(); //$NON-NLS-1$ + + private static final char[] BYTE = "byte".toCharArray(); //$NON-NLS-1$ + + /** + * Character constant indicating an array type in a signature. + * Value is '['. + */ + public static final char C_ARRAY = org.eclipse.jdt.internal.compiler.util.Util.C_ARRAY; + + /** + * Character constant indicating the primitive type boolean in a signature. + * Value is 'Z'. + */ + public static final char C_BOOLEAN = org.eclipse.jdt.internal.compiler.util.Util.C_BOOLEAN; + + /** + * Character constant indicating the primitive type byte in a signature. + * Value is 'B'. + */ + public static final char C_BYTE = org.eclipse.jdt.internal.compiler.util.Util.C_BYTE; + + /** + * Character constant indicating a capture of a wildcard type in a + * signature. Value is '!'. + * @since 3.1 + */ + public static final char C_CAPTURE = org.eclipse.jdt.internal.compiler.util.Util.C_CAPTURE; + + /** + * Character constant indicating the primitive type char in a signature. + * Value is 'C'. + */ + public static final char C_CHAR = org.eclipse.jdt.internal.compiler.util.Util.C_CHAR; + + /** + * Character constant indicating the colon in a signature. + * Value is ':'. + * @since 3.0 + */ + public static final char C_COLON = org.eclipse.jdt.internal.compiler.util.Util.C_COLON; + + /** + * Character constant indicating the dollar in a signature. + * Value is '$'. + */ + public static final char C_DOLLAR = org.eclipse.jdt.internal.compiler.util.Util.C_DOLLAR; + + /** + * Character constant indicating the dot in a signature. + * Value is '.'. + */ + public static final char C_DOT = org.eclipse.jdt.internal.compiler.util.Util.C_DOT; + + /** + * Character constant indicating the primitive type double in a signature. + * Value is 'D'. + */ + public static final char C_DOUBLE = org.eclipse.jdt.internal.compiler.util.Util.C_DOUBLE; + + /** + * Character constant indicating an exception in a signature. + * Value is '^'. + * @since 3.1 + */ + public static final char C_EXCEPTION_START = org.eclipse.jdt.internal.compiler.util.Util.C_EXCEPTION_START; + + /** + * Character constant indicating a bound wildcard type argument + * in a signature with extends clause. + * Value is '+'. + * @since 3.1 + */ + public static final char C_EXTENDS = org.eclipse.jdt.internal.compiler.util.Util.C_EXTENDS; + + /** + * Character constant indicating the primitive type float in a signature. + * Value is 'F'. + */ + public static final char C_FLOAT = org.eclipse.jdt.internal.compiler.util.Util.C_FLOAT; + + /** + * Character constant indicating the end of a generic type list in a + * signature. Value is '>'. + * @since 3.0 + */ + public static final char C_GENERIC_END = org.eclipse.jdt.internal.compiler.util.Util.C_GENERIC_END; + + /** + * Character constant indicating the start of a formal type parameter + * (or type argument) list in a signature. Value is '<'. + * @since 3.0 + */ + public static final char C_GENERIC_START = org.eclipse.jdt.internal.compiler.util.Util.C_GENERIC_START; + + /** + * Character constant indicating the primitive type int in a signature. + * Value is 'I'. + */ + public static final char C_INT = org.eclipse.jdt.internal.compiler.util.Util.C_INT; + + /** + * Character constant indicating an intersection type in a + * signature. Value is '|'. + * @since 3.7.1 + */ + public static final char C_INTERSECTION = '|'; + + /** + * Character constant indicating the primitive type long in a signature. + * Value is 'J'. + */ + public static final char C_LONG = org.eclipse.jdt.internal.compiler.util.Util.C_LONG; + + /** + * Character constant indicating the end of a named type in a signature. + * Value is ';'. + */ + public static final char C_NAME_END = org.eclipse.jdt.internal.compiler.util.Util.C_NAME_END; + + /** + * Character constant indicating the end of a parameter type list in a + * signature. Value is ')'. + */ + public static final char C_PARAM_END = org.eclipse.jdt.internal.compiler.util.Util.C_PARAM_END; + + /** + * Character constant indicating the start of a parameter type list in a + * signature. Value is '('. + */ + public static final char C_PARAM_START = org.eclipse.jdt.internal.compiler.util.Util.C_PARAM_START; + + /** + * Character constant indicating the start of a resolved, named type in a + * signature. Value is 'L'. + */ + public static final char C_RESOLVED = org.eclipse.jdt.internal.compiler.util.Util.C_RESOLVED; + + /** + * Character constant indicating the semicolon in a signature. + * Value is ';'. + */ + public static final char C_SEMICOLON = org.eclipse.jdt.internal.compiler.util.Util.C_SEMICOLON; + + /** + * Character constant indicating the primitive type short in a signature. + * Value is 'S'. + */ + public static final char C_SHORT = org.eclipse.jdt.internal.compiler.util.Util.C_SHORT; + + /** + * Character constant indicating an unbound wildcard type argument + * in a signature. + * Value is '*'. + * @since 3.0 + */ + public static final char C_STAR = org.eclipse.jdt.internal.compiler.util.Util.C_STAR; + + /** + * Character constant indicating a bound wildcard type argument + * in a signature with super clause. + * Value is '-'. + * @since 3.1 + */ + public static final char C_SUPER = org.eclipse.jdt.internal.compiler.util.Util.C_SUPER; + + /** + * Character constant indicating the start of a resolved type variable in a + * signature. Value is 'T'. + * @since 3.0 + */ + public static final char C_TYPE_VARIABLE = org.eclipse.jdt.internal.compiler.util.Util.C_TYPE_VARIABLE; + + /** + * Character constant indicating the start of an unresolved, named type in a + * signature. Value is 'Q'. + */ + public static final char C_UNRESOLVED = org.eclipse.jdt.internal.compiler.util.Util.C_UNRESOLVED; + + /** + * Character constant indicating result type void in a signature. + * Value is 'V'. + */ + public static final char C_VOID = org.eclipse.jdt.internal.compiler.util.Util.C_VOID; + + private static final char[] CAPTURE = "capture-of".toCharArray(); //$NON-NLS-1$ + + /** + * Kind constant for the capture of a wildcard type signature. + * @see #getTypeSignatureKind(String) + * @since 3.1 + */ + public static final int CAPTURE_TYPE_SIGNATURE = 6; + + private static final char[] CHAR = "char".toCharArray(); //$NON-NLS-1$ + + /** + * Kind constant for a class type signature. + * @see #getTypeSignatureKind(String) + * @since 3.0 + */ + public static final int CLASS_TYPE_SIGNATURE = 1; + + private static final char[] DOUBLE = "double".toCharArray(); //$NON-NLS-1$ + + + private static final char[] EXTENDS = "extends".toCharArray(); //$NON-NLS-1$ + + private static final char[] FLOAT = "float".toCharArray(); //$NON-NLS-1$ + + private static final char[] INT = "int".toCharArray(); //$NON-NLS-1$ + + /** + * Kind constant for the intersection type signature. + * @see #getTypeSignatureKind(String) + * @since 3.7.1 + */ + public static final int INTERSECTION_TYPE_SIGNATURE = 7; + + private static final char[] LONG = "long".toCharArray(); //$NON-NLS-1$ + + private static final char[] SHORT = "short".toCharArray(); //$NON-NLS-1$ + + /** + * String constant for the signature of the primitive type boolean. + * Value is "Z". + */ + public static final String SIG_BOOLEAN = "Z"; //$NON-NLS-1$ + + /** + * String constant for the signature of the primitive type byte. + * Value is "B". + */ + public static final String SIG_BYTE = "B"; //$NON-NLS-1$ + /** + * String constant for the signature of the primitive type char. + * Value is "C". + */ + public static final String SIG_CHAR = "C"; //$NON-NLS-1$ + /** + * String constant for the signature of the primitive type double. + * Value is "D". + */ + public static final String SIG_DOUBLE = "D"; //$NON-NLS-1$ + /** + * String constant for the signature of the primitive type float. + * Value is "F". + */ + public static final String SIG_FLOAT = "F"; //$NON-NLS-1$ + /** + * String constant for the signature of the primitive type int. + * Value is "I". + */ + public static final String SIG_INT = "I"; //$NON-NLS-1$ + /** + * String constant for the signature of the primitive type long. + * Value is "J". + */ + public static final String SIG_LONG = "J"; //$NON-NLS-1$ + /** + * String constant for the signature of the primitive type short. + * Value is "S". + */ + public static final String SIG_SHORT = "S"; //$NON-NLS-1$ + /** String constant for the signature of result type void. + * Value is "V". + */ + public static final String SIG_VOID = "V"; //$NON-NLS-1$ + private static final char[] SUPER = "super".toCharArray(); //$NON-NLS-1$ + /** + * Kind constant for a type variable signature. + * @see #getTypeSignatureKind(String) + * @since 3.0 + */ + public static final int TYPE_VARIABLE_SIGNATURE = 3; + private static final char[] VOID = "void".toCharArray(); //$NON-NLS-1$ + /** + * Kind constant for a wildcard type signature. + * @see #getTypeSignatureKind(String) + * @since 3.1 + */ + public static final int WILDCARD_TYPE_SIGNATURE = 5; + +// .d> --> > +private static void appendArgumentSimpleNames(char[] name, int start, int end, StringBuffer buffer) { + buffer.append('<'); + int depth = 0; + int argumentStart = -1; + int argumentCount = 0; + for (int i = start; i <= end; i++) { + switch(name[i]) { + case '<' : + depth++; + if (depth == 1) { + argumentStart = i+1; + } + break; + case '>' : + if (depth == 1) { + if (argumentCount > 0) buffer.append(','); + appendSimpleName(name, argumentStart, i-1, buffer); + argumentCount++; + } + depth--; + break; + case ',' : + if (depth == 1) { + if (argumentCount > 0) buffer.append(','); + appendSimpleName(name, argumentStart, i-1, buffer); + argumentCount++; + argumentStart = i+1; + } + break; + } + } + buffer.append('>'); +} + +/** + * Scans the given string for an array type signature starting at the given + * index and appends it to the given buffer, and returns the index of the last + * character. + * + * @param string the signature string + * @param start the 0-based character index of the first character + * @param fullyQualifyTypeNames true if type names should be fully + * qualified, and false to use only simple names + * @return the 0-based character index of the last character + * @exception IllegalArgumentException if this is not an array type signature + * @see Util#scanArrayTypeSignature(char[], int) + */ +private static int appendArrayTypeSignature(char[] string, int start, boolean fullyQualifyTypeNames, StringBuffer buffer) { + return appendArrayTypeSignature(string, start, fullyQualifyTypeNames, buffer, false); +} + +/** + * Scans the given string for an array type signature starting at the given + * index and appends it to the given buffer, and returns the index of the last + * character. + * + * @param string the signature string + * @param start the 0-based character index of the first character + * @param fullyQualifyTypeNames true if type names should be fully + * qualified, and false to use only simple names + * @param isVarArgs true if the array type must be displayed as a + * variable argument, false otherwise + * @return the 0-based character index of the last character + * @exception IllegalArgumentException if this is not an array type signature + * @see Util#scanArrayTypeSignature(char[], int) + */ +private static int appendArrayTypeSignature(char[] string, int start, boolean fullyQualifyTypeNames, StringBuffer buffer, boolean isVarArgs) { + int length = string.length; + // need a minimum 2 char + if (start >= length - 1) { + throw new IllegalArgumentException(); + } + char c = string[start]; + if (c != C_ARRAY) { + throw new IllegalArgumentException(); + } + + int index = start; + c = string[++index]; + while(c == C_ARRAY) { + // need a minimum 2 char + if (index >= length - 1) { + throw new IllegalArgumentException(); + } + c = string[++index]; + } + + int e = appendTypeSignature(string, index, fullyQualifyTypeNames, buffer); + + for(int i = 1, dims = index - start; i < dims; i++) { + buffer.append('[').append(']'); + } + + if (isVarArgs) { + buffer.append('.').append('.').append('.'); + } else { + buffer.append('[').append(']'); + } + return e; +} +/** + * Scans the given string for an capture type signature starting at the given + * index and appends it to the given buffer, and returns the index of the last + * character. + * + * @param string the signature string + * @param start the 0-based character index of the first character + * @param fullyQualifyTypeNames true if type names should be fully + * qualified, and false to use only simple names + * @return the 0-based character index of the last character + * @exception IllegalArgumentException if this is not an array type signature + * @see Util#scanArrayTypeSignature(char[], int) + */ +private static int appendCaptureTypeSignature(char[] string, int start, boolean fullyQualifyTypeNames, StringBuffer buffer) { + // need a minimum 2 char + if (start >= string.length - 1) { + throw new IllegalArgumentException(); + } + char c = string[start]; + if (c != C_CAPTURE) { + throw new IllegalArgumentException(); + } + buffer.append(CAPTURE).append(' '); + return appendTypeArgumentSignature(string, start + 1, fullyQualifyTypeNames, buffer); +} +/** + * Scans the given string for a class type signature starting at the given + * index and appends it to the given buffer, and returns the index of the last + * character. + * + * @param string the signature string + * @param start the 0-based character index of the first character + * @param fullyQualifyTypeNames true if type names should be fully + * qualified, and false to use only simple names + * @param buffer the string buffer to append to + * @return the 0-based character index of the last character + * @exception IllegalArgumentException if this is not a class type signature + * @see Util#scanClassTypeSignature(char[], int) + */ +private static int appendClassTypeSignature(char[] string, int start, boolean fullyQualifyTypeNames, StringBuffer buffer) { + // need a minimum 3 chars "Lx;" + if (start >= string.length - 2) { + throw new IllegalArgumentException(); + } + // must start in "L" or "Q" + char c = string[start]; + if (c != C_RESOLVED && c != C_UNRESOLVED) { + throw new IllegalArgumentException(); + } + boolean resolved = (c == C_RESOLVED); + boolean removePackageQualifiers = !fullyQualifyTypeNames; + if (!resolved) { + // keep everything in an unresolved name + removePackageQualifiers = false; + } + int p = start + 1; + int checkpoint = buffer.length(); + int innerTypeStart = -1; + boolean inAnonymousType = false; + while (true) { + if (p >= string.length) { + throw new IllegalArgumentException(); + } + c = string[p]; + switch(c) { + case C_SEMICOLON : + // all done + return p; + case C_GENERIC_START : + int e = appendTypeArgumentSignatures(string, p, fullyQualifyTypeNames, buffer); + // once we hit type arguments there are no more package prefixes + removePackageQualifiers = false; + p = e; + break; + case C_DOT : + if (removePackageQualifiers) { + // erase package prefix + buffer.setLength(checkpoint); + } else { + buffer.append('.'); + } + break; + case '/' : + if (removePackageQualifiers) { + // erase package prefix + buffer.setLength(checkpoint); + } else { + buffer.append('/'); + } + break; + case C_DOLLAR : + innerTypeStart = buffer.length(); + inAnonymousType = false; + if (resolved) { + // once we hit "$" there are no more package prefixes + removePackageQualifiers = false; + /** + * Convert '$' in resolved type signatures into '.'. + * NOTE: This assumes that the type signature is an inner type + * signature. This is true in most cases, but someone can define a + * non-inner type name containing a '$'. + */ + buffer.append('.'); + } + break; + default : + if (innerTypeStart != -1 && !inAnonymousType && Character.isDigit(c)) { + inAnonymousType = true; + buffer.setLength(innerTypeStart); // remove '.' + buffer.insert(checkpoint, "new "); //$NON-NLS-1$ + buffer.append("(){}"); //$NON-NLS-1$ + } + if (!inAnonymousType) + buffer.append(c); + innerTypeStart = -1; + } + p++; + } +} +/** + * Scans the given string for an intersection type signature starting at the given + * index and appends it to the given buffer, and returns the index of the last + * character. + * + * @param string the signature string + * @param start the 0-based character index of the first character + * @param fullyQualifyTypeNames true if type names should be fully + * qualified, and false to use only simple names + * @return the 0-based character index of the last character + * @exception IllegalArgumentException if this is not an array type signature + * @see Util#scanArrayTypeSignature(char[], int) + */ +private static int appendIntersectionTypeSignature(char[] string, int start, boolean fullyQualifyTypeNames, StringBuffer buffer) { + // need a minimum 2 char + if (start >= string.length - 1) { + throw new IllegalArgumentException(); + } + char c = string[start]; + if (c != C_INTERSECTION) { + throw new IllegalArgumentException(); + } + start = appendClassTypeSignature(string, start + 1, fullyQualifyTypeNames, buffer); + if (start < string.length - 1) { + start++; + if (string[start] != C_COLON) { + throw new IllegalArgumentException("should be a colon at this location"); //$NON-NLS-1$ + } + while (string[start] == C_COLON) { + buffer.append(" | "); //$NON-NLS-1$ + start = appendClassTypeSignature(string, start + 1, fullyQualifyTypeNames, buffer); + if (start == string.length - 1) { + return start; + } else if (start > string.length - 1) { + throw new IllegalArgumentException("Should be at the end"); //$NON-NLS-1$ + } + start++; + } + } + return start; +} +private static void appendSimpleName(char[] name, int start, int end, StringBuffer buffer) { + int lastDot = -1, lastGenericStart = -1, lastGenericEnd = -1; + int depth = 0; + if (name[start] == '?') { // wildcard + buffer.append("?"); //$NON-NLS-1$ + int index = consumeWhitespace(name, start+1, end+1); + switch (name[index]) { + case 'e' : + int checkPos = checkName(EXTENDS, name, index, end); + if (checkPos > 0) { + buffer.append(' ').append(EXTENDS).append(' '); + index = consumeWhitespace(name, checkPos, end+1); + } + break; + case 's' : + checkPos = checkName(SUPER, name, index, end+1); + if (checkPos > 0) { + buffer.append(' ').append(SUPER).append(' '); + index = consumeWhitespace(name, checkPos, end+1); + } + break; + } + start = index; // leading segment got processed + } + lastDotLookup: for (int i = end; i >= start; i--) { + switch (name[i]) { + case '.': + if (depth == 0) { + lastDot = i; + char c = name[start]; + if (c == C_EXTENDS || c == C_SUPER) { + buffer.append(c); + } + break lastDotLookup; + } + break; + case '<': + depth--; + if (depth == 0) lastGenericStart = i; + break; + case '>': + if (depth == 0) lastGenericEnd = i; + depth++; + break; + } + } + int nameStart = lastDot < 0 ? start : lastDot+1; + int nameEnd = lastGenericStart < 0 ? end+1 : lastGenericStart; + buffer.append(name, nameStart, nameEnd - nameStart); + if (lastGenericStart >= 0) { + appendArgumentSimpleNames(name, lastGenericStart, lastGenericEnd, buffer); + buffer.append(name, lastGenericEnd+1, end - lastGenericEnd); // copy trailing portion, may contain dimensions + } +} + +/** + * Scans the given string for a type argument signature starting at the given + * index and appends it to the given buffer, and returns the index of the last + * character. + * + * @param string the signature string + * @param start the 0-based character index of the first character + * @param fullyQualifyTypeNames true if type names should be fully + * qualified, and false to use only simple names + * @param buffer the string buffer to append to + * @return the 0-based character index of the last character + * @exception IllegalArgumentException if this is not a type argument signature + * @see Util#scanTypeArgumentSignature(char[], int) + */ +private static int appendTypeArgumentSignature(char[] string, int start, boolean fullyQualifyTypeNames, StringBuffer buffer) { + // need a minimum 1 char + if (start >= string.length) { + throw new IllegalArgumentException(); + } + char c = string[start]; + switch(c) { + case C_STAR : + buffer.append('?'); + return start; + case C_EXTENDS : + buffer.append("? extends "); //$NON-NLS-1$ + return appendTypeSignature(string, start + 1, fullyQualifyTypeNames, buffer); + case C_SUPER : + buffer.append("? super "); //$NON-NLS-1$ + return appendTypeSignature(string, start + 1, fullyQualifyTypeNames, buffer); + default : + return appendTypeSignature(string, start, fullyQualifyTypeNames, buffer); + } +} + +/** + * Scans the given string for a list of type arguments signature starting at the + * given index and appends it to the given buffer, and returns the index of the + * last character. + * + * @param string the signature string + * @param start the 0-based character index of the first character + * @param fullyQualifyTypeNames true if type names should be fully + * qualified, and false to use only simple names + * @param buffer the string buffer to append to + * @return the 0-based character index of the last character + * @exception IllegalArgumentException if this is not a list of type argument + * signatures + * @see Util#scanTypeArgumentSignatures(char[], int) + */ +private static int appendTypeArgumentSignatures(char[] string, int start, boolean fullyQualifyTypeNames, StringBuffer buffer) { + // need a minimum 2 char "<>" + if (start >= string.length - 1) { + throw new IllegalArgumentException(); + } + char c = string[start]; + if (c != C_GENERIC_START) { + throw new IllegalArgumentException(); + } + buffer.append('<'); + int p = start + 1; + int count = 0; + while (true) { + if (p >= string.length) { + throw new IllegalArgumentException(); + } + c = string[p]; + if (c == C_GENERIC_END) { + buffer.append('>'); + return p; + } + if (count != 0) { + buffer.append(','); + } + int e = appendTypeArgumentSignature(string, p, fullyQualifyTypeNames, buffer); + count++; + p = e + 1; + } +} +/** + * Scans the given string for a type signature starting at the given + * index and appends it to the given buffer, and returns the index of the last + * character. + * + * @param string the signature string + * @param start the 0-based character index of the first character + * @param fullyQualifyTypeNames true if type names should be fully + * qualified, and false to use only simple names + * @param buffer the string buffer to append to + * @return the 0-based character index of the last character + * @exception IllegalArgumentException if this is not a type signature + * @see Util#scanTypeSignature(char[], int) + */ +private static int appendTypeSignature(char[] string, int start, boolean fullyQualifyTypeNames, StringBuffer buffer) { + return appendTypeSignature(string, start, fullyQualifyTypeNames, buffer, false); +} + +/** + * Scans the given string for a type signature starting at the given + * index and appends it to the given buffer, and returns the index of the last + * character. + * + * @param string the signature string + * @param start the 0-based character index of the first character + * @param fullyQualifyTypeNames true if type names should be fully + * qualified, and false to use only simple names + * @param buffer the string buffer to append to + * @param isVarArgs true if the type must be displayed as a + * variable argument, false otherwise. In this case, the type must be an array type + * @return the 0-based character index of the last character + * @exception IllegalArgumentException if this is not a type signature, or if isVarArgs is true, + * and the type is not an array type signature. + * @see Util#scanTypeSignature(char[], int) + */ +private static int appendTypeSignature(char[] string, int start, boolean fullyQualifyTypeNames, StringBuffer buffer, boolean isVarArgs) { + // need a minimum 1 char + if (start >= string.length) { + throw new IllegalArgumentException(); + } + char c = string[start]; + if (isVarArgs) { + switch (c) { + case C_ARRAY : + return appendArrayTypeSignature(string, start, fullyQualifyTypeNames, buffer, true); + case C_RESOLVED : + case C_UNRESOLVED : + case C_TYPE_VARIABLE : + case C_BOOLEAN : + case C_BYTE : + case C_CHAR : + case C_DOUBLE : + case C_FLOAT : + case C_INT : + case C_LONG : + case C_SHORT : + case C_VOID : + case C_STAR: + case C_EXTENDS: + case C_SUPER: + case C_CAPTURE: + case C_INTERSECTION : + default: + throw new IllegalArgumentException(); // a var args is an array type + } + } else { + switch (c) { + case C_ARRAY : + return appendArrayTypeSignature(string, start, fullyQualifyTypeNames, buffer); + case C_RESOLVED : + case C_UNRESOLVED : + return appendClassTypeSignature(string, start, fullyQualifyTypeNames, buffer); + case C_TYPE_VARIABLE : + int e = Util.scanTypeVariableSignature(string, start); + buffer.append(string, start + 1, e - start - 1); + return e; + case C_BOOLEAN : + buffer.append(BOOLEAN); + return start; + case C_BYTE : + buffer.append(BYTE); + return start; + case C_CHAR : + buffer.append(CHAR); + return start; + case C_DOUBLE : + buffer.append(DOUBLE); + return start; + case C_FLOAT : + buffer.append(FLOAT); + return start; + case C_INT : + buffer.append(INT); + return start; + case C_LONG : + buffer.append(LONG); + return start; + case C_SHORT : + buffer.append(SHORT); + return start; + case C_VOID : + buffer.append(VOID); + return start; + case C_CAPTURE : + return appendCaptureTypeSignature(string, start, fullyQualifyTypeNames, buffer); + case C_INTERSECTION : + return appendIntersectionTypeSignature(string, start, fullyQualifyTypeNames, buffer); + case C_STAR: + case C_EXTENDS: + case C_SUPER: + return appendTypeArgumentSignature(string, start, fullyQualifyTypeNames, buffer); + default : + throw new IllegalArgumentException(); + } + } +} + +private static int checkArrayDimension(char[] typeName, int pos, int length) { + int genericBalance = 0; + while (pos < length) { + switch(typeName[pos]) { + case '<' : + genericBalance++; + break; + case ',' : + if (genericBalance == 0) return -1; + break; + case '>': + if (genericBalance == 0) return -1; + genericBalance--; + break; + case '[': + if (genericBalance == 0) { + return pos; + } + } + pos++; + } + return -1; +} +private static int checkName(char[] name, char[] typeName, int pos, int length) { + if (CharOperation.fragmentEquals(name, typeName, pos, true)) { + pos += name.length; + if (pos == length) return pos; + char currentChar = typeName[pos]; + switch (currentChar) { + case ' ' : + case '.' : + case '<' : + case '>' : + case '[' : + case ',' : + return pos; + default: + if (ScannerHelper.isWhitespace(currentChar)) + return pos; + + } + } + return -1; +} +private static int checkNextChar(char[] typeName, char expectedChar, int pos, int length, boolean isOptional) { + pos = consumeWhitespace(typeName, pos, length); + if (pos < length && typeName[pos] == expectedChar) + return pos + 1; + if (!isOptional) throw new IllegalArgumentException(new String(typeName)); + return -1; +} + +private static int consumeWhitespace(char[] typeName, int pos, int length) { + while (pos < length) { + char currentChar = typeName[pos]; + if (currentChar != ' ' && !ScannerHelper.isWhitespace(currentChar)) { + break; + } + pos++; + } + return pos; +} +/** + * Creates a new type signature with the given amount of array nesting added + * to the given type signature. + * + * @param typeSignature the type signature + * @param arrayCount the desired number of levels of array nesting + * @return the encoded array type signature + * + * @since 2.0 + */ +public static char[] createArraySignature(char[] typeSignature, int arrayCount) { + if (arrayCount == 0) return typeSignature; + int sigLength = typeSignature.length; + char[] result = new char[arrayCount + sigLength]; + for (int i = 0; i < arrayCount; i++) { + result[i] = C_ARRAY; + } + System.arraycopy(typeSignature, 0, result, arrayCount, sigLength); + return result; +} +/** + * Creates a new type signature with the given amount of array nesting added + * to the given type signature. + * + * @param typeSignature the type signature + * @param arrayCount the desired number of levels of array nesting + * @return the encoded array type signature + */ +public static String createArraySignature(String typeSignature, int arrayCount) { + return new String(createArraySignature(typeSignature.toCharArray(), arrayCount)); +} + +/** + * Creates a new type signature from the given type name encoded as a character + * array. The type name may contain primitive types or array types or parameterized types. + * This method is equivalent to + * createTypeSignature(new String(typeName),isResolved).toCharArray(), + * although more efficient for callers with character arrays rather than strings. + * If the type name is qualified, then it is expected to be dot-based. + * + * @param typeName the possibly qualified type name + * @param isResolved true if the type name is to be considered + * resolved (for example, a type name from a binary class file), and + * false if the type name is to be considered unresolved + * (for example, a type name found in source code) + * @return the encoded type signature + * @see #createTypeSignature(java.lang.String,boolean) + * + * @since 2.0 + */ +public static char[] createCharArrayTypeSignature(char[] typeName, boolean isResolved) { + if (typeName == null) throw new IllegalArgumentException("null"); //$NON-NLS-1$ + int length = typeName.length; + if (length == 0) throw new IllegalArgumentException(new String(typeName)); + StringBuffer buffer = new StringBuffer(5); + int pos = encodeTypeSignature(typeName, 0, isResolved, length, buffer); + pos = consumeWhitespace(typeName, pos, length); + if (pos < length) throw new IllegalArgumentException(new String(typeName)); + char[] result = new char[length = buffer.length()]; + buffer.getChars(0, length, result, 0); + return result; +} + +/** + * Creates a new intersection type signature from the given type signatures. + * + *

The encoded type signature is dot-based.

+ * + * @param typeSignatures the given type signatures + * @return the encoded type signature + * @since 3.7.1 + */ +public static String createIntersectionTypeSignature(char[][] typeSignatures) { + StringBuffer buffer = new StringBuffer(); + buffer.append(Signature.C_INTERSECTION); + for (int i = 0, max = typeSignatures.length; i < max; i++) { + if (i > 0) { + buffer.append(Signature.C_COLON); + } + buffer.append(typeSignatures[i]); + } + return String.valueOf(buffer); +} + +/** + * Creates a new intersection type signature from the given type signatures. + * + *

The encoded type signature is dot-based.

+ * + * @param typeSignatures the given type signatures + * @return the encoded type signature + * @since 3.7.1 + */ +public static String createIntersectionTypeSignature(String[] typeSignatures) { + int typeSignaturesLenth = typeSignatures.length; + char[][] signatures = new char[typeSignaturesLenth][]; + for (int i = 0; i < typeSignaturesLenth; i++) { + signatures[i] = typeSignatures[i].toCharArray(); + } + return createIntersectionTypeSignature(signatures); +} +/** + * Creates a method signature from the given parameter and return type + * signatures. The encoded method signature is dot-based. + * + * @param parameterTypes the list of parameter type signatures + * @param returnType the return type signature + * @return the encoded method signature + * + * @since 2.0 + */ +public static char[] createMethodSignature(char[][] parameterTypes, char[] returnType) { + int parameterTypesLength = parameterTypes.length; + int parameterLength = 0; + for (int i = 0; i < parameterTypesLength; i++) { + parameterLength += parameterTypes[i].length; + + } + int returnTypeLength = returnType.length; + char[] result = new char[1 + parameterLength + 1 + returnTypeLength]; + result[0] = C_PARAM_START; + int index = 1; + for (int i = 0; i < parameterTypesLength; i++) { + char[] parameterType = parameterTypes[i]; + int length = parameterType.length; + System.arraycopy(parameterType, 0, result, index, length); + index += length; + } + result[index] = C_PARAM_END; + System.arraycopy(returnType, 0, result, index+1, returnTypeLength); + return result; +} +/** + * Creates a method signature from the given parameter and return type + * signatures. The encoded method signature is dot-based. This method + * is equivalent to + * createMethodSignature(parameterTypes, returnType). + * + * @param parameterTypes the list of parameter type signatures + * @param returnType the return type signature + * @return the encoded method signature + * @see Signature#createMethodSignature(char[][], char[]) + */ +public static String createMethodSignature(String[] parameterTypes, String returnType) { + int parameterTypesLenth = parameterTypes.length; + char[][] parameters = new char[parameterTypesLenth][]; + for (int i = 0; i < parameterTypesLenth; i++) { + parameters[i] = parameterTypes[i].toCharArray(); + } + return new String(createMethodSignature(parameters, returnType.toCharArray())); +} +/** + * Creates a new type parameter signature with the given name and bounds. + * + * @param typeParameterName the type parameter name + * @param boundSignatures the signatures of associated bounds or empty array if none + * @return the encoded type parameter signature + * + * @since 3.1 + */ +public static char[] createTypeParameterSignature(char[] typeParameterName, char[][] boundSignatures) { + int length = boundSignatures.length; + if (length == 0) { + return CharOperation.append(typeParameterName, C_COLON); // param signature with no bounds still gets trailing colon + } + int boundsSize = 0; + for (int i = 0; i < length; i++) { + boundsSize += boundSignatures[i].length + 1; + } + int nameLength = typeParameterName.length; + char[] result = new char[nameLength + boundsSize]; + System.arraycopy(typeParameterName, 0, result, 0, nameLength); + int index = nameLength; + for (int i = 0; i < length; i++) { + result[index++] = C_COLON; + int boundLength = boundSignatures[i].length; + System.arraycopy(boundSignatures[i], 0, result, index, boundLength); + index += boundLength; + } + return result; +} +/** + * Creates a new type parameter signature with the given name and bounds. + * + * @param typeParameterName the type parameter name + * @param boundSignatures the signatures of associated bounds or empty array if none + * @return the encoded type parameter signature + * + * @since 3.1 + */ +public static String createTypeParameterSignature(String typeParameterName, String[] boundSignatures) { + int length = boundSignatures.length; + char[][] boundSignatureChars = new char[length][]; + for (int i = 0; i < length; i++) { + boundSignatureChars[i] = boundSignatures[i].toCharArray(); + } + return new String(createTypeParameterSignature(typeParameterName.toCharArray(), boundSignatureChars)); +} + +/** + * Creates a new type signature from the given type name encoded as a character + * array. The type name may contain primitive types, array types or parameterized types. + * This method is equivalent to + * createTypeSignature(new String(typeName),isResolved), although + * more efficient for callers with character arrays rather than strings. If the + * type name is qualified, then it is expected to be dot-based. + * + * @param typeName the possibly qualified type name + * @param isResolved true if the type name is to be considered + * resolved (for example, a type name from a binary class file), and + * false if the type name is to be considered unresolved + * (for example, a type name found in source code) + * @return the encoded type signature + * @see #createTypeSignature(java.lang.String,boolean) + */ +public static String createTypeSignature(char[] typeName, boolean isResolved) { + return new String(createCharArrayTypeSignature(typeName, isResolved)); +} + +/** + * Creates a new type signature from the given type name. If the type name is qualified, + * then it is expected to be dot-based. The type name may contain primitive + * types or array types. However, parameterized types are not supported. + *

+ * For example: + *

+ * 
+ * createTypeSignature("int", hucairz) -> "I"
+ * createTypeSignature("java.lang.String", true) -> "Ljava.lang.String;"
+ * createTypeSignature("String", false) -> "QString;"
+ * createTypeSignature("java.lang.String", false) -> "Qjava.lang.String;"
+ * createTypeSignature("int []", false) -> "[I"
+ * 
+ * 
+ *

+ * + * @param typeName the possibly qualified type name + * @param isResolved true if the type name is to be considered + * resolved (for example, a type name from a binary class file), and + * false if the type name is to be considered unresolved + * (for example, a type name found in source code) + * @return the encoded type signature + */ +public static String createTypeSignature(String typeName, boolean isResolved) { + return createTypeSignature(typeName == null ? null : typeName.toCharArray(), isResolved); +} + +private static int encodeArrayDimension(char[] typeName, int pos, int length, StringBuffer buffer) { + int checkPos; + while (pos < length && (checkPos = checkNextChar(typeName, '[', pos, length, true)) > 0) { + pos = checkNextChar(typeName, ']', checkPos, length, false); + buffer.append(C_ARRAY); + } + return pos; +} + +private static int encodeQualifiedName(char[] typeName, int pos, int length, StringBuffer buffer) { + int count = 0; + char lastAppendedChar = 0; + nameLoop: while (pos < length) { + char currentChar = typeName[pos]; + switch (currentChar) { + case '<' : + case '>' : + case '[' : + case ',' : + break nameLoop; + case '.' : + buffer.append(C_DOT); + lastAppendedChar = C_DOT; + count++; + break; + default: + if (currentChar == ' ' || ScannerHelper.isWhitespace(currentChar)) { + if (lastAppendedChar == C_DOT) { // allow spaces after a dot + pos = consumeWhitespace(typeName, pos, length) - 1; // will be incremented + break; + } + // allow spaces before a dot + int checkPos = checkNextChar(typeName, '.', pos, length, true); + if (checkPos > 0) { + buffer.append(C_DOT); // process dot immediately to avoid one iteration + lastAppendedChar = C_DOT; + count++; + pos = checkPos; + break; + } + break nameLoop; + } + buffer.append(currentChar); + lastAppendedChar = currentChar; + count++; + break; + } + pos++; + } + if (count == 0) throw new IllegalArgumentException(new String(typeName)); + return pos; +} + +private static int encodeTypeSignature(char[] typeName, int start, boolean isResolved, int length, StringBuffer buffer) { + int pos = start; + pos = consumeWhitespace(typeName, pos, length); + if (pos >= length) throw new IllegalArgumentException(new String(typeName)); + int checkPos; + char currentChar = typeName[pos]; + switch (currentChar) { + // primitive type? + case 'b' : + checkPos = checkName(BOOLEAN, typeName, pos, length); + if (checkPos > 0) { + pos = encodeArrayDimension(typeName, checkPos, length, buffer); + buffer.append(C_BOOLEAN); + return pos; + } + checkPos = checkName(BYTE, typeName, pos, length); + if (checkPos > 0) { + pos = encodeArrayDimension(typeName, checkPos, length, buffer); + buffer.append(C_BYTE); + return pos; + } + break; + case 'd': + checkPos = checkName(DOUBLE, typeName, pos, length); + if (checkPos > 0) { + pos = encodeArrayDimension(typeName, checkPos, length, buffer); + buffer.append(C_DOUBLE); + return pos; + } + break; + case 'f': + checkPos = checkName(FLOAT, typeName, pos, length); + if (checkPos > 0) { + pos = encodeArrayDimension(typeName, checkPos, length, buffer); + buffer.append(C_FLOAT); + return pos; + } + break; + case 'i': + checkPos = checkName(INT, typeName, pos, length); + if (checkPos > 0) { + pos = encodeArrayDimension(typeName, checkPos, length, buffer); + buffer.append(C_INT); + return pos; + } + break; + case 'l': + checkPos = checkName(LONG, typeName, pos, length); + if (checkPos > 0) { + pos = encodeArrayDimension(typeName, checkPos, length, buffer); + buffer.append(C_LONG); + return pos; + } + break; + case 's': + checkPos = checkName(SHORT, typeName, pos, length); + if (checkPos > 0) { + pos = encodeArrayDimension(typeName, checkPos, length, buffer); + buffer.append(C_SHORT); + return pos; + } + break; + case 'v': + checkPos = checkName(VOID, typeName, pos, length); + if (checkPos > 0) { + pos = encodeArrayDimension(typeName, checkPos, length, buffer); + buffer.append(C_VOID); + return pos; + } + break; + case 'c': + checkPos = checkName(CHAR, typeName, pos, length); + if (checkPos > 0) { + pos = encodeArrayDimension(typeName, checkPos, length, buffer); + buffer.append(C_CHAR); + return pos; + } else { + checkPos = checkName(CAPTURE, typeName, pos, length); + if (checkPos > 0) { + pos = consumeWhitespace(typeName, checkPos, length); + if (typeName[pos] != '?') { + break; + } + } else { + break; + } + } + buffer.append(C_CAPTURE); + //$FALL-THROUGH$ for wildcard part of capture typecheckPos + case '?': + // wildcard + pos = consumeWhitespace(typeName, pos+1, length); + checkPos = checkName(EXTENDS, typeName, pos, length); + if (checkPos > 0) { + buffer.append(C_EXTENDS); + pos = encodeTypeSignature(typeName, checkPos, isResolved, length, buffer); + return pos; + } + checkPos = checkName(SUPER, typeName, pos, length); + if (checkPos > 0) { + buffer.append(C_SUPER); + pos = encodeTypeSignature(typeName, checkPos, isResolved, length, buffer); + return pos; + } + buffer.append(C_STAR); + return pos; + } + // non primitive type + checkPos = checkArrayDimension(typeName, pos, length); + int end; + if (checkPos > 0) { + end = encodeArrayDimension(typeName, checkPos, length, buffer); + } else { + end = -1; + } + buffer.append(isResolved ? C_RESOLVED : C_UNRESOLVED); + while (true) { // loop on qualifiedName[][.qualifiedName[]* + pos = encodeQualifiedName(typeName, pos, length, buffer); + checkPos = checkNextChar(typeName, '<', pos, length, true); + if (checkPos > 0) { + buffer.append(C_GENERIC_START); + // Stop gap fix for <>. + if ((pos = checkNextChar(typeName, '>', checkPos, length, true)) > 0) { + buffer.append(C_GENERIC_END); + } else { + pos = encodeTypeSignature(typeName, checkPos, isResolved, length, buffer); + while ((checkPos = checkNextChar(typeName, ',', pos, length, true)) > 0) { + pos = encodeTypeSignature(typeName, checkPos, isResolved, length, buffer); + } + pos = checkNextChar(typeName, '>', pos, length, false); + buffer.append(C_GENERIC_END); + } + } + checkPos = checkNextChar(typeName, '.', pos, length, true); + if (checkPos > 0) { + buffer.append(C_DOT); + pos = checkPos; + } else { + break; + } + } + buffer.append(C_NAME_END); + if (end > 0) pos = end; // skip array dimension which were preprocessed + return pos; +} + +/** + * Returns the array count (array nesting depth) of the given type signature. + * + * @param typeSignature the type signature + * @return the array nesting depth, or 0 if not an array + * @exception IllegalArgumentException if the signature is not syntactically + * correct + * + * @since 2.0 + */ +public static int getArrayCount(char[] typeSignature) throws IllegalArgumentException { + try { + int count = 0; + while (typeSignature[count] == C_ARRAY) { + ++count; + } + return count; + } catch (ArrayIndexOutOfBoundsException e) { // signature is syntactically incorrect if last character is C_ARRAY + throw new IllegalArgumentException(); + } +} + +/** + * Returns the array count (array nesting depth) of the given type signature. + * + * @param typeSignature the type signature + * @return the array nesting depth, or 0 if not an array + * @exception IllegalArgumentException if the signature is not syntactically + * correct + */ +public static int getArrayCount(String typeSignature) throws IllegalArgumentException { + return getArrayCount(typeSignature.toCharArray()); +} + +/** + * Returns the type signature without any array nesting. + *

+ * For example: + *

+ * 
+ * getElementType({'[', '[', 'I'}) --> {'I'}.
+ * 
+ * 
+ *

+ * + * @param typeSignature the type signature + * @return the type signature without arrays + * @exception IllegalArgumentException if the signature is not syntactically + * correct + * + * @since 2.0 + */ +public static char[] getElementType(char[] typeSignature) throws IllegalArgumentException { + int count = getArrayCount(typeSignature); + if (count == 0) return typeSignature; + int length = typeSignature.length; + char[] result = new char[length-count]; + System.arraycopy(typeSignature, count, result, 0, length-count); + return result; +} + +/** + * Returns the type signature without any array nesting. + *

+ * For example: + *

+ * 
+ * getElementType("[[I") --> "I".
+ * 
+ * 
+ *

+ * + * @param typeSignature the type signature + * @return the type signature without arrays + * @exception IllegalArgumentException if the signature is not syntactically + * correct + */ +public static String getElementType(String typeSignature) throws IllegalArgumentException { + char[] signature = typeSignature.toCharArray(); + char[] elementType = getElementType(signature); + return signature == elementType ? typeSignature : new String(elementType); +} +/** + * Extracts the type bounds' signatures from the given intersection type signature. + * Returns an empty array if the type signature is not an intersection type signature. + * + * @param intersectionTypeSignature the intersection type signature + * @return the signatures of the type bounds + * @exception IllegalArgumentException if the signature is syntactically incorrect + * + * @since 3.7.1 + */ +public static char[][] getIntersectionTypeBounds(char[] intersectionTypeSignature) throws IllegalArgumentException { + if (getTypeSignatureKind(intersectionTypeSignature) != INTERSECTION_TYPE_SIGNATURE) { + return CharOperation.NO_CHAR_CHAR; + } + ArrayList args = new ArrayList(); + int i = 1; // skip the '|' + int length = intersectionTypeSignature.length; + for (;;) { + int e = Util.scanClassTypeSignature(intersectionTypeSignature, i); + if (e < 0) { + throw new IllegalArgumentException("Invalid format"); //$NON-NLS-1$ + } + args.add(CharOperation.subarray(intersectionTypeSignature, i, e + 1)); + if (e == length - 1) { + int size = args.size(); + char[][] result = new char[size][]; + args.toArray(result); + return result; + } else if (intersectionTypeSignature[e + 1] != C_COLON) { + throw new IllegalArgumentException("Invalid format"); //$NON-NLS-1$ + } + i = e + 2; // add one to skip C_COLON + } +} +/** + * Extracts the type bounds' signatures from the given intersection type signature. + * Returns an empty array if the type signature is not an intersection type signature. + * + * @param intersectionTypeSignature the intersection type signature + * @return the signatures of the type bounds + * @exception IllegalArgumentException if the signature is syntactically incorrect + * + * @since 3.7.1 + */ +public static String[] getIntersectionTypeBounds(String intersectionTypeSignature) throws IllegalArgumentException { + char[][] args = getIntersectionTypeBounds(intersectionTypeSignature.toCharArray()); + return CharOperation.toStrings(args); +} +/** + * Returns the number of parameter types in the given method signature. + * + * @param methodSignature the method signature + * @return the number of parameters + * @exception IllegalArgumentException if the signature is not syntactically + * correct + * @since 2.0 + */ +public static int getParameterCount(char[] methodSignature) throws IllegalArgumentException { + try { + int count = 0; + int i = CharOperation.indexOf(C_PARAM_START, methodSignature); + if (i < 0) { + throw new IllegalArgumentException(); + } else { + i++; + } + for (;;) { + if (methodSignature[i] == C_PARAM_END) { + return count; + } + int e= Util.scanTypeSignature(methodSignature, i); + if (e < 0) { + throw new IllegalArgumentException(); + } else { + i = e + 1; + } + count++; + } + } catch (ArrayIndexOutOfBoundsException e) { + throw new IllegalArgumentException(); + } +} + +/** + * Returns the number of parameter types in the given method signature. + * + * @param methodSignature the method signature + * @return the number of parameters + * @exception IllegalArgumentException if the signature is not syntactically + * correct + */ +public static int getParameterCount(String methodSignature) throws IllegalArgumentException { + return getParameterCount(methodSignature.toCharArray()); +} + +/** + * Extracts the parameter type signatures from the given method signature. + * The method signature is expected to be dot-based. + * + * @param methodSignature the method signature + * @return the list of parameter type signatures + * @exception IllegalArgumentException if the signature is syntactically + * incorrect + * + * @since 2.0 + */ +public static char[][] getParameterTypes(char[] methodSignature) throws IllegalArgumentException { + try { + int count = getParameterCount(methodSignature); + char[][] result = new char[count][]; + if (count == 0) { + return result; + } + int i = CharOperation.indexOf(C_PARAM_START, methodSignature); + if (i < 0) { + throw new IllegalArgumentException(); + } else { + i++; + } + int t = 0; + for (;;) { + if (methodSignature[i] == C_PARAM_END) { + return result; + } + int e = Util.scanTypeSignature(methodSignature, i); + if (e < 0) { + throw new IllegalArgumentException(); + } + result[t] = CharOperation.subarray(methodSignature, i, e + 1); + t++; + i = e + 1; + } + } catch (ArrayIndexOutOfBoundsException e) { + throw new IllegalArgumentException(); + } +} +/** + * Extracts the parameter type signatures from the given method signature. + * The method signature is expected to be dot-based. + * + * @param methodSignature the method signature + * @return the list of parameter type signatures + * @exception IllegalArgumentException if the signature is syntactically + * incorrect + */ +public static String[] getParameterTypes(String methodSignature) throws IllegalArgumentException { + char[][] parameterTypes = getParameterTypes(methodSignature.toCharArray()); + return CharOperation.toStrings(parameterTypes); +} + +/** + * Returns a char array containing all but the last segment of the given + * dot-separated qualified name. Returns the empty char array if it is not qualified. + *

+ * For example: + *

+ * 
+ * getQualifier({'j', 'a', 'v', 'a', '.', 'l', 'a', 'n', 'g', '.', 'O', 'b', 'j', 'e', 'c', 't'}) -> {'j', 'a', 'v', 'a', '.', 'l', 'a', 'n', 'g'}
+ * getQualifier({'O', 'u', 't', 'e', 'r', '.', 'I', 'n', 'n', 'e', 'r'}) -> {'O', 'u', 't', 'e', 'r'}
+ * getQualifier({'j', 'a', 'v', 'a', '.', 'u', 't', 'i', 'l', '.', 'L', 'i', 's', 't', '<', 'j', 'a', 'v', 'a', '.', 'l', 'a', 'n', 'g', '.', 'S', 't', 'r', 'i', 'n', 'g', '>'}) -> {'j', 'a', 'v', 'a', '.', 'u', 't', 'i', 'l'}
+ * 
+ * 
+ *

+ * + * @param name the name + * @return the qualifier prefix, or the empty char array if the name contains no + * dots + * @exception NullPointerException if name is null + * @since 2.0 + */ +public static char[] getQualifier(char[] name) { + int firstGenericStart = CharOperation.indexOf(C_GENERIC_START, name); + int lastDot = CharOperation.lastIndexOf(C_DOT, name, 0, firstGenericStart == -1 ? name.length-1 : firstGenericStart); + if (lastDot == -1) { + return CharOperation.NO_CHAR; + } + return CharOperation.subarray(name, 0, lastDot); +} + +/** + * Returns a string containing all but the last segment of the given + * dot-separated qualified name. Returns the empty string if it is not qualified. + *

+ * For example: + *

+ * 
+ * getQualifier("java.lang.Object") -> "java.lang"
+ * getQualifier("Outer.Inner") -> "Outer"
+ * getQualifier("java.util.List<java.lang.String>") -> "java.util"
+ * 
+ * 
+ *

+ * + * @param name the name + * @return the qualifier prefix, or the empty string if the name contains no + * dots + * @exception NullPointerException if name is null + */ +public static String getQualifier(String name) { + char[] qualifier = getQualifier(name.toCharArray()); + if (qualifier.length == 0) return org.eclipse.jdt.internal.compiler.util.Util.EMPTY_STRING; + return new String(qualifier); +} + +/** + * Extracts the return type from the given method signature. The method signature is + * expected to be dot-based. + * + * @param methodSignature the method signature + * @return the type signature of the return type + * @exception IllegalArgumentException if the signature is syntactically + * incorrect + * + * @since 2.0 + */ +public static char[] getReturnType(char[] methodSignature) throws IllegalArgumentException { + // skip type parameters + int paren = CharOperation.lastIndexOf(C_PARAM_END, methodSignature); + if (paren == -1) { + throw new IllegalArgumentException(); + } + // there could be thrown exceptions behind, thus scan one type exactly + int last = Util.scanTypeSignature(methodSignature, paren+1); + return CharOperation.subarray(methodSignature, paren + 1, last+1); +} + +/** + * Extracts the return type from the given method signature. The method signature is + * expected to be dot-based. + * + * @param methodSignature the method signature + * @return the type signature of the return type + * @exception IllegalArgumentException if the signature is syntactically + * incorrect + */ +public static String getReturnType(String methodSignature) throws IllegalArgumentException { + return new String(getReturnType(methodSignature.toCharArray())); +} + +/** + * Returns package fragment of a type signature. The package fragment separator must be '.' + * and the type fragment separator must be '$'. + *

+ * For example: + *

+ * 
+ * getSignatureQualifier({'L', 'j', 'a', 'v', 'a', '.', 'u', 't', 'i', 'l', '.', 'M', 'a', 'p', '$', 'E', 'n', 't', 'r', 'y', ';'}) -> {'j', 'a', 'v', 'a', '.', 'u', 't', 'i', 'l'}
+ * 
+ * 
+ *

+ * + * @param typeSignature the type signature + * @return the package fragment (separators are '.') + * @since 3.1 + */ +public static char[] getSignatureQualifier(char[] typeSignature) { + if(typeSignature == null) return CharOperation.NO_CHAR; + + char[] qualifiedType = Signature.toCharArray(typeSignature); + + int dotCount = 0; + indexFound: for(int i = 0; i < typeSignature.length; i++) { + switch(typeSignature[i]) { + case C_DOT: + dotCount++; + break; + case C_GENERIC_START: + break indexFound; + case C_DOLLAR: + break indexFound; + } + } + + if(dotCount > 0) { + for(int i = 0; i < qualifiedType.length; i++) { + if(qualifiedType[i] == '.') { + dotCount--; + } + if(dotCount <= 0) { + return CharOperation.subarray(qualifiedType, 0, i); + } + } + } + return CharOperation.NO_CHAR; +} +/** + * Returns package fragment of a type signature. The package fragment separator must be '.' + * and the type fragment separator must be '$'. + *

+ * For example: + *

+ * 
+ * getSignatureQualifier("Ljava.util.Map$Entry") -> "java.util"
+ * 
+ * 
+ *

+ * + * @param typeSignature the type signature + * @return the package fragment (separators are '.') + * @since 3.1 + */ +public static String getSignatureQualifier(String typeSignature) { + return new String(getSignatureQualifier(typeSignature == null ? null : typeSignature.toCharArray())); +} +/** + * Returns type fragment of a type signature. The package fragment separator must be '.' + * and the type fragment separator must be '$'. + *

+ * For example: + *

+ * 
+ * getSignatureSimpleName({'L', 'j', 'a', 'v', 'a', '.', 'u', 't', 'i', 'l', '.', 'M', 'a', 'p', '$', 'E', 'n', 't', 'r', 'y', ';'}) -> {'M', 'a', 'p', '.', 'E', 'n', 't', 'r', 'y'}
+ * 
+ * 
+ *

+ * + * @param typeSignature the type signature + * @return the type fragment (separators are '.') + * @since 3.1 + */ +public static char[] getSignatureSimpleName(char[] typeSignature) { + if(typeSignature == null) return CharOperation.NO_CHAR; + + char[] qualifiedType = Signature.toCharArray(typeSignature); + + int dotCount = 0; + indexFound: for(int i = 0; i < typeSignature.length; i++) { + switch(typeSignature[i]) { + case C_DOT: + dotCount++; + break; + case C_GENERIC_START: + break indexFound; + case C_DOLLAR: + break indexFound; + } + } + + if(dotCount > 0) { + int typeStart = 0; + for(int i = 0; i < qualifiedType.length; i++) { + switch (qualifiedType[i]) { + case '.': + dotCount--; + break; + case ' ': + typeStart = i+1; + break; + } + if(dotCount <= 0) { + char[] simpleName = CharOperation.subarray(qualifiedType, i + 1, qualifiedType.length); + if (typeStart > 0 && typeStart < qualifiedType.length) + return CharOperation.concat(CharOperation.subarray(qualifiedType, 0, typeStart), simpleName); + return simpleName; + } + } + } + return qualifiedType; +} +/** + * Returns type fragment of a type signature. The package fragment separator must be '.' + * and the type fragment separator must be '$'. + *

+ * For example: + *

+ * 
+ * getSignatureSimpleName("Ljava.util.Map$Entry") -> "Map.Entry"
+ * 
+ * 
+ *

+ * + * @param typeSignature the type signature + * @return the type fragment (separators are '.') + * @since 3.1 + */ +public static String getSignatureSimpleName(String typeSignature) { + return new String(getSignatureSimpleName(typeSignature == null ? null : typeSignature.toCharArray())); +} +/** + * Returns the last segment of the given dot-separated qualified name. + * Returns the given name if it is not qualified. + *

+ * For example: + *

+ * 
+ * getSimpleName({'j', 'a', 'v', 'a', '.', 'l', 'a', 'n', 'g', '.', 'O', 'b', 'j', 'e', 'c', 't'}) -> {'O', 'b', 'j', 'e', 'c', 't'}
+ * 
+ * 
+ *

+ * + * @param name the name + * @return the last segment of the qualified name + * @exception NullPointerException if name is null + * @since 2.0 + */ +public static char[] getSimpleName(char[] name) { + + int lastDot = -1, lastGenericStart = -1, lastGenericEnd = -1; + int depth = 0; + int length = name.length; + lastDotLookup: for (int i = length -1; i >= 0; i--) { + switch (name[i]) { + case '.': + if (depth == 0) { + lastDot = i; + break lastDotLookup; + } + break; + case '<': + depth--; + if (depth == 0) lastGenericStart = i; + break; + case '>': + if (depth == 0) lastGenericEnd = i; + depth++; + break; + } + } + if (lastGenericStart < 0) { + if (lastDot < 0) { + return name; + } + return CharOperation.subarray(name, lastDot + 1, length); + } + StringBuffer buffer = new StringBuffer(10); + int nameStart = lastDot < 0 ? 0 : lastDot+1; + buffer.append(name, nameStart, lastGenericStart - nameStart); + appendArgumentSimpleNames(name, lastGenericStart, lastGenericEnd, buffer); + buffer.append(name, lastGenericEnd+1, length-lastGenericEnd-1); // copy trailing portion, may contain dimensions + char[] result = new char[length = buffer.length()]; + buffer.getChars(0, length, result, 0); + return result; +} +/** + * Returns the last segment of the given dot-separated qualified name. + * Returns the given name if it is not qualified. + *

+ * For example: + *

+ * 
+ * getSimpleName("java.lang.Object") -> "Object"
+ * 
+ * 
+ * getSimpleName("java.util.Map<java.lang.String, java.lang.Object>") -> "Map<String,Object>"
+ * 
+ * 
+ *

+ * + * @param name the name + * @return the last segment of the qualified name + * @exception NullPointerException if name is null + */ +public static String getSimpleName(String name) { + int lastDot = -1, lastGenericStart = -1, lastGenericEnd = -1; + int depth = 0; + int length = name.length(); + lastDotLookup: for (int i = length -1; i >= 0; i--) { + switch (name.charAt(i)) { + case '.': + if (depth == 0) { + lastDot = i; + break lastDotLookup; + } + break; + case '<': + depth--; + if (depth == 0) lastGenericStart = i; + break; + case '>': + if (depth == 0) lastGenericEnd = i; + depth++; + break; + } + } + if (lastGenericStart < 0) { + if (lastDot < 0) { + return name; + } + return name.substring(lastDot + 1, length); + } + StringBuffer buffer = new StringBuffer(10); + char[] nameChars = name.toCharArray(); + int nameStart = lastDot < 0 ? 0 : lastDot+1; + buffer.append(nameChars, nameStart, lastGenericStart - nameStart); + appendArgumentSimpleNames(nameChars, lastGenericStart, lastGenericEnd, buffer); + buffer.append(nameChars, lastGenericEnd+1, length-lastGenericEnd-1); // copy trailing portion, may contain dimensions + return buffer.toString(); +} +/** + * Returns all segments of the given dot-separated qualified name. + * Returns an array with only the given name if it is not qualified. + * Returns an empty array if the name is empty. + *

+ * For example: + *

+ * 
+ * getSimpleNames({'j', 'a', 'v', 'a', '.', 'l', 'a', 'n', 'g', '.', 'O', 'b', 'j', 'e', 'c', 't'}) -> {{'j', 'a', 'v', 'a'}, {'l', 'a', 'n', 'g'}, {'O', 'b', 'j', 'e', 'c', 't'}}
+ * getSimpleNames({'O', 'b', 'j', 'e', 'c', 't'}) -> {{'O', 'b', 'j', 'e', 'c', 't'}}
+ * getSimpleNames({}) -> {}
+ * getSimpleNames({'j', 'a', 'v', 'a', '.', 'u', 't', 'i', 'l', '.', 'L', 'i', 's', 't', '<', 'j', 'a', 'v', 'a', '.', 'l', 'a', 'n', 'g', '.', 'S', 't', 'r', 'i', 'n', 'g', '>'}) -> {{'j', 'a', 'v', 'a'}, {'l', 'a', 'n', 'g'}, {'L', 'i', 's', 't', '<', 'j', 'a', 'v', 'a', '.', 'l', 'a', 'n', 'g', '.', 'S', 't', 'r', 'i', 'n', 'g'}}
+ * 
+ * 
+ * + * @param name the name + * @return the list of simple names, possibly empty + * @exception NullPointerException if name is null + * @since 2.0 + */ +public static char[][] getSimpleNames(char[] name) { + int length = name == null ? 0 : name.length; + if (length == 0) + return CharOperation.NO_CHAR_CHAR; + + int wordCount = 1; + countingWords: for (int i = 0; i < length; i++) + switch(name[i]) { + case C_DOT: + wordCount++; + break; + case C_GENERIC_START: + break countingWords; + } + char[][] split = new char[wordCount][]; + int last = 0, currentWord = 0; + for (int i = 0; i < length; i++) { + if (name[i] == C_GENERIC_START) break; + if (name[i] == C_DOT) { + split[currentWord] = new char[i - last]; + System.arraycopy( + name, + last, + split[currentWord++], + 0, + i - last); + last = i + 1; + } + } + split[currentWord] = new char[length - last]; + System.arraycopy(name, last, split[currentWord], 0, length - last); + return split; +} +/** + * Returns all segments of the given dot-separated qualified name. + * Returns an array with only the given name if it is not qualified. + * Returns an empty array if the name is empty. + *

+ * For example: + *

+ * 
+ * getSimpleNames("java.lang.Object") -> {"java", "lang", "Object"}
+ * getSimpleNames("Object") -> {"Object"}
+ * getSimpleNames("") -> {}
+ * getSimpleNames("java.util.List<java.lang.String>") ->
+ *   {"java", "util", "List<java.lang.String>"}
+ * 
+ * 
+ * + * @param name the name + * @return the list of simple names, possibly empty + * @exception NullPointerException if name is null + */ +public static String[] getSimpleNames(String name) { + return CharOperation.toStrings(getSimpleNames(name.toCharArray())); +} + +/** + * Extracts the thrown exception type signatures from the given method signature if any + * The method signature is expected to be dot-based. + * + * @param methodSignature the method signature + * @return the list of thrown exception type signatures + * @exception IllegalArgumentException if the signature is syntactically + * incorrect + * + * @since 3.1 + */ +public static char[][] getThrownExceptionTypes(char[] methodSignature) throws IllegalArgumentException { + // skip type parameters + int exceptionStart = CharOperation.indexOf(C_EXCEPTION_START, methodSignature); + if (exceptionStart == -1) { + int paren = CharOperation.lastIndexOf(C_PARAM_END, methodSignature); + if (paren == -1) { + throw new IllegalArgumentException(); + } + // ignore return type + exceptionStart = Util.scanTypeSignature(methodSignature, paren+1) + 1; + int length = methodSignature.length; + if (exceptionStart == length) return CharOperation.NO_CHAR_CHAR; + throw new IllegalArgumentException(); + } + int length = methodSignature.length; + int i = exceptionStart; + ArrayList exceptionList = new ArrayList(1); + while (i < length) { + if (methodSignature[i] == C_EXCEPTION_START) { + exceptionStart++; + i++; + } else { + throw new IllegalArgumentException(); + } + i = Util.scanTypeSignature(methodSignature, i) + 1; + exceptionList.add(CharOperation.subarray(methodSignature, exceptionStart,i)); + exceptionStart = i; + } + char[][] result; + exceptionList.toArray(result = new char[exceptionList.size()][]); + return result; +} +/** + * Extracts the thrown exception type signatures from the given method signature if any + * The method signature is expected to be dot-based. + * + * @param methodSignature the method signature + * @return the list of thrown exception type signatures + * @exception IllegalArgumentException if the signature is syntactically + * incorrect + * + * @since 3.1 + */ +public static String[] getThrownExceptionTypes(String methodSignature) throws IllegalArgumentException { + char[][] parameterTypes = getThrownExceptionTypes(methodSignature.toCharArray()); + return CharOperation.toStrings(parameterTypes); +} +/** + * Extracts the type argument signatures from the given type signature. + * Returns an empty array if the type signature is not a parameterized type signature. + * + * @param parameterizedTypeSignature the parameterized type signature + * @return the signatures of the type arguments + * @exception IllegalArgumentException if the signature is syntactically incorrect + * + * @since 3.1 + */ +public static char[][] getTypeArguments(char[] parameterizedTypeSignature) throws IllegalArgumentException { + int length = parameterizedTypeSignature.length; + if (length < 2 || parameterizedTypeSignature[length-2] != C_GENERIC_END) + // cannot have type arguments otherwise signature would end by ">;" + return CharOperation.NO_CHAR_CHAR; + int count = 1; // start to count generic end/start peers + int start = length - 2; + while (start >= 0 && count > 0) { + switch (parameterizedTypeSignature[--start]) { + case C_GENERIC_START: + count--; + break; + case C_GENERIC_END: + count++; + break; + } + } + if (start < 0) // invalid number of generic start/end + throw new IllegalArgumentException(); + ArrayList args = new ArrayList(); + int p = start + 1; + while (true) { + if (p >= parameterizedTypeSignature.length) { + throw new IllegalArgumentException(); + } + char c = parameterizedTypeSignature[p]; + if (c == C_GENERIC_END) { + int size = args.size(); + char[][] result = new char[size][]; + args.toArray(result); + return result; + } + int e = Util.scanTypeArgumentSignature(parameterizedTypeSignature, p); + args.add(CharOperation.subarray(parameterizedTypeSignature, p, e+1)); + p = e + 1; + } +} +/** + * Extracts the type argument signatures from the given type signature. + * Returns an empty array if the type signature is not a parameterized type signature. + * + * @param parameterizedTypeSignature the parameterized type signature + * @return the signatures of the type arguments + * @exception IllegalArgumentException if the signature is syntactically incorrect + * + * @since 3.1 + */ +public static String[] getTypeArguments(String parameterizedTypeSignature) throws IllegalArgumentException { + char[][] args = getTypeArguments(parameterizedTypeSignature.toCharArray()); + return CharOperation.toStrings(args); +} +/** + * Extracts the type erasure signature from the given parameterized type signature. + * Returns the given type signature if it is not parameterized. + * + * @param parameterizedTypeSignature the parameterized type signature + * @return the signature of the type erasure + * @exception IllegalArgumentException if the signature is syntactically + * incorrect + * + * @since 3.1 + */ +public static char[] getTypeErasure(char[] parameterizedTypeSignature) throws IllegalArgumentException { + int end = CharOperation.indexOf(C_GENERIC_START, parameterizedTypeSignature); + if (end == -1) return parameterizedTypeSignature; + int length = parameterizedTypeSignature.length; + char[] result = new char[length]; + int pos = 0; + int start = 0; + int deep= 0; + for (int idx=end; idx 0) throw new IllegalArgumentException(); + int size = pos+length-start; + char[] resized = new char[size]; + System.arraycopy(result, 0, resized, 0, pos); + System.arraycopy(parameterizedTypeSignature, start, resized, pos, length-start); + return resized; +} +/** + * Extracts the type erasure signature from the given parameterized type signature. + * Returns the given type signature if it is not parameterized. + * + * @param parameterizedTypeSignature the parameterized type signature + * @return the signature of the type erasure + * @exception IllegalArgumentException if the signature is syntactically + * incorrect + * + * @since 3.1 + */ +public static String getTypeErasure(String parameterizedTypeSignature) throws IllegalArgumentException { + char[] signature = parameterizedTypeSignature.toCharArray(); + char[] erasure = getTypeErasure(signature); + return signature == erasure ? parameterizedTypeSignature : new String(erasure); +} + +/** + * Extracts the class and interface bounds from the given formal type + * parameter signature. The class bound, if present, is listed before + * the interface bounds. The signature is expected to be dot-based. + * + * @param formalTypeParameterSignature the formal type parameter signature + * @return the (possibly empty) list of type signatures for the bounds + * @exception IllegalArgumentException if the signature is syntactically + * incorrect + * @since 3.0 + */ +public static char[][] getTypeParameterBounds(char[] formalTypeParameterSignature) throws IllegalArgumentException { + int p1 = CharOperation.indexOf(C_COLON, formalTypeParameterSignature); + if (p1 < 0) { + // no ":" means can't be a formal type parameter signature + throw new IllegalArgumentException(); + } + if (p1 == formalTypeParameterSignature.length - 1) { + // no class or interface bounds + return CharOperation.NO_CHAR_CHAR; + } + int p2 = CharOperation.indexOf(C_COLON, formalTypeParameterSignature, p1 + 1); + char[] classBound; + if (p2 < 0) { + // no interface bounds + classBound = CharOperation.subarray(formalTypeParameterSignature, p1 + 1, formalTypeParameterSignature.length); + return new char[][] {classBound}; + } + if (p2 == p1 + 1) { + // no class bound, but 1 or more interface bounds + classBound = null; + } else { + classBound = CharOperation.subarray(formalTypeParameterSignature, p1 + 1, p2); + } + char[][] interfaceBounds = CharOperation.splitOn(C_COLON, formalTypeParameterSignature, p2 + 1, formalTypeParameterSignature.length); + if (classBound == null) { + return interfaceBounds; + } + int resultLength = interfaceBounds.length + 1; + char[][] result = new char[resultLength][]; + result[0] = classBound; + System.arraycopy(interfaceBounds, 0, result, 1, interfaceBounds.length); + return result; +} + +/** + * Extracts the class and interface bounds from the given formal type + * parameter signature. The class bound, if present, is listed before + * the interface bounds. The signature is expected to be dot-based. + * + * @param formalTypeParameterSignature the formal type parameter signature + * @return the (possibly empty) list of type signatures for the bounds + * @exception IllegalArgumentException if the signature is syntactically + * incorrect + * @since 3.0 + */ +public static String[] getTypeParameterBounds(String formalTypeParameterSignature) throws IllegalArgumentException { + char[][] bounds = getTypeParameterBounds(formalTypeParameterSignature.toCharArray()); + return CharOperation.toStrings(bounds); +} + +/** + * Extracts the type parameter signatures from the given method or type signature. + * The method or type signature is expected to be dot-based. + * + * @param methodOrTypeSignature the method or type signature + * @return the list of type parameter signatures + * @exception IllegalArgumentException if the signature is syntactically + * incorrect + * + * @since 3.1 + */ +public static char[][] getTypeParameters(char[] methodOrTypeSignature) throws IllegalArgumentException { + try { + int length = methodOrTypeSignature.length; + if (length == 0) return CharOperation.NO_CHAR_CHAR; + if (methodOrTypeSignature[0] != C_GENERIC_START) return CharOperation.NO_CHAR_CHAR; + + ArrayList paramList = new ArrayList(1); + int paramStart = 1, i = 1; // start after leading '<' + while (i < length) { + if (methodOrTypeSignature[i] == C_GENERIC_END) { + int size = paramList.size(); + if (size == 0) throw new IllegalArgumentException(); + char[][] result; + paramList.toArray(result = new char[size][]); + return result; + } + i = CharOperation.indexOf(C_COLON, methodOrTypeSignature, i); + if (i < 0 || i >= length) + throw new IllegalArgumentException(); + // iterate over bounds + while (methodOrTypeSignature[i] == ':') { + i++; // skip colon + switch (methodOrTypeSignature[i]) { + case ':': + // no class bound + break; + case C_GENERIC_END: + break; + case C_RESOLVED: + try { + i = Util.scanClassTypeSignature(methodOrTypeSignature, i); + i++; // position at start of next param if any + } catch (IllegalArgumentException e) { + // not a class type signature -> it is a new type parameter + } + break; + case C_ARRAY: + try { + i = Util.scanArrayTypeSignature(methodOrTypeSignature, i); + i++; // position at start of next param if any + } catch (IllegalArgumentException e) { + // not an array type signature -> it is a new type parameter + } + break; + case C_TYPE_VARIABLE: + try { + i = Util.scanTypeVariableSignature(methodOrTypeSignature, i); + i++; // position at start of next param if any + } catch (IllegalArgumentException e) { + // not a type variable signature -> it is a new type parameter + } + break; + case C_CAPTURE: + try { + i = Util.scanCaptureTypeSignature(methodOrTypeSignature, i); + i++; // position at start of next param if any + } catch (IllegalArgumentException e) { + // not a capture variable signature -> it is a new type parameter + } + break; + // default: another type parameter is starting + } + } + paramList.add(CharOperation.subarray(methodOrTypeSignature, paramStart, i)); + paramStart = i; // next param start from here + } + } catch (ArrayIndexOutOfBoundsException e) { + // invalid signature, fall through + } + throw new IllegalArgumentException(); +} +/** + * Extracts the type parameter signatures from the given method or type signature. + * The method or type signature is expected to be dot-based. + * + * @param methodOrTypeSignature the method or type signature + * @return the list of type parameter signatures + * @exception IllegalArgumentException if the signature is syntactically + * incorrect + * + * @since 3.1 + */ +public static String[] getTypeParameters(String methodOrTypeSignature) throws IllegalArgumentException { + char[][] params = getTypeParameters(methodOrTypeSignature.toCharArray()); + return CharOperation.toStrings(params); +} +/** + * Returns the kind of type signature encoded by the given string. + * + * @param typeSignature the type signature string + * @return the kind of type signature; one of the kind constants: + * {@link #ARRAY_TYPE_SIGNATURE}, {@link #CLASS_TYPE_SIGNATURE}, + * {@link #BASE_TYPE_SIGNATURE}, or {@link #TYPE_VARIABLE_SIGNATURE}, + * or (since 3.1) {@link #WILDCARD_TYPE_SIGNATURE} or {@link #CAPTURE_TYPE_SIGNATURE}, + * or (since 3.7) {@link #INTERSECTION_TYPE_SIGNATURE} + * @exception IllegalArgumentException if this is not a type signature + * @since 3.0 + */ +public static int getTypeSignatureKind(char[] typeSignature) { + // need a minimum 1 char + if (typeSignature.length < 1) { + throw new IllegalArgumentException(); + } + char c = typeSignature[0]; + if (c == C_GENERIC_START) { + int count = 1; + for (int i = 1, length = typeSignature.length; i < length; i++) { + switch (typeSignature[i]) { + case C_GENERIC_START: + count++; + break; + case C_GENERIC_END: + count--; + break; + } + if (count == 0) { + if (i+1 < length) + c = typeSignature[i+1]; + break; + } + } + } + switch (c) { + case C_ARRAY : + return ARRAY_TYPE_SIGNATURE; + case C_RESOLVED : + case C_UNRESOLVED : + return CLASS_TYPE_SIGNATURE; + case C_TYPE_VARIABLE : + return TYPE_VARIABLE_SIGNATURE; + case C_BOOLEAN : + case C_BYTE : + case C_CHAR : + case C_DOUBLE : + case C_FLOAT : + case C_INT : + case C_LONG : + case C_SHORT : + case C_VOID : + return BASE_TYPE_SIGNATURE; + case C_STAR : + case C_SUPER : + case C_EXTENDS : + return WILDCARD_TYPE_SIGNATURE; + case C_CAPTURE : + return CAPTURE_TYPE_SIGNATURE; + case C_INTERSECTION : + return INTERSECTION_TYPE_SIGNATURE; + default : + throw new IllegalArgumentException(); + } +} + +/** + * Returns the kind of type signature encoded by the given string. + * + * @param typeSignature the type signature string + * @return the kind of type signature; one of the kind constants: + * {@link #ARRAY_TYPE_SIGNATURE}, {@link #CLASS_TYPE_SIGNATURE}, + * {@link #BASE_TYPE_SIGNATURE}, or {@link #TYPE_VARIABLE_SIGNATURE}, + * or (since 3.1) {@link #WILDCARD_TYPE_SIGNATURE} or {@link #CAPTURE_TYPE_SIGNATURE} + * or (since 3.7) {@link #INTERSECTION_TYPE_SIGNATURE} + * @exception IllegalArgumentException if this is not a type signature + * @since 3.0 + */ +public static int getTypeSignatureKind(String typeSignature) { + return getTypeSignatureKind(typeSignature.toCharArray()); +} +/** + * Extracts the type variable name from the given formal type parameter + * signature. The signature is expected to be dot-based. + * + * @param formalTypeParameterSignature the formal type parameter signature + * @return the name of the type variable + * @exception IllegalArgumentException if the signature is syntactically + * incorrect + * @since 3.0 + */ +public static char[] getTypeVariable(char[] formalTypeParameterSignature) throws IllegalArgumentException { + int p = CharOperation.indexOf(C_COLON, formalTypeParameterSignature); + if (p < 0) { + // no ":" means can't be a formal type parameter signature + throw new IllegalArgumentException(); + } + return CharOperation.subarray(formalTypeParameterSignature, 0, p); +} +/** + * Extracts the type variable name from the given formal type parameter + * signature. The signature is expected to be dot-based. + * + * @param formalTypeParameterSignature the formal type parameter signature + * @return the name of the type variable + * @exception IllegalArgumentException if the signature is syntactically + * incorrect + * @since 3.0 + */ +public static String getTypeVariable(String formalTypeParameterSignature) throws IllegalArgumentException { + return new String(getTypeVariable(formalTypeParameterSignature.toCharArray())); +} +/** + * Removes any capture information from the given type or method signature + * and returns the resulting signature. + * Returns the type or method signature itself if no capture information is + * present. + *

+ * For example (using equivalent string-based method): + *

+ * 
+ * removeCapture("LTest<!+Ljava.lang.Throwable;>;")
+ * will return: "LTest<+Ljava.lang.Throwable;>;"
+ * 
+ * 
+ *

+ * + * @param methodOrTypeSignature the signature which may have been captured + * @return a new signature without capture information or the signature itself + * if no specific capture information is present + * @exception NullPointerException if methodOrTypeSignature is null + * + * @since 3.1 + */ +public static char[] removeCapture(char[] methodOrTypeSignature) { + return CharOperation.remove(methodOrTypeSignature, C_CAPTURE); +} +/** + * Removes any capture information from the given type or method signature + * and returns the resulting signature. + * Returns the type or method signature itself if no capture information is + * present. + *

+ * For example: + *

+ * 
+ * removeCapture("LTest<!+Ljava.lang.Throwable;>;")
+ * will return: "LTest<+Ljava.lang.Throwable;>;"
+ * 
+ * 
+ *

+ * + * @param methodOrTypeSignature the signature which may have been captured + * @return a new signature without capture information or the signature itself + * if no specific capture information is present + * @exception NullPointerException if methodOrTypeSignature is null + * + * @since 3.1 + */ +public static String removeCapture(String methodOrTypeSignature) { + char[] array = methodOrTypeSignature.toCharArray(); + char[] result = removeCapture(array); + if (array == result) return methodOrTypeSignature; + return new String(result); +} +/** + * Converts the given type signature to a readable string. The signature is expected to + * be dot-based. + * + *

+ * For example: + *

+ * 
+ * toString({'[', 'L', 'j', 'a', 'v', 'a', '.', 'l', 'a', 'n', 'g', '.', 'S', 't', 'r', 'i', 'n', 'g', ';'}) -> {'j', 'a', 'v', 'a', '.', 'l', 'a', 'n', 'g', '.', 'S', 't', 'r', 'i', 'n', 'g', '[', ']'}
+ * toString({'I'}) -> {'i', 'n', 't'}
+ * toString({'+', 'L', 'O', 'b', 'j', 'e', 'c', 't', ';'}) -> {'?', ' ', 'e', 'x', 't', 'e', 'n', 'd', 's', ' ', 'O', 'b', 'j', 'e', 'c', 't'}
+ * 
+ * 
+ *

+ *

+ * Note: This method assumes that a type signature containing a '$' + * is an inner type signature. While this is correct in most cases, someone could + * define a non-inner type name containing a '$'. Handling this + * correctly in all cases would have required resolving the signature, which + * generally not feasible. + *

+ * + * @param signature the type signature + * @return the string representation of the type + * @exception IllegalArgumentException if the signature is syntactically incorrect + * + * @since 2.0 + */ +public static char[] toCharArray(char[] signature) throws IllegalArgumentException { + int sigLength = signature.length; + if (sigLength == 0) { + throw new IllegalArgumentException(); + } + if (signature[0] == C_PARAM_START || signature[0] == C_GENERIC_START) { + return toCharArray(signature, CharOperation.NO_CHAR, null, true, true); + } + + StringBuffer buffer = new StringBuffer(signature.length + 10); + appendTypeSignature(signature, 0, true, buffer); + char[] result = new char[buffer.length()]; + buffer.getChars(0, buffer.length(), result, 0); + return result; +} +/** + * Converts the given method signature to a readable form. The method signature is expected to + * be dot-based. + *

+ * For example: + *

+ * 
+ * toString("([Ljava.lang.String;)V", "main", new String[] {"args"}, false, true) -> "void main(String[] args)"
+ * 
+ * 
+ *

+ * + * @param methodSignature the method signature to convert + * @param methodName the name of the method to insert in the result, or + * null if no method name is to be included + * @param parameterNames the parameter names to insert in the result, or + * null if no parameter names are to be included; if supplied, + * the number of parameter names must match that of the method signature + * @param fullyQualifyTypeNames true if type names should be fully + * qualified, and false to use only simple names + * @param includeReturnType true if the return type is to be + * included + * @return the char array representation of the method signature + * @throws IllegalArgumentException if the method signature is syntactically incorrect + * @since 2.0 + */ +public static char[] toCharArray(char[] methodSignature, char[] methodName, char[][] parameterNames, boolean fullyQualifyTypeNames, boolean includeReturnType) { + return toCharArray(methodSignature, methodName, parameterNames, fullyQualifyTypeNames, includeReturnType, false); +} + +/** + * Converts the given method signature to a readable form. The method signature is expected to + * be dot-based. + *

+ * For example: + *

+ * 
+ * toString("([Ljava.lang.String;)V", "main", new String[] {"args"}, false, true) -> "void main(String[] args)"
+ * 
+ * 
+ *

+ * + * @param methodSignature the method signature to convert + * @param methodName the name of the method to insert in the result, or + * null if no method name is to be included + * @param parameterNames the parameter names to insert in the result, or + * null if no parameter names are to be included; if supplied, + * the number of parameter names must match that of the method signature + * @param fullyQualifyTypeNames true if type names should be fully + * qualified, and false to use only simple names + * @param includeReturnType true if the return type is to be + * included + * @param isVargArgs true if the last argument should be displayed as a + * variable argument, false otherwise. + * @return the char array representation of the method signature + * @throws IllegalArgumentException if the method signature is syntactically incorrect + * + * @since 3.1 + */ +public static char[] toCharArray(char[] methodSignature, char[] methodName, char[][] parameterNames, boolean fullyQualifyTypeNames, boolean includeReturnType, boolean isVargArgs) { + int firstParen = CharOperation.indexOf(C_PARAM_START, methodSignature); + if (firstParen == -1) { + throw new IllegalArgumentException(); + } + + StringBuffer buffer = new StringBuffer(methodSignature.length + 10); + + // return type + if (includeReturnType) { + char[] rts = getReturnType(methodSignature); + appendTypeSignature(rts, 0 , fullyQualifyTypeNames, buffer); + buffer.append(' '); + } + + // selector + if (methodName != null) { + buffer.append(methodName); + } + + // parameters + buffer.append('('); + char[][] pts = getParameterTypes(methodSignature); + // search for the last array in the signature + int max = pts.length; + int index = max - 1; + loop: for (int i = index; i >= 0; i--) { + if (pts[i][0] == Signature.C_ARRAY) { + break loop; + } + index--; + } + for (int i = 0; i < max; i++) { + if (i == index) { + appendTypeSignature(pts[i], 0 , fullyQualifyTypeNames, buffer, isVargArgs); + } else { + appendTypeSignature(pts[i], 0 , fullyQualifyTypeNames, buffer); + } + if (parameterNames != null) { + buffer.append(' '); + buffer.append(parameterNames[i]); + } + if (i != pts.length - 1) { + buffer.append(','); + buffer.append(' '); + } + } + buffer.append(')'); + char[] result = new char[buffer.length()]; + buffer.getChars(0, buffer.length(), result, 0); + return result; +} + +/** + * Converts the given array of qualified name segments to a qualified name. + *

+ * For example: + *

+ * 
+ * toQualifiedName({{'j', 'a', 'v', 'a'}, {'l', 'a', 'n', 'g'}, {'O', 'b', 'j', 'e', 'c', 't'}}) -> {'j', 'a', 'v', 'a', '.', 'l', 'a', 'n', 'g', '.', 'O', 'b', 'j', 'e', 'c', 't'}
+ * toQualifiedName({{'O', 'b', 'j', 'e', 'c', 't'}}) -> {'O', 'b', 'j', 'e', 'c', 't'}
+ * toQualifiedName({{}}) -> {}
+ * 
+ * 
+ *

+ * + * @param segments the list of name segments, possibly empty + * @return the dot-separated qualified name, or the empty string + * + * @since 2.0 + */ +public static char[] toQualifiedName(char[][] segments) { + int length = segments.length; + if (length == 0) return CharOperation.NO_CHAR; + if (length == 1) return segments[0]; + + int resultLength = 0; + for (int i = 0; i < length; i++) { + resultLength += segments[i].length+1; + } + resultLength--; + char[] result = new char[resultLength]; + int index = 0; + for (int i = 0; i < length; i++) { + char[] segment = segments[i]; + int segmentLength = segment.length; + System.arraycopy(segment, 0, result, index, segmentLength); + index += segmentLength; + if (i != length-1) { + result[index++] = C_DOT; + } + } + return result; +} + +/** + * Converts the given array of qualified name segments to a qualified name. + *

+ * For example: + *

+ * 
+ * toQualifiedName(new String[] {"java", "lang", "Object"}) -> "java.lang.Object"
+ * toQualifiedName(new String[] {"Object"}) -> "Object"
+ * toQualifiedName(new String[0]) -> ""
+ * 
+ * 
+ *

+ * + * @param segments the list of name segments, possibly empty + * @return the dot-separated qualified name, or the empty string + */ +public static String toQualifiedName(String[] segments) { + int length = segments.length; + char[][] charArrays = new char[length][]; + for (int i = 0; i < length; i++) { + charArrays[i] = segments[i].toCharArray(); + } + return new String(toQualifiedName(charArrays)); +} +/** + * Converts the given type signature to a readable string. The signature is expected to + * be dot-based. + * + *

+ * For example: + *

+ * 
+ * toString("[Ljava.lang.String;") -> "java.lang.String[]"
+ * toString("I") -> "int"
+ * toString("+QObject;") -> "? extends Object"
+ * 
+ * 
+ *

+ *

+ * Note: This method assumes that a type signature containing a '$' + * is an inner type signature. While this is correct in most cases, someone could + * define a non-inner type name containing a '$'. Handling this + * correctly in all cases would have required resolving the signature, which + * generally not feasible. + *

+ * + * @param signature the type signature + * @return the string representation of the type + * @exception IllegalArgumentException if the signature is not syntactically + * correct + */ +public static String toString(String signature) throws IllegalArgumentException { + return new String(toCharArray(signature.toCharArray())); +} +/** + * Converts the given method signature to a readable string. The method signature is expected to + * be dot-based. + * + * @param methodSignature the method signature to convert + * @param methodName the name of the method to insert in the result, or + * null if no method name is to be included + * @param parameterNames the parameter names to insert in the result, or + * null if no parameter names are to be included; if supplied, + * the number of parameter names must match that of the method signature + * @param fullyQualifyTypeNames true if type names should be fully + * qualified, and false to use only simple names + * @param includeReturnType true if the return type is to be + * included + * @see #toCharArray(char[], char[], char[][], boolean, boolean) + * @return the string representation of the method signature + */ +public static String toString(String methodSignature, String methodName, String[] parameterNames, boolean fullyQualifyTypeNames, boolean includeReturnType) { + return toString(methodSignature, methodName, parameterNames, fullyQualifyTypeNames, includeReturnType, false); +} +/** + * Converts the given method signature to a readable string. The method signature is expected to + * be dot-based. + * + * @param methodSignature the method signature to convert + * @param methodName the name of the method to insert in the result, or + * null if no method name is to be included + * @param parameterNames the parameter names to insert in the result, or + * null if no parameter names are to be included; if supplied, + * the number of parameter names must match that of the method signature + * @param fullyQualifyTypeNames true if type names should be fully + * qualified, and false to use only simple names + * @param includeReturnType true if the return type is to be + * included + * @param isVarArgs true if the last argument should be displayed as a + * variable argument, false otherwise + * @see #toCharArray(char[], char[], char[][], boolean, boolean) + * @return the string representation of the method signature + * + * @since 3.1 + */ +public static String toString(String methodSignature, String methodName, String[] parameterNames, boolean fullyQualifyTypeNames, boolean includeReturnType, boolean isVarArgs) { + char[][] params; + if (parameterNames == null) { + params = null; + } else { + int paramLength = parameterNames.length; + params = new char[paramLength][]; + for (int i = 0; i < paramLength; i++) { + params[i] = parameterNames[i].toCharArray(); + } + } + return new String(toCharArray(methodSignature.toCharArray(), methodName == null ? null : methodName.toCharArray(), params, fullyQualifyTypeNames, includeReturnType, isVarArgs)); +} +private Signature() { + // Not instantiable +} +} diff --git a/app/src/main/java/org/eclipse/jdt/internal/compiler/tool/EclipseCompiler.java b/app/src/main/java/org/eclipse/jdt/internal/compiler/tool/EclipseCompiler.java new file mode 100644 index 000000000..732695f62 --- /dev/null +++ b/app/src/main/java/org/eclipse/jdt/internal/compiler/tool/EclipseCompiler.java @@ -0,0 +1,253 @@ +/******************************************************************************* + * Copyright (c) 2006, 2015 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.jdt.internal.compiler.tool; + +import com.itsaky.androidide.services.compiler.CompilerProgress; +import java.io.File; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.Writer; +import java.net.URI; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Iterator; +import java.util.Locale; +import java.util.Set; +import java.util.WeakHashMap; +import javax.annotation.processing.Processor; +import javax.lang.model.SourceVersion; +import javax.tools.DiagnosticListener; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileManager; +import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.StandardLocation; +import org.eclipse.jdt.internal.compiler.batch.CompilationUnit; +import org.eclipse.jdt.internal.compiler.batch.Main; +import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; + +/** + * Implementation of a batch compiler that supports the jsr199 + */ +public class EclipseCompiler implements JavaCompiler { + + private static Set SupportedSourceVersions; + + static { + // Eclipse compiler supports all possible versions from version 0 to + // version 6 + // we don't care about the order + EnumSet enumSet = EnumSet.range(SourceVersion.RELEASE_0, SourceVersion.RELEASE_6); + // we don't want anybody to modify this list + EclipseCompiler.SupportedSourceVersions = Collections.unmodifiableSet(enumSet); + } + + WeakHashMap threadCache; + public DiagnosticListener diagnosticListener; + + private CompilerProgress progress; + + public EclipseCompiler() { + this.threadCache = new WeakHashMap<>(); + } + + public EclipseCompiler setCompilationCancelled() { + return setCompilationCancelled(true); + } + + public EclipseCompiler setCompilationCancelled(boolean p0) { + if(this.progress != null) + this.progress.setCanceled(p0); + return this; + } + + public EclipseCompiler newProgress() { + this.progress = new CompilerProgress(); + return this; + } + + /* + * (non-Javadoc) + * + * @see javax.tools.Tool#getSourceVersions() + */ + @Override + public Set getSourceVersions() { + return EclipseCompiler.SupportedSourceVersions; + } + /* + * (non-Javadoc) + * + * @see javax.tools.JavaCompiler#getStandardFileManager(javax.tools.DiagnosticListener, + * java.util.Locale, java.nio.charset.Charset) + */ + @Override + public StandardJavaFileManager getStandardFileManager(DiagnosticListener someDiagnosticListener, Locale locale, Charset charset) { + this.diagnosticListener = someDiagnosticListener; + return new EclipseFileManager(locale, charset); + } + /* + * (non-Javadoc) + * + * @see javax.tools.JavaCompiler#getTask(java.io.Writer, + * javax.tools.JavaFileManager, javax.tools.DiagnosticListener, + * java.lang.Iterable, java.lang.Iterable, java.lang.Iterable) + */ + @Override + public CompilationTask getTask(Writer out, JavaFileManager fileManager, DiagnosticListener someDiagnosticListener, Iterable options, Iterable classes, Iterable compilationUnits) { + PrintWriter writerOut = null; + PrintWriter writerErr = null; + if (out == null) { + writerOut = new PrintWriter(System.err); + writerErr = new PrintWriter(System.err); + } else { + writerOut = new PrintWriter(out); + writerErr = new PrintWriter(out); + } + final Thread currentThread = Thread.currentThread(); + EclipseCompilerImpl eclipseCompiler = this.threadCache.get(currentThread); + if (eclipseCompiler == null) { + eclipseCompiler = new EclipseCompilerImpl(writerOut, writerErr, false); + this.threadCache.put(currentThread, eclipseCompiler); + } else { + eclipseCompiler.initialize(writerOut, writerErr, false, null/*options*/, null/*progress*/); + } + final EclipseCompilerImpl eclipseCompiler2 = new EclipseCompilerImpl(writerOut, writerErr, false); + eclipseCompiler2.compilationUnits = compilationUnits; + eclipseCompiler2.diagnosticListener = someDiagnosticListener; + eclipseCompiler2.progress = eclipseCompiler.progress = this.progress; + if (fileManager != null) { + eclipseCompiler2.fileManager = fileManager; + } else { + eclipseCompiler2.fileManager = this.getStandardFileManager(someDiagnosticListener, null, null); + } + + eclipseCompiler2.options.put(CompilerOptions.OPTION_Compliance, CompilerOptions.VERSION_1_6); + eclipseCompiler2.options.put(CompilerOptions.OPTION_Source, CompilerOptions.VERSION_1_6); + eclipseCompiler2.options.put(CompilerOptions.OPTION_TargetPlatform, CompilerOptions.VERSION_1_6); + + ArrayList allOptions = new ArrayList<>(); + if (options != null) { + for (Iterator iterator = options.iterator(); iterator.hasNext(); ) { + eclipseCompiler2.fileManager.handleOption(iterator.next(), iterator); + } + for (String option : options) { + allOptions.add(option); + } + } + + if (compilationUnits != null) { + for (JavaFileObject javaFileObject : compilationUnits) { + // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6419926 + // compells us to check that the returned URIs are absolute, + // which they happen not to be for the default compiler on some + // unices + URI uri = javaFileObject.toUri(); + if (!uri.isAbsolute()) { + uri = URI.create("file://" + uri.toString()); //$NON-NLS-1$ + } + if (uri.getScheme().equals("file")) { //$NON-NLS-1$ + allOptions.add(new File(uri).getAbsolutePath()); + } else { + allOptions.add(uri.toString()); + } + } + } + + if (classes != null) { + allOptions.add("-classNames"); //$NON-NLS-1$ + StringBuilder builder = new StringBuilder(); + int i = 0; + for (String className : classes) { + if (i != 0) { + builder.append(','); + } + builder.append(className); + i++; + } + allOptions.add(String.valueOf(builder)); + } + + final String[] optionsToProcess = new String[allOptions.size()]; + allOptions.toArray(optionsToProcess); + try { + eclipseCompiler2.configure(optionsToProcess); + } catch (IllegalArgumentException e) { + throw e; + } + + if (eclipseCompiler2.fileManager instanceof StandardJavaFileManager) { + StandardJavaFileManager javaFileManager = (StandardJavaFileManager) eclipseCompiler2.fileManager; + Iterable location = javaFileManager.getLocation(StandardLocation.CLASS_OUTPUT); + if (location != null) { + eclipseCompiler2.setDestinationPath(location.iterator().next().getAbsolutePath()); + } + } + + return new CompilationTask() { + private boolean hasRun = false; + @Override + public Boolean call() { + // set up compiler with passed options + if (this.hasRun) { + throw new IllegalStateException("This task has already been run"); //$NON-NLS-1$ + } + Boolean value = eclipseCompiler2.call() ? Boolean.TRUE : Boolean.FALSE; + this.hasRun = true; + return value; + } + @Override + public void setLocale(Locale locale) { + eclipseCompiler2.setLocale(locale); + } + @Override + public void setProcessors(Iterable processors) { + ArrayList temp = new ArrayList<>(); + for (Processor processor : processors) { + temp.add(processor); + } + Processor[] processors2 = new Processor[temp.size()]; + temp.toArray(processors2); + eclipseCompiler2.processors = processors2; + } + }; + } + /* + * (non-Javadoc) + * + * @see javax.tools.OptionChecker#isSupportedOption(java.lang.String) + */ + @Override + public int isSupportedOption(String option) { + return Options.processOptions(option); + } + + /* + * (non-Javadoc) + * + * @see javax.tools.Tool#run(java.io.InputStream, java.io.OutputStream, + * java.io.OutputStream, java.lang.String[]) + */ + @Override + public int run(InputStream in, OutputStream out, OutputStream err, String... arguments) { + boolean succeed = new Main( + new PrintWriter(new OutputStreamWriter(out != null ? out : System.out)), + new PrintWriter(new OutputStreamWriter(err != null ? err : System.err)), + true/* systemExit */, + null/* options */, + null/* progress */).compile(arguments); + return succeed ? 0 : -1; + } +} diff --git a/app/src/main/java/org/eclipse/jdt/internal/compiler/tool/EclipseCompilerImpl.java b/app/src/main/java/org/eclipse/jdt/internal/compiler/tool/EclipseCompilerImpl.java new file mode 100644 index 000000000..d0f7e40a7 --- /dev/null +++ b/app/src/main/java/org/eclipse/jdt/internal/compiler/tool/EclipseCompilerImpl.java @@ -0,0 +1,594 @@ +/******************************************************************************* + * Copyright (c) 2007, 2015 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * IBM Corporation - fix for 342936 + * Kenneth Olson - Contribution for bug 188796 - [jsr199] Using JSR199 to extend ECJ + * Dennis Hendriks - Contribution for bug 188796 - [jsr199] Using JSR199 to extend ECJ + *******************************************************************************/ +package org.eclipse.jdt.internal.compiler.tool; + +import com.blankj.utilcode.util.ThrowableUtils; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; +import javax.annotation.processing.Processor; +import javax.tools.Diagnostic; +import javax.tools.DiagnosticListener; +import javax.tools.JavaFileManager; +import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.StandardLocation; +import org.eclipse.jdt.core.compiler.CategorizedProblem; +import org.eclipse.jdt.core.compiler.CharOperation; +import org.eclipse.jdt.core.compiler.CompilationProgress; +import org.eclipse.jdt.internal.compiler.ClassFile; +import org.eclipse.jdt.internal.compiler.CompilationResult; +import org.eclipse.jdt.internal.compiler.IErrorHandlingPolicy; +import org.eclipse.jdt.internal.compiler.IProblemFactory; +import org.eclipse.jdt.internal.compiler.batch.ClasspathJsr199; +import org.eclipse.jdt.internal.compiler.batch.CompilationUnit; +import org.eclipse.jdt.internal.compiler.batch.FileSystem; +import org.eclipse.jdt.internal.compiler.batch.FileSystem.Classpath; +import org.eclipse.jdt.internal.compiler.batch.Main; +import org.eclipse.jdt.internal.compiler.problem.AbortCompilationUnit; +import org.eclipse.jdt.internal.compiler.problem.DefaultProblem; +import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory; +import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities; +import org.eclipse.jdt.internal.compiler.util.Messages; +import org.eclipse.jdt.internal.compiler.util.SuffixConstants; + +public class EclipseCompilerImpl extends Main { + private static final CompilationUnit[] NO_UNITS = new CompilationUnit[0]; + private HashMap javaFileObjectMap; + Iterable compilationUnits; + public JavaFileManager fileManager; + protected Processor[] processors; + public DiagnosticListener diagnosticListener; + + public EclipseCompilerImpl(PrintWriter out, PrintWriter err, boolean systemExitWhenFinished) { + super(out, err, systemExitWhenFinished, null/*options*/, null/*progress*/); + } + + public boolean call() { + try { + if(proceed) { + this.globalProblemsCount = 0; + this.globalErrorsCount = 0; + this.globalWarningsCount = 0; + this.globalTasksCount = 0; + this.exportedClassFilesCounter = 0; + // request compilation + performCompilation(); + } + } catch(IllegalArgumentException e) { + this.logger.logException(e); + com.itsaky.androidide.utils.Logger.instance().e("[EclipseCompilerImpl]", ThrowableUtils.getFullStackTrace(e)); + if (this.systemExitWhenFinished) { + cleanup(); + System.exit(-1); + } + return false; + } catch (RuntimeException e) { // internal compiler failure + com.itsaky.androidide.utils.Logger.instance().e("[EclipseCompilerImpl]", ThrowableUtils.getFullStackTrace(e)); + this.logger.logException(e); + return false; + } finally { + cleanup(); + } + if (this.globalErrorsCount == 0) + return true; + return false; + } + + private void cleanup() { + this.logger.flush(); + this.logger.close(); + this.processors = null; + try { + if (this.fileManager != null) { + this.fileManager.flush(); + } + } catch (IOException e) { + // ignore + } + } + + @Override + public CompilationUnit[] getCompilationUnits() { + if (this.compilationUnits == null) return EclipseCompilerImpl.NO_UNITS; + ArrayList units = new ArrayList<>(); + for (final JavaFileObject javaFileObject : this.compilationUnits) { + if (javaFileObject.getKind() != JavaFileObject.Kind.SOURCE) { + throw new IllegalArgumentException(); + } + String name = javaFileObject.getName(); + name = name.replace('\\', '/'); + CompilationUnit compilationUnit = new CompilationUnit(null, + name, + null) { + + @Override + public char[] getContents() { + try { + return javaFileObject.getCharContent(true).toString().toCharArray(); + } catch(IOException e) { + e.printStackTrace(); + throw new AbortCompilationUnit(null, e, null); + } + } + }; + units.add(compilationUnit); + this.javaFileObjectMap.put(compilationUnit, javaFileObject); + } + CompilationUnit[] result = new CompilationUnit[units.size()]; + units.toArray(result); + return result; + } + /* + * Low-level API performing the actual compilation + */ + @Override + public IErrorHandlingPolicy getHandlingPolicy() { + // passes the initial set of files to the batch oracle (to avoid finding more than once the same units when case insensitive match) + return new IErrorHandlingPolicy() { + @Override + public boolean proceedOnErrors() { + return false; // stop if there are some errors + } + @Override + public boolean stopOnFirstError() { + return false; + } + @Override + public boolean ignoreAllErrors() { + return false; + } + }; + } + + @Override + public IProblemFactory getProblemFactory() { + return new DefaultProblemFactory() { + @Override + public CategorizedProblem createProblem( + final char[] originatingFileName, + final int problemId, + final String[] problemArguments, + final String[] messageArguments, + final int severity, + final int startPosition, + final int endPosition, + final int lineNumber, + final int columnNumber) { + + DiagnosticListener diagListener = EclipseCompilerImpl.this.diagnosticListener; + if (diagListener != null) { + diagListener.report(new Diagnostic() { + @Override + public String getCode() { + return Integer.toString(problemId); + } + @Override + public long getColumnNumber() { + return columnNumber; + } + @Override + public long getEndPosition() { + return endPosition; + } + @Override + public Kind getKind() { + if ((severity & ProblemSeverities.Error) != 0) { + return Diagnostic.Kind.ERROR; + } + if ((severity & ProblemSeverities.Optional) != 0) { + return Diagnostic.Kind.WARNING; + } + if ((severity & ProblemSeverities.Warning) != 0) { + return Diagnostic.Kind.MANDATORY_WARNING; + } + return Diagnostic.Kind.OTHER; + } + @Override + public long getLineNumber() { + return lineNumber; + } + @Override + public String getMessage(Locale locale) { + if (locale != null) { + setLocale(locale); + } + return getLocalizedMessage(problemId, problemArguments); + } + @Override + public long getPosition() { + return startPosition; + } + @Override + public JavaFileObject getSource() { + File f = new File(new String(originatingFileName)); + if (f.exists()) { + return new EclipseFileObject(null, f.toURI(), JavaFileObject.Kind.SOURCE, null); + } + return null; + } + @Override + public long getStartPosition() { + return startPosition; + } + }); + } + return super.createProblem(originatingFileName, problemId, problemArguments, messageArguments, severity, startPosition, endPosition, lineNumber, columnNumber); + } + @Override + public CategorizedProblem createProblem( + final char[] originatingFileName, + final int problemId, + final String[] problemArguments, + final int elaborationID, + final String[] messageArguments, + final int severity, + final int startPosition, + final int endPosition, + final int lineNumber, + final int columnNumber) { + + DiagnosticListener diagListener = EclipseCompilerImpl.this.diagnosticListener; + if (diagListener != null) { + diagListener.report(new Diagnostic() { + @Override + public String getCode() { + return Integer.toString(problemId); + } + @Override + public long getColumnNumber() { + return columnNumber; + } + @Override + public long getEndPosition() { + return endPosition; + } + @Override + public Kind getKind() { + if ((severity & ProblemSeverities.Error) != 0) { + return Diagnostic.Kind.ERROR; + } + if ((severity & ProblemSeverities.Info) != 0) { + return Diagnostic.Kind.NOTE; + } + if ((severity & ProblemSeverities.Optional) != 0) { + return Diagnostic.Kind.WARNING; + } + if ((severity & ProblemSeverities.Warning) != 0) { + return Diagnostic.Kind.MANDATORY_WARNING; + } + return Diagnostic.Kind.OTHER; + } + @Override + public long getLineNumber() { + return lineNumber; + } + @Override + public String getMessage(Locale locale) { + if (locale != null) { + setLocale(locale); + } + return getLocalizedMessage(problemId, problemArguments); + } + @Override + public long getPosition() { + return startPosition; + } + @Override + public JavaFileObject getSource() { + File f = new File(new String(originatingFileName)); + if (f.exists()) { + return new EclipseFileObject(null, f.toURI(), JavaFileObject.Kind.SOURCE, null); + } + return null; + } + @Override + public long getStartPosition() { + return startPosition; + } + }); + } + return super.createProblem(originatingFileName, problemId, problemArguments, elaborationID, messageArguments, severity, startPosition, endPosition, lineNumber, columnNumber); + } + }; + } + + @Override + protected void initialize(PrintWriter outWriter, PrintWriter errWriter, boolean systemExit, Map customDefaultOptions, CompilationProgress compilationProgress) { + super.initialize(outWriter, errWriter, systemExit, customDefaultOptions, compilationProgress); + this.javaFileObjectMap = new HashMap<>(); + } + + @Override + protected void initializeAnnotationProcessorManager() { + super.initializeAnnotationProcessorManager(); + if (this.batchCompiler.annotationProcessorManager != null && this.processors != null) { + this.batchCompiler.annotationProcessorManager.setProcessors(this.processors); + } else if (this.processors != null) { + throw new UnsupportedOperationException("Cannot handle annotation processing"); //$NON-NLS-1$ + } + } + + // Dump classfiles onto disk for all compilation units that where successful + // and do not carry a -d none spec, either directly or inherited from Main. + @Override + public void outputClassFiles(CompilationResult unitResult) { + if (!((unitResult == null) || (unitResult.hasErrors() && !this.proceedOnError))) { + ClassFile[] classFiles = unitResult.getClassFiles(); + boolean generateClasspathStructure = this.fileManager.hasLocation(StandardLocation.CLASS_OUTPUT); + String currentDestinationPath = this.destinationPath; + File outputLocation = null; + if (currentDestinationPath != null) { + outputLocation = new File(currentDestinationPath); + outputLocation.mkdirs(); + } + for (int i = 0, fileCount = classFiles.length; i < fileCount; i++) { + // retrieve the key and the corresponding classfile + ClassFile classFile = classFiles[i]; + char[] filename = classFile.fileName(); + int length = filename.length; + char[] relativeName = new char[length + 6]; + System.arraycopy(filename, 0, relativeName, 0, length); + System.arraycopy(SuffixConstants.SUFFIX_class, 0, relativeName, length, 6); + CharOperation.replace(relativeName, '/', File.separatorChar); + String relativeStringName = new String(relativeName); + if (this.compilerOptions.verbose) { + EclipseCompilerImpl.this.out.println( + Messages.bind( + Messages.compilation_write, + new String[] { + String.valueOf(this.exportedClassFilesCounter+1), + relativeStringName + })); + } + try { + JavaFileObject javaFileForOutput = + this.fileManager.getJavaFileForOutput( + StandardLocation.CLASS_OUTPUT, + new String(filename), + JavaFileObject.Kind.CLASS, + this.javaFileObjectMap.get(unitResult.compilationUnit)); + + if (generateClasspathStructure) { + if (currentDestinationPath != null) { + int index = CharOperation.lastIndexOf(File.separatorChar, relativeName); + if (index != -1) { + File currentFolder = new File(currentDestinationPath, relativeStringName.substring(0, index)); + currentFolder.mkdirs(); + } + } else { + // create the subfolfers is necessary + // need a way to retrieve the folders to create + String path = javaFileForOutput.toUri().getPath(); + int index = path.lastIndexOf('/'); + if (index != -1) { + File file = new File(path.substring(0, index)); + file.mkdirs(); + } + } + } + + try (OutputStream openOutputStream = javaFileForOutput.openOutputStream(); BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(openOutputStream)) { + bufferedOutputStream.write(classFile.header, 0, classFile.headerOffset); + bufferedOutputStream.write(classFile.contents, 0, classFile.contentsOffset); + bufferedOutputStream.flush(); + } + } catch (IOException e) { + this.logger.logNoClassFileCreated(currentDestinationPath, relativeStringName, e); + } + this.logger.logClassFile( + generateClasspathStructure, + currentDestinationPath, + relativeStringName); + this.exportedClassFilesCounter++; + } + this.batchCompiler.lookupEnvironment.releaseClassFiles(classFiles); + } + } + + @Override + protected void setPaths(ArrayList bootclasspaths, + String sourcepathClasspathArg, + ArrayList sourcepathClasspaths, + ArrayList classpaths, + ArrayList extdirsClasspaths, + ArrayList endorsedDirClasspaths, + String customEncoding) { + + ArrayList fileSystemClasspaths = new ArrayList<>(); + EclipseFileManager eclipseJavaFileManager = null; + StandardJavaFileManager standardJavaFileManager = null; + JavaFileManager javaFileManager = null; + boolean havePlatformPaths = false; + boolean haveClassPaths = false; + if (this.fileManager instanceof EclipseFileManager) { + eclipseJavaFileManager = (EclipseFileManager) this.fileManager; + } + if (this.fileManager instanceof StandardJavaFileManager) { + standardJavaFileManager = (StandardJavaFileManager) this.fileManager; + } + javaFileManager = this.fileManager; + + if (eclipseJavaFileManager != null) { + if ((eclipseJavaFileManager.flags & EclipseFileManager.HAS_ENDORSED_DIRS) == 0 + && (eclipseJavaFileManager.flags & EclipseFileManager.HAS_BOOTCLASSPATH) != 0) { + fileSystemClasspaths.addAll(this.handleEndorseddirs(null)); + } + } + Iterable location = null; + if (standardJavaFileManager != null) { + location = standardJavaFileManager.getLocation(StandardLocation.PLATFORM_CLASS_PATH); + if (location != null) { + for (File file : location) { + Classpath classpath = FileSystem.getClasspath( + file.getAbsolutePath(), + null, + null, this.options); + if (classpath != null) { + fileSystemClasspaths.add(classpath); + havePlatformPaths = true; + } + } + } + } else if (javaFileManager != null) { + Classpath classpath = new ClasspathJsr199(this.fileManager, StandardLocation.PLATFORM_CLASS_PATH); + fileSystemClasspaths.add(classpath); + havePlatformPaths = true; + } + if (eclipseJavaFileManager != null) { + if ((eclipseJavaFileManager.flags & EclipseFileManager.HAS_EXT_DIRS) == 0 + && (eclipseJavaFileManager.flags & EclipseFileManager.HAS_BOOTCLASSPATH) != 0) { + fileSystemClasspaths.addAll(this.handleExtdirs(null)); + } + } + if (standardJavaFileManager != null) { + location = standardJavaFileManager.getLocation(StandardLocation.SOURCE_PATH); + if (location != null) { + for (File file : location) { + Classpath classpath = FileSystem.getClasspath( + file.getAbsolutePath(), + null, + null, this.options); + if (classpath != null) { + fileSystemClasspaths.add(classpath); + } + } + } + location = standardJavaFileManager.getLocation(StandardLocation.CLASS_PATH); + if (location != null) { + for (File file : location) { + Classpath classpath = FileSystem.getClasspath( + file.getAbsolutePath(), + null, + null, this.options); + if (classpath != null) { + fileSystemClasspaths.add(classpath); + haveClassPaths = true; + } + } + } + } else if (javaFileManager != null) { + Classpath classpath = null; + if (this.fileManager.hasLocation(StandardLocation.SOURCE_PATH)) { + classpath = new ClasspathJsr199(this.fileManager, StandardLocation.SOURCE_PATH); + fileSystemClasspaths.add(classpath); + } + classpath = new ClasspathJsr199(this.fileManager, StandardLocation.CLASS_PATH); + fileSystemClasspaths.add(classpath); + haveClassPaths = true; + } + if (this.checkedClasspaths == null) { + // It appears to be necessary to handleBootclasspath() for IBM JVMs + // in order to have visibility to java.lang.String (not present in rt.jar). + // The jars returned by StandardFileManager.getLocation(PLATFORM_CLASS_PATH) are + // not sufficient to resolve all standard classes. + if (!havePlatformPaths) fileSystemClasspaths.addAll(this.handleBootclasspath(null, null)); + if (!haveClassPaths) fileSystemClasspaths.addAll(this.handleClasspath(null, null)); + } + fileSystemClasspaths = FileSystem.ClasspathNormalizer.normalize(fileSystemClasspaths); + final int size = fileSystemClasspaths.size(); + if (size != 0) { + this.checkedClasspaths = new FileSystem.Classpath[size]; + int i = 0; + for (FileSystem.Classpath classpath : fileSystemClasspaths) { + this.checkedClasspaths[i++] = classpath; + } + } + } + @Override + protected void loggingExtraProblems() { + super.loggingExtraProblems(); + for (@SuppressWarnings("rawtypes") + Iterator iterator = this.extraProblems.iterator(); iterator.hasNext(); ) { + final CategorizedProblem problem = (CategorizedProblem) iterator.next(); + if (this.diagnosticListener != null) { + this.diagnosticListener.report(new Diagnostic() { + @Override + public String getCode() { + return null; + } + @Override + public long getColumnNumber() { + if (problem instanceof DefaultProblem) { + return ((DefaultProblem) problem).column; + } + return Diagnostic.NOPOS; + } + @Override + public long getEndPosition() { + if (problem instanceof DefaultProblem) { + return ((DefaultProblem) problem).getSourceEnd(); + } + return Diagnostic.NOPOS; + } + @Override + public Kind getKind() { + if (problem.isError()) { + return Diagnostic.Kind.ERROR; + } + if (problem.isWarning()) { + return Diagnostic.Kind.WARNING; + } else if (problem instanceof DefaultProblem && ((DefaultProblem) problem).isInfo()) { + return Diagnostic.Kind.NOTE; + } + return Diagnostic.Kind.OTHER; + } + @Override + public long getLineNumber() { + if (problem instanceof DefaultProblem) { + return ((DefaultProblem) problem).getSourceLineNumber(); + } + return Diagnostic.NOPOS; + } + @Override + public String getMessage(Locale locale) { + return problem.getMessage(); + } + @Override + public long getPosition() { + if (problem instanceof DefaultProblem) { + return ((DefaultProblem) problem).getSourceStart(); + } + return Diagnostic.NOPOS; + } + @Override + public JavaFileObject getSource() { + if (problem instanceof DefaultProblem) { + File f = new File(new String(((DefaultProblem) problem).getOriginatingFileName())); + if (f.exists()) { + Charset charset = (EclipseCompilerImpl.this.fileManager instanceof EclipseFileManager) ? + ((EclipseFileManager) EclipseCompilerImpl.this.fileManager).charset : Charset.defaultCharset(); + return new EclipseFileObject(null, f.toURI(), JavaFileObject.Kind.SOURCE, charset); + } + return null; + } + return null; + } + @Override + public long getStartPosition() { + return getPosition(); + } + }); + } + } + } +} diff --git a/editor/src/main/res/anim/popup_in.xml b/app/src/main/res/anim/popup_in.xml similarity index 100% rename from editor/src/main/res/anim/popup_in.xml rename to app/src/main/res/anim/popup_in.xml diff --git a/editor/src/main/res/anim/popup_out.xml b/app/src/main/res/anim/popup_out.xml similarity index 100% rename from editor/src/main/res/anim/popup_out.xml rename to app/src/main/res/anim/popup_out.xml diff --git a/app/src/main/res/drawable/ic_compilation_error.xml b/app/src/main/res/drawable/ic_compilation_error.xml new file mode 100644 index 000000000..e0768321f --- /dev/null +++ b/app/src/main/res/drawable/ic_compilation_error.xml @@ -0,0 +1,30 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_diagnostics.xml b/app/src/main/res/drawable/ic_diagnostics.xml new file mode 100644 index 000000000..c9b814565 --- /dev/null +++ b/app/src/main/res/drawable/ic_diagnostics.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_info.xml b/app/src/main/res/drawable/ic_info.xml index 66773f0fb..7156b37b7 100644 --- a/app/src/main/res/drawable/ic_info.xml +++ b/app/src/main/res/drawable/ic_info.xml @@ -5,7 +5,7 @@ android:viewportHeight="24" android:viewportWidth="24"> - + \ No newline at end of file diff --git a/editor/src/main/res/drawable/ic_more_vert_black_20dp.xml b/app/src/main/res/drawable/ic_more_vert_black_20dp.xml similarity index 100% rename from editor/src/main/res/drawable/ic_more_vert_black_20dp.xml rename to app/src/main/res/drawable/ic_more_vert_black_20dp.xml diff --git a/editor/src/main/res/drawable/round_content_copy_20.xml b/app/src/main/res/drawable/round_content_copy_20.xml similarity index 100% rename from editor/src/main/res/drawable/round_content_copy_20.xml rename to app/src/main/res/drawable/round_content_copy_20.xml diff --git a/editor/src/main/res/drawable/round_content_cut_20.xml b/app/src/main/res/drawable/round_content_cut_20.xml similarity index 100% rename from editor/src/main/res/drawable/round_content_cut_20.xml rename to app/src/main/res/drawable/round_content_cut_20.xml diff --git a/editor/src/main/res/drawable/round_content_paste_20.xml b/app/src/main/res/drawable/round_content_paste_20.xml similarity index 100% rename from editor/src/main/res/drawable/round_content_paste_20.xml rename to app/src/main/res/drawable/round_content_paste_20.xml diff --git a/editor/src/main/res/drawable/round_save_20.xml b/app/src/main/res/drawable/round_save_20.xml similarity index 100% rename from editor/src/main/res/drawable/round_save_20.xml rename to app/src/main/res/drawable/round_save_20.xml diff --git a/editor/src/main/res/drawable/round_select_all_20.xml b/app/src/main/res/drawable/round_select_all_20.xml similarity index 100% rename from editor/src/main/res/drawable/round_select_all_20.xml rename to app/src/main/res/drawable/round_select_all_20.xml diff --git a/app/src/main/res/layout/activity_editor.xml b/app/src/main/res/layout/activity_editor.xml index b6198bfaf..857e904f3 100644 --- a/app/src/main/res/layout/activity_editor.xml +++ b/app/src/main/res/layout/activity_editor.xml @@ -133,6 +133,23 @@ app:icon="@drawable/ic_build_output" app:backgroundTint="@color/primaryColor" app:rippleColor="@color/rippleHighlightColor_light"/> + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/layout_diagnostic_info.xml b/app/src/main/res/layout/layout_diagnostic_info.xml new file mode 100644 index 000000000..af339099c --- /dev/null +++ b/app/src/main/res/layout/layout_diagnostic_info.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/layout_diagnostic_item.xml b/app/src/main/res/layout/layout_diagnostic_item.xml new file mode 100644 index 000000000..b0c02d250 --- /dev/null +++ b/app/src/main/res/layout/layout_diagnostic_item.xml @@ -0,0 +1,34 @@ + + + + + + + + + diff --git a/editor/src/main/res/layout/problem_view.xml b/app/src/main/res/layout/problem_view.xml similarity index 100% rename from editor/src/main/res/layout/problem_view.xml rename to app/src/main/res/layout/problem_view.xml diff --git a/editor/src/main/res/layout/text_compose_panel.xml b/app/src/main/res/layout/text_compose_panel.xml similarity index 100% rename from editor/src/main/res/layout/text_compose_panel.xml rename to app/src/main/res/layout/text_compose_panel.xml diff --git a/editor/src/main/res/layout/text_compose_popup_window.xml b/app/src/main/res/layout/text_compose_popup_window.xml similarity index 100% rename from editor/src/main/res/layout/text_compose_popup_window.xml rename to app/src/main/res/layout/text_compose_popup_window.xml diff --git a/app/src/main/res/menu/menu_editor_nav.xml b/app/src/main/res/menu/menu_editor_nav.xml index 029787291..4fd89364c 100644 --- a/app/src/main/res/menu/menu_editor_nav.xml +++ b/app/src/main/res/menu/menu_editor_nav.xml @@ -9,10 +9,10 @@ android:id="@+id/editornav_discuss" android:icon="@drawable/ic_telegram" android:title="@string/discussions_on_telegram"/> - + android:title="@string/user_suggestions"/> #bdbdbd #70ffffff #50ffffff + #f0ffffff #90424242 #ffffff #9f9f9f @@ -32,7 +33,13 @@ #FF7043 #4CAF50 @color/primaryTextColor + @color/secondaryDarkColor + @color/log_warning #212121 #4CAF50 #70000000 + + @android:color/tertiary_text_light + #eeeeee + @android:color/black diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6a853a468..4e5188394 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,4 +1,9 @@ + Diagnostics + Line: %s, Column: %s + ERROR + WARNING + NOTE Project deleted Failed to delete project! Failed to load icon path! @@ -224,4 +229,18 @@ Notifications from Developers App Update Notifications Nothing here … + + Paste + Copy + Select All + Project + Cut + Last + Next + Replace + Replace All + Replacement + Text to search + More + Long click on an item to show JavaDoc if available. diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index bf47b13af..af392f016 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -103,4 +103,71 @@ @color/secondaryLightColor @color/secondaryColor + + + + + + + + + + + + + + + + + diff --git a/build.gradle b/build.gradle index c2aa5bdf6..8a82caabf 100644 --- a/build.gradle +++ b/build.gradle @@ -8,8 +8,8 @@ buildscript { buildTools = "30.0.2" minSdk = 21 targetSdk = 28 - versionCode = 101210625 - versionName = "1.0.1-alpha" + versionCode = 102210625 + versionName = "1.0.2-alpha" packageName = "com.itsaky.androidide" } diff --git a/editor/.gitignore b/editor/.gitignore deleted file mode 100644 index 42afabfd2..000000000 --- a/editor/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/editor/build.gradle b/editor/build.gradle deleted file mode 100644 index 85b258431..000000000 --- a/editor/build.gradle +++ /dev/null @@ -1,58 +0,0 @@ -/******************************************************************************* - * Copyright 2020-2021 Rosemoe - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - ******************************************************************************/ - -apply plugin: 'com.android.library' - -android { - compileSdkVersion rootProject.ext.compileSdk - buildToolsVersion rootProject.ext.buildTools - - defaultConfig { - minSdkVersion rootProject.ext.minSdk - targetSdkVersion rootProject.ext.targetSdk - versionCode rootProject.ext.versionCode - versionName rootProject.ext.versionName - vectorDrawables.useSupportLibrary = true - } - - buildFeatures { - viewBinding true - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } -} - -dependencies { - implementation 'com.blankj:utilcodex:1.30.6' - implementation 'com.elvishew:xlog:1.8.0' - implementation 'com.github.Rosemoe:BlockLinkedList:1.0.0' - - implementation 'androidx.annotation:annotation:1.2.0' - implementation 'androidx.vectordrawable:vectordrawable:1.1.0' - implementation 'androidx.vectordrawable:vectordrawable-animated:1.1.0' - implementation 'com.google.android.material:material:1.3.0' - implementation fileTree(dir: "libs", include: ["*.jar"]) -} diff --git a/editor/consumer-rules.pro b/editor/consumer-rules.pro deleted file mode 100644 index e69de29bb..000000000 diff --git a/editor/proguard-rules.pro b/editor/proguard-rules.pro deleted file mode 100644 index 481bb4348..000000000 --- a/editor/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/editor/src/main/AndroidManifest.xml b/editor/src/main/AndroidManifest.xml deleted file mode 100644 index ef8d25f33..000000000 --- a/editor/src/main/AndroidManifest.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/editor/src/main/res/drawable/bg_ripple.xml b/editor/src/main/res/drawable/bg_ripple.xml deleted file mode 100644 index 9b1a5df11..000000000 --- a/editor/src/main/res/drawable/bg_ripple.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/editor/src/main/res/drawable/bg_ripple_disabled.xml b/editor/src/main/res/drawable/bg_ripple_disabled.xml deleted file mode 100644 index 34782662b..000000000 --- a/editor/src/main/res/drawable/bg_ripple_disabled.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - diff --git a/editor/src/main/res/drawable/bg_ripple_pressed.xml b/editor/src/main/res/drawable/bg_ripple_pressed.xml deleted file mode 100644 index b8c1e9872..000000000 --- a/editor/src/main/res/drawable/bg_ripple_pressed.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - diff --git a/editor/src/main/res/drawable/ic_info.xml b/editor/src/main/res/drawable/ic_info.xml deleted file mode 100644 index 7156b37b7..000000000 --- a/editor/src/main/res/drawable/ic_info.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - \ No newline at end of file diff --git a/editor/src/main/res/values-zh/strings.xml b/editor/src/main/res/values-zh/strings.xml deleted file mode 100644 index 31991a6f0..000000000 --- a/editor/src/main/res/values-zh/strings.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - 粘贴 - 复制 - 全选 - 剪切 - 上个 - 下个 - 替换 - 替换全部 - 替换文本 - 取消 - 欲搜索文本 - \ No newline at end of file diff --git a/editor/src/main/res/values/attrs.xml b/editor/src/main/res/values/attrs.xml deleted file mode 100644 index c8c1cb818..000000000 --- a/editor/src/main/res/values/attrs.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/editor/src/main/res/values/colors.xml b/editor/src/main/res/values/colors.xml deleted file mode 100644 index 00486d6b4..000000000 --- a/editor/src/main/res/values/colors.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - @android:color/tertiary_text_light - #90010101 - #eeeeee - @android:color/black - diff --git a/editor/src/main/res/values/dimens.xml b/editor/src/main/res/values/dimens.xml deleted file mode 100644 index 55344e519..000000000 --- a/editor/src/main/res/values/dimens.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/editor/src/main/res/values/strings.xml b/editor/src/main/res/values/strings.xml deleted file mode 100644 index 68c370951..000000000 --- a/editor/src/main/res/values/strings.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - Paste - Copy - Select All - Project - Cut - Last - Next - Replace - Replace All - Replacement - Cancel - Text to search - More - Long click on an item to show JavaDoc if available. - diff --git a/editor/src/main/res/values/styles.xml b/editor/src/main/res/values/styles.xml deleted file mode 100644 index 6c67ea2cf..000000000 --- a/editor/src/main/res/values/styles.xml +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/settings.gradle b/settings.gradle index c6e33a765..cee3ae1c0 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,6 +1,5 @@ include ':app' include ':treeview' -include ':editor' include ':spinkit' include ':slidr' include ':javadocparser'