From 5cf9121f130b55c9417029e11dfa75cc04198353 Mon Sep 17 00:00:00 2001 From: Peter Kriens Date: Fri, 19 Jan 2024 18:28:38 +0100 Subject: [PATCH] Work in progress, fixes static imports --- Signed-off-by: Peter Kriens Signed-off-by: Peter Kriens --- bndtools.core/_plugin.xml | 6 + bndtools.core/bnd.bnd | 6 +- bndtools.core/icons.properties | 7 + .../unprocessed/icons/block-to-string.png | Bin 0 -> 1565 bytes .../unprocessed/icons/final-field.png | Bin 0 -> 1566 bytes .../resources/unprocessed/icons/openai.png | Bin 0 -> 1972 bytes .../unprocessed/icons/set-selection.png | Bin 0 -> 1525 bytes .../unprocessed/icons/string-to-block.png | Bin 0 -> 1587 bytes .../bndtools/refactor/ai/AIRefactorer.java | 77 ++++++ .../org/bndtools/refactor/ai/ChatImpl.java | 120 +++++++++ .../org/bndtools/refactor/ai/JavaNode.java | 54 +++++ .../bndtools/refactor/ai/OpenAIComponent.java | 40 +++ .../bndtools/refactor/ai/OpenAIDialog.java | 229 ++++++++++++++++++ .../bndtools/refactor/ai/OpenAIProvider.java | 161 ++++++++++++ .../org/bndtools/refactor/ai/api/Chat.java | 18 ++ .../bndtools/refactor/ai/api/Embedder.java | 6 + .../org/bndtools/refactor/ai/api/OpenAI.java | 28 +++ .../org/bndtools/refactor/ai/api/Reply.java | 5 + .../refactor/types/DiverseRefactorer.java | 41 +++- .../refactor/types/LiteralRefactorer.java | 4 +- .../org/bndtools/refactor/util/Cursor.java | 1 + .../refactor/util/RefactorAssistant.java | 112 +++++---- .../types/ComponentRefactorerTest.java | 18 ++ 23 files changed, 885 insertions(+), 48 deletions(-) create mode 100644 bndtools.core/resources/unprocessed/icons/block-to-string.png create mode 100644 bndtools.core/resources/unprocessed/icons/final-field.png create mode 100644 bndtools.core/resources/unprocessed/icons/openai.png create mode 100644 bndtools.core/resources/unprocessed/icons/set-selection.png create mode 100644 bndtools.core/resources/unprocessed/icons/string-to-block.png create mode 100644 bndtools.core/src/org/bndtools/refactor/ai/AIRefactorer.java create mode 100644 bndtools.core/src/org/bndtools/refactor/ai/ChatImpl.java create mode 100644 bndtools.core/src/org/bndtools/refactor/ai/JavaNode.java create mode 100644 bndtools.core/src/org/bndtools/refactor/ai/OpenAIComponent.java create mode 100644 bndtools.core/src/org/bndtools/refactor/ai/OpenAIDialog.java create mode 100644 bndtools.core/src/org/bndtools/refactor/ai/OpenAIProvider.java create mode 100644 bndtools.core/src/org/bndtools/refactor/ai/api/Chat.java create mode 100644 bndtools.core/src/org/bndtools/refactor/ai/api/Embedder.java create mode 100644 bndtools.core/src/org/bndtools/refactor/ai/api/OpenAI.java create mode 100644 bndtools.core/src/org/bndtools/refactor/ai/api/Reply.java diff --git a/bndtools.core/_plugin.xml b/bndtools.core/_plugin.xml index 49cf0b1d3e..04756e7ad9 100644 --- a/bndtools.core/_plugin.xml +++ b/bndtools.core/_plugin.xml @@ -224,6 +224,12 @@ icon="icons/database.png" id="bndtools.repositoriesView" name="Repositories" restorable="true"> + + 8Nmh_KB;-{d?3YH;1OBOz`!jG!i)^F=12g|SeY3TQ4-E(HYzo1&C7s~{IQsCFRFRw<*Tq`*pFzr4I$uiRKKzbIYb(9+UU z-@r)U$VeBcLbtdwuOzWTH?LS3VhGF}m(=3qqRfJl%=|nBkhzIT`K2YcN=hJ$-~i&z zlFT%OO?kyoZvj2150cS0)HBe>rY*H16NfgC3{GvpFoJsv#j=XT0*EGXU_jhx19E|t zbAE0?QEG89&@XldHegqwh#~w0Q6H3AoS#z)@_J}qW{I5R>yq!5r53(S- zhDdB0kOk2-_!niS0%Hs0Fgru2qmadr)PsW13Y@SbTrzW0^T2*LG_%piDuJ%iIX@@A zD7YXoITd6NSPPO6x@xdBk+`ful0ec3j72NwqEt|(3(3#VvBSuNHu~sF&~*Fer(~v8 zx+IpQ+JUmGu7RPhp=AgxyF#=hse$UY(FY}Fq{I!$Ibcy>*0JLPYJ_D&JFen00>H9} z(caU=F~mY}X|S(%OM-x$xALhAO;%qLw=d$8-Bog9f}UW-%LnlsABCzvtFz>)|Bo!$ z>fO=QTDhWop`YSp#%v#2H|GQ#e y@&%K%(+`~Subg*FN0@Ezt-m_E9$)d<|5-ig_;T0PXC6)im2#f0elF{r5}E*r$n4Vq literal 0 HcmV?d00001 diff --git a/bndtools.core/resources/unprocessed/icons/final-field.png b/bndtools.core/resources/unprocessed/icons/final-field.png new file mode 100644 index 0000000000000000000000000000000000000000..b6a273366e2c464e2fa70b3e9c8adbd986cca064 GIT binary patch literal 1566 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|4y8tTruq6Z zXaU(A4D5_T49p-UK*+!-#lQ+?GcY7EO2gS%j2ciiOh7e;3_y}W2#AXqjlgUXAiJeK zlYs@QcLtCK0S_PsnhK!8Nmh_t}VHX14wZectjR6FmMZlFeAgPITAoKR%V7oltlRYSS9D@ z>LsS+C#C9DzCHb_`sNdc^+B->Ug!Z$#{ zIlm}X!A#FU&p^qJOF==wrYI%ND#*nRsvXF)RmvzSDX`MlFE20GD>v55FG|-pw6wI; zH!#vSGSUUA&@HaaD@m--%_~-h7y>iLCAB!YD6^m>Ge1uOWNu1d)q*CBt}6l|Z)XnEgDi-y zArhMgWI=Qd{zaLoz}Nyg%+3(%C}c4t^`IcM0w?SUm(1MMJh0yl&203sN}y|W&d50HLh|!->@f16jXt^(G~K@WDVb@N zE{P?ncA)I4Yhb8rXc+>_t`O}=YM{Dp^g)RkDRDz`4plBsH1xyu2<@=4X-+yh zI*XK)PN~g~J^x0TQ_Y;EC?Vn7J~%Bu zohUl9utRaxf%W75%0BjCbyXTO`m_iY!12YTUWd;L3SCRcz9y5 vyq44?&3Td#S3j3^P68 z<^_xh_bgz7n<=n>8Nmh_UcprfvWK(4BeIx*fm;}a85w5HkpP;pGBYHiB*NFnDmgz_ zFEJ%QDOIl`w*aJz!KT6r$jnVGNmQuF&B-gas<2f8tFQvHLBje<3ScEA*|tg%z5xo( z`9-M;W_kvC21<5Z3JMA~MJZ`kK`w4k?LeNbQbtKhft9{~d3m{Bxv^e;QM$gNrKP35 zfswwEkuFe$ZgFK^Nn(X=Ua>O75STeGsl~}fnFS@8`FRQ;a}$&DOG|8(lt3220mPjp znP~`{@`|C}0(wv%B%^PrXP}QwTWUon4s9SAoZ5h41osw-Wfh495KZ8~fVj~H?~)Z${GU+fHQz^+0OL-+}zJ}9*~Kc^Jr_0YV`5<4TP7BpdWT@eU*J9C&GWI=Qd zk=QgK3!-cAFUm{>#umt7c7{+#A&ViY2L+)OIAKS)Wag&kf&Ff1W}}Z)0$rnXeolT- za6w{nD##wN79=5b)nIEPaan^Tfus=_i&oA>sh~_3lAoVrhmi+u^wE`|>GsV}$xN$s zNi0dV17%lT14CUyix60Lg=j}o1J!M#4@%5Pi5rr0z@oscW5)&52+M|cT+8>y080v{ zYo0ESAr^wU!O`BKjv{f{Y2S9LNtR6v+TVXUQG@sFBRW4-W>J&hFB+C+W9x7Kd)`qYyL1_)aGKi z6tDX&4hOYKE30p^$IAato+kWoKiAc&#n(7thN zjq?nu9%gz6ZA=?PTRw5DP-u8|;Dq5?PivRzI}0xtva+w!*tqe>F0qB5FD*-UnBi(v zEc7N>cTS!_qfLqU;~7`;brWPg0uLo9K30>O=^AtL(4wkyJEc#2vQPcl>@wxyiBtxq z#U-D%IR+kCQ^q~Hx3N@N>}lY(=m)$_0U?baybhYi|FY;26iRV(ns7_>E z0lQ1;Ouc5vm_0m|EX?92lr$q`#WAM0K7mUt*6+#WSv`v@GR|8?N+L&g@?S2eu!_vZ z8z=9y^x)3Ard8Cn`n%t&6!}FzoE(0hY}hfU?L*7f7hmt5J|?;*h3$^lyUs27#SCjs zBtMkWo?GT!A<_H0D|^>e$LbqK8iMx5*>WuBE(aZ0z1Lu7oUyU-ENPYHPoMFHuPR(7 zc53VA_ZL3c99go|Q#nOeJm0$i>lT++j}p3n#T~a`KJ-cD)MV!4x@`J|yUjL!n5^>m z4#(E!V!IQUmrKkkKDTGVZ`t3r?)5)48+P8ebSnOQHLzW4Wy!8K`9m6y-!s3|v5dAo z-T!LQ`xo<1^VA$XWqjdQ{Jia2cg{Iy39WpRkoMxu6|cSJ|Cy!j-4EKbh5ZLrKAx_A JF6*2UngDt2uw4KE literal 0 HcmV?d00001 diff --git a/bndtools.core/resources/unprocessed/icons/set-selection.png b/bndtools.core/resources/unprocessed/icons/set-selection.png new file mode 100644 index 0000000000000000000000000000000000000000..8c03fa5f19260d9d501fdb491d3cabf46ff1c32a GIT binary patch literal 1525 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|4y8tTruq6Z zXaU(A4D5_T49p-UK*+!-#lQ+?GcY7EO2gS%j2ciiOh7e;3_y}W2#AXqjlgUXAiJeK zlYs@QcLtCK0S_PsnhK!8Nmh_J}u;yI*{Tl@Q5sCVBi)8VMc~ob0mOftjr9FD2ed(u}aR* z)k{ptPfFFR$SnZrVz8;O0y1+`OA-|-a&z*EttxDlz$&bOY>=?Nk^)#sNw%$0gl~X? zbAC~(f|;Iyo`I4bmx6+VO;JjkRgjAtR6CGotCUevQedU8UtV6WS8lAAUzDzIXlZGw zZ(yWvWTXpJp<7&;SCUwvn^&w1F$89gOKNd)QD#9&W`3Rm$lS!F{L&IzB_)tWZ~$>< zNoE?tro3XPw}2kh2g&Fg>KW)`)0SG1i9;Jm2B$V)7{R@TVp&CE0YnoxFd%NU0lC1+ zIX}0cD7Cm4=odRf8?dWT#1MXhs1Hgl&d(_Yc|9~Qv&7B_ss&9LT~`D`-p(AR2U!qZ zLnJm0$b#q^{EISEfw2X07~CJoVo2&iL1+a|*by$7xv6;&znj_UW0gSH=$xOEUld%B zn4Ai-2do822wgSUnn+yMAW0x;1jeG3b5SZN(}m>c=h$K7K^uK^C1|>R^HVa@DqRvw zQtd$5RoB2!*U&NqmR%v*k<>tS+vtN5Gg9J)o`ACo%>j)KBFxPTRE}h% zNKWWGpD!tK&xWCUlfvnR8)Pp_N*v0Pl&D+OVPvp@@hAIq1A{X?%-#>o7*x)(Z0X}% z!{^xWlf&^2+aLBS@n^{`Wh0wTHY zVvZ#ZJk|eY4zW#Y;bq9IsOxTg_$qIQS_<1ChOja=Hiaf@(PdmJ$Ih`nPQ1Xu%%`7_ zP*K1&Y3YQijeDv;TWsQBo4x1^i&5{QH#`hCk0%Kki`>2dD!n{i{an^LB{Ts58UnZV literal 0 HcmV?d00001 diff --git a/bndtools.core/resources/unprocessed/icons/string-to-block.png b/bndtools.core/resources/unprocessed/icons/string-to-block.png new file mode 100644 index 0000000000000000000000000000000000000000..86635a4b4694a082a8bc0c21425d71175454ad61 GIT binary patch literal 1587 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|4y8tTruq6Z zXaU(A4D5_T49p-UK*+!-#lQ+?GcY7EO2gS%j2ciiOh7e;3_y}W2#AXqjlgUXAiJeK zlYs@QcLtCK0S_PsnhK!8Nmh_zI?)dCm_XH;1OBOz`!jG!i)^F=12g|SeY3TQ4-E(HYzo1&C7s~{IQsCFRFRw<*Tq`*pFzr4I$uiRKKzbIYb(9+UU z-@r)U$VeBcLbtdwuOzWTH?LS3VhGF}m(=3qqRfJl%=|nBkhzIT`K2YcN=hJ$-~i&z zlFT%OO?kyoZvj2150cS0)HBe>rY*H16NfgC3{GvpFoJsv#j=XT0*EGXU_jhx19E|t zbAE0?QEG89&@XldHegqwh#~w0Q6H3AoS#z)@_J}qW{I5R>yq!5r53(S- zhDdB0kOk2-_!niS0%Hs0Fgru2qmadr)PsW13Y@SbTrzW0^T2*LG_%piDuJ%iIX@@A zD7YXoITd6NSPPO6x@xdBk+`ful0ec3j72NwqEt|(3(3#VvBSuNHu~sF&~*Fer(~v8 zx+IpQ+JUmGu7RPhp=AgxyF#=hse$UY(FY}Fq{I!$Ibcy>*0JLPYJ_D&JFc@nZjXQk zMX0BXV~B-dazaAF1BOR_3jer|{O>%XuvNiUVJ}atgU=eblG50ofU^qCCzyOvUOHWB zNorWNN~?L-F01AhD>RxrJ2{!PwY6h18}{)v@7p3Wr~gdD@9cYfD*sfrou@s0=o{{H6ipKq61@!jJC=?3d(w;xJ_38huyEgs*&AsVg^9_SijW`EW@u|l-c>?^x%-UGh{S5#7 z target, + IInvocationContext context) { + + try { + int start = context.getSelectionOffset(); + int end = start + context.getSelectionLength(); + if (start == end) { + ASTNode covered = context.getCoveredNode(); + if (covered == null) { + covered = context.getCoveringNode(); + } + while (covered instanceof Name) { + covered = covered.getParent(); + } + if (covered == null) + return; + + start = covered.getStartPosition(); + end = start + covered.getLength(); + } + String completeSource = assistant.getSource(); + String source = assistant.getSource() + .substring(start, end); + + final int begin = start, l = end - start; + builder.build("ai", "Open AI", "openai", -10, () -> { + OpenAIDialog dialog = new OpenAIDialog(new Shell(), source, openai, s -> { + Job job = Job.create("update text buffer", mon -> { + ICompilationUnit compilationUnit = context.getCompilationUnit(); + if (compilationUnit.getSource() + .equals(completeSource)) { + ReplaceEdit re = new ReplaceEdit(begin, l, s); + compilationUnit.applyTextEdit(re, null); + } + }); + job.schedule(); + }); + dialog.open(); + }); + } catch (Exception e) { + e.printStackTrace(); + // ok no proposal + } + } + +} diff --git a/bndtools.core/src/org/bndtools/refactor/ai/ChatImpl.java b/bndtools.core/src/org/bndtools/refactor/ai/ChatImpl.java new file mode 100644 index 0000000000..823256e849 --- /dev/null +++ b/bndtools.core/src/org/bndtools/refactor/ai/ChatImpl.java @@ -0,0 +1,120 @@ +package org.bndtools.refactor.ai; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.bndtools.refactor.ai.api.Chat; +import org.bndtools.refactor.ai.api.OpenAI.Configuration; +import org.bndtools.refactor.ai.api.Reply; +import org.osgi.dto.DTO; + + +class ChatImpl implements Chat { + final static String CHAT_URL = "https://api.openai.com/v1/chat/completions"; + + final OpenAIProvider openAIProvider; + + ChatRequest request = new ChatRequest(); + + final Configuration configuration; + + public static class Message extends DTO { + public String role; + public String content; + } + + public static class ChatRequest extends DTO { + public String model = "gpt-3.5-turbo-1106"; + public List messages = new ArrayList<>(); + public double temperature = 0.1; + public double top_p = 1; + public int n = 1; + public boolean stream = false; + public String stop; + public Integer max_tokens = null; + public double presence_penalty = 0D; + public double frequency_penalty = 0D; + public Map logit_bias = new HashMap<>(); + public String user = "bndtools"; + } + + public static class ChatUsage extends DTO { + public int prompt_tokens; + public int completion_tokens; + public int total_tokens; + } + + public static class ChatChoice extends DTO { + public Message message; + public String finish_reason; + public int index; + } + + public static class ChatResponse extends DTO { + public String id; + public String object; + public long created; + public String model; + public ChatUsage usage; + public List choices; + + } + + ChatImpl(OpenAIProvider openAIProvider, Configuration configuration) { + this.configuration = configuration; + this.openAIProvider = openAIProvider; + } + + @Override + public Reply ask(String question) { + Message m = new Message(); + m.role = "user"; + m.content = question; + if (configuration.model != null) + request.model = configuration.model; + request.messages.add(m); + ChatResponse chatResponse = openAIProvider.get(CHAT_URL, request, ChatResponse.class); + + Reply reply = new Reply(); + reply.reply = chatResponse.choices.get(0).message.content; + return reply; + } + + @Override + public void system(String command) { + Message m = new Message(); + m.role = "system"; + m.content = command; + request.messages.add(m); + } + + @Override + public void assistant(String command) { + Message m = new Message(); + m.role = "assistant"; + m.content = command; + request.messages.add(m); + } + @Override + public void model(String model) { + request.model = model; + } + + @Override + public void clear() { + request.messages.clear(); + } + + @Override + public void clear(String role) { + request.messages.removeIf(m -> m.role.equals(role)); + } + + @Override + public void setProlog(String string) { + request.messages.removeIf( m -> m.role.equals("system")); + system(string); + } +} diff --git a/bndtools.core/src/org/bndtools/refactor/ai/JavaNode.java b/bndtools.core/src/org/bndtools/refactor/ai/JavaNode.java new file mode 100644 index 0000000000..9792207847 --- /dev/null +++ b/bndtools.core/src/org/bndtools/refactor/ai/JavaNode.java @@ -0,0 +1,54 @@ +package org.bndtools.refactor.ai; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +import org.eclipse.compare.IEditableContent; +import org.eclipse.compare.IStreamContentAccessor; +import org.eclipse.compare.ITypedElement; +import org.eclipse.swt.graphics.Image; + +public class JavaNode implements ITypedElement, IStreamContentAccessor, IEditableContent { + private String name; + private String content; + + public JavaNode(String name, String content) { + this.name = name; + this.content = content; + } + + @Override + public String getName() { + return name; + } + + @Override + public Image getImage() { + return null; // You can return an appropriate image for Java files here + } + + @Override + public String getType() { + return "java"; // Return the type of the node, in this case, Java + } + + @Override + public InputStream getContents() { + return new ByteArrayInputStream(content.getBytes()); + } + + @Override + public boolean isEditable() { + return true; + } + + @Override + public void setContent(byte[] newContent) { + content = new String(newContent, StandardCharsets.UTF_8); + } + + @Override + public ITypedElement replace(ITypedElement dest, ITypedElement src) { + return null; + } +} diff --git a/bndtools.core/src/org/bndtools/refactor/ai/OpenAIComponent.java b/bndtools.core/src/org/bndtools/refactor/ai/OpenAIComponent.java new file mode 100644 index 0000000000..6a5b111952 --- /dev/null +++ b/bndtools.core/src/org/bndtools/refactor/ai/OpenAIComponent.java @@ -0,0 +1,40 @@ +package org.bndtools.refactor.ai; + +import org.bndtools.refactor.ai.api.OpenAI; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +import aQute.bnd.build.Workspace; +import aQute.bnd.http.HttpClient; +import aQute.lib.settings.Settings; +import aQute.lib.strings.Strings; + +@Component +public class OpenAIComponent { + final static Settings settings = new Settings(); + final OpenAIProvider provider; + final Workspace workspace; + final ServiceRegistration registerService; + + @Activate + public OpenAIComponent(@Reference + Workspace workspace, ComponentContext context) { + this.workspace = workspace; + HttpClient client = workspace.getPlugin(HttpClient.class); + if (client != null) { + String apiKey = settings.getOrDefault("openai.apikey", System.getProperty("OPENAI_APIKEY")); + String models = settings.getOrDefault("openai.models", null); + if (apiKey != null) { + provider = new OpenAIProvider(client, apiKey, models == null ? null : Strings.split(models)); + registerService = context.getBundleContext() + .registerService(OpenAI.class, provider, null); + return; + } + } + provider = null; + registerService = null; + } +} diff --git a/bndtools.core/src/org/bndtools/refactor/ai/OpenAIDialog.java b/bndtools.core/src/org/bndtools/refactor/ai/OpenAIDialog.java new file mode 100644 index 0000000000..43d3cebd32 --- /dev/null +++ b/bndtools.core/src/org/bndtools/refactor/ai/OpenAIDialog.java @@ -0,0 +1,229 @@ +package org.bndtools.refactor.ai; + +import static aQute.libg.re.Catalog.dotall; +import static aQute.libg.re.Catalog.g; +import static aQute.libg.re.Catalog.lit; +import static aQute.libg.re.Catalog.multiline; +import static aQute.libg.re.Catalog.setAll; +import static aQute.libg.re.Catalog.startOfLine; + +import java.util.function.Consumer; + +import org.bndtools.refactor.ai.api.Chat; +import org.bndtools.refactor.ai.api.OpenAI; +import org.bndtools.refactor.ai.api.Reply; +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.CompareViewerPane; +import org.eclipse.compare.contentmergeviewer.TextMergeViewer; +import org.eclipse.compare.structuremergeviewer.DiffNode; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.widgets.WidgetFactory; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.KeyAdapter; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +import aQute.libg.re.RE; + +public class OpenAIDialog extends Dialog { + final static RE CODE_BEGIN_P = g(startOfLine, lit("```java\n")); + final static RE CODE_END_P = g(startOfLine, lit("```")); + final static RE CODE_P = g(multiline(), dotall(), CODE_BEGIN_P, g("code", setAll), + CODE_END_P); + + final OpenAI openai; + String sourceCode; + String modifiedCode; + Text promptText; + Button submitButton; + int version = 1000; + Job active; + + private TextMergeViewer viewer; + final Consumer update; + private Combo modelCombo; + + public OpenAIDialog(Shell parentShell, String sourceCode, OpenAI openai, Consumer update) { + super(parentShell); + this.openai = openai; + this.sourceCode = sourceCode; + this.update = update; + } + + @Override + protected Control createDialogArea(Composite parent) { + Composite container = (Composite) super.createDialogArea(parent); + GridLayout overalllayout = new GridLayout(1, false); + container.setLayout(overalllayout); + + CompareViewerPane viewerPane = new CompareViewerPane(container, SWT.BORDER); + GridData viewerPaneGridData = new GridData(SWT.FILL, SWT.FILL, true, true); + viewerPaneGridData.heightHint = 600; + viewerPaneGridData.widthHint = 1200; + viewerPane.setLayoutData(viewerPaneGridData); + + CompareConfiguration configuration = new CompareConfiguration(); + configuration.setLeftLabel("Original"); + configuration.setRightLabel("Generated"); + configuration.setRightEditable(true); + + viewer = new TextMergeViewer(viewerPane, SWT.NONE, configuration); + viewer.setInput(new DiffNode(new JavaNode("Left", sourceCode), new JavaNode("right", ""))); + Control control = viewer.getControl(); + viewerPane.setContent(control); + viewer.setSelection(() -> true); + + GridLayout controlLayout = new GridLayout(8, true); + Composite controlArea = WidgetFactory.composite(SWT.NONE) + .layout(controlLayout) + .layoutData(new GridData(GridData.FILL_BOTH)) + .create(container); + + modelCombo = new Combo(controlArea, SWT.DROP_DOWN | SWT.READ_ONLY); + GridData modelComboGridData = new GridData(SWT.FILL, SWT.CENTER, true, false); + modelComboGridData.horizontalSpan = 7; + modelCombo.setLayoutData(modelComboGridData); + modelCombo.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + int selectedIndex = modelCombo.getSelectionIndex(); + String selectedModel = modelCombo.getItem(selectedIndex); + // Handle the selection of a model here + } + }); + for (String model : openai.getModels()) { + modelCombo.add(model); + } + modelCombo.select(0); + + Label comboLabel = new Label(controlArea, SWT.NONE); + comboLabel.setText("Select Model"); + GridData comboLabelGridData = new GridData(SWT.RIGHT, SWT.CENTER, false, false); + comboLabel.setLayoutData(comboLabelGridData); + + promptText = new Text(controlArea, SWT.BORDER | SWT.MULTI | SWT.V_SCROLL | SWT.H_SCROLL); + GridData promptTextGridData = new GridData(SWT.FILL, SWT.CENTER, true, false); + promptTextGridData.horizontalSpan = 7; + promptTextGridData.heightHint = 100; // Set height for multiple lines + promptText.setLayoutData(promptTextGridData); + promptText.setFont(JFaceResources.getTextFont()); + + submitButton = new Button(controlArea, SWT.PUSH); + submitButton.setText("Send"); + submitButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + processPrompt(); + } + }); + GridData submitButtonData = new GridData(SWT.FILL, SWT.TOP, true, false); + submitButtonData.horizontalSpan = 1; + submitButton.setLayoutData(submitButtonData); + + getShell().setDefaultButton(null); + promptText.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if ((e.stateMask == SWT.NONE) && (e.keyCode == SWT.CR)) { + processPrompt(); + } + } + }); + + refreshCompareEditor(sourceCode, ""); + return container; + } + + private void refreshCompareEditor(String original, String modified) { + viewer.setInput(new DiffNode(new JavaNode("Source", original), new JavaNode("Generated", modified))); + viewer.setSelection(() -> true); + } + + private void processPrompt() { + processPrompt(promptText.getText()); + } + + private void processPrompt(String prompt) { + int current = ++version; + if (active != null) { + active.cancel(); + active = null; + } + + OpenAI.Configuration config = new OpenAI.Configuration(); + config.model = modelCombo.getText(); + Chat chat = openai.createChat(config); + chat.system(""" + you're a very smart assistant in a bnd Bndtools Eclipse workspace + with the task to help the user with her source code. + The user selects a piece of code, could be a whole class, and then + asks a transformation of the whole. The output you generate must + contain the Java code in a markdown code format using triple + back quotes like: + ```java + class Foo { + } + ``` + The Java code must be an exact replacement for the source code. For example, + if the user asks for a method to write, add + it to the class and output the WHOLE class, not partial, since it + will replace the whole class in the editor. + The generated code will directly replace the given source code in the editor + unless the user explicitly asks for a new construct! + After the ### is the source code. + ### + """ + sourceCode); + + refreshCompareEditor(sourceCode, current + ":generating answer ..."); + active = Job.create("openai", mon -> { + chat.clear("user"); + Reply ask = chat.ask(prompt); + getShell().getDisplay() + .asyncExec(() -> { + StringBuilder comments = new StringBuilder("\n"); + + if (current == this.version) { + modifiedCode = CODE_P.findIn(ask.reply) + .flatMap(m -> { + comments.append(ask.reply.substring(0, m.start())); + comments.append("\n"); + comments.append(ask.reply.substring(m.end())); + return m.group("code"); + }) + .map(group -> { + return group.toString(); + }) + .orElse(ask.reply); + promptText.append(comments.toString()); + this.refreshCompareEditor(sourceCode, modifiedCode); + } + }); + }); + active.schedule(); + } + + @Override + protected void createButtonsForButtonBar(Composite parent) { + createButton(parent, IDialogConstants.OK_ID, "Replace", true); + createButton(parent, IDialogConstants.CANCEL_ID, "Cancel", false); + } + + @Override + protected void okPressed() { + update.accept(modifiedCode); + super.okPressed(); + } +} diff --git a/bndtools.core/src/org/bndtools/refactor/ai/OpenAIProvider.java b/bndtools.core/src/org/bndtools/refactor/ai/OpenAIProvider.java new file mode 100644 index 0000000000..7616641210 --- /dev/null +++ b/bndtools.core/src/org/bndtools/refactor/ai/OpenAIProvider.java @@ -0,0 +1,161 @@ +package org.bndtools.refactor.ai; + +import java.net.URI; +import java.util.List; + +import org.bndtools.refactor.ai.api.Chat; +import org.bndtools.refactor.ai.api.Embedder; +import org.bndtools.refactor.ai.api.OpenAI; +import org.osgi.dto.DTO; + +import aQute.bnd.exceptions.Exceptions; +import aQute.bnd.http.HttpClient; +import aQute.bnd.service.url.TaggedData; +import aQute.lib.json.JSONCodec; +import aQute.lib.strings.Strings; + +public class OpenAIProvider implements OpenAI { + final static String DEFAULT_MODELS = """ + gpt-3.5-turbo-1106; + context=16385; + output=4096, + gpt-3.5-turbo; + context=4096; + output=4096, + gpt-3.5-turbo-16k; + context=16385; + output=4096, + gpt-4-1106-preview; + context=128000; + output=4096, + gpt-4 + context=8192; + output=4096, + gpt-4-32k; + context=32000; + output=4096"""; + + final static JSONCodec codec = new JSONCodec(); + static { + codec.setIgnorenull(true); + } + final static String openaiApiUrl = "https://api.openai.com/v1/chat/completions"; + final static String modelsUrl = "https://api.openai.com/v1/models"; + + final String apiKey; + final HttpClient client; + final List models; + + public OpenAIProvider(HttpClient client, String apiKey, List models) { + this.models = models == null ? Strings.split(DEFAULT_MODELS) : models; + this.apiKey = apiKey; + this.client = client; + } + + @Override + public Chat createChat(Configuration configuration) { + return new ChatImpl(this, configuration); + } + + R get(String url, M msg, Class replyType) { + try { + + TaggedData reply; + if (msg != null) { + String payload = codec.enc() + .writeDefaults() + .put(msg) + .toString(); + System.out.println(payload); + reply = client.build() + .headers("Content-Type", "application/json") + .headers("Authorization", "Bearer " + apiKey) + .upload(payload) + .post() + .asTag() + .go(new URI(url)); + } else { + reply = client.build() + .headers("Content-Type", "application/json") + .headers("Authorization", "Bearer " + apiKey) + .get() + .asTag() + .go(new URI(url)); + } + + if (reply.getResponseCode() >= 300) { + throw new RuntimeException("Error response: " + reply); + } else { + return codec.dec() + .from(reply.getInputStream()) + .get(replyType); + } + } catch (Exception e) { + throw Exceptions.duck(e); + } + } + + public static class Model extends DTO { + public String id; + public String object; + public String owned_by; + public List permission; + } + + public static class Models extends DTO { + public List data; + public String object; + } + + @Override + public List models() { + return get(modelsUrl, null, Models.class).data.stream() + .map(m -> m.id) + .toList(); + } + + public static class Usage extends DTO { + public int prompt_tokens; + public int total_tokens; + } + + public static class EmbeddingRequestDTO extends DTO { + public String input; + public String model; + } + + public static class EmbeddingPayloadDTO extends DTO { + public float[] embedding; + public int index; + public String object; + } + + public static class EmbeddingResponseDTO extends DTO { + public List data; + public String model; + public String object; + public Usage usage; + } + + @Override + public Embedder getEmbedder(Configuration configuration) { + return new Embedder() { + + @Override + public float[] getEmbedding(String text) { + EmbeddingRequestDTO request = new EmbeddingRequestDTO(); + request.model = configuration.model == null ? "gpt-3.5-turbo" : configuration.model; + request.input = text; + EmbeddingResponseDTO response = get("https://api.openai.com/v1/embeddings", request, + EmbeddingResponseDTO.class); + return response.data.get(0).embedding; + } + }; + } + + @Override + public List getModels() { + return models; + } + +} diff --git a/bndtools.core/src/org/bndtools/refactor/ai/api/Chat.java b/bndtools.core/src/org/bndtools/refactor/ai/api/Chat.java new file mode 100644 index 0000000000..ade303d9f6 --- /dev/null +++ b/bndtools.core/src/org/bndtools/refactor/ai/api/Chat.java @@ -0,0 +1,18 @@ +package org.bndtools.refactor.ai.api; + +public interface Chat { + + Reply ask(String question); + + void system(String command); + + void assistant(String command); + + void model(String model); + + void clear(); + + void setProlog(String string); + + void clear(String role); +} diff --git a/bndtools.core/src/org/bndtools/refactor/ai/api/Embedder.java b/bndtools.core/src/org/bndtools/refactor/ai/api/Embedder.java new file mode 100644 index 0000000000..2cfa47e081 --- /dev/null +++ b/bndtools.core/src/org/bndtools/refactor/ai/api/Embedder.java @@ -0,0 +1,6 @@ +package org.bndtools.refactor.ai.api; + +public interface Embedder { + + float[] getEmbedding(String text); +} diff --git a/bndtools.core/src/org/bndtools/refactor/ai/api/OpenAI.java b/bndtools.core/src/org/bndtools/refactor/ai/api/OpenAI.java new file mode 100644 index 0000000000..65849b1f9f --- /dev/null +++ b/bndtools.core/src/org/bndtools/refactor/ai/api/OpenAI.java @@ -0,0 +1,28 @@ +package org.bndtools.refactor.ai.api; + +import java.util.List; + +import org.osgi.dto.DTO; + +public interface OpenAI { + class Configuration extends DTO { + public String model; + } + List models(); + + Embedder getEmbedder(Configuration configuration); + + Chat createChat(Configuration configuration); + + default Chat createChat() { + return createChat(new Configuration()); + } + + /** + * Get a list of preferred models. + * + * @return the list of preferred models + */ + List getModels(); + +} diff --git a/bndtools.core/src/org/bndtools/refactor/ai/api/Reply.java b/bndtools.core/src/org/bndtools/refactor/ai/api/Reply.java new file mode 100644 index 0000000000..1ce1ddf237 --- /dev/null +++ b/bndtools.core/src/org/bndtools/refactor/ai/api/Reply.java @@ -0,0 +1,5 @@ +package org.bndtools.refactor.ai.api; + +public class Reply { + public String reply; +} diff --git a/bndtools.core/src/org/bndtools/refactor/types/DiverseRefactorer.java b/bndtools.core/src/org/bndtools/refactor/types/DiverseRefactorer.java index aa8d98724b..6e388593ba 100644 --- a/bndtools.core/src/org/bndtools/refactor/types/DiverseRefactorer.java +++ b/bndtools.core/src/org/bndtools/refactor/types/DiverseRefactorer.java @@ -17,12 +17,19 @@ import org.eclipse.jdt.core.dom.FieldAccess; import org.eclipse.jdt.core.dom.FieldDeclaration; import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.Name; import org.eclipse.jdt.core.dom.SingleVariableDeclaration; import org.eclipse.jdt.core.dom.Statement; import org.eclipse.jdt.core.dom.SuperConstructorInvocation; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.ui.text.java.IInvocationContext; import org.eclipse.jdt.ui.text.java.IQuickFixProcessor; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.ITextSelection; +import org.eclipse.jface.text.TextSelection; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.texteditor.ITextEditor; import org.osgi.service.component.annotations.Component; /** @@ -43,16 +50,44 @@ public void addCompletions(ProposalBuilder builder, RefactorAssistant assistant, .filter(MethodDeclaration::isConstructor)) .forEach((ass, svd) -> { String name = makeUniqueName(assistant, svd); - builder.build("div.constr.final", "Assign to final field " + name, "final", 0, + builder.build("div.constr.final", "Assign to final field " + name, "final-field", 0, () -> addField(ass, svd, name, JavaModifier.FINAL)); }); + + if (context != null && context.getSelectionLength() == 0) { + ASTNode node = context.getCoveringNode(); + while (node instanceof Name) { + node = node.getParent(); + } + if (node != null) { + ASTNode fixed = node; + builder.build("div.select", "Select complete " + node.getClass() + .getSimpleName(), "set-selection", 0, () -> addSelection(fixed)); + } + } + } + + private void addSelection(ASTNode node) { + IEditorPart editorPart = PlatformUI.getWorkbench() + .getActiveWorkbenchWindow() + .getActivePage() + .getActiveEditor(); + if (editorPart instanceof ITextEditor) { + ITextEditor textEditor = (ITextEditor) editorPart; + IDocument document = textEditor.getDocumentProvider() + .getDocument(textEditor.getEditorInput()); + + ITextSelection selection = new TextSelection(document, node.getStartPosition(), node.getLength()); + textEditor.getSelectionProvider() + .setSelection(selection); + } } private String makeUniqueName(RefactorAssistant ass, ASTNode svd) { String name = ass.getIdentifier(svd); - int n =1; + int n = 1; Set fieldNames = ass.getFieldNames(ass.getAncestor(svd, TypeDeclaration.class)); - while( fieldNames.contains(name)) { + while (fieldNames.contains(name)) { name = ass.getIdentifier(svd) + n++; } return name; diff --git a/bndtools.core/src/org/bndtools/refactor/types/LiteralRefactorer.java b/bndtools.core/src/org/bndtools/refactor/types/LiteralRefactorer.java index 9c8685ee82..4984f426ea 100644 --- a/bndtools.core/src/org/bndtools/refactor/types/LiteralRefactorer.java +++ b/bndtools.core/src/org/bndtools/refactor/types/LiteralRefactorer.java @@ -110,7 +110,7 @@ private void textblock(TextBlock textBlock, ProposalBuilder builder) { RefactorAssistant assistant = builder.getAssistant(); String content = textBlock.getLiteralValue(); - builder.build("lit.blck.strn", "Convert to string", null, 0, () -> { + builder.build("lit.blck.strn", "Convert to string", "block-to-string", 0, () -> { assistant.replace(textBlock, assistant.newStringLiteral(content)); }); @@ -142,7 +142,7 @@ private void stringliteral(StringLiteral stringLiteral, ProposalBuilder builder, RefactorAssistant assistant = builder.getAssistant(); String content = stringLiteral.getLiteralValue(); - builder.build("lit.strn.blck", "Convert to text block", null, 0, () -> { + builder.build("lit.strn.blck", "Convert to text block", "string-to-block", 0, () -> { assistant.replace(stringLiteral, assistant.newTextBlock(content)); }); diff --git a/bndtools.core/src/org/bndtools/refactor/util/Cursor.java b/bndtools.core/src/org/bndtools/refactor/util/Cursor.java index 55d561007d..b3b03475f8 100644 --- a/bndtools.core/src/org/bndtools/refactor/util/Cursor.java +++ b/bndtools.core/src/org/bndtools/refactor/util/Cursor.java @@ -331,4 +331,5 @@ default Cursor downTo(Class class1) { */ boolean isEmpty(); + } diff --git a/bndtools.core/src/org/bndtools/refactor/util/RefactorAssistant.java b/bndtools.core/src/org/bndtools/refactor/util/RefactorAssistant.java index 99202baa3a..4147e0c632 100644 --- a/bndtools.core/src/org/bndtools/refactor/util/RefactorAssistant.java +++ b/bndtools.core/src/org/bndtools/refactor/util/RefactorAssistant.java @@ -562,6 +562,7 @@ Cursor map0(Function mapper, String format, Object. else return new CursorImpl<>(result, start, length); } + } class FailedCursor implements Cursor { @@ -986,11 +987,11 @@ static Class commonType(Class a, Class b) { return Object.class; } - final Memoize engine; - final Map imports = new LinkedHashMap<>(); - final @Nullable ICompilationUnit iunit; - final Set onDemand = new HashSet<>(); - final String source; + final Memoize engine; + final Map imports = new LinkedHashMap<>(); + final Set added = new LinkedHashSet<>(); + final @Nullable ICompilationUnit iunit; + final String source; /** * Create an assistant when there is only a CompilationUnit @@ -1316,12 +1317,14 @@ public Entry entry(String name, Object value) { */ public void fixup() { Set referred = getReferredTypes(unit()); - for (ImportDeclaration import_ : getImports()) { - String name = import_.getName() - .toString(); - if (!referred.contains(name)) - delete(import_); - } + imports.forEach((k, v) -> { + if (!k.startsWith(".") && !added.contains(v)) { + String fqn = v.getName() + .getFullyQualifiedName(); + if (!referred.contains(fqn)) + delete(v); + } + }); } /** @@ -1748,9 +1751,12 @@ public Name createImportedName(String name) { QualifiedName qualifiedName = ast().newQualifiedName(qualifier, simpleName); simpleName = ast().newSimpleName(parts[1]); - String imported = imports.get(parts[1]); + ImportDeclaration imported = imports.get(parts[1]); if (imported != null) { - if (name.equals(imported)) + String fqn = imported.getName() + .getFullyQualifiedName(); + + if (name.equals(fqn)) return simpleName; return qualifiedName; @@ -1759,8 +1765,8 @@ public Name createImportedName(String name) { ImportDeclaration newImportDeclaration = ast().newImportDeclaration(); newImportDeclaration.setName(qualifiedName); engine().insertLast(unit(), ImportDeclaration.class, newImportDeclaration); - imports.put(parts[1], name); - + added.add(newImportDeclaration); + imports.put(toImportKey(qualifiedName, false, false), newImportDeclaration); return simpleName; } @@ -1941,11 +1947,12 @@ public Predicate isEqualFQN(Class type, String a) { int aN = aName[0] == null ? 0 : 1; Function identity = getIdentity(type); + return left -> { String b = identity.apply(left); String[] bName = splitName(b); - if (!bName[1].equals(aName[1])) + if (!bName[1].equals(aName[1])) // simple name return false; int bN = bName[0] == null ? 0 : 2; @@ -1959,12 +1966,12 @@ public Predicate isEqualFQN(Class type, String a) { * a=foo.X and b=X */ case 1 -> resolve(b).map(bFqn -> bFqn.equals(a)) - .orElseGet(() -> onDemand.contains(aName[0])); + .orElseGet(() -> imports.containsKey(".D_" + aName[0])); /* * a=X and b=foo.X */ case 2 -> resolve(a).map(aFqn -> aFqn.equals(b)) - .orElseGet(() -> onDemand.contains(bName[0])); + .orElseGet(() -> imports.containsKey(".D_" + bName[0])); /* * a=foo.X and b= bar.X */ @@ -2315,8 +2322,13 @@ public void replace(TextBlock older, String token) { public Optional resolve(String t) { init(); - String resolved = imports.get(t); - return Optional.ofNullable(resolved); + ImportDeclaration resolved = imports.get(t); + if (resolved == null || resolved.isOnDemand() || resolved.isStatic()) + return Optional.empty(); + + String fullyQualifiedName = resolved.getName() + .getFullyQualifiedName(); + return Optional.ofNullable(fullyQualifiedName); } public Optional resolve(Type type) { @@ -2325,22 +2337,25 @@ public Optional resolve(Type type) { return Optional.of(resolveBinding.getQualifiedName()); } - init(); if (type instanceof PrimitiveType pt) { return Optional.of(pt.getPrimitiveTypeCode() .toString()); } + String resolve = null; if (type instanceof SimpleType st) { Name name = st.getName(); if (name instanceof SimpleName sn) { resolve = sn.getIdentifier(); - String resolved = imports.get(resolve); + ImportDeclaration resolved = imports.get(resolve); if (resolved == null) { if (javalang.contains(resolve)) return Optional.of("java.lang." + resolve); + return Optional.empty(); + } else { + return Optional.ofNullable(resolved.getName() + .getFullyQualifiedName()); } - return Optional.ofNullable(resolved); } else if (name instanceof QualifiedName qn) { return Optional.of(qn.getFullyQualifiedName()); } @@ -2356,11 +2371,12 @@ public Optional resolve(Type type) { } else if (type instanceof NameQualifiedType nt) { SimpleName name = nt.getName(); resolve = name.getIdentifier(); - String resolved = imports.get(resolve); + ImportDeclaration resolved = imports.get(resolve); if (resolved == null) { - - } - return Optional.ofNullable(resolved); + return Optional.empty(); + } else + return Optional.of(resolved.getName() + .getFullyQualifiedName()); } else if (type instanceof WildcardType wt) { return resolve(wt.getBound()); } else if (type instanceof IntersectionType it) { @@ -2395,21 +2411,40 @@ private AST ast() { } private void init() { + int onDemand = 1000; if (imports.isEmpty()) { for (ImportDeclaration id : getImports()) { - Name name = id.getName(); - String fqn = name.getFullyQualifiedName(); - if (!id.isOnDemand()) { - String simple = toSimple(fqn); - String old = imports.put(simple, fqn); - assert old == null; - } else { - onDemand.add(fqn); - } + String importKey = toImportKey(id.getName(), id.isStatic(), id.isOnDemand()); + imports.put(importKey, id); } } } + /** + *
    + *
  • For a regular on-demand import, this is the name of a package. + *
  • For a static on-demand import, this is the qualified name of a type. + *
  • For a regular single-type import, this is the qualified name of a + * type. + *
  • For a static single-type import, this is the qualified name of a + * static member of a type. + *
+ */ + + private String toImportKey(Name name, boolean static1, boolean onDemand2) { + int n = static1 ? 1 : 0; + n += onDemand2 ? 2 : 0; + return switch (n) { + case 0 -> ((QualifiedName) name).getName() + .getIdentifier(); + case 1 -> ".S_" + ((QualifiedName) name).getName() + .getIdentifier(); + case 2 -> ".D_" + name.getFullyQualifiedName(); + case 3 -> ".SD_" + name.getFullyQualifiedName(); + default -> throw new IllegalArgumentException("Unexpected value: " + n); + }; + } + private Annotation newAnnotation0(String annName, Entry... entries) { NormalAnnotation normalAnnotation = ast().newNormalAnnotation(); normalAnnotation.setTypeName(createImportedName(annName)); @@ -2482,11 +2517,6 @@ private String[] splitName(String fqn) { } } - private String toSimple(String fqn) { - int n = fqn.lastIndexOf('.'); - return fqn.substring(n + 1); - } - private CompilationUnit unit() { return engine.get().unit; } diff --git a/bndtools.core/test/org/bndtools/refactor/types/ComponentRefactorerTest.java b/bndtools.core/test/org/bndtools/refactor/types/ComponentRefactorerTest.java index e575215f2c..ea19f16671 100644 --- a/bndtools.core/test/org/bndtools/refactor/types/ComponentRefactorerTest.java +++ b/bndtools.core/test/org/bndtools/refactor/types/ComponentRefactorerTest.java @@ -19,6 +19,8 @@ void testGogoRefactoring(Scenario s) throws Exception { static List scenarios() { String empty = """ + import static com.example.Static.*; + import static com.example.Static.abc; import com.example.Foo; import com.example.Bar; class F { @@ -36,6 +38,8 @@ void unsetService( Bar service){ } """; String emptyPublic = """ + import static com.example.Static.*; + import static com.example.Static.abc; import com.example.Foo; import com.example.Bar; class F { @@ -53,6 +57,8 @@ void unsetService( Bar service){ } """; String C = """ + import static com.example.Static.*; + import static com.example.Static.abc; import com.example.Foo; import com.example.Bar; import org.osgi.service.component.annotations.Component; @@ -71,6 +77,8 @@ void unsetService( Bar service){ } """; String Cpublic = """ + import static com.example.Static.*; + import static com.example.Static.abc; import com.example.Foo; import com.example.Bar; import org.osgi.service.component.annotations.Component; @@ -89,6 +97,8 @@ void unsetService( Bar service){ } """; String CAc = """ + import static com.example.Static.*; + import static com.example.Static.abc; import com.example.Foo; import com.example.Bar; import org.osgi.service.component.annotations.Component; @@ -108,6 +118,8 @@ void unsetService( Bar service){ } """; String CAcRp = """ + import static com.example.Static.*; + import static com.example.Static.abc; import com.example.Foo; import com.example.Bar; import org.osgi.service.component.annotations.Component; @@ -128,6 +140,8 @@ void unsetService( Bar service){ } """; String CAm = """ + import static com.example.Static.*; + import static com.example.Static.abc; import com.example.Foo; import com.example.Bar; import org.osgi.service.component.annotations.Component; @@ -147,6 +161,8 @@ void unsetService( Bar service){ } """; String CAmDm = """ + import static com.example.Static.*; + import static com.example.Static.abc; import com.example.Foo; import com.example.Bar; import org.osgi.service.component.annotations.Component; @@ -167,6 +183,8 @@ void unsetService( Bar service){ } """; String CAmDmRm = """ + import static com.example.Static.*; + import static com.example.Static.abc; import com.example.Foo; import com.example.Bar; import org.osgi.service.component.annotations.Component;