Generaliaze java_import_external to jvm_import_external

This PR copies "upstream" a change made to java_import_external in`rules_scala` (see [PR](https://github.com/bazelbuild/rules_scala/pull/473))

This change was originally proposed by @dslomov [here](https://github.com/bazelbuild/bazel/issues/3528) (search for 'jvm_import_external')

java_import_external was changed to `jvm_import_external` 'template like' rule + `java_import_external` macro in order to allow for other jvm languages (e.g. scala and kotlin) to utilize the 'import_external' functionality without copying the boiler plate again and again.

This has already been used in `rules_scala` with the introduction of `scala_import_external` and `scala_maven_import_external`

In addition to the `import rule name`, `jvm_import_external` can also be called with custom attributes needed by the underlying import rules, as well as a custom load statement.

`java_import_external` is used as a macro in rules_scala to make sure it's still functioning properly after the change.

`jvm_maven_import_external` exposes maven artifact terminology.
This will also allow to create a `maven_import_external` macro that will delegate to `jvm_maven_import_external` with a `maven_import` rule which will have `MavenCoordinates` Provider as discussed [here](https://github.com/bazelbuild/bazel/issues/4654)

Closes #5068.

PiperOrigin-RevId: 198398621
diff --git a/tools/build_defs/repo/java.bzl b/tools/build_defs/repo/java.bzl
index 559cc77..9803658 100644
--- a/tools/build_defs/repo/java.bzl
+++ b/tools/build_defs/repo/java.bzl
@@ -169,111 +169,11 @@
 reasonably expected to already be provided.
 """
 
-_HEADER = "# DO NOT EDIT: generated by java_import_external()"
+load("@bazel_tools//tools/build_defs/repo:jvm.bzl", "jvm_import_external")
 
-_PASS_PROPS = (
-    "neverlink",
-    "testonly_",
-    "visibility",
-    "exports",
-    "runtime_deps",
-    "deps",
-    "tags",
-)
-
-def _java_import_external(repository_ctx):
-  """Implementation of `java_import_external` rule."""
-  if (repository_ctx.attr.generated_linkable_rule_name and
-      not repository_ctx.attr.neverlink):
-    fail("Only use generated_linkable_rule_name if neverlink is set")
-  name = repository_ctx.attr.generated_rule_name or repository_ctx.name
-  urls = repository_ctx.attr.jar_urls
-  sha = repository_ctx.attr.jar_sha256
-  path = repository_ctx.name + ".jar"
-  for url in urls:
-    if url.endswith(".jar"):
-      path = url[url.rindex("/") + 1:]
-      break
-  srcurls = repository_ctx.attr.srcjar_urls
-  srcsha = repository_ctx.attr.srcjar_sha256
-  srcpath = repository_ctx.name + "-src.jar" if srcurls else ""
-  for url in srcurls:
-    if url.endswith(".jar"):
-      srcpath = url[url.rindex("/") + 1:].replace("-sources.jar", "-src.jar")
-      break
-  lines = [_HEADER, ""]
-  if repository_ctx.attr.default_visibility:
-    lines.append("package(default_visibility = %s)" % (
-        repository_ctx.attr.default_visibility))
-    lines.append("")
-  lines.append("licenses(%s)" % repr(repository_ctx.attr.licenses))
-  lines.append("")
-  lines.extend(_make_java_import(
-      name, path, srcpath, repository_ctx.attr, _PASS_PROPS))
-  if (repository_ctx.attr.neverlink and
-      repository_ctx.attr.generated_linkable_rule_name):
-    lines.extend(_make_java_import(
-        repository_ctx.attr.generated_linkable_rule_name,
-        path,
-        srcpath,
-        repository_ctx.attr,
-        [p for p in _PASS_PROPS if p != "neverlink"]))
-  extra = repository_ctx.attr.extra_build_file_content
-  if extra:
-    lines.append(extra)
-    if not extra.endswith("\n"):
-      lines.append("")
-  repository_ctx.download(urls, path, sha)
-  if srcurls:
-    repository_ctx.download(srcurls, srcpath, srcsha)
-  repository_ctx.file("BUILD", "\n".join(lines))
-  repository_ctx.file("jar/BUILD", "\n".join([
-      _HEADER,
-      "",
-      "package(default_visibility = %r)" % (
-          repository_ctx.attr.visibility or
-          repository_ctx.attr.default_visibility),
-      "",
-      "alias(",
-      "    name = \"jar\",",
-      "    actual = \"@%s\"," % repository_ctx.name,
-      ")",
-      "",
-  ]))
-
-def _make_java_import(name, path, srcpath, attrs, props):
-  lines = [
-      "java_import(",
-      "    name = %s," % repr(name),
-      "    jars = [%s]," % repr(path),
-  ]
-  if srcpath:
-    lines.append("    srcjar = %s," % repr(srcpath))
-  for prop in props:
-    value = getattr(attrs, prop, None)
-    if value:
-      if prop.endswith("_"):
-        prop = prop[:-1]
-      lines.append("    %s = %s," % (prop, repr(value)))
-  lines.append(")")
-  lines.append("")
-  return lines
-
-java_import_external = repository_rule(
-    implementation=_java_import_external,
-    attrs={
-        "licenses": attr.string_list(mandatory=True, allow_empty=False),
-        "jar_urls": attr.string_list(mandatory=True, allow_empty=False),
-        "jar_sha256": attr.string(mandatory=True),
-        "srcjar_urls": attr.string_list(),
-        "srcjar_sha256": attr.string(),
-        "deps": attr.string_list(),
-        "runtime_deps": attr.string_list(),
-        "testonly_": attr.bool(),
-        "exports": attr.string_list(),
-        "neverlink": attr.bool(),
-        "generated_rule_name": attr.string(),
-        "generated_linkable_rule_name": attr.string(),
-        "default_visibility": attr.string_list(default=["//visibility:public"]),
-        "extra_build_file_content": attr.string(),
-    })
+def java_import_external(jar_sha256, **kwargs):
+  jvm_import_external(
+    rule_name = "java_import",
+    jar_sha256 = jar_sha256,
+    **kwargs
+  )
diff --git a/tools/build_defs/repo/jvm.bzl b/tools/build_defs/repo/jvm.bzl
new file mode 100644
index 0000000..f0d80ff
--- /dev/null
+++ b/tools/build_defs/repo/jvm.bzl
@@ -0,0 +1,185 @@
+# Copyright 2018 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+'jvm_import_external' offers additional functionality above what maven_jar has to offer.
+In addition to downloading the jars, it allows to define this jar's dependencies.
+thus it enables the explicit definition of the entire transitive dependency graph.
+
+The rule achieves this by writing 'import' build rules in BUILD files next to the downloaded jars.
+The name of the underlying 'import' rule needs to be specified.
+An optional 'load' statement can also be provided, along with any other relevant custom attribute.
+These import rules must have the following attributes:
+- "jars"
+- "deps"
+- "runtime_deps"
+- "exports"
+
+the following macros are defined below that utilize jvm_import_external:
+
+- jvm_maven_import_external - offers a 'maven' like api for identifying jars using 'artifact' format
+- java_import_external - uses `java_import` as the underlying build rule
+"""
+
+_HEADER = "# DO NOT EDIT: generated by jvm_import_external()"
+_PASS_PROPS = (
+    "neverlink",
+    "testonly_",
+    "visibility",
+    "exports",
+    "runtime_deps",
+    "deps",
+    "tags",
+)
+
+def _jvm_import_external(repository_ctx):
+  """Implementation of `java_import_external` rule."""
+  if (repository_ctx.attr.generated_linkable_rule_name and
+      not repository_ctx.attr.neverlink):
+    fail("Only use generated_linkable_rule_name if neverlink is set")
+  name = repository_ctx.attr.generated_rule_name or repository_ctx.name
+  urls = repository_ctx.attr.jar_urls
+  sha = repository_ctx.attr.jar_sha256
+  path = repository_ctx.name + ".jar"
+  for url in urls:
+    if url.endswith(".jar"):
+      path = url[url.rindex("/") + 1:]
+      break
+  srcurls = repository_ctx.attr.srcjar_urls
+  srcsha = repository_ctx.attr.srcjar_sha256
+  srcpath = repository_ctx.name + "-src.jar" if srcurls else ""
+  for url in srcurls:
+    if url.endswith(".jar"):
+      srcpath = url[url.rindex("/") + 1:].replace("-sources.jar", "-src.jar")
+      break
+  lines = [_HEADER, ""]
+  if repository_ctx.attr.rule_load:
+    lines.append(repository_ctx.attr.rule_load)
+    lines.append("")
+  if repository_ctx.attr.default_visibility:
+    lines.append("package(default_visibility = %s)" % (
+        repository_ctx.attr.default_visibility))
+    lines.append("")
+  lines.append("licenses(%s)" % repr(repository_ctx.attr.licenses))
+  lines.append("")
+  lines.extend(_serialize_given_rule_import(
+      repository_ctx.attr.rule_name, name, path, srcpath, repository_ctx.attr, _PASS_PROPS, repository_ctx.attr.additional_rule_attrs))
+  if (repository_ctx.attr.neverlink and
+      repository_ctx.attr.generated_linkable_rule_name):
+    lines.extend(_serialize_given_rule_import(
+        repository_ctx.attr.rule_name,
+        repository_ctx.attr.generated_linkable_rule_name,
+        path,
+        srcpath,
+        repository_ctx.attr,
+        [p for p in _PASS_PROPS if p != "neverlink"],
+        repository_ctx.attr.additional_rule_attrs))
+  extra = repository_ctx.attr.extra_build_file_content
+  if extra:
+    lines.append(extra)
+    if not extra.endswith("\n"):
+      lines.append("")
+  repository_ctx.download(urls, path, sha)
+  if srcurls:
+    repository_ctx.download(srcurls, srcpath, srcsha)
+  repository_ctx.file("BUILD", "\n".join(lines))
+  repository_ctx.file("jar/BUILD", "\n".join([
+      _HEADER,
+      "",
+      "package(default_visibility = %r)" % (
+          repository_ctx.attr.visibility or
+          repository_ctx.attr.default_visibility),
+      "",
+      "alias(",
+      "    name = \"jar\",",
+      "    actual = \"@%s\"," % repository_ctx.name,
+      ")",
+      "",
+  ]))
+
+def _convert_to_url(artifact, server_urls):
+    parts = artifact.split(":")
+    group_id_part = parts[0].replace(".","/")
+    artifact_id = parts[1]
+    version = parts[2]
+    packaging = "jar"
+    classifier_part = ""
+    if len(parts) == 4:
+      packaging = parts[2]
+      version = parts[3]
+    elif len(parts) == 5:
+      packaging = parts[2]
+      classifier_part = "-"+parts[3]
+      version = parts[4]
+
+    final_name = artifact_id + "-" + version + classifier_part + "." + packaging
+    url_suffix = group_id_part+"/"+artifact_id + "/" + version + "/" + final_name
+    urls = []
+    for server_url in server_urls:
+      urls.append(_concat_with_needed_slash(server_url, url_suffix))
+    return urls
+
+def _concat_with_needed_slash(server_url, url_suffix):
+  if server_url.endswith("/"):
+    return server_url + url_suffix
+  else:
+    return server_url + "/" + url_suffix
+
+def _serialize_given_rule_import(rule_name, name, path, srcpath, attrs, props, additional_rule_attrs):
+  lines = [
+      "%s(" % rule_name,
+      "    name = %s," % repr(name),
+      "    jars = [%s]," % repr(path),
+  ]
+  if srcpath:
+    lines.append("    srcjar = %s," % repr(srcpath))
+  for prop in props:
+    value = getattr(attrs, prop, None)
+    if value:
+      if prop.endswith("_"):
+        prop = prop[:-1]
+      lines.append("    %s = %s," % (prop, repr(value)))
+  for attr_key in additional_rule_attrs:
+    lines.append("    %s = %s," % (attr_key, additional_rule_attrs[attr_key]))
+  lines.append(")")
+  lines.append("")
+  return lines
+
+jvm_import_external = repository_rule(
+    implementation=_jvm_import_external,
+    attrs={
+        "rule_name": attr.string(mandatory=True),
+        "licenses": attr.string_list(mandatory=True, allow_empty=False),
+        "jar_urls": attr.string_list(mandatory=True, allow_empty=False),
+        "jar_sha256": attr.string(),
+        "rule_load": attr.string(),
+        "additional_rule_attrs": attr.string_dict(),
+        "srcjar_urls": attr.string_list(),
+        "srcjar_sha256": attr.string(),
+        "deps": attr.string_list(),
+        "runtime_deps": attr.string_list(),
+        "testonly_": attr.bool(),
+        "exports": attr.string_list(),
+        "neverlink": attr.bool(),
+        "generated_rule_name": attr.string(),
+        "generated_linkable_rule_name": attr.string(),
+        "default_visibility": attr.string_list(default=["//visibility:public"]),
+        "extra_build_file_content": attr.string(),
+    })
+
+def jvm_maven_import_external(artifact, server_urls, **kwargs):
+  jvm_import_external(
+      jar_urls = _convert_to_url(artifact, server_urls),
+      **kwargs
+  )
\ No newline at end of file