osscodeiq 0.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. osscodeiq/__init__.py +0 -0
  2. osscodeiq/analyzer.py +467 -0
  3. osscodeiq/cache/__init__.py +0 -0
  4. osscodeiq/cache/hasher.py +23 -0
  5. osscodeiq/cache/store.py +300 -0
  6. osscodeiq/classifiers/__init__.py +0 -0
  7. osscodeiq/classifiers/layer_classifier.py +69 -0
  8. osscodeiq/cli.py +721 -0
  9. osscodeiq/config.py +113 -0
  10. osscodeiq/detectors/__init__.py +0 -0
  11. osscodeiq/detectors/auth/__init__.py +0 -0
  12. osscodeiq/detectors/auth/certificate_auth.py +139 -0
  13. osscodeiq/detectors/auth/ldap_auth.py +89 -0
  14. osscodeiq/detectors/auth/session_header_auth.py +120 -0
  15. osscodeiq/detectors/base.py +41 -0
  16. osscodeiq/detectors/config/__init__.py +0 -0
  17. osscodeiq/detectors/config/batch_structure.py +128 -0
  18. osscodeiq/detectors/config/cloudformation.py +183 -0
  19. osscodeiq/detectors/config/docker_compose.py +179 -0
  20. osscodeiq/detectors/config/github_actions.py +150 -0
  21. osscodeiq/detectors/config/gitlab_ci.py +216 -0
  22. osscodeiq/detectors/config/helm_chart.py +187 -0
  23. osscodeiq/detectors/config/ini_structure.py +101 -0
  24. osscodeiq/detectors/config/json_structure.py +72 -0
  25. osscodeiq/detectors/config/kubernetes.py +305 -0
  26. osscodeiq/detectors/config/kubernetes_rbac.py +212 -0
  27. osscodeiq/detectors/config/openapi.py +194 -0
  28. osscodeiq/detectors/config/package_json.py +99 -0
  29. osscodeiq/detectors/config/properties_detector.py +108 -0
  30. osscodeiq/detectors/config/pyproject_toml.py +169 -0
  31. osscodeiq/detectors/config/sql_structure.py +155 -0
  32. osscodeiq/detectors/config/toml_structure.py +93 -0
  33. osscodeiq/detectors/config/tsconfig_json.py +105 -0
  34. osscodeiq/detectors/config/yaml_structure.py +82 -0
  35. osscodeiq/detectors/cpp/__init__.py +0 -0
  36. osscodeiq/detectors/cpp/cpp_structures.py +192 -0
  37. osscodeiq/detectors/csharp/__init__.py +0 -0
  38. osscodeiq/detectors/csharp/csharp_efcore.py +184 -0
  39. osscodeiq/detectors/csharp/csharp_minimal_apis.py +156 -0
  40. osscodeiq/detectors/csharp/csharp_structures.py +317 -0
  41. osscodeiq/detectors/docs/__init__.py +0 -0
  42. osscodeiq/detectors/docs/markdown_structure.py +117 -0
  43. osscodeiq/detectors/frontend/__init__.py +0 -0
  44. osscodeiq/detectors/frontend/angular_components.py +177 -0
  45. osscodeiq/detectors/frontend/frontend_routes.py +259 -0
  46. osscodeiq/detectors/frontend/react_components.py +148 -0
  47. osscodeiq/detectors/frontend/svelte_components.py +84 -0
  48. osscodeiq/detectors/frontend/vue_components.py +150 -0
  49. osscodeiq/detectors/generic/__init__.py +1 -0
  50. osscodeiq/detectors/generic/imports_detector.py +413 -0
  51. osscodeiq/detectors/go/__init__.py +0 -0
  52. osscodeiq/detectors/go/go_orm.py +202 -0
  53. osscodeiq/detectors/go/go_structures.py +162 -0
  54. osscodeiq/detectors/go/go_web.py +157 -0
  55. osscodeiq/detectors/iac/__init__.py +0 -0
  56. osscodeiq/detectors/iac/bicep.py +135 -0
  57. osscodeiq/detectors/iac/dockerfile.py +182 -0
  58. osscodeiq/detectors/iac/terraform.py +188 -0
  59. osscodeiq/detectors/java/__init__.py +0 -0
  60. osscodeiq/detectors/java/azure_functions.py +424 -0
  61. osscodeiq/detectors/java/azure_messaging.py +350 -0
  62. osscodeiq/detectors/java/class_hierarchy.py +349 -0
  63. osscodeiq/detectors/java/config_def.py +82 -0
  64. osscodeiq/detectors/java/cosmos_db.py +105 -0
  65. osscodeiq/detectors/java/graphql_resolver.py +188 -0
  66. osscodeiq/detectors/java/grpc_service.py +142 -0
  67. osscodeiq/detectors/java/ibm_mq.py +178 -0
  68. osscodeiq/detectors/java/jaxrs.py +160 -0
  69. osscodeiq/detectors/java/jdbc.py +196 -0
  70. osscodeiq/detectors/java/jms.py +116 -0
  71. osscodeiq/detectors/java/jpa_entity.py +143 -0
  72. osscodeiq/detectors/java/kafka.py +113 -0
  73. osscodeiq/detectors/java/kafka_protocol.py +70 -0
  74. osscodeiq/detectors/java/micronaut.py +248 -0
  75. osscodeiq/detectors/java/module_deps.py +191 -0
  76. osscodeiq/detectors/java/public_api.py +206 -0
  77. osscodeiq/detectors/java/quarkus.py +176 -0
  78. osscodeiq/detectors/java/rabbitmq.py +150 -0
  79. osscodeiq/detectors/java/raw_sql.py +136 -0
  80. osscodeiq/detectors/java/repository.py +131 -0
  81. osscodeiq/detectors/java/rmi.py +129 -0
  82. osscodeiq/detectors/java/spring_events.py +117 -0
  83. osscodeiq/detectors/java/spring_rest.py +168 -0
  84. osscodeiq/detectors/java/spring_security.py +212 -0
  85. osscodeiq/detectors/java/tibco_ems.py +193 -0
  86. osscodeiq/detectors/java/websocket.py +188 -0
  87. osscodeiq/detectors/kotlin/__init__.py +0 -0
  88. osscodeiq/detectors/kotlin/kotlin_structures.py +124 -0
  89. osscodeiq/detectors/kotlin/ktor_routes.py +163 -0
  90. osscodeiq/detectors/proto/__init__.py +0 -0
  91. osscodeiq/detectors/proto/proto_structure.py +153 -0
  92. osscodeiq/detectors/python/__init__.py +0 -0
  93. osscodeiq/detectors/python/celery_tasks.py +88 -0
  94. osscodeiq/detectors/python/django_auth.py +132 -0
  95. osscodeiq/detectors/python/django_models.py +157 -0
  96. osscodeiq/detectors/python/django_views.py +74 -0
  97. osscodeiq/detectors/python/fastapi_auth.py +143 -0
  98. osscodeiq/detectors/python/fastapi_routes.py +68 -0
  99. osscodeiq/detectors/python/flask_routes.py +67 -0
  100. osscodeiq/detectors/python/kafka_python.py +175 -0
  101. osscodeiq/detectors/python/pydantic_models.py +115 -0
  102. osscodeiq/detectors/python/python_structures.py +234 -0
  103. osscodeiq/detectors/python/sqlalchemy_models.py +82 -0
  104. osscodeiq/detectors/registry.py +100 -0
  105. osscodeiq/detectors/rust/__init__.py +0 -0
  106. osscodeiq/detectors/rust/actix_web.py +234 -0
  107. osscodeiq/detectors/rust/rust_structures.py +174 -0
  108. osscodeiq/detectors/scala/__init__.py +0 -0
  109. osscodeiq/detectors/scala/scala_structures.py +128 -0
  110. osscodeiq/detectors/shell/__init__.py +0 -0
  111. osscodeiq/detectors/shell/bash_detector.py +127 -0
  112. osscodeiq/detectors/shell/powershell_detector.py +118 -0
  113. osscodeiq/detectors/typescript/__init__.py +0 -0
  114. osscodeiq/detectors/typescript/express_routes.py +55 -0
  115. osscodeiq/detectors/typescript/fastify_routes.py +156 -0
  116. osscodeiq/detectors/typescript/graphql_resolvers.py +100 -0
  117. osscodeiq/detectors/typescript/kafka_js.py +164 -0
  118. osscodeiq/detectors/typescript/mongoose_orm.py +151 -0
  119. osscodeiq/detectors/typescript/nestjs_controllers.py +99 -0
  120. osscodeiq/detectors/typescript/nestjs_guards.py +138 -0
  121. osscodeiq/detectors/typescript/passport_jwt.py +133 -0
  122. osscodeiq/detectors/typescript/prisma_orm.py +96 -0
  123. osscodeiq/detectors/typescript/remix_routes.py +160 -0
  124. osscodeiq/detectors/typescript/sequelize_orm.py +136 -0
  125. osscodeiq/detectors/typescript/typeorm_entities.py +86 -0
  126. osscodeiq/detectors/typescript/typescript_structures.py +185 -0
  127. osscodeiq/detectors/utils.py +49 -0
  128. osscodeiq/discovery/__init__.py +11 -0
  129. osscodeiq/discovery/change_detector.py +97 -0
  130. osscodeiq/discovery/file_discovery.py +342 -0
  131. osscodeiq/flow/__init__.py +0 -0
  132. osscodeiq/flow/engine.py +78 -0
  133. osscodeiq/flow/models.py +72 -0
  134. osscodeiq/flow/renderer.py +127 -0
  135. osscodeiq/flow/templates/interactive.html +252 -0
  136. osscodeiq/flow/vendor/cytoscape-dagre.min.js +8 -0
  137. osscodeiq/flow/vendor/cytoscape.min.js +32 -0
  138. osscodeiq/flow/vendor/dagre.min.js +3809 -0
  139. osscodeiq/flow/views.py +357 -0
  140. osscodeiq/graph/__init__.py +0 -0
  141. osscodeiq/graph/backend.py +52 -0
  142. osscodeiq/graph/backends/__init__.py +23 -0
  143. osscodeiq/graph/backends/kuzu.py +576 -0
  144. osscodeiq/graph/backends/networkx.py +135 -0
  145. osscodeiq/graph/backends/sqlite_backend.py +406 -0
  146. osscodeiq/graph/builder.py +297 -0
  147. osscodeiq/graph/query.py +228 -0
  148. osscodeiq/graph/store.py +183 -0
  149. osscodeiq/graph/views.py +231 -0
  150. osscodeiq/models/__init__.py +17 -0
  151. osscodeiq/models/graph.py +116 -0
  152. osscodeiq/output/__init__.py +0 -0
  153. osscodeiq/output/dot.py +171 -0
  154. osscodeiq/output/mermaid.py +160 -0
  155. osscodeiq/output/safety.py +58 -0
  156. osscodeiq/output/serializers.py +42 -0
  157. osscodeiq/parsing/__init__.py +5 -0
  158. osscodeiq/parsing/languages/__init__.py +0 -0
  159. osscodeiq/parsing/languages/base.py +23 -0
  160. osscodeiq/parsing/languages/java.py +68 -0
  161. osscodeiq/parsing/languages/python.py +57 -0
  162. osscodeiq/parsing/languages/typescript.py +95 -0
  163. osscodeiq/parsing/parser_manager.py +125 -0
  164. osscodeiq/parsing/structured/__init__.py +0 -0
  165. osscodeiq/parsing/structured/gradle_parser.py +78 -0
  166. osscodeiq/parsing/structured/json_parser.py +24 -0
  167. osscodeiq/parsing/structured/properties_parser.py +56 -0
  168. osscodeiq/parsing/structured/sql_parser.py +54 -0
  169. osscodeiq/parsing/structured/xml_parser.py +148 -0
  170. osscodeiq/parsing/structured/yaml_parser.py +38 -0
  171. osscodeiq/server/__init__.py +7 -0
  172. osscodeiq/server/app.py +53 -0
  173. osscodeiq/server/mcp_server.py +174 -0
  174. osscodeiq/server/middleware.py +16 -0
  175. osscodeiq/server/routes.py +184 -0
  176. osscodeiq/server/service.py +445 -0
  177. osscodeiq/server/templates/welcome.html +56 -0
  178. osscodeiq-0.0.0.dist-info/METADATA +30 -0
  179. osscodeiq-0.0.0.dist-info/RECORD +183 -0
  180. osscodeiq-0.0.0.dist-info/WHEEL +5 -0
  181. osscodeiq-0.0.0.dist-info/entry_points.txt +2 -0
  182. osscodeiq-0.0.0.dist-info/licenses/LICENSE +21 -0
  183. osscodeiq-0.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,160 @@
