c7e682f200a5b08a229d85caa0a219c5ee1477ac
[idea/community.git] / java / debugger / impl / src / com / intellij / debugger / impl / DebuggerUtilsEx.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /*
18  * Class DebuggerUtilsEx
19  * @author Jeka
20  */
21 package com.intellij.debugger.impl;
22
23 import com.intellij.debugger.DebuggerBundle;
24 import com.intellij.debugger.SourcePosition;
25 import com.intellij.debugger.engine.DebuggerManagerThreadImpl;
26 import com.intellij.debugger.engine.DebuggerUtils;
27 import com.intellij.debugger.engine.SuspendContextImpl;
28 import com.intellij.debugger.engine.evaluation.*;
29 import com.intellij.debugger.engine.evaluation.expression.EvaluatorBuilder;
30 import com.intellij.debugger.engine.requests.RequestManagerImpl;
31 import com.intellij.debugger.jdi.VirtualMachineProxyImpl;
32 import com.intellij.debugger.requests.Requestor;
33 import com.intellij.debugger.ui.CompletionEditor;
34 import com.intellij.debugger.ui.breakpoints.Breakpoint;
35 import com.intellij.debugger.ui.tree.DebuggerTreeNode;
36 import com.intellij.openapi.actionSystem.DataContext;
37 import com.intellij.openapi.application.ApplicationManager;
38 import com.intellij.openapi.diagnostic.Logger;
39 import com.intellij.openapi.editor.Editor;
40 import com.intellij.openapi.fileTypes.FileType;
41 import com.intellij.openapi.fileTypes.StdFileTypes;
42 import com.intellij.openapi.project.Project;
43 import com.intellij.openapi.util.*;
44 import com.intellij.openapi.util.text.StringUtil;
45 import com.intellij.openapi.vfs.VirtualFile;
46 import com.intellij.pom.Navigatable;
47 import com.intellij.psi.*;
48 import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
49 import com.intellij.ui.classFilter.ClassFilter;
50 import com.intellij.util.SmartList;
51 import com.intellij.util.containers.ContainerUtil;
52 import com.intellij.xdebugger.XSourcePosition;
53 import com.intellij.xdebugger.frame.XValueNode;
54 import com.intellij.xdebugger.impl.XSourcePositionImpl;
55 import com.sun.jdi.*;
56 import com.sun.jdi.event.Event;
57 import com.sun.jdi.event.EventSet;
58 import org.jdom.Attribute;
59 import org.jdom.Element;
60 import org.jetbrains.annotations.NonNls;
61 import org.jetbrains.annotations.NotNull;
62 import org.jetbrains.annotations.Nullable;
63
64 import java.util.*;
65 import java.util.regex.PatternSyntaxException;
66
67 public abstract class DebuggerUtilsEx extends DebuggerUtils {
68   private static final Logger LOG = Logger.getInstance("#com.intellij.debugger.impl.DebuggerUtilsEx");
69
70   /**
71    * @param context
72    * @return all CodeFragmentFactoryProviders that provide code fragment factories suitable in the context given
73    */
74   public static List<CodeFragmentFactory> getCodeFragmentFactories(@Nullable PsiElement context) {
75     final DefaultCodeFragmentFactory defaultFactory = DefaultCodeFragmentFactory.getInstance();
76     final CodeFragmentFactory[] providers = ApplicationManager.getApplication().getExtensions(CodeFragmentFactory.EXTENSION_POINT_NAME);
77     final List<CodeFragmentFactory> suitableFactories = new ArrayList<CodeFragmentFactory>(providers.length);
78     if (providers.length > 0) {
79       for (CodeFragmentFactory factory : providers) {
80         if (factory != defaultFactory && factory.isContextAccepted(context)) {
81           suitableFactories.add(factory);
82         }
83       }
84     }
85     suitableFactories.add(defaultFactory); // let default factory be the last one
86     return suitableFactories;
87   }
88
89
90   public static PsiMethod findPsiMethod(PsiFile file, int offset) {
91     PsiElement element = null;
92
93     while(offset >= 0) {
94       element = file.findElementAt(offset);
95       if(element != null) {
96         break;
97       }
98       offset --;
99     }
100
101     for (; element != null; element = element.getParent()) {
102       if (element instanceof PsiClass || element instanceof PsiLambdaExpression) {
103         return null;
104       }
105       if (element instanceof PsiMethod) {
106         return (PsiMethod)element;
107       }
108     }
109     return null;
110   }
111
112
113   public static boolean isAssignableFrom(final String baseQualifiedName, ReferenceType checkedType) {
114     if (CommonClassNames.JAVA_LANG_OBJECT.equals(baseQualifiedName)) {
115       return true;
116     }
117     return getSuperClass(baseQualifiedName, checkedType) != null;
118   }
119
120   public static ReferenceType getSuperClass(final String baseQualifiedName, ReferenceType checkedType) {
121     if (baseQualifiedName.equals(checkedType.name())) {
122       return checkedType;
123     }
124
125     if (checkedType instanceof ClassType) {
126       ClassType classType = (ClassType)checkedType;
127       ClassType superClassType = classType.superclass();
128       if (superClassType != null) {
129         ReferenceType superClass = getSuperClass(baseQualifiedName, superClassType);
130         if (superClass != null) {
131           return superClass;
132         }
133       }
134       List<InterfaceType> interfaces = classType.allInterfaces();
135       for (InterfaceType iface : interfaces) {
136         ReferenceType superClass = getSuperClass(baseQualifiedName, iface);
137         if (superClass != null) {
138           return superClass;
139         }
140       }
141     }
142
143     if (checkedType instanceof InterfaceType) {
144       List<InterfaceType> list = ((InterfaceType)checkedType).superinterfaces();
145       for (InterfaceType superInterface : list) {
146         ReferenceType superClass = getSuperClass(baseQualifiedName, superInterface);
147         if (superClass != null) {
148           return superClass;
149         }
150       }
151     }
152     return null;
153   }
154
155   public static boolean valuesEqual(Value val1, Value val2) {
156     if (val1 == null) {
157       return val2 == null;
158     }
159     if (val2 == null) {
160       return false;
161     }
162     if (val1 instanceof StringReference && val2 instanceof StringReference) {
163       return ((StringReference)val1).value().equals(((StringReference)val2).value());
164     }
165     return val1.equals(val2);
166   }
167
168   public static String getValueOrErrorAsString(final EvaluationContext evaluationContext, Value value) {
169     try {
170       return getValueAsString(evaluationContext, value);
171     }
172     catch (EvaluateException e) {
173       return e.getMessage();
174     }
175   }
176
177   public static boolean isCharOrInteger(Value value) {
178     return value instanceof CharValue || isInteger(value);
179   }
180
181   private static Set<String> myCharOrIntegers;
182
183   @SuppressWarnings({"HardCodedStringLiteral"})
184   public static boolean isCharOrIntegerArray(Value value) {
185     if (value == null) return false;
186     if (myCharOrIntegers == null) {
187       myCharOrIntegers = new HashSet<String>();
188       myCharOrIntegers.add("C");
189       myCharOrIntegers.add("B");
190       myCharOrIntegers.add("S");
191       myCharOrIntegers.add("I");
192       myCharOrIntegers.add("J");
193     }
194
195     String signature = value.type().signature();
196     int i;
197     for (i = 0; signature.charAt(i) == '['; i++) ;
198     if (i == 0) return false;
199     signature = signature.substring(i, signature.length());
200     return myCharOrIntegers.contains(signature);
201   }
202
203   public static ClassFilter create(Element element) throws InvalidDataException {
204     ClassFilter filter = new ClassFilter();
205     DefaultJDOMExternalizer.readExternal(filter, element);
206     return filter;
207   }
208
209   private static boolean isFiltered(ClassFilter classFilter, String qName) {
210     if (!classFilter.isEnabled()) {
211       return false;
212     }
213     try {
214       if (classFilter.matches(qName)) {
215         return true;
216       }
217     }
218     catch (PatternSyntaxException e) {
219       LOG.debug(e);
220     }
221     return false;
222   }
223
224   public static boolean isFiltered(String qName, ClassFilter[] classFilters) {
225     return isFiltered(qName, Arrays.asList(classFilters));
226   }
227   
228   public static boolean isFiltered(String qName, List<ClassFilter> classFilters) {
229     if(qName.indexOf('[') != -1) {
230       return false; //is array
231     }
232
233     for (ClassFilter filter : classFilters) {
234       if (isFiltered(filter, qName)) {
235         return true;
236       }
237     }
238     return false;
239   }
240   
241   public static int getEnabledNumber(ClassFilter[] classFilters) {
242     int res = 0;
243     for (ClassFilter filter : classFilters) {
244       if (filter.isEnabled()) {
245         res++;
246       }
247     }
248     return res;
249   }
250
251   public static ClassFilter[] readFilters(List<Element> children) throws InvalidDataException {
252     if (ContainerUtil.isEmpty(children)) {
253       return ClassFilter.EMPTY_ARRAY;
254     }
255
256     ClassFilter[] filters = new ClassFilter[children.size()];
257     for (int i = 0, size = children.size(); i < size; i++) {
258       filters[i] = create(children.get(i));
259     }
260     return filters;
261   }
262
263   public static void writeFilters(Element parentNode, @NonNls String tagName, ClassFilter[] filters) throws WriteExternalException {
264     for (ClassFilter filter : filters) {
265       Element element = new Element(tagName);
266       parentNode.addContent(element);
267       DefaultJDOMExternalizer.writeExternal(filter, element);
268     }
269   }
270
271   public static boolean filterEquals(ClassFilter[] filters1, ClassFilter[] filters2) {
272     if (filters1.length != filters2.length) {
273       return false;
274     }
275     final Set<ClassFilter> f1 = new HashSet<ClassFilter>(Math.max((int) (filters1.length/.75f) + 1, 16));
276     final Set<ClassFilter> f2 = new HashSet<ClassFilter>(Math.max((int) (filters2.length/.75f) + 1, 16));
277     Collections.addAll(f1, filters1);
278     Collections.addAll(f2, filters2);
279     return f2.equals(f1);
280   }
281
282   private static boolean elementListsEqual(List<Element> l1, List<Element> l2) {
283     if(l1 == null) return l2 == null;
284     if(l2 == null) return false;
285
286     if(l1.size() != l2.size()) return false;
287
288     Iterator<Element> i1 = l1.iterator();
289
290     for (Element aL2 : l2) {
291       Element elem1 = i1.next();
292
293       if (!elementsEqual(elem1, aL2)) return false;
294     }
295     return true;
296   }
297
298   private static boolean attributeListsEqual(List<Attribute> l1, List<Attribute> l2) {
299     if(l1 == null) return l2 == null;
300     if(l2 == null) return false;
301
302     if(l1.size() != l2.size()) return false;
303
304     Iterator<Attribute> i1 = l1.iterator();
305
306     for (Attribute aL2 : l2) {
307       Attribute attr1 = i1.next();
308
309       if (!Comparing.equal(attr1.getName(), aL2.getName()) || !Comparing.equal(attr1.getValue(), aL2.getValue())) {
310         return false;
311       }
312     }
313     return true;
314   }
315
316   public static boolean elementsEqual(Element e1, Element e2) {
317     if(e1 == null) {
318       return e2 == null;
319     }
320     if (!Comparing.equal(e1.getName(), e2.getName())) {
321       return false;
322     }
323     if (!elementListsEqual  (e1.getChildren(), e2.getChildren())) {
324       return false;
325     }
326     if (!attributeListsEqual(e1.getAttributes(), e2.getAttributes())) {
327       return false;
328     }
329     return true;
330   }
331
332   @SuppressWarnings({"HardCodedStringLiteral"})
333   public static boolean externalizableEqual(JDOMExternalizable  e1, JDOMExternalizable e2) {
334     Element root1 = new Element("root");
335     Element root2 = new Element("root");
336     try {
337       e1.writeExternal(root1);
338     }
339     catch (WriteExternalException e) {
340       LOG.debug(e);
341     }
342     try {
343       e2.writeExternal(root2);
344     }
345     catch (WriteExternalException e) {
346       LOG.debug(e);
347     }
348
349     return elementsEqual(root1, root2);
350   }
351
352   @NotNull
353   public static List<Pair<Breakpoint, Event>> getEventDescriptors(SuspendContextImpl suspendContext) {
354     DebuggerManagerThreadImpl.assertIsManagerThread();
355     if(suspendContext == null) {
356       return Collections.emptyList();
357     }
358     final EventSet events = suspendContext.getEventSet();
359     if(events == null) {
360       return Collections.emptyList();
361     }
362     final List<Pair<Breakpoint, Event>> eventDescriptors = new SmartList<Pair<Breakpoint, Event>>();
363
364     final RequestManagerImpl requestManager = suspendContext.getDebugProcess().getRequestsManager();
365     for (final Event event : events) {
366       final Requestor requestor = requestManager.findRequestor(event.request());
367       if (requestor instanceof Breakpoint) {
368         eventDescriptors.add(Pair.create((Breakpoint)requestor, event));
369       }
370     }
371     return eventDescriptors;
372   }
373
374   public static TextWithImports getEditorText(final Editor editor) {
375     if (editor == null) {
376       return null;
377     }
378     final Project project = editor.getProject();
379     if (project == null) return null;
380
381     String defaultExpression = editor.getSelectionModel().getSelectedText();
382     if (defaultExpression == null) {
383       int offset = editor.getCaretModel().getOffset();
384       PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
385       if (psiFile != null) {
386         PsiElement elementAtCursor = psiFile.findElementAt(offset);
387         if (elementAtCursor != null) {
388           final EditorTextProvider textProvider = EditorTextProvider.EP.forLanguage(elementAtCursor.getLanguage());
389           if (textProvider != null) {
390             final TextWithImports editorText = textProvider.getEditorText(elementAtCursor);
391             if (editorText != null) return editorText;
392           }
393         }
394       }
395     }
396     else {
397       return new TextWithImportsImpl(CodeFragmentKind.EXPRESSION, defaultExpression);
398     }
399     return null;
400   }
401
402   public abstract DebuggerTreeNode  getSelectedNode    (DataContext context);
403
404   public abstract EvaluatorBuilder  getEvaluatorBuilder();
405
406   public abstract CompletionEditor createEditor(Project project, PsiElement context, @NonNls String recentsId);
407
408   @NotNull
409   public static CodeFragmentFactory findAppropriateCodeFragmentFactory(final TextWithImports text, final PsiElement context) {
410     CodeFragmentFactory factory = ApplicationManager.getApplication().runReadAction(new Computable<CodeFragmentFactory>() {
411       @Override
412       public CodeFragmentFactory compute() {
413         final FileType fileType = text.getFileType();
414         final List<CodeFragmentFactory> factories = getCodeFragmentFactories(context);
415         if (fileType == null) {
416           return factories.get(0);
417         }
418         for (CodeFragmentFactory factory : factories) {
419           if (factory.getFileType().equals(fileType)) {
420             return factory;
421           }
422         }
423         return DefaultCodeFragmentFactory.getInstance();
424       }
425     });
426     return new CodeFragmentFactoryContextWrapper(factory);
427   }
428
429   private static class SigReader {
430     final String buffer;
431     int pos = 0;
432
433     SigReader(String s) {
434       buffer = s;
435     }
436
437     int get() {
438       return buffer.charAt(pos++);
439     }
440
441     int peek() {
442       return buffer.charAt(pos);
443     }
444
445     boolean eof() {
446       return buffer.length() <= pos;
447     }
448
449     @NonNls String getSignature() {
450       if (eof()) return "";
451
452       switch (get()) {
453         case 'Z':
454           return "boolean";
455         case 'B':
456           return "byte";
457         case 'C':
458           return "char";
459         case 'S':
460           return "short";
461         case 'I':
462           return "int";
463         case 'J':
464           return "long";
465         case 'F':
466           return "float";
467         case 'D':
468           return "double";
469         case 'V':
470           return "void";
471         case 'L':
472           int start = pos;
473           pos = buffer.indexOf(';', start) + 1;
474           LOG.assertTrue(pos > 0);
475           return buffer.substring(start, pos - 1).replace('/', '.');
476         case '[':
477           return getSignature() + "[]";
478         case '(':
479           StringBuilder result = new StringBuilder("(");
480           String separator = "";
481           while (peek() != ')') {
482             result.append(separator);
483             result.append(getSignature());
484             separator = ", ";
485           }
486           get();
487           result.append(")");
488           return getSignature() + " " + getClassName() + "." + getMethodName() + " " + result;
489         default:
490 //          LOG.assertTrue(false, "unknown signature " + buffer);
491           return null;
492       }
493     }
494
495     String getMethodName() {
496       return "";
497     }
498
499     String getClassName() {
500       return "";
501     }
502   }
503
504   public static String methodName(final Method m) {
505     return methodName(signatureToName(m.declaringType().signature()), m.name(), m.signature());
506   }
507
508   public static String methodName(final String className, final String methodName, final String signature) {
509     try {
510       return new SigReader(signature) {
511         @Override
512         String getMethodName() {
513           return methodName;
514         }
515
516         @Override
517         String getClassName() {
518           return className;
519         }
520       }.getSignature();
521     }
522     catch (Exception ignored) {
523       if (LOG.isDebugEnabled()) {
524         LOG.debug("Internal error : unknown signature" + signature);
525       }
526       return className + "." + methodName;
527     }
528   }
529
530   public static String signatureToName(String s) {
531     return new SigReader(s).getSignature();
532   }
533
534   public static Value createValue(VirtualMachineProxyImpl vm, String expectedType, double value) {
535     if (PsiType.DOUBLE.getPresentableText().equals(expectedType)) {
536       return vm.mirrorOf(value);
537     }
538     if (PsiType.FLOAT.getPresentableText().equals(expectedType)) {
539       return vm.mirrorOf((float)value);
540     }
541     return createValue(vm, expectedType, (long)value);
542   }
543
544   public static Value createValue(VirtualMachineProxyImpl vm, String expectedType, long value) {
545     if (PsiType.LONG.getPresentableText().equals(expectedType)) {
546       return vm.mirrorOf(value);
547     }
548     if (PsiType.INT.getPresentableText().equals(expectedType)) {
549       return vm.mirrorOf((int)value);
550     }
551     if (PsiType.SHORT.getPresentableText().equals(expectedType)) {
552       return vm.mirrorOf((short)value);
553     }
554     if (PsiType.BYTE.getPresentableText().equals(expectedType)) {
555       return vm.mirrorOf((byte)value);
556     }
557     if (PsiType.CHAR.getPresentableText().equals(expectedType)) {
558       return vm.mirrorOf((char)value);
559     }
560     if (PsiType.DOUBLE.getPresentableText().equals(expectedType)) {
561       return vm.mirrorOf((double)value);
562     }
563     if (PsiType.FLOAT.getPresentableText().equals(expectedType)) {
564       return vm.mirrorOf((float)value);
565     }
566     return null;
567   }
568
569   public static Value createValue(VirtualMachineProxyImpl vm, String expectedType, boolean value) {
570     if (PsiType.BOOLEAN.getPresentableText().equals(expectedType)) {
571       return vm.mirrorOf(value);
572     }
573     return null;
574   }
575
576   public static Value createValue(VirtualMachineProxyImpl vm, String expectedType, char value) {
577     if (PsiType.CHAR.getPresentableText().equals(expectedType)) {
578       return vm.mirrorOf(value);
579     }
580     if (PsiType.LONG.getPresentableText().equals(expectedType)) {
581       return vm.mirrorOf((long)value);
582     }
583     if (PsiType.INT.getPresentableText().equals(expectedType)) {
584       return vm.mirrorOf((int)value);
585     }
586     if (PsiType.SHORT.getPresentableText().equals(expectedType)) {
587       return vm.mirrorOf((short)value);
588     }
589     if (PsiType.BYTE.getPresentableText().equals(expectedType)) {
590       return vm.mirrorOf((byte)value);
591     }
592     return null;
593   }
594
595   public static String truncateString(final String str) {
596     // leave a small gap over XValueNode.MAX_VALUE_LENGTH to detect oversize
597     if (str.length() > XValueNode.MAX_VALUE_LENGTH + 5) {
598       return str.substring(0, XValueNode.MAX_VALUE_LENGTH + 5);
599     }
600     return str;
601   }
602
603   public static String getThreadStatusText(int statusId) {
604     switch (statusId) {
605       case ThreadReference.THREAD_STATUS_MONITOR:
606         return DebuggerBundle.message("status.thread.monitor");
607       case ThreadReference.THREAD_STATUS_NOT_STARTED:
608         return DebuggerBundle.message("status.thread.not.started");
609       case ThreadReference.THREAD_STATUS_RUNNING:
610         return DebuggerBundle.message("status.thread.running");
611       case ThreadReference.THREAD_STATUS_SLEEPING:
612         return DebuggerBundle.message("status.thread.sleeping");
613       case ThreadReference.THREAD_STATUS_UNKNOWN:
614         return DebuggerBundle.message("status.thread.unknown");
615       case ThreadReference.THREAD_STATUS_WAIT:
616         return DebuggerBundle.message("status.thread.wait");
617       case ThreadReference.THREAD_STATUS_ZOMBIE:
618         return DebuggerBundle.message("status.thread.zombie");
619       default:
620         return DebuggerBundle.message("status.thread.undefined");
621     }
622   }
623
624   public static String prepareValueText(String text, Project project) {
625     text = StringUtil.unquoteString(text);
626     text = StringUtil.unescapeStringCharacters(text);
627     int tabSize = CodeStyleSettingsManager.getSettings(project).getTabSize(StdFileTypes.JAVA);
628     if (tabSize < 0) {
629       tabSize = 0;
630     }
631     return text.replace("\t", StringUtil.repeat(" ", tabSize));
632   }
633
634   @Nullable
635   public static XSourcePosition toXSourcePosition(@NotNull SourcePosition position) {
636     VirtualFile file = position.getFile().getVirtualFile();
637     if (file == null) {
638       file = position.getFile().getOriginalFile().getVirtualFile();
639     }
640     if (file == null) {
641       return null;
642     }
643     return new JavaXSourcePosition(position, file);
644   }
645
646   private static class JavaXSourcePosition implements XSourcePosition {
647     private final SourcePosition mySourcePosition;
648     @NotNull private final VirtualFile myFile;
649
650     public JavaXSourcePosition(@NotNull SourcePosition sourcePosition, @NotNull VirtualFile file) {
651       mySourcePosition = sourcePosition;
652       myFile = file;
653     }
654
655     @Override
656     public int getLine() {
657       return mySourcePosition.getLine();
658     }
659
660     @Override
661     public int getOffset() {
662       return mySourcePosition.getOffset();
663     }
664
665     @NotNull
666     @Override
667     public VirtualFile getFile() {
668       return myFile;
669     }
670
671     @NotNull
672     @Override
673     public Navigatable createNavigatable(@NotNull Project project) {
674       return XSourcePositionImpl.doCreateOpenFileDescriptor(project, this);
675     }
676   }
677
678   /**
679    * Decompiler aware version
680    */
681   @Nullable
682   public static PsiElement findElementAt(@Nullable PsiFile file, int offset) {
683     if (file instanceof PsiCompiledFile) {
684       file = ((PsiCompiledFile)file).getDecompiledPsiFile();
685     }
686     if (file == null) return null;
687     return file.findElementAt(offset);
688   }
689 }