├── .gitignore ├── .jshintrc ├── .bowerrc ├── img └── calls.map.png ├── README.md ├── css └── style.css ├── bower.json ├── index.html ├── .cls ├── calls.parserInner.CLS ├── calls.parserOuter.CLS ├── sc.plain.CLS └── calls.map.CLS ├── LICENSE └── js └── callsmap.js /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { "globals": ["Viva"] } -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "vendor" 3 | } -------------------------------------------------------------------------------- /img/calls.map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intersystems-community/callsmap/master/img/calls.map.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # callsmap 2 | calls map for InterSystems Caché database 3 | ![example](img/calls.map.png) 4 | -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | body{ 2 | background-color: #333; 3 | } 4 | .icon.select { 5 | width: auto; 6 | color: black; 7 | border: 0; 8 | vertical-align: top; 9 | padding-left: 3px; 10 | border-radius: 12px; 11 | height: 24px; 12 | padding-left: 3px; 13 | } 14 | 15 | #graph1{ 16 | position: absolute; 17 | vertical-align:middle; 18 | width: 100%; 19 | height: 100%; 20 | } 21 | #graph1 > svg { 22 | width: 100%; 23 | height: 100%; 24 | } -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "callsmap", 3 | "version": "0.0.1", 4 | "homepage": "https://github.com/doublefint/callsmap", 5 | "authors": [ 6 | "doublefint " 7 | ], 8 | "description": "callsmap for cache", 9 | "main": "callsmap", 10 | "keywords": [ 11 | "callsmap" 12 | ], 13 | "license": "MIT", 14 | "ignore": [ 15 | "**/.*", 16 | "node_modules", 17 | "bower_components", 18 | "vendor", 19 | "test", 20 | "tests" 21 | ], 22 | "dependencies": { 23 | "vivagraphjs": "~0.8.1" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | calls map 6 | 7 | 8 | 9 | 10 | 11 |
12 | 15 |
16 | 17 |
18 |
19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /.cls/calls.parserInner.CLS: -------------------------------------------------------------------------------- 1 | /// вызовы ..{METHODNAME}( ... ) 2 | Class calls.parserInner Extends %RegisteredObject 3 | { 4 | 5 | Property matcher As %Regex.Matcher [ Private, ReadOnly ]; 6 | 7 | Method %OnNew() As %Status [ Private, ServerOnly = 1 ] 8 | { 9 | s re = "\.\." ;две точки 10 | _"(%?\p{L}+\p{N}*)" ;возможно %, обязательно буква, возможно цифры 11 | _"\(" ;открывающая скобка 12 | 13 | s i%matcher = ##class(%Regex.Matcher).%New( re ) 14 | Quit $$$OK 15 | } 16 | 17 | Method parse(text, ByRef data) As %Status 18 | { 19 | s ..matcher.Text = text 20 | if ..matcher.Locate() { 21 | s data = ..matcher.Group(1) 22 | Q 1 23 | } 24 | Q 0 25 | } 26 | 27 | ClassMethod Test() 28 | { 29 | s p =..%New() 30 | s test = "s map = ..%New()", method ="" 31 | w p.parse(test, .method), !, method 32 | s test = "s nsp = ..namespace, class = ..class", method ="" 33 | w p.parse(test, .method), !, method 34 | } 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 doublefint 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /.cls/calls.parserOuter.CLS: -------------------------------------------------------------------------------- 1 | /// Парсер для внешних вызовов 2 | Class calls.parserOuter Extends %RegisteredObject 3 | { 4 | 5 | Property matcher As %Regex.Matcher [ Private, ReadOnly ]; 6 | 7 | /// This callback method is invoked by the %New method to 8 | /// provide notification that a new instance of an object is being created. 9 | /// 10 | ///

If this method returns an error then the object will not be created. 11 | ///

It is passed the arguments provided in the %New call. 12 | /// When customizing this method, override the arguments with whatever variables and types you expect to receive from %New(). 13 | /// For example, if you're going to call %New, passing 2 arguments, %OnNew's signature could be: 14 | ///

