51d9be0f23d3a66862cdf91eda69625f3e151c7d
[idea/community.git] / python / helpers / pydev / pydevd_resolver.py
1 try:
2     import StringIO
3 except:
4     import io as StringIO
5 import traceback
6 from os.path import basename
7
8 try:
9     __setFalse = False
10 except:
11     import __builtin__
12     setattr(__builtin__, 'True', 1)
13     setattr(__builtin__, 'False', 0)
14
15 import pydevd_constants
16 from pydevd_constants import DictIterItems, xrange
17
18
19 # Note: 300 is already a lot to see in the outline (after that the user should really use the shell to get things)
20 # and this also means we'll pass less information to the client side (which makes debugging faster).
21 MAX_ITEMS_TO_HANDLE = 300 
22
23 TOO_LARGE_MSG = 'Too large to show contents. Max items to show: ' + str(MAX_ITEMS_TO_HANDLE)
24 TOO_LARGE_ATTR = 'Unable to handle:'
25
26 #=======================================================================================================================
27 # UnableToResolveVariableException
28 #=======================================================================================================================
29 class UnableToResolveVariableException(Exception):
30     pass
31
32
33 #=======================================================================================================================
34 # InspectStub
35 #=======================================================================================================================
36 class InspectStub:
37     def isbuiltin(self, _args):
38         return False
39     def isroutine(self, object):
40         return False
41
42 try:
43     import inspect
44 except:
45     inspect = InspectStub()
46
47 try:
48     import java.lang #@UnresolvedImport
49 except:
50     pass
51
52 #types does not include a MethodWrapperType
53 try:
54     MethodWrapperType = type([].__str__)
55 except:
56     MethodWrapperType = None
57
58
59 #=======================================================================================================================
60 # AbstractResolver
61 #=======================================================================================================================
62 class AbstractResolver:
63     '''
64         This class exists only for documentation purposes to explain how to create a resolver.
65
66         Some examples on how to resolve things:
67         - list: getDictionary could return a dict with index->item and use the index to resolve it later
68         - set: getDictionary could return a dict with id(object)->object and reiterate in that array to resolve it later
69         - arbitrary instance: getDictionary could return dict with attr_name->attr and use getattr to resolve it later
70     '''
71
72     def resolve(self, var, attribute):
73         '''
74             In this method, we'll resolve some child item given the string representation of the item in the key
75             representing the previously asked dictionary.
76
77             @param var: this is the actual variable to be resolved.
78             @param attribute: this is the string representation of a key previously returned in getDictionary.
79         '''
80         raise NotImplementedError
81
82     def getDictionary(self, var):
83         '''
84             @param var: this is the variable that should have its children gotten.
85
86             @return: a dictionary where each pair key, value should be shown to the user as children items
87             in the variables view for the given var.
88         '''
89         raise NotImplementedError
90
91
92 #=======================================================================================================================
93 # DefaultResolver
94 #=======================================================================================================================
95 class DefaultResolver:
96     '''
97         DefaultResolver is the class that'll actually resolve how to show some variable.
98     '''
99
100     def resolve(self, var, attribute):
101         return getattr(var, attribute)
102
103     def getDictionary(self, var):
104         if MethodWrapperType:
105             return self._getPyDictionary(var)
106         else:
107             return self._getJyDictionary(var)
108
109     def _getJyDictionary(self, obj):
110         ret = {}
111         found = java.util.HashMap()
112
113         original = obj
114         if hasattr(obj, '__class__') and obj.__class__ == java.lang.Class:
115
116             #get info about superclasses
117             classes = []
118             classes.append(obj)
119             c = obj.getSuperclass()
120             while c != None:
121                 classes.append(c)
122                 c = c.getSuperclass()
123
124             #get info about interfaces
125             interfs = []
126             for obj in classes:
127                 interfs.extend(obj.getInterfaces())
128             classes.extend(interfs)
129
130             #now is the time when we actually get info on the declared methods and fields
131             for obj in classes:
132
133                 declaredMethods = obj.getDeclaredMethods()
134                 declaredFields = obj.getDeclaredFields()
135                 for i in xrange(len(declaredMethods)):
136                     name = declaredMethods[i].getName()
137                     ret[name] = declaredMethods[i].toString()
138                     found.put(name, 1)
139
140                 for i in xrange(len(declaredFields)):
141                     name = declaredFields[i].getName()
142                     found.put(name, 1)
143                     #if declaredFields[i].isAccessible():
144                     declaredFields[i].setAccessible(True)
145                     #ret[name] = declaredFields[i].get( declaredFields[i] )
146                     try:
147                         ret[name] = declaredFields[i].get(original)
148                     except:
149                         ret[name] = declaredFields[i].toString()
150
151         #this simple dir does not always get all the info, that's why we have the part before
152         #(e.g.: if we do a dir on String, some methods that are from other interfaces such as
153         #charAt don't appear)
154         try:
155             d = dir(original)
156             for name in d:
157                 if found.get(name) is not 1:
158                     ret[name] = getattr(original, name)
159         except:
160             #sometimes we're unable to do a dir
161             pass
162
163         return ret
164
165     def _getPyDictionary(self, var):
166         filterPrivate = False
167         filterSpecial = True
168         filterFunction = True
169         filterBuiltIn = True
170
171         names = dir(var)
172         if not names and hasattr(var, '__members__'):
173             names = var.__members__
174         d = {}
175
176         #Be aware that the order in which the filters are applied attempts to
177         #optimize the operation by removing as many items as possible in the
178         #first filters, leaving fewer items for later filters
179
180         if filterBuiltIn or filterFunction:
181             for n in names:
182                 if filterSpecial:
183                     if n.startswith('__') and n.endswith('__'):
184                         continue
185
186                 if filterPrivate:
187                     if n.startswith('_') or n.endswith('__'):
188                         continue
189
190                 try:
191                     attr = getattr(var, n)
192
193                     #filter builtins?
194                     if filterBuiltIn:
195                         if inspect.isbuiltin(attr):
196                             continue
197
198                     #filter functions?
199                     if filterFunction:
200                         if inspect.isroutine(attr) or isinstance(attr, MethodWrapperType):
201                             continue
202                 except:
203                     #if some error occurs getting it, let's put it to the user.
204                     strIO = StringIO.StringIO()
205                     traceback.print_exc(file=strIO)
206                     attr = strIO.getvalue()
207
208                 d[ n ] = attr
209
210         return d
211
212
213 #=======================================================================================================================
214 # DictResolver
215 #=======================================================================================================================
216 class DictResolver:
217
218     def resolve(self, dict, key):
219         if key in ('__len__', TOO_LARGE_ATTR):
220             return None
221
222         if '(' not in key:
223             #we have to treat that because the dict resolver is also used to directly resolve the global and local
224             #scopes (which already have the items directly)
225             return dict[key]
226
227         #ok, we have to iterate over the items to find the one that matches the id, because that's the only way
228         #to actually find the reference from the string we have before.
229         expected_id = int(key.split('(')[-1][:-1])
230         for key, val in DictIterItems(dict):
231             if id(key) == expected_id:
232                 return val
233
234         raise UnableToResolveVariableException()
235
236     def keyStr(self, key):
237         if isinstance(key, str):
238             return "'%s'"%key
239         else:
240             if not pydevd_constants.IS_PY3K:
241                 if isinstance(key, unicode):
242                     return "u'%s'"%key
243             return key
244
245     def getDictionary(self, dict):
246         ret = {}
247
248         i = 0
249         for key, val in DictIterItems(dict):
250             i += 1
251             #we need to add the id because otherwise we cannot find the real object to get its contents later on.
252             key = '%s (%s)' % (self.keyStr(key), id(key))
253             ret[key] = val
254             if i > MAX_ITEMS_TO_HANDLE:
255                 ret[TOO_LARGE_ATTR] = TOO_LARGE_MSG
256                 break
257
258         ret['__len__'] = len(dict)
259         return ret
260
261
262
263 #=======================================================================================================================
264 # TupleResolver
265 #=======================================================================================================================
266 class TupleResolver: #to enumerate tuples and lists
267
268     def resolve(self, var, attribute):
269         '''
270             @param var: that's the original attribute
271             @param attribute: that's the key passed in the dict (as a string)
272         '''
273         if attribute in ('__len__', TOO_LARGE_ATTR):
274             return None
275         return var[int(attribute)]
276
277     def getDictionary(self, var):
278         l = len(var)
279         d = {}
280
281         format_str = '%0' + str(int(len(str(l)))) + 'd'
282
283         i = 0
284         for item in var:
285             d[format_str % i] = item
286             i += 1
287             
288             if i > MAX_ITEMS_TO_HANDLE:
289                 d[TOO_LARGE_ATTR] = TOO_LARGE_MSG
290                 break
291                 
292         d['__len__'] = len(var)
293         return d
294
295
296
297 #=======================================================================================================================
298 # SetResolver
299 #=======================================================================================================================
300 class SetResolver:
301     '''
302         Resolves a set as dict id(object)->object
303     '''
304
305     def resolve(self, var, attribute):
306         if attribute in ('__len__', TOO_LARGE_ATTR):
307             return None
308
309         attribute = int(attribute)
310         for v in var:
311             if id(v) == attribute:
312                 return v
313
314         raise UnableToResolveVariableException('Unable to resolve %s in %s' % (attribute, var))
315
316     def getDictionary(self, var):
317         d = {}
318         i = 0
319         for item in var:
320             i+= 1
321             d[id(item)] = item
322             
323             if i > MAX_ITEMS_TO_HANDLE:
324                 d[TOO_LARGE_ATTR] = TOO_LARGE_MSG
325                 break
326
327             
328         d['__len__'] = len(var)
329         return d
330
331
332 #=======================================================================================================================
333 # InstanceResolver
334 #=======================================================================================================================
335 class InstanceResolver:
336
337     def resolve(self, var, attribute):
338         field = var.__class__.getDeclaredField(attribute)
339         field.setAccessible(True)
340         return field.get(var)
341
342     def getDictionary(self, obj):
343         ret = {}
344
345         declaredFields = obj.__class__.getDeclaredFields()
346         for i in xrange(len(declaredFields)):
347             name = declaredFields[i].getName()
348             try:
349                 declaredFields[i].setAccessible(True)
350                 ret[name] = declaredFields[i].get(obj)
351             except:
352                 traceback.print_exc()
353
354         return ret
355
356
357 #=======================================================================================================================
358 # JyArrayResolver
359 #=======================================================================================================================
360 class JyArrayResolver:
361     '''
362         This resolves a regular Object[] array from java
363     '''
364
365     def resolve(self, var, attribute):
366         if attribute == '__len__':
367             return None
368         return var[int(attribute)]
369
370     def getDictionary(self, obj):
371         ret = {}
372
373         for i in xrange(len(obj)):
374             ret[ i ] = obj[i]
375
376         ret['__len__'] = len(obj)
377         return ret
378
379
380 #=======================================================================================================================
381 # NdArrayResolver
382 #=======================================================================================================================
383 class NdArrayResolver:
384     '''
385         This resolves a numpy ndarray returning some metadata about the NDArray
386     '''
387
388     def is_numeric(self, obj):
389         if not hasattr(obj, 'dtype'):
390             return False
391         return obj.dtype.kind in 'biufc'
392
393     def resolve(self, obj, attribute):
394         if attribute == '__internals__':
395             return defaultResolver.getDictionary(obj)
396         if attribute == 'min':
397             if self.is_numeric(obj):
398                 return obj.min()
399             else:
400                 return None
401         if attribute == 'max':
402             if self.is_numeric(obj):
403                 return obj.max()
404             else:
405                 return None
406         if attribute == 'shape':
407             return obj.shape
408         if attribute == 'dtype':
409             return obj.dtype
410         if attribute == 'size':
411             return obj.size
412         if attribute.startswith('['):
413             l = len(obj)
414             container = NdArrayItemsContainer()
415             if l > MAX_ITEMS_TO_HANDLE:
416                 setattr(container, TOO_LARGE_ATTR, TOO_LARGE_MSG)
417             else:
418                 i = 0
419                 format_str = '%0' + str(int(len(str(l)))) + 'd'
420                 for item in obj:
421                     setattr(container, format_str % i, item)
422                     i += 1
423             return container
424         return None
425
426     def getDictionary(self, obj):
427         ret = dict()
428         ret['__internals__'] = defaultResolver.getDictionary(obj)
429         if obj.size > 1024 * 1024:
430             ret['min'] = 'ndarray too big, calculating min would slow down debugging'
431             ret['max'] = 'ndarray too big, calculating max would slow down debugging'
432         else:
433             if self.is_numeric(obj):
434                 ret['min'] = obj.min()
435                 ret['max'] = obj.max()
436             else:
437                 ret['min'] = 'not a numeric object'
438                 ret['max'] = 'not a numeric object'
439         ret['shape'] = obj.shape
440         ret['dtype'] = obj.dtype
441         ret['size'] = obj.size
442         ret['[0:%s]' % (len(obj))] = {'items' : str(obj)}
443         return ret
444
445 class NdArrayItemsContainer: pass
446 #=======================================================================================================================
447 # FrameResolver
448 #=======================================================================================================================
449 class FrameResolver:
450     '''
451     This resolves a frame.
452     '''
453
454     def resolve(self, obj, attribute):
455         if attribute == '__internals__':
456             return defaultResolver.getDictionary(obj)
457
458         if attribute == 'stack':
459             return self.getFrameStack(obj)
460
461         if attribute == 'f_locals':
462             return obj.f_locals
463
464         return None
465
466
467     def getDictionary(self, obj):
468         ret = dict()
469         ret['__internals__'] = defaultResolver.getDictionary(obj)
470         ret['stack'] = self.getFrameStack(obj)
471         ret['f_locals'] = obj.f_locals
472         return ret
473
474
475     def getFrameStack(self, frame):
476         ret = []
477         if frame is not None:
478             ret.append(self.getFrameName(frame))
479
480             while frame.f_back:
481                 frame = frame.f_back
482                 ret.append(self.getFrameName(frame))
483
484         return ret
485
486     def getFrameName(self, frame):
487         if frame is None:
488             return 'None'
489         try:
490             name = basename(frame.f_code.co_filename)
491             return 'frame: %s [%s:%s]  id:%s' % (frame.f_code.co_name, name, frame.f_lineno, id(frame))
492         except:
493             return 'frame object'
494
495
496 defaultResolver = DefaultResolver()
497 dictResolver = DictResolver()
498 tupleResolver = TupleResolver()
499 instanceResolver = InstanceResolver()
500 jyArrayResolver = JyArrayResolver()
501 setResolver = SetResolver()
502 ndarrayResolver = NdArrayResolver()
503 frameResolver = FrameResolver()