xref: /aosp_15_r20/external/google-cloud-java/owl-bot-postprocessor/synthtool/languages/java.py (revision 55e87721aa1bc457b326496a7ca40f3ea1a63287)
1*55e87721SMatt Gilbride# Copyright 2018 Google LLC
2*55e87721SMatt Gilbride#
3*55e87721SMatt Gilbride# Licensed under the Apache License, Version 2.0 (the "License");
4*55e87721SMatt Gilbride# you may not use this file except in compliance with the License.
5*55e87721SMatt Gilbride# You may obtain a copy of the License at
6*55e87721SMatt Gilbride#
7*55e87721SMatt Gilbride#     https://www.apache.org/licenses/LICENSE-2.0
8*55e87721SMatt Gilbride#
9*55e87721SMatt Gilbride# Unless required by applicable law or agreed to in writing, software
10*55e87721SMatt Gilbride# distributed under the License is distributed on an "AS IS" BASIS,
11*55e87721SMatt Gilbride# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*55e87721SMatt Gilbride# See the License for the specific language governing permissions and
13*55e87721SMatt Gilbride# limitations under the License.
14*55e87721SMatt Gilbride
15*55e87721SMatt Gilbrideimport glob
16*55e87721SMatt Gilbrideimport os
17*55e87721SMatt Gilbrideimport xml.etree.ElementTree as ET
18*55e87721SMatt Gilbrideimport re
19*55e87721SMatt Gilbrideimport requests
20*55e87721SMatt Gilbrideimport yaml
21*55e87721SMatt Gilbrideimport synthtool as s
22*55e87721SMatt Gilbrideimport synthtool.gcp as gcp
23*55e87721SMatt Gilbridefrom synthtool import cache, shell
24*55e87721SMatt Gilbridefrom synthtool.gcp import common, partials, pregenerated, samples, snippets
25*55e87721SMatt Gilbridefrom synthtool.log import logger
26*55e87721SMatt Gilbridefrom pathlib import Path
27*55e87721SMatt Gilbridefrom typing import Any, Optional, Dict, Iterable, List
28*55e87721SMatt Gilbride
29*55e87721SMatt GilbrideJAR_DOWNLOAD_URL = "https://github.com/google/google-java-format/releases/download/google-java-format-{version}/google-java-format-{version}-all-deps.jar"
30*55e87721SMatt GilbrideDEFAULT_FORMAT_VERSION = "1.7"
31*55e87721SMatt GilbrideGOOD_LICENSE = """/*
32*55e87721SMatt Gilbride * Copyright 2020 Google LLC
33*55e87721SMatt Gilbride *
34*55e87721SMatt Gilbride * Licensed under the Apache License, Version 2.0 (the "License");
35*55e87721SMatt Gilbride * you may not use this file except in compliance with the License.
36*55e87721SMatt Gilbride * You may obtain a copy of the License at
37*55e87721SMatt Gilbride *
38*55e87721SMatt Gilbride *     https://www.apache.org/licenses/LICENSE-2.0
39*55e87721SMatt Gilbride *
40*55e87721SMatt Gilbride * Unless required by applicable law or agreed to in writing, software
41*55e87721SMatt Gilbride * distributed under the License is distributed on an "AS IS" BASIS,
42*55e87721SMatt Gilbride * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
43*55e87721SMatt Gilbride * See the License for the specific language governing permissions and
44*55e87721SMatt Gilbride * limitations under the License.
45*55e87721SMatt Gilbride */
46*55e87721SMatt Gilbride"""
47*55e87721SMatt GilbridePROTOBUF_HEADER = "// Generated by the protocol buffer compiler.  DO NOT EDIT!"
48*55e87721SMatt GilbrideBAD_LICENSE = """/\\*
49*55e87721SMatt Gilbride \\* Copyright \\d{4} Google LLC
50*55e87721SMatt Gilbride \\*
51*55e87721SMatt Gilbride \\* Licensed under the Apache License, Version 2.0 \\(the "License"\\); you may not use this file except
52*55e87721SMatt Gilbride \\* in compliance with the License. You may obtain a copy of the License at
53*55e87721SMatt Gilbride \\*
54*55e87721SMatt Gilbride \\* http://www.apache.org/licenses/LICENSE-2.0
55*55e87721SMatt Gilbride \\*
56*55e87721SMatt Gilbride \\* Unless required by applicable law or agreed to in writing, software distributed under the License
57*55e87721SMatt Gilbride \\* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
58*55e87721SMatt Gilbride \\* or implied. See the License for the specific language governing permissions and limitations under
59*55e87721SMatt Gilbride \\* the License.
60*55e87721SMatt Gilbride \\*/
61*55e87721SMatt Gilbride"""
62*55e87721SMatt GilbrideDEFAULT_MIN_SUPPORTED_JAVA_VERSION = 8
63*55e87721SMatt Gilbride
64*55e87721SMatt Gilbride
65*55e87721SMatt Gilbridedef format_code(
66*55e87721SMatt Gilbride    path: str, version: str = DEFAULT_FORMAT_VERSION, times: int = 2
67*55e87721SMatt Gilbride) -> None:
68*55e87721SMatt Gilbride    """
69*55e87721SMatt Gilbride    Runs the google-java-format jar against all .java files found within the
70*55e87721SMatt Gilbride    provided path.
71*55e87721SMatt Gilbride    """
72*55e87721SMatt Gilbride    jar_name = f"google-java-format-{version}.jar"
73*55e87721SMatt Gilbride    jar = cache.get_cache_dir() / jar_name
74*55e87721SMatt Gilbride    if not jar.exists():
75*55e87721SMatt Gilbride        _download_formatter(version, jar)
76*55e87721SMatt Gilbride
77*55e87721SMatt Gilbride    # Find all .java files in path and run the formatter on them
78*55e87721SMatt Gilbride    files = list(glob.iglob(os.path.join(path, "**/*.java"), recursive=True))
79*55e87721SMatt Gilbride
80*55e87721SMatt Gilbride    # Run the formatter as a jar file
81*55e87721SMatt Gilbride    logger.info("Running java formatter on {} files".format(len(files)))
82*55e87721SMatt Gilbride    for _ in range(times):
83*55e87721SMatt Gilbride        shell.run(["java", "-jar", str(jar), "--replace"] + files)
84*55e87721SMatt Gilbride
85*55e87721SMatt Gilbride
86*55e87721SMatt Gilbridedef _download_formatter(version: str, dest: Path) -> None:
87*55e87721SMatt Gilbride    logger.info("Downloading java formatter")
88*55e87721SMatt Gilbride    url = JAR_DOWNLOAD_URL.format(version=version)
89*55e87721SMatt Gilbride    response = requests.get(url)
90*55e87721SMatt Gilbride    response.raise_for_status()
91*55e87721SMatt Gilbride    with open(dest, "wb") as fh:
92*55e87721SMatt Gilbride        fh.write(response.content)
93*55e87721SMatt Gilbride
94*55e87721SMatt Gilbride
95*55e87721SMatt GilbrideHEADER_REGEX = re.compile("\\* Copyright \\d{4} Google LLC")
96*55e87721SMatt Gilbride
97*55e87721SMatt Gilbride
98*55e87721SMatt Gilbridedef _file_has_header(path: Path) -> bool:
99*55e87721SMatt Gilbride    """Return true if the file already contains a license header."""
100*55e87721SMatt Gilbride    with open(path, "rt") as fp:
101*55e87721SMatt Gilbride        for line in fp:
102*55e87721SMatt Gilbride            if HEADER_REGEX.search(line):
103*55e87721SMatt Gilbride                return True
104*55e87721SMatt Gilbride    return False
105*55e87721SMatt Gilbride
106*55e87721SMatt Gilbride
107*55e87721SMatt Gilbridedef _filter_no_header(paths: Iterable[Path]) -> Iterable[Path]:
108*55e87721SMatt Gilbride    """Return a subset of files that do not already have a header."""
109*55e87721SMatt Gilbride    for path in paths:
110*55e87721SMatt Gilbride        anchor = Path(path.anchor)
111*55e87721SMatt Gilbride        remainder = str(path.relative_to(path.anchor))
112*55e87721SMatt Gilbride        for file in anchor.glob(remainder):
113*55e87721SMatt Gilbride            if not _file_has_header(file):
114*55e87721SMatt Gilbride                yield file
115*55e87721SMatt Gilbride
116*55e87721SMatt Gilbride
117*55e87721SMatt Gilbridedef fix_proto_headers(proto_root: Path) -> None:
118*55e87721SMatt Gilbride    """Helper to ensure that generated proto classes have appropriate license headers.
119*55e87721SMatt Gilbride
120*55e87721SMatt Gilbride    If the file does not already contain a license header, inject one at the top of the file.
121*55e87721SMatt Gilbride    Some resource name classes may contain malformed license headers. In those cases, replace
122*55e87721SMatt Gilbride    those with our standard license header.
123*55e87721SMatt Gilbride    """
124*55e87721SMatt Gilbride    s.replace(
125*55e87721SMatt Gilbride        _filter_no_header([proto_root / "src/**/*.java"]),
126*55e87721SMatt Gilbride        PROTOBUF_HEADER,
127*55e87721SMatt Gilbride        f"{GOOD_LICENSE}{PROTOBUF_HEADER}",
128*55e87721SMatt Gilbride    )
129*55e87721SMatt Gilbride    # https://github.com/googleapis/gapic-generator/issues/3074
130*55e87721SMatt Gilbride    s.replace(
131*55e87721SMatt Gilbride        [proto_root / "src/**/*Name.java", proto_root / "src/**/*Names.java"],
132*55e87721SMatt Gilbride        BAD_LICENSE,
133*55e87721SMatt Gilbride        GOOD_LICENSE,
134*55e87721SMatt Gilbride    )
135*55e87721SMatt Gilbride
136*55e87721SMatt Gilbride
137*55e87721SMatt Gilbridedef fix_grpc_headers(grpc_root: Path, package_name: str = "unused") -> None:
138*55e87721SMatt Gilbride    """Helper to ensure that generated grpc stub classes have appropriate license headers.
139*55e87721SMatt Gilbride
140*55e87721SMatt Gilbride    If the file does not already contain a license header, inject one at the top of the file.
141*55e87721SMatt Gilbride    """
142*55e87721SMatt Gilbride    s.replace(
143*55e87721SMatt Gilbride        _filter_no_header([grpc_root / "src/**/*.java"]),
144*55e87721SMatt Gilbride        "^package (.*);",
145*55e87721SMatt Gilbride        f"{GOOD_LICENSE}package \\1;",
146*55e87721SMatt Gilbride    )
147*55e87721SMatt Gilbride
148*55e87721SMatt Gilbride
149*55e87721SMatt Gilbridedef latest_maven_version(group_id: str, artifact_id: str) -> Optional[str]:
150*55e87721SMatt Gilbride    """Helper function to find the latest released version of a Maven artifact.
151*55e87721SMatt Gilbride
152*55e87721SMatt Gilbride    Fetches metadata from Maven Central and parses out the latest released
153*55e87721SMatt Gilbride    version.
154*55e87721SMatt Gilbride
155*55e87721SMatt Gilbride    Args:
156*55e87721SMatt Gilbride        group_id (str): The groupId of the Maven artifact
157*55e87721SMatt Gilbride        artifact_id (str): The artifactId of the Maven artifact
158*55e87721SMatt Gilbride
159*55e87721SMatt Gilbride    Returns:
160*55e87721SMatt Gilbride        The latest version of the artifact as a string or None
161*55e87721SMatt Gilbride    """
162*55e87721SMatt Gilbride    group_path = "/".join(group_id.split("."))
163*55e87721SMatt Gilbride    url = (
164*55e87721SMatt Gilbride        f"https://repo1.maven.org/maven2/{group_path}/{artifact_id}/maven-metadata.xml"
165*55e87721SMatt Gilbride    )
166*55e87721SMatt Gilbride    response = requests.get(url)
167*55e87721SMatt Gilbride    if response.status_code >= 400:
168*55e87721SMatt Gilbride        return "0.0.0"
169*55e87721SMatt Gilbride
170*55e87721SMatt Gilbride    return version_from_maven_metadata(response.text)
171*55e87721SMatt Gilbride
172*55e87721SMatt Gilbride
173*55e87721SMatt Gilbridedef version_from_maven_metadata(metadata: str) -> Optional[str]:
174*55e87721SMatt Gilbride    """Helper function to parse the latest released version from the Maven
175*55e87721SMatt Gilbride    metadata XML file.
176*55e87721SMatt Gilbride
177*55e87721SMatt Gilbride    Args:
178*55e87721SMatt Gilbride        metadata (str): The XML contents of the Maven metadata file
179*55e87721SMatt Gilbride
180*55e87721SMatt Gilbride    Returns:
181*55e87721SMatt Gilbride        The latest version of the artifact as a string or None
182*55e87721SMatt Gilbride    """
183*55e87721SMatt Gilbride    root = ET.fromstring(metadata)
184*55e87721SMatt Gilbride    latest = root.find("./versioning/latest")
185*55e87721SMatt Gilbride    if latest is not None:
186*55e87721SMatt Gilbride        return latest.text
187*55e87721SMatt Gilbride
188*55e87721SMatt Gilbride    return None
189*55e87721SMatt Gilbride
190*55e87721SMatt Gilbride
191*55e87721SMatt Gilbridedef _common_generation(
192*55e87721SMatt Gilbride    service: str,
193*55e87721SMatt Gilbride    version: str,
194*55e87721SMatt Gilbride    library: Path,
195*55e87721SMatt Gilbride    package_pattern: str,
196*55e87721SMatt Gilbride    suffix: str = "",
197*55e87721SMatt Gilbride    destination_name: str = None,
198*55e87721SMatt Gilbride    cloud_api: bool = True,
199*55e87721SMatt Gilbride    diregapic: bool = False,
200*55e87721SMatt Gilbride    preserve_gapic: bool = False,
201*55e87721SMatt Gilbride):
202*55e87721SMatt Gilbride    """Helper function to execution the common generation cleanup actions.
203*55e87721SMatt Gilbride
204*55e87721SMatt Gilbride    Fixes headers for protobuf classes and generated gRPC stub services. Copies
205*55e87721SMatt Gilbride    code and samples to their final destinations by convention. Runs the code
206*55e87721SMatt Gilbride    formatter on the generated code.
207*55e87721SMatt Gilbride
208*55e87721SMatt Gilbride    Args:
209*55e87721SMatt Gilbride        service (str): Name of the service.
210*55e87721SMatt Gilbride        version (str): Service API version.
211*55e87721SMatt Gilbride        library (Path): Path to the temp directory with the generated library.
212*55e87721SMatt Gilbride        package_pattern (str): Package name template for fixing file headers.
213*55e87721SMatt Gilbride        suffix (str, optional): Suffix that the generated library folder. The
214*55e87721SMatt Gilbride            artman output differs from bazel's output directory. Defaults to "".
215*55e87721SMatt Gilbride        destination_name (str, optional): Override the service name for the
216*55e87721SMatt Gilbride            destination of the output code. Defaults to the service name.
217*55e87721SMatt Gilbride        preserve_gapic (bool, optional): Whether to preserve the gapic directory
218*55e87721SMatt Gilbride            prefix. Default False.
219*55e87721SMatt Gilbride    """
220*55e87721SMatt Gilbride
221*55e87721SMatt Gilbride    if destination_name is None:
222*55e87721SMatt Gilbride        destination_name = service
223*55e87721SMatt Gilbride
224*55e87721SMatt Gilbride    cloud_prefix = "cloud-" if cloud_api else ""
225*55e87721SMatt Gilbride    package_name = package_pattern.format(service=service, version=version)
226*55e87721SMatt Gilbride    fix_proto_headers(
227*55e87721SMatt Gilbride        library / f"proto-google-{cloud_prefix}{service}-{version}{suffix}"
228*55e87721SMatt Gilbride    )
229*55e87721SMatt Gilbride    fix_grpc_headers(
230*55e87721SMatt Gilbride        library / f"grpc-google-{cloud_prefix}{service}-{version}{suffix}", package_name
231*55e87721SMatt Gilbride    )
232*55e87721SMatt Gilbride
233*55e87721SMatt Gilbride    if preserve_gapic:
234*55e87721SMatt Gilbride        s.copy(
235*55e87721SMatt Gilbride            [library / f"gapic-google-{cloud_prefix}{service}-{version}{suffix}/src"],
236*55e87721SMatt Gilbride            f"gapic-google-{cloud_prefix}{destination_name}-{version}/src",
237*55e87721SMatt Gilbride            required=True,
238*55e87721SMatt Gilbride        )
239*55e87721SMatt Gilbride    else:
240*55e87721SMatt Gilbride        s.copy(
241*55e87721SMatt Gilbride            [library / f"gapic-google-{cloud_prefix}{service}-{version}{suffix}/src"],
242*55e87721SMatt Gilbride            f"google-{cloud_prefix}{destination_name}/src",
243*55e87721SMatt Gilbride            required=True,
244*55e87721SMatt Gilbride        )
245*55e87721SMatt Gilbride
246*55e87721SMatt Gilbride    s.copy(
247*55e87721SMatt Gilbride        [library / f"grpc-google-{cloud_prefix}{service}-{version}{suffix}/src"],
248*55e87721SMatt Gilbride        f"grpc-google-{cloud_prefix}{destination_name}-{version}/src",
249*55e87721SMatt Gilbride        # For REST-only clients, like java-compute, gRPC artifact does not exist
250*55e87721SMatt Gilbride        required=(not diregapic),
251*55e87721SMatt Gilbride    )
252*55e87721SMatt Gilbride    s.copy(
253*55e87721SMatt Gilbride        [library / f"proto-google-{cloud_prefix}{service}-{version}{suffix}/src"],
254*55e87721SMatt Gilbride        f"proto-google-{cloud_prefix}{destination_name}-{version}/src",
255*55e87721SMatt Gilbride        required=True,
256*55e87721SMatt Gilbride    )
257*55e87721SMatt Gilbride
258*55e87721SMatt Gilbride    if preserve_gapic:
259*55e87721SMatt Gilbride        format_code(f"gapic-google-{cloud_prefix}{destination_name}-{version}/src")
260*55e87721SMatt Gilbride    else:
261*55e87721SMatt Gilbride        format_code(f"google-{cloud_prefix}{destination_name}/src")
262*55e87721SMatt Gilbride    format_code(f"grpc-google-{cloud_prefix}{destination_name}-{version}/src")
263*55e87721SMatt Gilbride    format_code(f"proto-google-{cloud_prefix}{destination_name}-{version}/src")
264*55e87721SMatt Gilbride
265*55e87721SMatt Gilbride
266*55e87721SMatt Gilbridedef gapic_library(
267*55e87721SMatt Gilbride    service: str,
268*55e87721SMatt Gilbride    version: str,
269*55e87721SMatt Gilbride    config_pattern: str = "/google/cloud/{service}/artman_{service}_{version}.yaml",
270*55e87721SMatt Gilbride    package_pattern: str = "com.google.cloud.{service}.{version}",
271*55e87721SMatt Gilbride    gapic: gcp.GAPICGenerator = None,
272*55e87721SMatt Gilbride    destination_name: str = None,
273*55e87721SMatt Gilbride    diregapic: bool = False,
274*55e87721SMatt Gilbride    preserve_gapic: bool = False,
275*55e87721SMatt Gilbride    **kwargs,
276*55e87721SMatt Gilbride) -> Path:
277*55e87721SMatt Gilbride    """Generate a Java library using the gapic-generator via artman via Docker.
278*55e87721SMatt Gilbride
279*55e87721SMatt Gilbride    Generates code into a temp directory, fixes missing header fields, and
280*55e87721SMatt Gilbride    copies into the expected locations.
281*55e87721SMatt Gilbride
282*55e87721SMatt Gilbride    Args:
283*55e87721SMatt Gilbride        service (str): Name of the service.
284*55e87721SMatt Gilbride        version (str): Service API version.
285*55e87721SMatt Gilbride        config_pattern (str, optional): Path template to artman config YAML
286*55e87721SMatt Gilbride            file. Defaults to "/google/cloud/{service}/artman_{service}_{version}.yaml"
287*55e87721SMatt Gilbride        package_pattern (str, optional): Package name template for fixing file
288*55e87721SMatt Gilbride            headers. Defaults to "com.google.cloud.{service}.{version}".
289*55e87721SMatt Gilbride        gapic (GAPICGenerator, optional): Generator instance.
290*55e87721SMatt Gilbride        destination_name (str, optional): Override the service name for the
291*55e87721SMatt Gilbride            destination of the output code. Defaults to the service name.
292*55e87721SMatt Gilbride        preserve_gapic (bool, optional): Whether to preserve the gapic directory
293*55e87721SMatt Gilbride            prefix. Default False.
294*55e87721SMatt Gilbride        **kwargs: Additional options for gapic.java_library()
295*55e87721SMatt Gilbride
296*55e87721SMatt Gilbride    Returns:
297*55e87721SMatt Gilbride        The path to the temp directory containing the generated client.
298*55e87721SMatt Gilbride    """
299*55e87721SMatt Gilbride    if gapic is None:
300*55e87721SMatt Gilbride        gapic = gcp.GAPICGenerator()
301*55e87721SMatt Gilbride
302*55e87721SMatt Gilbride    library = gapic.java_library(
303*55e87721SMatt Gilbride        service=service,
304*55e87721SMatt Gilbride        version=version,
305*55e87721SMatt Gilbride        config_path=config_pattern.format(service=service, version=version),
306*55e87721SMatt Gilbride        artman_output_name="",
307*55e87721SMatt Gilbride        include_samples=True,
308*55e87721SMatt Gilbride        diregapic=diregapic,
309*55e87721SMatt Gilbride        **kwargs,
310*55e87721SMatt Gilbride    )
311*55e87721SMatt Gilbride
312*55e87721SMatt Gilbride    _common_generation(
313*55e87721SMatt Gilbride        service=service,
314*55e87721SMatt Gilbride        version=version,
315*55e87721SMatt Gilbride        library=library,
316*55e87721SMatt Gilbride        package_pattern=package_pattern,
317*55e87721SMatt Gilbride        destination_name=destination_name,
318*55e87721SMatt Gilbride        diregapic=diregapic,
319*55e87721SMatt Gilbride        preserve_gapic=preserve_gapic,
320*55e87721SMatt Gilbride    )
321*55e87721SMatt Gilbride
322*55e87721SMatt Gilbride    return library
323*55e87721SMatt Gilbride
324*55e87721SMatt Gilbride
325*55e87721SMatt Gilbridedef bazel_library(
326*55e87721SMatt Gilbride    service: str,
327*55e87721SMatt Gilbride    version: str,
328*55e87721SMatt Gilbride    package_pattern: str = "com.google.cloud.{service}.{version}",
329*55e87721SMatt Gilbride    gapic: gcp.GAPICBazel = None,
330*55e87721SMatt Gilbride    destination_name: str = None,
331*55e87721SMatt Gilbride    cloud_api: bool = True,
332*55e87721SMatt Gilbride    diregapic: bool = False,
333*55e87721SMatt Gilbride    preserve_gapic: bool = False,
334*55e87721SMatt Gilbride    **kwargs,
335*55e87721SMatt Gilbride) -> Path:
336*55e87721SMatt Gilbride    """Generate a Java library using the gapic-generator via bazel.
337*55e87721SMatt Gilbride
338*55e87721SMatt Gilbride    Generates code into a temp directory, fixes missing header fields, and
339*55e87721SMatt Gilbride    copies into the expected locations.
340*55e87721SMatt Gilbride
341*55e87721SMatt Gilbride    Args:
342*55e87721SMatt Gilbride        service (str): Name of the service.
343*55e87721SMatt Gilbride        version (str): Service API version.
344*55e87721SMatt Gilbride        package_pattern (str, optional): Package name template for fixing file
345*55e87721SMatt Gilbride            headers. Defaults to "com.google.cloud.{service}.{version}".
346*55e87721SMatt Gilbride        gapic (GAPICBazel, optional): Generator instance.
347*55e87721SMatt Gilbride        destination_name (str, optional): Override the service name for the
348*55e87721SMatt Gilbride            destination of the output code. Defaults to the service name.
349*55e87721SMatt Gilbride        preserve_gapic (bool, optional): Whether to preserve the gapic directory
350*55e87721SMatt Gilbride            prefix. Default False.
351*55e87721SMatt Gilbride        **kwargs: Additional options for gapic.java_library()
352*55e87721SMatt Gilbride
353*55e87721SMatt Gilbride    Returns:
354*55e87721SMatt Gilbride        The path to the temp directory containing the generated client.
355*55e87721SMatt Gilbride    """
356*55e87721SMatt Gilbride    if gapic is None:
357*55e87721SMatt Gilbride        gapic = gcp.GAPICBazel()
358*55e87721SMatt Gilbride
359*55e87721SMatt Gilbride    library = gapic.java_library(
360*55e87721SMatt Gilbride        service=service, version=version, diregapic=diregapic, **kwargs
361*55e87721SMatt Gilbride    )
362*55e87721SMatt Gilbride
363*55e87721SMatt Gilbride    _common_generation(
364*55e87721SMatt Gilbride        service=service,
365*55e87721SMatt Gilbride        version=version,
366*55e87721SMatt Gilbride        library=library / f"google-cloud-{service}-{version}-java",
367*55e87721SMatt Gilbride        package_pattern=package_pattern,
368*55e87721SMatt Gilbride        suffix="-java",
369*55e87721SMatt Gilbride        destination_name=destination_name,
370*55e87721SMatt Gilbride        cloud_api=cloud_api,
371*55e87721SMatt Gilbride        diregapic=diregapic,
372*55e87721SMatt Gilbride        preserve_gapic=preserve_gapic,
373*55e87721SMatt Gilbride    )
374*55e87721SMatt Gilbride
375*55e87721SMatt Gilbride    return library
376*55e87721SMatt Gilbride
377*55e87721SMatt Gilbride
378*55e87721SMatt Gilbridedef pregenerated_library(
379*55e87721SMatt Gilbride    path: str,
380*55e87721SMatt Gilbride    service: str,
381*55e87721SMatt Gilbride    version: str,
382*55e87721SMatt Gilbride    destination_name: str = None,
383*55e87721SMatt Gilbride    cloud_api: bool = True,
384*55e87721SMatt Gilbride) -> Path:
385*55e87721SMatt Gilbride    """Generate a Java library using the gapic-generator via bazel.
386*55e87721SMatt Gilbride
387*55e87721SMatt Gilbride    Generates code into a temp directory, fixes missing header fields, and
388*55e87721SMatt Gilbride    copies into the expected locations.
389*55e87721SMatt Gilbride
390*55e87721SMatt Gilbride    Args:
391*55e87721SMatt Gilbride        path (str): Path in googleapis-gen to un-versioned generated code.
392*55e87721SMatt Gilbride        service (str): Name of the service.
393*55e87721SMatt Gilbride        version (str): Service API version.
394*55e87721SMatt Gilbride        destination_name (str, optional): Override the service name for the
395*55e87721SMatt Gilbride            destination of the output code. Defaults to the service name.
396*55e87721SMatt Gilbride        cloud_api (bool, optional): Whether or not this is a cloud API (for naming)
397*55e87721SMatt Gilbride
398*55e87721SMatt Gilbride    Returns:
399*55e87721SMatt Gilbride        The path to the temp directory containing the generated client.
400*55e87721SMatt Gilbride    """
401*55e87721SMatt Gilbride    generator = pregenerated.Pregenerated()
402*55e87721SMatt Gilbride    library = generator.generate(path)
403*55e87721SMatt Gilbride
404*55e87721SMatt Gilbride    cloud_prefix = "cloud-" if cloud_api else ""
405*55e87721SMatt Gilbride    _common_generation(
406*55e87721SMatt Gilbride        service=service,
407*55e87721SMatt Gilbride        version=version,
408*55e87721SMatt Gilbride        library=library / f"google-{cloud_prefix}{service}-{version}-java",
409*55e87721SMatt Gilbride        package_pattern="unused",
410*55e87721SMatt Gilbride        suffix="-java",
411*55e87721SMatt Gilbride        destination_name=destination_name,
412*55e87721SMatt Gilbride        cloud_api=cloud_api,
413*55e87721SMatt Gilbride    )
414*55e87721SMatt Gilbride
415*55e87721SMatt Gilbride    return library
416*55e87721SMatt Gilbride
417*55e87721SMatt Gilbride
418*55e87721SMatt Gilbridedef _merge_release_please(destination_text: str):
419*55e87721SMatt Gilbride    config = yaml.safe_load(destination_text)
420*55e87721SMatt Gilbride    if "handleGHRelease" in config:
421*55e87721SMatt Gilbride        return destination_text
422*55e87721SMatt Gilbride
423*55e87721SMatt Gilbride    config["handleGHRelease"] = True
424*55e87721SMatt Gilbride
425*55e87721SMatt Gilbride    if "branches" in config:
426*55e87721SMatt Gilbride        for branch in config["branches"]:
427*55e87721SMatt Gilbride            branch["handleGHRelease"] = True
428*55e87721SMatt Gilbride    return yaml.dump(config)
429*55e87721SMatt Gilbride
430*55e87721SMatt Gilbride
431*55e87721SMatt Gilbridedef _merge_common_templates(
432*55e87721SMatt Gilbride    source_text: str, destination_text: str, file_path: Path
433*55e87721SMatt Gilbride) -> str:
434*55e87721SMatt Gilbride    # keep any existing pom.xml
435*55e87721SMatt Gilbride    if file_path.match("pom.xml") or file_path.match("sync-repo-settings.yaml"):
436*55e87721SMatt Gilbride        logger.debug(f"existing pom file found ({file_path}) - keeping the existing")
437*55e87721SMatt Gilbride        return destination_text
438*55e87721SMatt Gilbride
439*55e87721SMatt Gilbride    if file_path.match("release-please.yml"):
440*55e87721SMatt Gilbride        return _merge_release_please(destination_text)
441*55e87721SMatt Gilbride
442*55e87721SMatt Gilbride    # by default return the newly generated content
443*55e87721SMatt Gilbride    return source_text
444*55e87721SMatt Gilbride
445*55e87721SMatt Gilbride
446*55e87721SMatt Gilbridedef _common_template_metadata() -> Dict[str, Any]:
447*55e87721SMatt Gilbride    metadata = {}  # type: Dict[str, Any]
448*55e87721SMatt Gilbride    repo_metadata = common._load_repo_metadata()
449*55e87721SMatt Gilbride    if repo_metadata:
450*55e87721SMatt Gilbride        metadata["repo"] = repo_metadata
451*55e87721SMatt Gilbride        group_id, artifact_id = repo_metadata["distribution_name"].split(":")
452*55e87721SMatt Gilbride
453*55e87721SMatt Gilbride        metadata["latest_version"] = latest_maven_version(
454*55e87721SMatt Gilbride            group_id=group_id, artifact_id=artifact_id
455*55e87721SMatt Gilbride        )
456*55e87721SMatt Gilbride
457*55e87721SMatt Gilbride    metadata["latest_bom_version"] = latest_maven_version(
458*55e87721SMatt Gilbride        group_id="com.google.cloud",
459*55e87721SMatt Gilbride        artifact_id="libraries-bom",
460*55e87721SMatt Gilbride    )
461*55e87721SMatt Gilbride
462*55e87721SMatt Gilbride    metadata["samples"] = samples.all_samples(["samples/**/src/main/java/**/*.java"])
463*55e87721SMatt Gilbride    metadata["snippets"] = snippets.all_snippets(
464*55e87721SMatt Gilbride        ["samples/**/src/main/java/**/*.java", "samples/**/pom.xml"]
465*55e87721SMatt Gilbride    )
466*55e87721SMatt Gilbride    if repo_metadata and "min_java_version" in repo_metadata:
467*55e87721SMatt Gilbride        metadata["min_java_version"] = repo_metadata["min_java_version"]
468*55e87721SMatt Gilbride    else:
469*55e87721SMatt Gilbride        metadata["min_java_version"] = DEFAULT_MIN_SUPPORTED_JAVA_VERSION
470*55e87721SMatt Gilbride
471*55e87721SMatt Gilbride    return metadata
472*55e87721SMatt Gilbride
473*55e87721SMatt Gilbride
474*55e87721SMatt Gilbridedef common_templates(
475*55e87721SMatt Gilbride    excludes: List[str] = [], template_path: Optional[Path] = None, **kwargs
476*55e87721SMatt Gilbride) -> None:
477*55e87721SMatt Gilbride    """Generate common templates for a Java Library
478*55e87721SMatt Gilbride
479*55e87721SMatt Gilbride    Fetches information about the repository from the .repo-metadata.json file,
480*55e87721SMatt Gilbride    information about the latest artifact versions and copies the files into
481*55e87721SMatt Gilbride    their expected location.
482*55e87721SMatt Gilbride
483*55e87721SMatt Gilbride    Args:
484*55e87721SMatt Gilbride        excludes (List[str], optional): List of template paths to ignore
485*55e87721SMatt Gilbride        **kwargs: Additional options for CommonTemplates.java_library()
486*55e87721SMatt Gilbride    """
487*55e87721SMatt Gilbride    metadata = _common_template_metadata()
488*55e87721SMatt Gilbride    kwargs["metadata"] = metadata
489*55e87721SMatt Gilbride
490*55e87721SMatt Gilbride    # Generate flat to tell this repository is a split repo that have migrated
491*55e87721SMatt Gilbride    # to monorepo. The owlbot.py in the monorepo sets monorepo=True.
492*55e87721SMatt Gilbride    monorepo = kwargs.get("monorepo", False)
493*55e87721SMatt Gilbride    split_repo = not monorepo
494*55e87721SMatt Gilbride    repo_metadata = metadata["repo"]
495*55e87721SMatt Gilbride    repo_short = repo_metadata["repo_short"]
496*55e87721SMatt Gilbride    # Special libraries that are not GAPIC_AUTO but in the monorepo
497*55e87721SMatt Gilbride    special_libs_in_monorepo = [
498*55e87721SMatt Gilbride        "java-translate",
499*55e87721SMatt Gilbride        "java-dns",
500*55e87721SMatt Gilbride        "java-notification",
501*55e87721SMatt Gilbride        "java-resourcemanager",
502*55e87721SMatt Gilbride    ]
503*55e87721SMatt Gilbride    kwargs["migrated_split_repo"] = split_repo and (
504*55e87721SMatt Gilbride        repo_metadata["library_type"] == "GAPIC_AUTO"
505*55e87721SMatt Gilbride        or (repo_short and repo_short in special_libs_in_monorepo)
506*55e87721SMatt Gilbride    )
507*55e87721SMatt Gilbride    logger.info(
508*55e87721SMatt Gilbride        "monorepo: {}, split_repo: {}, library_type: {},"
509*55e87721SMatt Gilbride        " repo_short: {}, migrated_split_repo: {}".format(
510*55e87721SMatt Gilbride            monorepo,
511*55e87721SMatt Gilbride            split_repo,
512*55e87721SMatt Gilbride            repo_metadata["library_type"],
513*55e87721SMatt Gilbride            repo_short,
514*55e87721SMatt Gilbride            kwargs["migrated_split_repo"],
515*55e87721SMatt Gilbride        )
516*55e87721SMatt Gilbride    )
517*55e87721SMatt Gilbride
518*55e87721SMatt Gilbride    templates = gcp.CommonTemplates(template_path=template_path).java_library(**kwargs)
519*55e87721SMatt Gilbride
520*55e87721SMatt Gilbride    # skip README generation on Kokoro (autosynth)
521*55e87721SMatt Gilbride    if os.environ.get("KOKORO_ROOT") is not None:
522*55e87721SMatt Gilbride        # README.md is now synthesized separately. This prevents synthtool from deleting the
523*55e87721SMatt Gilbride        # README as it's no longer generated here.
524*55e87721SMatt Gilbride        excludes.append("README.md")
525*55e87721SMatt Gilbride
526*55e87721SMatt Gilbride    s.copy([templates], excludes=excludes, merge=_merge_common_templates)
527*55e87721SMatt Gilbride
528*55e87721SMatt Gilbride
529*55e87721SMatt Gilbridedef custom_templates(files: List[str], **kwargs) -> None:
530*55e87721SMatt Gilbride    """Generate custom template files
531*55e87721SMatt Gilbride
532*55e87721SMatt Gilbride    Fetches information about the repository from the .repo-metadata.json file,
533*55e87721SMatt Gilbride    information about the latest artifact versions and copies the files into
534*55e87721SMatt Gilbride    their expected location.
535*55e87721SMatt Gilbride
536*55e87721SMatt Gilbride    Args:
537*55e87721SMatt Gilbride        files (List[str], optional): List of template paths to include
538*55e87721SMatt Gilbride        **kwargs: Additional options for CommonTemplates.render()
539*55e87721SMatt Gilbride    """
540*55e87721SMatt Gilbride    kwargs["metadata"] = _common_template_metadata()
541*55e87721SMatt Gilbride    kwargs["metadata"]["partials"] = partials.load_partials()
542*55e87721SMatt Gilbride    for file in files:
543*55e87721SMatt Gilbride        template = gcp.CommonTemplates().render(file, **kwargs)
544*55e87721SMatt Gilbride        s.copy([template])
545*55e87721SMatt Gilbride
546*55e87721SMatt Gilbride
547*55e87721SMatt Gilbridedef remove_method(filename: str, signature: str):
548*55e87721SMatt Gilbride    """Helper to remove an entire method.
549*55e87721SMatt Gilbride
550*55e87721SMatt Gilbride    Goes line-by-line to detect the start of the block. Determines
551*55e87721SMatt Gilbride    the end of the block by a closing brace at the same indentation
552*55e87721SMatt Gilbride    level. This requires the file to be correctly formatted.
553*55e87721SMatt Gilbride
554*55e87721SMatt Gilbride    Example: consider the following class:
555*55e87721SMatt Gilbride
556*55e87721SMatt Gilbride        class Example {
557*55e87721SMatt Gilbride            public void main(String[] args) {
558*55e87721SMatt Gilbride                System.out.println("Hello World");
559*55e87721SMatt Gilbride            }
560*55e87721SMatt Gilbride
561*55e87721SMatt Gilbride            public String foo() {
562*55e87721SMatt Gilbride                return "bar";
563*55e87721SMatt Gilbride            }
564*55e87721SMatt Gilbride        }
565*55e87721SMatt Gilbride
566*55e87721SMatt Gilbride    To remove the `main` method above, use:
567*55e87721SMatt Gilbride
568*55e87721SMatt Gilbride        remove_method('path/to/file', 'public void main(String[] args)')
569*55e87721SMatt Gilbride
570*55e87721SMatt Gilbride    Args:
571*55e87721SMatt Gilbride        filename (str): Path to source file
572*55e87721SMatt Gilbride        signature (str): Full signature of the method to remove. Example:
573*55e87721SMatt Gilbride            `public void main(String[] args)`.
574*55e87721SMatt Gilbride    """
575*55e87721SMatt Gilbride    lines = []
576*55e87721SMatt Gilbride    leading_regex = None
577*55e87721SMatt Gilbride    with open(filename, "r") as fp:
578*55e87721SMatt Gilbride        line = fp.readline()
579*55e87721SMatt Gilbride        while line:
580*55e87721SMatt Gilbride            # for each line, try to find the matching
581*55e87721SMatt Gilbride            regex = re.compile("(\\s*)" + re.escape(signature) + ".*")
582*55e87721SMatt Gilbride            match = regex.match(line)
583*55e87721SMatt Gilbride            if match:
584*55e87721SMatt Gilbride                leading_regex = re.compile(match.group(1) + "}")
585*55e87721SMatt Gilbride                line = fp.readline()
586*55e87721SMatt Gilbride                continue
587*55e87721SMatt Gilbride
588*55e87721SMatt Gilbride            # not in a ignore block - preserve the line
589*55e87721SMatt Gilbride            if not leading_regex:
590*55e87721SMatt Gilbride                lines.append(line)
591*55e87721SMatt Gilbride                line = fp.readline()
592*55e87721SMatt Gilbride                continue
593*55e87721SMatt Gilbride
594*55e87721SMatt Gilbride            # detect the closing tag based on the leading spaces
595*55e87721SMatt Gilbride            match = leading_regex.match(line)
596*55e87721SMatt Gilbride            if match:
597*55e87721SMatt Gilbride                # block is closed, resume capturing content
598*55e87721SMatt Gilbride                leading_regex = None
599*55e87721SMatt Gilbride
600*55e87721SMatt Gilbride            line = fp.readline()
601*55e87721SMatt Gilbride
602*55e87721SMatt Gilbride    with open(filename, "w") as fp:
603*55e87721SMatt Gilbride        for line in lines:
604*55e87721SMatt Gilbride            # print(line)
605*55e87721SMatt Gilbride            fp.write(line)
606*55e87721SMatt Gilbride
607*55e87721SMatt Gilbride
608*55e87721SMatt Gilbridedef copy_and_rename_method(filename: str, signature: str, before: str, after: str):
609*55e87721SMatt Gilbride    """Helper to make a copy an entire method and rename it.
610*55e87721SMatt Gilbride
611*55e87721SMatt Gilbride    Goes line-by-line to detect the start of the block. Determines
612*55e87721SMatt Gilbride    the end of the block by a closing brace at the same indentation
613*55e87721SMatt Gilbride    level. This requires the file to be correctly formatted.
614*55e87721SMatt Gilbride    The method is copied over and renamed in the method signature.
615*55e87721SMatt Gilbride    The calls to both methods are separate and unaffected.
616*55e87721SMatt Gilbride
617*55e87721SMatt Gilbride    Example: consider the following class:
618*55e87721SMatt Gilbride
619*55e87721SMatt Gilbride        class Example {
620*55e87721SMatt Gilbride            public void main(String[] args) {
621*55e87721SMatt Gilbride                System.out.println("Hello World");
622*55e87721SMatt Gilbride            }
623*55e87721SMatt Gilbride
624*55e87721SMatt Gilbride            public String foo() {
625*55e87721SMatt Gilbride                return "bar";
626*55e87721SMatt Gilbride            }
627*55e87721SMatt Gilbride        }
628*55e87721SMatt Gilbride
629*55e87721SMatt Gilbride    To copy and rename the `main` method above, use:
630*55e87721SMatt Gilbride
631*55e87721SMatt Gilbride    copy_and_rename_method('path/to/file', 'public void main(String[] args)',
632*55e87721SMatt Gilbride        'main', 'foo1')
633*55e87721SMatt Gilbride
634*55e87721SMatt Gilbride    Args:
635*55e87721SMatt Gilbride        filename (str): Path to source file
636*55e87721SMatt Gilbride        signature (str): Full signature of the method to remove. Example:
637*55e87721SMatt Gilbride            `public void main(String[] args)`.
638*55e87721SMatt Gilbride        before (str): name of the method to be copied
639*55e87721SMatt Gilbride        after (str): new name of the copied method
640*55e87721SMatt Gilbride    """
641*55e87721SMatt Gilbride    lines = []
642*55e87721SMatt Gilbride    method = []
643*55e87721SMatt Gilbride    leading_regex = None
644*55e87721SMatt Gilbride    with open(filename, "r") as fp:
645*55e87721SMatt Gilbride        line = fp.readline()
646*55e87721SMatt Gilbride        while line:
647*55e87721SMatt Gilbride            # for each line, try to find the matching
648*55e87721SMatt Gilbride            regex = re.compile("(\\s*)" + re.escape(signature) + ".*")
649*55e87721SMatt Gilbride            match = regex.match(line)
650*55e87721SMatt Gilbride            if match:
651*55e87721SMatt Gilbride                leading_regex = re.compile(match.group(1) + "}")
652*55e87721SMatt Gilbride                lines.append(line)
653*55e87721SMatt Gilbride                method.append(line.replace(before, after))
654*55e87721SMatt Gilbride                line = fp.readline()
655*55e87721SMatt Gilbride                continue
656*55e87721SMatt Gilbride
657*55e87721SMatt Gilbride            lines.append(line)
658*55e87721SMatt Gilbride            # not in a ignore block - preserve the line
659*55e87721SMatt Gilbride            if leading_regex:
660*55e87721SMatt Gilbride                method.append(line)
661*55e87721SMatt Gilbride            else:
662*55e87721SMatt Gilbride                line = fp.readline()
663*55e87721SMatt Gilbride                continue
664*55e87721SMatt Gilbride
665*55e87721SMatt Gilbride            # detect the closing tag based on the leading spaces
666*55e87721SMatt Gilbride            match = leading_regex.match(line)
667*55e87721SMatt Gilbride            if match:
668*55e87721SMatt Gilbride                # block is closed, resume capturing content
669*55e87721SMatt Gilbride                leading_regex = None
670*55e87721SMatt Gilbride                lines.append("\n")
671*55e87721SMatt Gilbride                lines.extend(method)
672*55e87721SMatt Gilbride
673*55e87721SMatt Gilbride            line = fp.readline()
674*55e87721SMatt Gilbride
675*55e87721SMatt Gilbride    with open(filename, "w") as fp:
676*55e87721SMatt Gilbride        for line in lines:
677*55e87721SMatt Gilbride            # print(line)
678*55e87721SMatt Gilbride            fp.write(line)
679*55e87721SMatt Gilbride
680*55e87721SMatt Gilbride
681*55e87721SMatt Gilbridedef add_javadoc(filename: str, signature: str, javadoc_type: str, content: List[str]):
682*55e87721SMatt Gilbride    """Helper to add a javadoc annoatation to a method.
683*55e87721SMatt Gilbride
684*55e87721SMatt Gilbride        Goes line-by-line to detect the start of the block.
685*55e87721SMatt Gilbride        Then finds the existing method comment (if it exists). If the
686*55e87721SMatt Gilbride        comment already exists, it will append the javadoc annotation
687*55e87721SMatt Gilbride        to the javadoc block. Otherwise, it will create a new javadoc
688*55e87721SMatt Gilbride        comment block.
689*55e87721SMatt Gilbride
690*55e87721SMatt Gilbride        Example: consider the following class:
691*55e87721SMatt Gilbride
692*55e87721SMatt Gilbride            class Example {
693*55e87721SMatt Gilbride                public void main(String[] args) {
694*55e87721SMatt Gilbride                    System.out.println("Hello World");
695*55e87721SMatt Gilbride                }
696*55e87721SMatt Gilbride
697*55e87721SMatt Gilbride                public String foo() {
698*55e87721SMatt Gilbride                    return "bar";
699*55e87721SMatt Gilbride                }
700*55e87721SMatt Gilbride            }
701*55e87721SMatt Gilbride
702*55e87721SMatt Gilbride        To add a javadoc annotation the `main` method above, use:
703*55e87721SMatt Gilbride
704*55e87721SMatt Gilbride        add_javadoc('path/to/file', 'public void main(String[] args)',
705*55e87721SMatt Gilbride            'deprecated', 'Please use foo instead.')
706*55e87721SMatt Gilbride
707*55e87721SMatt Gilbride    Args:
708*55e87721SMatt Gilbride        filename (str): Path to source file
709*55e87721SMatt Gilbride        signature (str): Full signature of the method to remove. Example:
710*55e87721SMatt Gilbride            `public void main(String[] args)`.
711*55e87721SMatt Gilbride        javadoc_type (str): The type of javadoc annotation. Example: `deprecated`.
712*55e87721SMatt Gilbride        content (List[str]): The javadoc lines
713*55e87721SMatt Gilbride    """
714*55e87721SMatt Gilbride    lines: List[str] = []
715*55e87721SMatt Gilbride    annotations: List[str] = []
716*55e87721SMatt Gilbride    with open(filename, "r") as fp:
717*55e87721SMatt Gilbride        line = fp.readline()
718*55e87721SMatt Gilbride        while line:
719*55e87721SMatt Gilbride            # for each line, try to find the matching
720*55e87721SMatt Gilbride            regex = re.compile("(\\s*)" + re.escape(signature) + ".*")
721*55e87721SMatt Gilbride            match = regex.match(line)
722*55e87721SMatt Gilbride            if match:
723*55e87721SMatt Gilbride                leading_spaces = len(line) - len(line.lstrip())
724*55e87721SMatt Gilbride                indent = leading_spaces * " "
725*55e87721SMatt Gilbride                last_line = lines.pop()
726*55e87721SMatt Gilbride                while last_line.lstrip() and last_line.lstrip()[0] == "@":
727*55e87721SMatt Gilbride                    annotations.append(last_line)
728*55e87721SMatt Gilbride                    last_line = lines.pop()
729*55e87721SMatt Gilbride                if last_line.strip() == "*/":
730*55e87721SMatt Gilbride                    first = True
731*55e87721SMatt Gilbride                    for content_line in content:
732*55e87721SMatt Gilbride                        if first:
733*55e87721SMatt Gilbride                            lines.append(
734*55e87721SMatt Gilbride                                indent
735*55e87721SMatt Gilbride                                + " * @"
736*55e87721SMatt Gilbride                                + javadoc_type
737*55e87721SMatt Gilbride                                + " "
738*55e87721SMatt Gilbride                                + content_line
739*55e87721SMatt Gilbride                                + "\n"
740*55e87721SMatt Gilbride                            )
741*55e87721SMatt Gilbride                            first = False
742*55e87721SMatt Gilbride                        else:
743*55e87721SMatt Gilbride                            lines.append(indent + " *   " + content_line + "\n")
744*55e87721SMatt Gilbride                    lines.append(last_line)
745*55e87721SMatt Gilbride                else:
746*55e87721SMatt Gilbride                    lines.append(last_line)
747*55e87721SMatt Gilbride                    lines.append(indent + "/**\n")
748*55e87721SMatt Gilbride                    first = True
749*55e87721SMatt Gilbride                    for content_line in content:
750*55e87721SMatt Gilbride                        if first:
751*55e87721SMatt Gilbride                            lines.append(
752*55e87721SMatt Gilbride                                indent
753*55e87721SMatt Gilbride                                + " * @"
754*55e87721SMatt Gilbride                                + javadoc_type
755*55e87721SMatt Gilbride                                + " "
756*55e87721SMatt Gilbride                                + content_line
757*55e87721SMatt Gilbride                                + "\n"
758*55e87721SMatt Gilbride                            )
759*55e87721SMatt Gilbride                            first = False
760*55e87721SMatt Gilbride                        else:
761*55e87721SMatt Gilbride                            lines.append(indent + " *   " + content_line + "\n")
762*55e87721SMatt Gilbride                    lines.append(indent + " */\n")
763*55e87721SMatt Gilbride                lines.extend(annotations[::-1])
764*55e87721SMatt Gilbride            lines.append(line)
765*55e87721SMatt Gilbride            line = fp.readline()
766*55e87721SMatt Gilbride
767*55e87721SMatt Gilbride    with open(filename, "w") as fp:
768*55e87721SMatt Gilbride        for line in lines:
769*55e87721SMatt Gilbride            # print(line)
770*55e87721SMatt Gilbride            fp.write(line)
771*55e87721SMatt Gilbride
772*55e87721SMatt Gilbride
773*55e87721SMatt Gilbridedef annotate_method(filename: str, signature: str, annotation: str):
774*55e87721SMatt Gilbride    """Helper to add an annotation to a method.
775*55e87721SMatt Gilbride
776*55e87721SMatt Gilbride        Goes line-by-line to detect the start of the block.
777*55e87721SMatt Gilbride        Then adds the annotation above the found method signature.
778*55e87721SMatt Gilbride
779*55e87721SMatt Gilbride        Example: consider the following class:
780*55e87721SMatt Gilbride
781*55e87721SMatt Gilbride            class Example {
782*55e87721SMatt Gilbride                public void main(String[] args) {
783*55e87721SMatt Gilbride                    System.out.println("Hello World");
784*55e87721SMatt Gilbride                }
785*55e87721SMatt Gilbride
786*55e87721SMatt Gilbride                public String foo() {
787*55e87721SMatt Gilbride                    return "bar";
788*55e87721SMatt Gilbride                }
789*55e87721SMatt Gilbride            }
790*55e87721SMatt Gilbride
791*55e87721SMatt Gilbride        To add an annotation the `main` method above, use:
792*55e87721SMatt Gilbride
793*55e87721SMatt Gilbride        annotate_method('path/to/file', 'public void main(String[] args)',
794*55e87721SMatt Gilbride            '@Generated()')
795*55e87721SMatt Gilbride
796*55e87721SMatt Gilbride    Args:
797*55e87721SMatt Gilbride        filename (str): Path to source file
798*55e87721SMatt Gilbride        signature (str): Full signature of the method to remove. Example:
799*55e87721SMatt Gilbride            `public void main(String[] args)`.
800*55e87721SMatt Gilbride        annotation (str): Full annotation. Example: `@Deprecated`
801*55e87721SMatt Gilbride    """
802*55e87721SMatt Gilbride    lines: List[str] = []
803*55e87721SMatt Gilbride    with open(filename, "r") as fp:
804*55e87721SMatt Gilbride        line = fp.readline()
805*55e87721SMatt Gilbride        while line:
806*55e87721SMatt Gilbride            # for each line, try to find the matching
807*55e87721SMatt Gilbride            regex = re.compile("(\\s*)" + re.escape(signature) + ".*")
808*55e87721SMatt Gilbride            match = regex.match(line)
809*55e87721SMatt Gilbride            if match:
810*55e87721SMatt Gilbride                leading_spaces = len(line) - len(line.lstrip())
811*55e87721SMatt Gilbride                indent = leading_spaces * " "
812*55e87721SMatt Gilbride                lines.append(indent + annotation + "\n")
813*55e87721SMatt Gilbride            lines.append(line)
814*55e87721SMatt Gilbride            line = fp.readline()
815*55e87721SMatt Gilbride
816*55e87721SMatt Gilbride    with open(filename, "w") as fp:
817*55e87721SMatt Gilbride        for line in lines:
818*55e87721SMatt Gilbride            # print(line)
819*55e87721SMatt Gilbride            fp.write(line)
820*55e87721SMatt Gilbride
821*55e87721SMatt Gilbride
822*55e87721SMatt Gilbridedef deprecate_method(filename: str, signature: str, alternative: str):
823*55e87721SMatt Gilbride    """Helper to deprecate a method.
824*55e87721SMatt Gilbride
825*55e87721SMatt Gilbride        Goes line-by-line to detect the start of the block.
826*55e87721SMatt Gilbride        Then adds the deprecation comment before the method signature.
827*55e87721SMatt Gilbride        The @Deprecation annotation is also added.
828*55e87721SMatt Gilbride
829*55e87721SMatt Gilbride        Example: consider the following class:
830*55e87721SMatt Gilbride
831*55e87721SMatt Gilbride            class Example {
832*55e87721SMatt Gilbride                public void main(String[] args) {
833*55e87721SMatt Gilbride                    System.out.println("Hello World");
834*55e87721SMatt Gilbride                }
835*55e87721SMatt Gilbride
836*55e87721SMatt Gilbride                public String foo() {
837*55e87721SMatt Gilbride                    return "bar";
838*55e87721SMatt Gilbride                }
839*55e87721SMatt Gilbride            }
840*55e87721SMatt Gilbride
841*55e87721SMatt Gilbride        To deprecate the `main` method above, use:
842*55e87721SMatt Gilbride
843*55e87721SMatt Gilbride        deprecate_method('path/to/file', 'public void main(String[] args)',
844*55e87721SMatt Gilbride            DEPRECATION_WARNING.format(new_method="foo"))
845*55e87721SMatt Gilbride
846*55e87721SMatt Gilbride    Args:
847*55e87721SMatt Gilbride        filename (str): Path to source file
848*55e87721SMatt Gilbride        signature (str): Full signature of the method to remove. Example:
849*55e87721SMatt Gilbride            `public void main(String[] args)`.
850*55e87721SMatt Gilbride        alternative: DEPRECATION WARNING: multiline javadoc comment with user
851*55e87721SMatt Gilbride            specified leading open/close comment tags
852*55e87721SMatt Gilbride    """
853*55e87721SMatt Gilbride    add_javadoc(filename, signature, "deprecated", alternative.splitlines())
854*55e87721SMatt Gilbride    annotate_method(filename, signature, "@Deprecated")
855