Method %OnNew(dob as %Date = "", name as %Name = "") as %Status 15 | /// If instead of returning a %Status code this returns an oref and this oref is a subclass of the current 16 | /// class then this oref will be the one returned to the caller of %New method. 17 | Method %OnNew() As %Status [ Private, ServerOnly = 1 ] 18 | { 19 | s re = "##class\(" ;ищем группу ##class(, которая повторяется один раз 20 | _"(" ;открываем группу 21 | _"[^\)]+" //любые символы кроме закрывающей скобки 22 | _")" 23 | _"\){1}" ;имя класса закрывается скобкой 24 | _"\.{1}" ; и точкой 25 | _"(" ;открываем группу для имени метода 26 | _"[^\(]+" //любые символы кроме открывающей скобки 27 | _")" 28 | _"\({1}" //открывающая скобка 29 | s i%matcher = ##class(%Regex.Matcher).%New( re ) 30 | Quit $$$OK 31 | } 32 | 33 | Method parse(text, ByRef class, ByRef method) As %Status 34 | { 35 | s ..matcher.Text = text 36 | if ..matcher.Locate() { 37 | s class = ..matcher.Group(1) 38 | s method = ..matcher.Group(2) 39 | Q 1 40 | } 41 | Q 0 42 | } 43 | 44 | } 45 | 46 | -------------------------------------------------------------------------------- /js/callsmap.js: -------------------------------------------------------------------------------- 1 | ( function( window ){ 2 | 'use strict'; 3 | 4 | function CM(){ 5 | 6 | this.graph = null; 7 | this.idealLength = 90; //расстояние между узлами 8 | this.elements = { 9 | namespaces: document.getElementById('namespaces') 10 | }; 11 | 12 | this.NAMESPACE = ''; 13 | 14 | this.data ={'nodes':[],'links':[]}; 15 | 16 | this.groups = {}; 17 | this.init(); 18 | 19 | } 20 | 21 | CM.prototype.init = function(){ 22 | 23 | var cm = this; 24 | // смена области 25 | cm.elements.namespaces.addEventListener('change', function onChangeNamespace(e) { 26 | var el = e.target || e.srcElement, 27 | ns = el.options[el.selectedIndex].value 28 | ; 29 | if (ns !== cm.NAMESPACE) { 30 | cm.setNamespace(ns); 31 | } 32 | }); 33 | ///создание графа 34 | cm.createGraph(); 35 | ///загрузка областей 36 | cm.loadNamespace(); 37 | }; 38 | 39 | CM.prototype.createGraph = function(){ 40 | 41 | var cm = this, graph = Viva.Graph.graph(); 42 | graph.Name = 'CallsMap'; 43 | cm.graph = graph; 44 | 45 | 46 | var options = { 47 | springLength: 80, 48 | springCoeff: 1e-4, 49 | dragCoeff: 0.05, 50 | gravity: -3, 51 | theta: 0.5 52 | }; 53 | 54 | var layout = Viva.Graph.Layout.forceDirected( graph, options ); 55 | 56 | var svgGraphics = Viva.Graph.View.svgGraphics(); 57 | 58 | svgGraphics 59 | .node( function( node ){ 60 | 61 | 62 | //var groupId = node.data.group; 63 | //var color = cm.groups[ groupId ]; 64 | var circle = Viva.Graph.svg('circle'); 65 | if (!circle) return; 66 | 67 | circle.attr('r', 10 ) 68 | .attr('stroke', '#fff') 69 | .attr('stroke-width', '1px') 70 | .attr('fill', '#ccc') 71 | ; 72 | 73 | circle.append('title').text( node.id ); 74 | 75 | return circle; 76 | 77 | }) 78 | .placeNode( function( nodeUI, pos ){ 79 | nodeUI.attr('cx', pos.x).attr('cy', pos.y ); 80 | } 81 | ); 82 | 83 | svgGraphics.link( function( link ){ 84 | 85 | return Viva.Graph.svg('line').attr('stroke', '#999') 86 | //.attr('stroke-width', Math.sqrt(link.data)) 87 | ; 88 | 89 | }); 90 | 91 | var renderer = Viva.Graph.View.renderer(graph, { 92 | container : document.getElementById('graph1'), 93 | layout : layout, 94 | graphics : svgGraphics, 95 | prerender: 20, 96 | renderLinks : true 97 | }); 98 | 99 | renderer.run( 1000 ); 100 | cm.renderer = renderer; 101 | 102 | //cm.update(); 103 | }; 104 | 105 | 106 | CM.prototype.loadNamespace = function(){ 107 | var cm = this, currentNamespace= cm.NAMESPACE ; 108 | cm.load('calls.map.cls?namespaces', null, function onLoadNamespace (err , namespaces ){ 109 | if (err) { console.log(err); return; } 110 | cm.elements.namespaces.textContent = ''; 111 | namespaces = namespaces || []; 112 | var i, ns, e, length = namespaces.length ; 113 | for ( i=0; i < length; i++ ) { 114 | ns = namespaces[i]; 115 | e = document.createElement('option'); 116 | e.setAttribute('value', ns); 117 | e.textContent = ns; 118 | if ( ns === currentNamespace ) e.setAttribute('selected', ''); 119 | cm.elements.namespaces.appendChild(e); 120 | } 121 | cm.setNamespace( cm.elements.namespaces.value ); 122 | }); 123 | }; 124 | 125 | CM.prototype.setNamespace = function (namespace) { 126 | 127 | var cm = this , graph = cm.graph; 128 | cm.NAMESPACE = namespace; 129 | graph.clear(); 130 | cm.loadData(); 131 | 132 | }; 133 | 134 | ///загрузка точек 135 | CM.prototype.loadData = function(){ 136 | 137 | var cm = this, url = 'calls.map.cls?namespace=' + cm.NAMESPACE; 138 | var graph = cm.graph; 139 | 140 | cm.load(url,'', function onLoadData(err,result){ 141 | 142 | cm.data.links = result.links; 143 | 144 | graph.beginUpdate(); 145 | 146 | var link, data = cm.data, len = data.links.length ; 147 | 148 | while (len--) { 149 | link = data.links[len]; 150 | graph.addLink( link.a, link.b ); 151 | } 152 | 153 | graph.endUpdate(); 154 | 155 | }); 156 | 157 | }; 158 | 159 | ///генерация цвета 160 | CM.prototype.color = function(){ 161 | 162 | var r=Math.floor(Math.random() * (256)); 163 | var g=Math.floor(Math.random() * (256)); 164 | var b=Math.floor(Math.random() * (256)); 165 | var color='#' + r.toString(16) + g.toString(16) + b.toString(16); 166 | return color; 167 | 168 | }; 169 | 170 | ///метод загрузки данных 171 | CM.prototype.load = function (url, data, callback) { 172 | 173 | var xhr = new XMLHttpRequest(); 174 | 175 | xhr.open(data ? 'POST' : 'GET', url); 176 | 177 | xhr.onreadystatechange = function () { 178 | if (xhr.readyState === 4 && xhr.status === 200) { 179 | return callback(null, JSON.parse(xhr.responseText) || {}); 180 | } else if (xhr.readyState === 4) { 181 | callback(xhr.responseText + ', ' + xhr.status + ': ' + xhr.statusText); 182 | } 183 | }; 184 | 185 | xhr.send(data ? JSON.stringify(data) : undefined); 186 | 187 | }; 188 | 189 | window.onload = function(){ new CM(); }; 190 | 191 | })( window ); 192 | -------------------------------------------------------------------------------- /.cls/sc.plain.CLS: -------------------------------------------------------------------------------- 1 | /// [s]ource [c]ontrol plugin for Cache v.2014.1 or greater 2 | /// ( use %Compiler.UDL.TextServices API ) 3 | /// export or import classes and routines as [plain] text 4 | /// Installation: 5 | ///

    6 | ///
  1. Create in Studio empty class sc.plain and copy-past this code. Compile class
  2. 7 | ///
  3. Set working directory for current namespace - d ##class(sc.plain).directory("c:\mysource\")
  4. 8 | ///
  5. Backup your sources
  6. 9 | ///
  7. Export all sources - d ##class(sc.plain).exportAll()
  8. 10 | ///
  9. Setup class sc.plain as source control class in System Managment Portal or via terminal 11 | /// w ##class(%Studio.SourceControl.Interface).SourceControlClassSet("sc.plain", $zu(5)) 12 | /// 13 | ///
  10. 14 | ///
  11. Reopen Studio
  12. 15 | ///