1
+ """JAX-RS REST endpoint detector for Java source files."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from typing import Any
7
+
8
+ from osscodeiq.detectors.base import DetectorContext, DetectorResult
9
+ from osscodeiq.detectors.utils import decode_text
10
+ from osscodeiq.models.graph import (
11
+ EdgeKind,
12
+ GraphEdge,
13
+ GraphNode,
14
+ NodeKind,
15
+ SourceLocation,
16
+ )
17
+
18
+ _PATH_RE = re.compile(r'@Path\s*\(\s*"([^"]*)"')
19
+ _HTTP_METHOD_RE = re.compile(r"@(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\b")
20
+ _PRODUCES_RE = re.compile(r'@Produces\s*\(\s*\{?\s*(?:MediaType\.\w+|"([^"]*)")')
21
+ _CONSUMES_RE = re.compile(r'@Consumes\s*\(\s*\{?\s*(?:MediaType\.\w+|"([^"]*)")')
22
+ _CLASS_RE = re.compile(r"(?:public\s+)?class\s+(\w+)")
23
+ _JAVA_METHOD_RE = re.compile(
24
+ r'(?:public|protected|private)?\s*(?:static\s+)?(?:[\w<>\[\],\s]+)\s+(\w+)\s*\('
25
+ )
26
+
27
+
28
+ class JaxrsDetector:
29
+ """Detects JAX-RS REST endpoints from annotations."""
30
+
31
+ name: str = "jaxrs"
32
+ supported_languages: tuple[str, ...] = ("java",)
33
+
34
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
35
+ result = DetectorResult()
36
+ text = decode_text(ctx)
37
+
38
+ # Fast bail
39
+ if "@Path" not in text and "javax.ws.rs" not in text and "jakarta.ws.rs" not in text:
40
+ return result
41
+
42
+ lines = text.split("\n")
43
+
44
+ # Find class name and class-level @Path
45
+ class_name: str | None = None
46
+ class_base_path = ""
47
+ for i, line in enumerate(lines):
48
+ cm = _CLASS_RE.search(line)
49
+ if cm:
50
+ class_name = cm.group(1)
51
+ # Look backwards for class-level @Path
52
+ for j in range(max(0, i - 5), i):
53
+ pm = _PATH_RE.search(lines[j])
54
+ if pm:
55
+ class_base_path = pm.group(1).rstrip("/")
56
+ break
57
+ break
58
+
59
+ if not class_name:
60
+ return result
61
+
62
+ class_node_id = f"{ctx.file_path}:{class_name}"
63
+
64
+ # Scan for method-level HTTP annotations
65
+ for i, line in enumerate(lines):
66
+ m = _HTTP_METHOD_RE.search(line)
67
+ if not m:
68
+ continue
69
+
70
+ http_method = m.group(1)
71
+
72
+ # Check whether this annotation is at the class level by
73
+ # inspecting the subsequent non-empty, non-annotation lines.
74
+ is_class_level = False
75
+ for k in range(i + 1, min(i + 5, len(lines))):
76
+ stripped = lines[k].strip()
77
+ if stripped.startswith("@") or not stripped:
78
+ continue
79
+ if "class " in stripped or "interface " in stripped:
80
+ is_class_level = True
81
+ break
82
+ if is_class_level:
83
+ continue
84
+
85
+ # Look for a nearby method-level @Path (within a few lines before
86
+ # and after the HTTP annotation)
87
+ method_path: str | None = None
88
+ for k in range(max(0, i - 3), min(i + 4, len(lines))):
89
+ if k == i:
90
+ continue
91
+ pm = _PATH_RE.search(lines[k])
92
+ if pm:
93
+ method_path = pm.group(1)
94
+ break
95
+
96
+ # Combine class base path with method path
97
+ if method_path is not None:
98
+ full_path = f"{class_base_path}/{method_path.lstrip('/')}"
99
+ else:
100
+ full_path = class_base_path or "/"
101
+ if not full_path.startswith("/"):
102
+ full_path = "/" + full_path
103
+
104
+ # Extract @Produces and @Consumes near the annotation
105
+ produces: str | None = None
106
+ consumes: str | None = None
107
+ for k in range(max(0, i - 5), min(i + 5, len(lines))):
108
+ if produces is None:
109
+ pm = _PRODUCES_RE.search(lines[k])
110
+ if pm:
111
+ produces = pm.group(1) # may be None for MediaType constant
112
+ if consumes is None:
113
+ cm = _CONSUMES_RE.search(lines[k])
114
+ if cm:
115
+ consumes = cm.group(1) # may be None for MediaType constant
116
+
117
+ # Find the method name on subsequent lines
118
+ method_name: str | None = None
119
+ for k in range(i + 1, min(i + 5, len(lines))):
120
+ mm = _JAVA_METHOD_RE.search(lines[k])
121
+ if mm:
122
+ method_name = mm.group(1)
123
+ break
124
+
125
+ endpoint_label = f"{http_method} {full_path}"
126
+ endpoint_id = (
127
+ f"{ctx.file_path}:{class_name}:{method_name or 'unknown'}"
128
+ f":{http_method}:{full_path}"
129
+ )
130
+
131
+ properties: dict[str, Any] = {
132
+ "http_method": http_method,
133
+ "path": full_path,
134
+ }
135
+ if produces:
136
+ properties["produces"] = produces
137
+ if consumes:
138
+ properties["consumes"] = consumes
139
+
140
+ node = GraphNode(
141
+ id=endpoint_id,
142
+ kind=NodeKind.ENDPOINT,
143
+ label=endpoint_label,
144
+ fqn=f"{class_name}.{method_name}" if method_name else class_name,
145
+ module=ctx.module_name,
146
+ location=SourceLocation(file_path=ctx.file_path, line_start=i + 1),
147
+ annotations=[f"@{http_method}"],
148
+ properties=properties,
149
+ )
150
+ result.nodes.append(node)
151
+
152
+ edge = GraphEdge(
153
+ source=class_node_id,
154
+ target=endpoint_id,
155
+ kind=EdgeKind.EXPOSES,
156
+ label=f"{class_name} exposes {endpoint_label}",
157
+ )
158
+ result.edges.append(edge)
159
+
160
+ return result
@@ -0,0 +1,196 @@
1
+ """JDBC/ODBC database connectivity detector for Java source files."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from typing import Any
7
+
8
+ from osscodeiq.detectors.base import DetectorContext, DetectorResult
9
+ from osscodeiq.detectors.utils import decode_text
10
+ from osscodeiq.models.graph import (
11
+ EdgeKind,
12
+ GraphEdge,
13
+ GraphNode,
14
+ NodeKind,
15
+ SourceLocation,
16
+ )
17
+
18
+ _CLASS_RE = re.compile(r"(?:public\s+)?class\s+(\w+)")
19
+
20
+ # DriverManager.getConnection("jdbc:...")
21
+ _DRIVER_MANAGER_RE = re.compile(
22
+ r'DriverManager\s*\.\s*getConnection\s*\(\s*"(jdbc:[^"]+)"'
23
+ )
24
+
25
+ # JdbcTemplate / NamedParameterJdbcTemplate / JdbcClient field or constructor usage
26
+ _JDBC_TEMPLATE_RE = re.compile(
27
+ r"(?:private|protected|public|final|\s)+"
28
+ r"(?:final\s+)?"
29
+ r"(JdbcTemplate|NamedParameterJdbcTemplate|JdbcClient)"
30
+ r"\s+\w+"
31
+ )
32
+
33
+ # DataSource bean definitions or annotations
34
+ _DATASOURCE_BEAN_RE = re.compile(
35
+ r"(?:@Bean|DataSource)\s*(?:\(|\.)"
36
+ )
37
+
38
+ # spring.datasource.url property
39
+ _SPRING_DATASOURCE_RE = re.compile(
40
+ r"spring\.datasource\.url\s*=\s*(jdbc:[^\s]+)"
41
+ )
42
+
43
+ # Generic JDBC URL pattern (captures db type and host info)
44
+ _JDBC_URL_RE = re.compile(
45
+ r"jdbc:(mysql|postgresql|sqlserver|oracle|db2|h2|sqlite|mariadb)"
46
+ r"(?::(?:thin:)?(?:@)?)?(?://([^/\"'\s;?]+))?"
47
+ )
48
+
49
+
50
+ def _parse_jdbc_url(url: str) -> dict[str, str]:
51
+ """Extract db_type and host from a JDBC URL."""
52
+ props: dict[str, str] = {"connection_url": url}
53
+ m = _JDBC_URL_RE.search(url)
54
+ if m:
55
+ props["db_type"] = m.group(1)
56
+ if m.group(2):
57
+ props["host"] = m.group(2)
58
+ return props
59
+
60
+
61
+ class JdbcDetector:
62
+ """Detects Java database connectivity patterns (JDBC, JdbcTemplate, DataSource)."""
63
+
64
+ name: str = "jdbc"
65
+ supported_languages: tuple[str, ...] = ("java",)
66
+
67
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
68
+ result = DetectorResult()
69
+ text = decode_text(ctx)
70
+
71
+ # Fast-bail
72
+ if (
73
+ "JdbcTemplate" not in text
74
+ and "DriverManager" not in text
75
+ and "DataSource" not in text
76
+ and "NamedParameterJdbcTemplate" not in text
77
+ and "JdbcClient" not in text
78
+ ):
79
+ return result
80
+
81
+ lines = text.split("\n")
82
+
83
+ # Find class name
84
+ class_name: str | None = None
85
+ for line in lines:
86
+ cm = _CLASS_RE.search(line)
87
+ if cm:
88
+ class_name = cm.group(1)
89
+ break
90
+
91
+ if not class_name:
92
+ return result
93
+
94
+ class_node_id = f"{ctx.file_path}:{class_name}"
95
+ seen_dbs: set[str] = set()
96
+
97
+ def _ensure_db_node(
98
+ db_id: str,
99
+ label: str,
100
+ line_num: int | None,
101
+ properties: dict[str, Any],
102
+ ) -> str:
103
+ if db_id not in seen_dbs:
104
+ seen_dbs.add(db_id)
105
+ result.nodes.append(GraphNode(
106
+ id=db_id,
107
+ kind=NodeKind.DATABASE_CONNECTION,
108
+ label=label,
109
+ module=ctx.module_name,
110
+ location=SourceLocation(
111
+ file_path=ctx.file_path,
112
+ line_start=line_num,
113
+ ) if line_num else None,
114
+ properties=properties,
115
+ ))
116
+ return db_id
117
+
118
+ def _add_connect_edge(db_id: str, label: str) -> None:
119
+ result.edges.append(GraphEdge(
120
+ source=class_node_id,
121
+ target=db_id,
122
+ kind=EdgeKind.CONNECTS_TO,
123
+ label=label,
124
+ ))
125
+
126
+ # Pattern 1: DriverManager.getConnection("jdbc:...")
127
+ for i, line in enumerate(lines):
128
+ m = _DRIVER_MANAGER_RE.search(line)
129
+ if not m:
130
+ continue
131
+ url = m.group(1)
132
+ props = _parse_jdbc_url(url)
133
+ db_type = props.get("db_type", "unknown")
134
+ host = props.get("host", "unknown")
135
+ db_id = f"db:{db_type}:{host}"
136
+ _ensure_db_node(db_id, f"{db_type}@{host}", i + 1, props)
137
+ _add_connect_edge(db_id, f"{class_name} connects to {db_type}@{host}")
138
+
139
+ # Pattern 2: JdbcTemplate / NamedParameterJdbcTemplate / JdbcClient usage
140
+ for i, line in enumerate(lines):
141
+ m = _JDBC_TEMPLATE_RE.search(line)
142
+ if not m:
143
+ continue
144
+ template_type = m.group(1)
145
+ db_id = f"{ctx.file_path}:jdbc:{class_name}"
146
+ _ensure_db_node(
147
+ db_id,
148
+ f"{class_name} ({template_type})",
149
+ i + 1,
150
+ {"template_type": template_type},
151
+ )
152
+ _add_connect_edge(db_id, f"{class_name} uses {template_type}")
153
+
154
+ # Pattern 3: DataSource bean definitions
155
+ for i, line in enumerate(lines):
156
+ m = _DATASOURCE_BEAN_RE.search(line)
157
+ if not m:
158
+ continue
159
+ db_id = f"{ctx.file_path}:jdbc:{class_name}"
160
+ _ensure_db_node(
161
+ db_id,
162
+ f"{class_name} (DataSource)",
163
+ i + 1,
164
+ {"datasource": True},
165
+ )
166
+
167
+ # Pattern 4: spring.datasource.url in properties content
168
+ if "spring.datasource" in text:
169
+ for i, line in enumerate(lines):
170
+ m = _SPRING_DATASOURCE_RE.search(line)
171
+ if not m:
172
+ continue
173
+ url = m.group(1)
174
+ props = _parse_jdbc_url(url)
175
+ db_type = props.get("db_type", "unknown")
176
+ host = props.get("host", "unknown")
177
+ db_id = f"db:{db_type}:{host}"
178
+ _ensure_db_node(db_id, f"{db_type}@{host}", i + 1, props)
179
+
180
+ # Pattern 5: Standalone JDBC URL strings (not already caught above)
181
+ for i, line in enumerate(lines):
182
+ # Skip lines already matched by DriverManager or spring.datasource
183
+ if "DriverManager" in line or "spring.datasource" in line:
184
+ continue
185
+ urls = re.findall(r'"(jdbc:[^"]+)"', line)
186
+ for url in urls:
187
+ props = _parse_jdbc_url(url)
188
+ db_type = props.get("db_type", "unknown")
189
+ host = props.get("host", "unknown")
190
+ db_id = f"db:{db_type}:{host}"
191
+ _ensure_db_node(db_id, f"{db_type}@{host}", i + 1, props)
192
+ _add_connect_edge(
193
+ db_id, f"{class_name} connects to {db_type}@{host}"
194
+ )
195
+
196
+ return result
@@ -0,0 +1,116 @@
1
+ """JMS (Java Message Service) detector for Java source files."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+
7
+ from osscodeiq.detectors.base import DetectorContext, DetectorResult
8
+ from osscodeiq.detectors.utils import decode_text
9
+ from osscodeiq.models.graph import (
10
+ EdgeKind,
11
+ GraphEdge,
12
+ GraphNode,
13
+ NodeKind,
14
+ SourceLocation,
15
+ )
16
+
17
+ _CLASS_RE = re.compile(r"(?:public\s+)?class\s+(\w+)")
18
+
19
+ # JMS listener annotation
20
+ _JMS_LISTENER_RE = re.compile(
21
+ r'@JmsListener\s*\(\s*(?:.*?destination\s*=\s*)?"([^"]+)"'
22
+ )
23
+
24
+ # JmsTemplate.send / convertAndSend
25
+ _JMS_SEND_RE = re.compile(
26
+ r'(?:jmsTemplate|JmsTemplate)\s*\.(?:send|convertAndSend)\s*\(\s*"([^"]+)"'
27
+ )
28
+
29
+ # JmsTemplate with destination bean
30
+ _JMS_DEST_RE = re.compile(
31
+ r'(?:ActiveMQQueue|ActiveMQTopic)\s*\(\s*"([^"]+)"'
32
+ )
33
+
34
+ _CONTAINER_FACTORY_RE = re.compile(r'containerFactory\s*=\s*"([^"]+)"')
35
+
36
+
37
+ class JmsDetector:
38
+ """Detects JMS consumers and producers."""
39
+
40
+ name: str = "jms"
41
+ supported_languages: tuple[str, ...] = ("java",)
42
+
43
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
44
+ result = DetectorResult()
45
+ text = decode_text(ctx)
46
+ lines = text.split("\n")
47
+
48
+ if "@JmsListener" not in text and "jmsTemplate" not in text and "JmsTemplate" not in text:
49
+ return result
50
+
51
+ # Find class name
52
+ class_name: str | None = None
53
+ for line in lines:
54
+ cm = _CLASS_RE.search(line)
55
+ if cm:
56
+ class_name = cm.group(1)
57
+ break
58
+
59
+ if not class_name:
60
+ return result
61
+
62
+ class_node_id = f"{ctx.file_path}:{class_name}"
63
+ seen_queues: set[str] = set()
64
+
65
+ def _ensure_queue_node(destination: str) -> str:
66
+ queue_id = f"jms:queue:{destination}"
67
+ if destination not in seen_queues:
68
+ seen_queues.add(destination)
69
+ result.nodes.append(GraphNode(
70
+ id=queue_id,
71
+ kind=NodeKind.QUEUE,
72
+ label=f"jms:{destination}",
73
+ properties={"broker": "jms", "destination": destination},
74
+ ))
75
+ return queue_id
76
+
77
+ # Detect @JmsListener consumers
78
+ for i, line in enumerate(lines):
79
+ m = _JMS_LISTENER_RE.search(line)
80
+ if not m:
81
+ continue
82
+
83
+ destination = m.group(1)
84
+ queue_id = _ensure_queue_node(destination)
85
+
86
+ props: dict[str, str] = {"destination": destination}
87
+ cf = _CONTAINER_FACTORY_RE.search(line)
88
+ if cf:
89
+ props["container_factory"] = cf.group(1)
90
+
91
+ result.edges.append(GraphEdge(
92
+ source=class_node_id,
93
+ target=queue_id,
94
+ kind=EdgeKind.CONSUMES,
95
+ label=f"{class_name} consumes from {destination}",
96
+ properties=props,
97
+ ))
98
+
99
+ # Detect JmsTemplate sends
100
+ for i, line in enumerate(lines):
101
+ m = _JMS_SEND_RE.search(line)
102
+ if not m:
103
+ continue
104
+
105
+ destination = m.group(1)
106
+ queue_id = _ensure_queue_node(destination)
107
+
108
+ result.edges.append(GraphEdge(
109
+ source=class_node_id,
110
+ target=queue_id,
111
+ kind=EdgeKind.PRODUCES,
112
+ label=f"{class_name} produces to {destination}",
113
+ properties={"destination": destination},
114
+ ))
115
+
116
+ return result
@@ -0,0 +1,143 @@
1
+ """JPA entity detector for Java source files."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from typing import Any
7
+
8
+ from osscodeiq.detectors.base import DetectorContext, DetectorResult
9
+ from osscodeiq.detectors.utils import decode_text
10
+ from osscodeiq.models.graph import (
11
+ EdgeKind,
12
+ GraphEdge,
13
+ GraphNode,
14
+ NodeKind,
15
+ SourceLocation,
16
+ )
17
+
18
+ _ENTITY_RE = re.compile(r"@Entity")
19
+ _TABLE_RE = re.compile(r'@Table\s*\(\s*(?:name\s*=\s*)?"(\w+)"')
20
+ _CLASS_RE = re.compile(r"(?:public\s+)?class\s+(\w+)")
21
+ _COLUMN_RE = re.compile(r'@Column\s*\(([^)]*)\)')
22
+ _COLUMN_NAME_RE = re.compile(r'name\s*=\s*"(\w+)"')
23
+ _FIELD_RE = re.compile(r"(?:private|protected|public)\s+([\w<>,\s]+)\s+(\w+)\s*[;=]")
24
+
25
+ _RELATIONSHIP_ANNOTATIONS = {
26
+ "OneToMany": "one_to_many",
27
+ "ManyToOne": "many_to_one",
28
+ "OneToOne": "one_to_one",
29
+ "ManyToMany": "many_to_many",
30
+ }
31
+ _RELATIONSHIP_RE = re.compile(r"@(OneToMany|ManyToOne|OneToOne|ManyToMany)")
32
+ _TARGET_ENTITY_RE = re.compile(r'targetEntity\s*=\s*(\w+)\.class')
33
+ _MAPPED_BY_RE = re.compile(r'mappedBy\s*=\s*"(\w+)"')
34
+ _GENERIC_TYPE_RE = re.compile(r'<(\w+)>')
35
+
36
+
37
+ class JpaEntityDetector:
38
+ """Detects JPA entities and their relationships."""
39
+
40
+ name: str = "jpa_entity"
41
+ supported_languages: tuple[str, ...] = ("java",)
42
+
43
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
44
+ result = DetectorResult()
45
+ text = decode_text(ctx)
46
+ lines = text.split("\n")
47
+
48
+ # Check if file contains @Entity
49
+ if not _ENTITY_RE.search(text):
50
+ return result
51
+
52
+ # Find class name
53
+ class_name: str | None = None
54
+ class_line: int = 0
55
+ for i, line in enumerate(lines):
56
+ cm = _CLASS_RE.search(line)
57
+ if cm:
58
+ class_name = cm.group(1)
59
+ class_line = i + 1
60
+ break
61
+
62
+ if not class_name:
63
+ return result
64
+
65
+ # Extract table name
66
+ table_match = _TABLE_RE.search(text)
67
+ table_name = table_match.group(1) if table_match else class_name.lower()
68
+
69
+ # Extract columns
70
+ columns: list[dict[str, str]] = []
71
+ for i, line in enumerate(lines):
72
+ col_match = _COLUMN_RE.search(line)
73
+ if col_match:
74
+ col_name_match = _COLUMN_NAME_RE.search(col_match.group(1))
75
+ # Find the field on the next line(s)
76
+ for k in range(i + 1, min(i + 3, len(lines))):
77
+ fm = _FIELD_RE.search(lines[k])
78
+ if fm:
79
+ col_name = col_name_match.group(1) if col_name_match else fm.group(2)
80
+ columns.append({"name": col_name, "field": fm.group(2), "type": fm.group(1).strip()})
81
+ break
82
+
83
+ entity_id = f"{ctx.file_path}:{class_name}"
84
+ properties: dict[str, Any] = {"table_name": table_name}
85
+ if columns:
86
+ properties["columns"] = columns
87
+
88
+ node = GraphNode(
89
+ id=entity_id,
90
+ kind=NodeKind.ENTITY,
91
+ label=f"{class_name} ({table_name})",
92
+ fqn=class_name,
93
+ module=ctx.module_name,
94
+ location=SourceLocation(file_path=ctx.file_path, line_start=class_line),
95
+ annotations=["@Entity"],
96
+ properties=properties,
97
+ )
98
+ result.nodes.append(node)
99
+
100
+ # Extract relationships
101
+ for i, line in enumerate(lines):
102
+ rel_match = _RELATIONSHIP_RE.search(line)
103
+ if not rel_match:
104
+ continue
105
+
106
+ rel_type = _RELATIONSHIP_ANNOTATIONS[rel_match.group(1)]
107
+
108
+ # Determine target entity
109
+ target_entity: str | None = None
110
+ # Check for targetEntity attribute
111
+ target_match = _TARGET_ENTITY_RE.search(line)
112
+ if target_match:
113
+ target_entity = target_match.group(1)
114
+ else:
115
+ # Look at field type on subsequent lines
116
+ for k in range(i + 1, min(i + 4, len(lines))):
117
+ fm = _FIELD_RE.search(lines[k])
118
+ if fm:
119
+ field_type = fm.group(1).strip()
120
+ gm = _GENERIC_TYPE_RE.search(field_type)
121
+ if gm:
122
+ target_entity = gm.group(1)
123
+ else:
124
+ # Direct type reference (e.g., ManyToOne Address)
125
+ target_entity = field_type.split()[-1] if field_type else None
126
+ break
127
+
128
+ if target_entity:
129
+ mapped_by = _MAPPED_BY_RE.search(line)
130
+ edge_props: dict[str, Any] = {"relationship_type": rel_type}
131
+ if mapped_by:
132
+ edge_props["mapped_by"] = mapped_by.group(1)
133
+
134
+ edge = GraphEdge(
135
+ source=entity_id,
136
+ target=f"*:{target_entity}", # Wildcard target resolved later
137
+ kind=EdgeKind.MAPS_TO,
138
+ label=f"{class_name} {rel_type} {target_entity}",
139
+ properties=edge_props,
140
+ )
141
+ result.edges.append(edge)
142
+
143
+ return result