16 | /// You can import all previously exported sources from your working directory- d ##class(sc.plain).importAll() 17 | Class sc.plain Extends %Studio.SourceControl.Base 18 | { 19 | 20 | /// known extensions 21 | Parameter EXTENSIONS = "CLS,MAC,INT,INC"; 22 | 23 | /// set or get working directory 24 | /// by default - current namespace database directory 25 | ClassMethod directory(directory) 26 | { 27 | #define gl ^sc 28 | if $d( directory ) { 29 | s $$$gl = directory Q directory 30 | } 31 | s directory = $g( $$$gl ) Q:directory="" $zu(12,"") 32 | Q directory 33 | } 34 | 35 | /// Will be silent 36 | /// Used to control which dialogs appear in Studio, if the position is '1' 37 | /// then this dialog will appears and if it is '0' then the dialog will not 38 | /// appear and it will assume that the answer to the dialog was to either accept it 39 | /// or say yes, depending on the dialog. The dialog are: 44 | Parameter Flags As STRING = 0000; 45 | 46 | /// Import the file from the external format into Cache. 47 | /// This is called before the actual load of data to give the chance 48 | /// to load the item from an external format. 49 | Method OnBeforeLoad(code As %String) As %Status 50 | { 51 | Q ..on( , code ) 52 | } 53 | 54 | /// Called when Studio save something 55 | Method OnAfterSave(code As %String, Object As %RegisteredObject = {$$$NULLOREF}) As %Status 56 | { 57 | Q ..on( , code ) 58 | } 59 | 60 | /// Called when Studio compile code 61 | /// and some parts of code may be changed 62 | /// Storage for class, for example 63 | Method OnAfterCompile(code As %String) As %Status 64 | { 65 | Q ..on( , code ) 66 | } 67 | 68 | /// Called when Studio delete code 69 | Method OnAfterDelete(code As %String) As %Status 70 | { 71 | Q ..on( "delete", code ) 72 | } 73 | 74 | ClassMethod on(event = "change", code) As %Status 75 | { 76 | 77 | d ..parse( code, .name, .ext ) 78 | 79 | s allowed = $lfs(..#EXTENSIONS) Q:'$lf( allowed, ext ) 1 80 | 81 | s file = ..filename( code ) 82 | if event = "delete" Q ..delete( code, file ) 83 | 84 | s fileTS = ..fileTS( file ) 85 | s codeTS = ..codeTS( code ) 86 | 87 | Q:codeTS=fileTS 1 ; timestamps equal 88 | if codeTS ] fileTS Q ..export( code, file ) ; code newer - export from database 89 | if fileTS ] codeTS Q ..import( file, code ) ; file newer - import from file 90 | } 91 | 92 | /// convert code name to file name 93 | ClassMethod filename(code) [ CodeMode = expression ] 94 | { 95 | ..directory() _ code 96 | } 97 | 98 | /// get file changed timestamp 99 | ClassMethod fileTS(file) [ ProcedureBlock = 0 ] 100 | { 101 | Q:'##class(%File).Exists( file ) "" 102 | Q $zdt( ##class(%File).GetFileDateModified( file ), 3 ) 103 | } 104 | 105 | /// get code changed timestamp 106 | ClassMethod codeTS(code) [ CodeMode = expression ] 107 | { 108 | ##class(%RoutineMgr).TS( code ) 109 | } 110 | 111 | /// split code name code , for example - "sc.plain.CLS", 112 | /// into code name name - "sc.plain" and extension ext cls 113 | ClassMethod parse(code, Output name, Output ext) As %Status 114 | { 115 | s list = $LFS( code, "." ), length = $ll( list ) 116 | s name = $li( list, 1, length - 1 ), name = $lts( name, "." ) 117 | s ext = $lg( list , length ), ext = $zcvt( ext, "U" ) 118 | Q 1 119 | } 120 | 121 | /// export code to file 122 | ClassMethod export(code, filename = "") As %Status 123 | { 124 | 125 | s:filename="" filename = ..filename( code ) ;full pathname with workdir and extension 126 | 127 | d ..w( code_" -> " _ filename ) 128 | 129 | s directory = ##class(%File).GetDirectory( filename) 130 | s sc = ##class(%File).CreateDirectoryChain( directory ) 131 | s file = ##class(%Stream.FileCharacter).%New() 132 | s file.TranslateTable = "UTF8" 133 | s file.Filename = filename 134 | 135 | d ..parse( code, .name, .ext ) 136 | 137 | if ext = "CLS" { 138 | s sc = ##class(%Compiler.UDL.TextServices).GetTextAsStream( , name, .file ) 139 | s sc = file.%Save() 140 | } else { 141 | s sc = file.CopyFromAndSave(##class(%Routine).%OpenId( code ) ) 142 | } 143 | Q sc 144 | } 145 | 146 | /// import code from file 147 | ClassMethod import(filename, code = "") As %Status 148 | { 149 | s:code="" code=##class(%File).GetFilename( filename ) 150 | 151 | d ..w( filename _ " -> " _ code ) 152 | 153 | s file = ##class(%Stream.FileCharacter).%New() 154 | s file.TranslateTable = "UTF8" 155 | s file.Filename = filename 156 | 157 | d ..parse( code, .name, .ext ) 158 | 159 | if ext = "CLS" { 160 | s sc = ##class(%Compiler.UDL.TextServices).SetTextFromStream( , name, file ) 161 | } else { 162 | 163 | if ##class(%Routine).Exists( code ) { 164 | s routine = ##class(%Routine).%OpenId( code ) 165 | } else { 166 | s routine = ##class(%Routine).%New( code ) 167 | } 168 | d routine.Clear() 169 | s sc = routine.CopyFromAndSave( file ) 170 | 171 | } 172 | 173 | Q sc 174 | } 175 | 176 | ClassMethod delete(code, file) As %Status 177 | { 178 | s sc = ##class(%File).Delete(file) 179 | d ..w( " DELETE " _ file _" -> "_+sc ) 180 | Q 1 181 | } 182 | 183 | /// Initial export classes and routines 184 | ClassMethod exportAll() 185 | { 186 | 187 | #; SummaryFunc - method generated by Caché for query "Summary" 188 | s rs=##class(%Dictionary.ClassDefinition).SummaryFunc() 189 | while rs.%Next() { 190 | if $e( rs.Name ) = "%" continue 191 | if rs.System && rs.Hidden continue 192 | s sc = ..export( rs.Name_".CLS" ) 193 | } 194 | 195 | #; RoutineListFunc - method generated by Cache for class query RoutineList 196 | s rs = ##class(%Routine).RoutineListFunc() 197 | while rs.%Next(){ 198 | s sc = ..export( rs.name) 199 | } 200 | } 201 | 202 | ClassMethod importAll() 203 | { 204 | s masks = $lfs( ..#EXTENSIONS ) 205 | s masks = "*." _ $lts( masks, ";*." ) 206 | s rs = ##class(%File).FileSetFunc(..directory(), masks ) 207 | while rs.%Next(){ 208 | s sc = ..import(rs.Name) d:'sc ..w($system.Status.GetOneErrorText(sc)) 209 | } 210 | } 211 | 212 | ClassMethod w(msg) 213 | { 214 | s io=$io u 0 w !, msg u io 215 | } 216 | 217 | } 218 | 219 | -------------------------------------------------------------------------------- /.cls/calls.map.CLS: -------------------------------------------------------------------------------- 1 | Class calls.map Extends (%Persistent, %CSP.Page) 2 | { 3 | 4 | Parameter CONTENTTYPE = "application/json"; 5 | 6 | /// Specifies the default character set for the page. This can be overriden using the 7 | /// <CSP:CONTENT CHARSET=> tag, or by setting the %response.CharSet property 8 | /// in the OnPreHTTP method. If this parameter is not specified, then 9 | /// for the default charset is utf-8. 10 | Parameter CHARSET; 11 | 12 | /// Event handler for PAGE event: this is invoked in order to 13 | /// generate the content of a csp page. 14 | ClassMethod OnPage(ByRef data) As %Status [ ServerOnly = 1 ] 15 | { 16 | if $d(%request){ 17 | m data = %request.Data 18 | } 19 | 20 | if $d(data("namespaces")) Q ..namespaces() 21 | if $d(data("namespace")){ 22 | s namespace = $g(data("namespace",1)) 23 | d ..Init(namespace) 24 | } 25 | 26 | s jp = ##class(%ZEN.Auxiliary.altJSONSQLProvider).%New() 27 | 28 | s jp.sql = "Select * From calls.link" 29 | s jp.arrayName = "links" 30 | s jp.%Format = "twu" 31 | s jp.maxRows = 10000 32 | 33 | s sc = jp.%DrawJSON() 34 | Quit $$$OK 35 | } 36 | 37 | ClassMethod namespaces() As %Status 38 | { 39 | s rs =##class(%SYS.Namespace).ListFunc() 40 | w "[" s first = 1 41 | while rs.%Next(){ 42 | w:'first "," s:first first=0 43 | w """",$zcvt(rs.Nsp,"O","JSON"),"""" 44 | } 45 | w "]" 46 | Q 1 47 | } 48 | 49 | Property class [ SqlColumnNumber = 3 ]; 50 | 51 | Property method [ SqlColumnNumber = 4 ]; 52 | 53 | Property nameFrom(COLLATION = "EXACT") [ SqlComputeCode = { s {*} = {class}_":"_{method} }, SqlComputed, SqlComputeOnChange = (%%INSERT, class, method) ]; 54 | 55 | Index nameFrom On nameFrom; 56 | 57 | Property line [ SqlColumnNumber = 5 ]; 58 | 59 | Property classcall [ SqlColumnNumber = 6 ]; 60 | 61 | Property methodcall [ SqlColumnNumber = 7 ]; 62 | 63 | Property nameTo(COLLATION = "EXACT") [ SqlComputeCode = { s {*} = {classcall}_":"_{methodcall} }, SqlComputed, SqlComputeOnChange = (%%INSERT, classcall, methodcall) ]; 64 | 65 | Index nameTo On nameTo; 66 | 67 | Property namespace [ SqlColumnNumber = 2 ]; 68 | 69 | /// Для разбора полных внешних вызовов 70 | Property parserOuter As parserOuter [ Transient ]; 71 | 72 | /// Для разбора сокращенных внутренних вызовов 73 | Property parserInner As parserOuter [ Transient ]; 74 | 75 | ClassMethod distance(classFrom, classTo) As %Numeric [ CodeMode = expression, SqlProc ] 76 | { 77 | $S(classFrom=classTo:.1,1:1) 78 | } 79 | 80 | Query Point() As %SQLQuery [ SqlView ] 81 | { 82 | Select Distinct name, calls.map_groupName(name) as "group" From 83 | (Select Distinct nameFrom as name From calls.map 84 | Union All %Parallel 85 | Select Distinct nameTo as name From calls.map) 86 | } 87 | 88 | ClassMethod groupName(name = "") As %String [ CodeMode = expression, SqlProc ] 89 | { 90 | $p($p(name,":"),".") 91 | } 92 | 93 | /// , calls.map_distance(class,classcall) 94 | Query Link() As %SQLQuery [ SqlView ] 95 | { 96 | Select nameFrom as a, nameTo as b 97 | --, count(1) as radius, calls.map_distance(class,classcall) as distance 98 | From calls.map 99 | --Group By nameTo, nameFrom 100 | } 101 | 102 | ClassMethod Init(namespace = {$namespace}) 103 | { 104 | s map = ..%New() 105 | d map.classes(namespace) 106 | } 107 | 108 | /// Список классов 109 | Method classes(nsp = {$namespace}, clsStart = "&", clsEnd = "") 110 | { 111 | d ..%KillExtent() 112 | 113 | s ..namespace = nsp, data="" 114 | s:'$IsObject(..parserInner) ..parserInner = ##class(parserInner).%New() 115 | s:'$IsObject(..parserOuter) ..parserOuter = ##class(parserOuter).%New() 116 | /// Системный индекс с классами 117 | #define CLSIDX(%cls) ^|nsp|rINDEXCLASS(%cls) 118 | 119 | #; не указан с какого класса или нет данных по указанному 120 | #; обращаем внимание на data - там окажется системная информация о классе 121 | if clsStart="" || ( '$d( $$$CLSIDX(clsStart), data ) ) { 122 | s clsStart=$o( $$$CLSIDX(clsStart), 1, data ) ;берем ближайший следующий 123 | } 124 | 125 | s class = clsStart, lend = $l(clsEnd) 126 | while class '= "", ( $e(class, 1, lend ) '] clsEnd ) { ;пока не прошли все или имя класса не содержит искомое 127 | 128 | s ..class = $list( data, 2 ) 129 | 130 | d ..methods() 131 | 132 | s class = $o( $$$CLSIDX(class), 1, data ) ;переходим к следующему 133 | 134 | } 135 | } 136 | 137 | /// for each methods in class 138 | Method methods() 139 | { 140 | s nsp = ..namespace, class = ..class 141 | 142 | #; NOTE: try use ^oddCOM 143 | /// System storage for class definition 144 | #define META ^[nsp]oddDEF 145 | 146 | if '$d($$$META(class)) Q 0 ;нет определения класса 147 | 148 | #define METHODS(%class, %meth) $$$META(%class,$$$cCLASSmethod,%meth) 149 | 150 | s method="" for { s method=$o($$$METHODS(class, method)) Quit:method="" 151 | #;w !, class,":",method 152 | s ..method = method 153 | d ..codes() 154 | 155 | } 156 | Quit 1 157 | } 158 | 159 | Method codes() 160 | { 161 | s nsp = ..namespace, class = ..class, method =..method 162 | #define META ^[nsp]oddDEF 163 | #define CODES(%class,%method,%line) $$$META(%class,$$$cCLASSmethod,%method,$$$cMETHimplementation,%line) 164 | 165 | s line="", code = "" 166 | for { s line = $o( $$$CODES(class, method, line ), 1, code ) Q:line="" 167 | s ..line = line 168 | d ..parse( code ) 169 | } 170 | } 171 | 172 | /// вызовы $zobjclassmethod({CLASSNAME},{METHODNAME}, args...) 173 | /// вызовы $classmethod({CLASSNAME},{METHODNAME},) 174 | Method parse(code) 175 | { 176 | k data,class,method 177 | if ..parserInner.parse(code,.method){ 178 | d ..toArray(.data) 179 | s data("classcall") = ..class 180 | s data("methodcall") = method 181 | s sc = ..Save(.data) 182 | Q 1 183 | } 184 | 185 | k data,class,method 186 | if ..parserOuter.parse(code,.class,.method){ 187 | d ..toArray(.data) 188 | s data("classcall") = class 189 | s data("methodcall") = method 190 | s sc = ..Save(.data) 191 | Q 1 192 | } 193 | Q 1 194 | } 195 | 196 | Method toArray(ByRef data) [ ProcedureBlock = 0 ] 197 | { 198 | s data("namespace")=..namespace 199 | s data("class")=..class 200 | s data("method")=..method 201 | s data("line")=..line 202 | } 203 | 204 | ClassMethod Save(data) 205 | { 206 | &sql(Insert Into calls.map (namespace, class, method, line, classcall, methodcall) 207 | Values( 208 | :data("namespace"), 209 | :data("class"), 210 | :data("method"), 211 | :data("line"), 212 | :data("classcall"), 213 | :data("methodcall") 214 | ) 215 | ) 216 | Q 1 217 | } 218 | 219 | Storage Default 220 | { 221 | 222 | 223 | %%CLASSNAME 224 | 225 | 226 | class 227 | 228 | 229 | method 230 | 231 | 232 | line 233 | 234 | 235 | classcall 236 | 237 | 238 | methodcall 239 | 240 | 241 | namespace 242 | 243 | 244 | pointFrom 245 | 246 | 247 | pointTo 248 | 249 | 250 | nameFrom 251 | 252 | 253 | nameTo 254 | 255 | 256 | ^calls.mapD 257 | mapDefaultData 258 | ^calls.mapD 259 | ^calls.mapI 260 | ^calls.mapS 261 | %Library.CacheStorage 262 | } 263 | 264 | } 265 | 266 | --------------------------------------------------------------------------------