| import re |
| import itertools |
| from dataclasses import dataclass |
| from collections import defaultdict |
| from typing import Optional |
| |
| import jira2markdown |
| from jira2markdown.elements import MarkupElements |
| from jira2markdown.markup.lists import UnorderedList, OrderedList |
| from jira2markdown.markup.text_effects import BlockQuote, Quote, Monospaced |
| from jira2markdown.markup.text_breaks import Ruler |
| |
| from markup.lists import UnorderedTweakedList, OrderedTweakedList |
| from markup.text_effects import EscapeHtmlTag, TweakedBlockQuote, TweakedQuote, TweakedMonospaced |
| from markup.text_breaks import LongRuler |
| |
| |
| @dataclass |
| class Attachment(object): |
| filename: str |
| created: str |
| content: str |
| mime_type: str |
| |
| |
| def extract_summary(o: dict) -> str: |
| return o.get("fields").get("summary", "") |
| |
| |
| def extract_description(o: dict) -> str: |
| description = o.get("fields").get("description", "") |
| return description if description else "" |
| |
| |
| def extract_status(o: dict) -> str: |
| status = o.get("fields").get("status") |
| return status.get("name", "") if status else "" |
| |
| |
| def extract_issue_type(o: dict) -> str: |
| issuetype = o.get("fields").get("issuetype") |
| return issuetype.get("name", "") if issuetype else "" |
| |
| |
| def extract_environment(o: dict) -> str: |
| environment = o.get("fields").get("environment", "") |
| return environment if environment else "" |
| |
| |
| def extract_resolution(o: dict) -> Optional[str]: |
| resolution = o.get("fields").get("resolution") |
| if resolution: |
| return resolution.get("name") |
| return None |
| |
| |
| def extract_priority(o: dict) -> Optional[str]: |
| priority = o.get("fields").get("priority") |
| if priority: |
| return priority.get("name") |
| return None |
| |
| |
| def extract_vote_count(o: dict) -> Optional[int]: |
| votes = o.get("fields").get("votes") |
| if votes: |
| vote_count = votes.get("votes") |
| if vote_count > 0: |
| return vote_count |
| return None |
| |
| |
| def extract_reporter(o: dict) -> tuple[str, str]: |
| reporter = o.get("fields").get("reporter") |
| name = reporter.get("name", "") if reporter else "" |
| disp_name = reporter.get("displayName", "") if reporter else "" |
| return (name, disp_name) |
| |
| |
| def extract_assignee(o: dict) -> tuple[str, str]: |
| assignee = o.get("fields").get("assignee") |
| name = assignee.get("name", "") if assignee else "" |
| disp_name = assignee.get("displayName", "") if assignee else "" |
| return (name, disp_name) |
| |
| |
| def extract_parent_key(o: dict) -> Optional[str]: |
| parent = o["fields"].get("parent") |
| if parent: |
| key = parent["key"] |
| if key: |
| return key |
| return None |
| |
| |
| def extract_created(o: dict) -> str: |
| return o.get("fields").get("created", "") |
| |
| |
| def extract_updated(o: dict) -> str: |
| return o.get("fields").get("updated", "") |
| |
| |
| def extract_resolutiondate(o: dict) -> str: |
| return o.get("fields").get("resolutiondate", "") |
| |
| |
| def extract_fixversions(o: dict) -> list[str]: |
| return [x.get("name", "") for x in o.get("fields").get("fixVersions", [])] |
| |
| |
| def extract_versions(o: dict) -> list[str]: |
| return [x.get("name", "") for x in o.get("fields").get("versions", [])] |
| |
| |
| def extract_components(o: dict) -> list[str]: |
| return [x.get("name", "") for x in o.get("fields").get("components", [])] |
| |
| |
| def extract_labels(o: dict) -> list[str]: |
| return o.get("fields").get("labels", []) |
| |
| |
| def extract_attachments(o: dict) -> list[tuple[str, int]]: |
| attachments = o.get("fields").get("attachment") |
| if not attachments: |
| return [] |
| files = {} |
| counts = defaultdict(int) |
| for a in attachments: |
| filename = a.get("filename") |
| created = a.get("created") |
| content = a.get("content") |
| mime_type = a.get("mimeType") |
| if not (filename and created and content and mime_type): |
| continue |
| if filename not in files or created > files[filename].created: |
| files[filename] = Attachment(filename=filename, created=created, content=content, mime_type=mime_type) |
| counts[filename] += 1 |
| result = [] |
| for name in files.keys(): |
| result.append((name, counts[name])) |
| return result |
| |
| |
| def extract_issue_links(o: dict) -> list[str]: |
| issue_links = o.get("fields").get("issuelinks", []) |
| if not issue_links: |
| return [] |
| |
| res = [] |
| for link in issue_links: |
| key = link.get("outwardIssue", {}).get("key") |
| if key: |
| res.append(key) |
| key = link.get("inwardIssue", {}).get("key") |
| if key: |
| res.append(key) |
| return res |
| |
| |
| def extract_subtasks(o: dict) -> list[str]: |
| return [x.get("key", "") for x in o.get("fields").get("subtasks", [])] |
| |
| |
| def extract_comments(o: dict) -> list[tuple[str, str, str, str, str, str]]: |
| comments = o.get("fields").get("comment", {}).get("comments", []) |
| if not comments: |
| return [] |
| res = [] |
| for c in comments: |
| author = c.get("author") |
| name = author.get("name", "") if author else "" |
| disp_name = author.get("displayName", "") if author else "" |
| body = c.get("body", "") |
| created = c.get("created", "") |
| updated = c.get("updated", "") |
| comment_id = c.get("id", "") |
| res.append((name, disp_name, body, created, updated, comment_id)) |
| return res |
| |
| |
| def extract_pull_requests(o: dict) -> list[str]: |
| worklogs = o.get("fields").get("worklog", {}).get("worklogs", []) |
| if not worklogs: |
| return [] |
| res = [] |
| for wl in worklogs: |
| if wl.get("author").get("name", "") != "githubbot": |
| continue |
| comment: str = wl.get("comment", "") |
| if not comment: |
| continue |
| if "opened a new pull request" not in comment and not "opened a pull request" in comment: |
| continue |
| comment = comment.replace('\n', ' ') |
| # detect pull request url |
| matches = re.match(r".*(https://github\.com/apache/lucene/pull/\d+)", comment) |
| if matches: |
| res.append(matches.group(1)) |
| # detect pull request url in old lucene-solr repo |
| matches = re.match(r".*(https://github\.com/apache/lucene-solr/pull/\d+)", comment) |
| if matches: |
| res.append(matches.group(1)) |
| return res |
| |
| |
| REGEX_EMBEDDED_IMAGE = r"!([^!\n]+)!" |
| |
| # space character + Jira emoji + space character |
| JIRA_EMOJI_TO_UNICODE = { |
| "(?<=\s)\(y\)((?=$)|(?=\s))": "\U0001F44D", |
| "(?<=\s)\(n\)((?=$)|(?=\s))": "\U0001F44E", |
| "(?<=\s)\(i\)((?=$)|(?=\s))": "\U0001F6C8", |
| "(?<=\s)\(/\)((?=$)|(?=\s))": "\u2714", |
| "(?<=\s)\(x\)((?=$)|(?=\s))": "\u274C", |
| "(?<=\s)\(\!\)((?=$)|(?=\s))": "\u26A0", |
| "(?<=\s)\(\+\)((?=$)|(?=\s))": "\u002B", |
| "(?<=\s)\(\-\)((?=$)|(?=\s))": "\u2212", |
| "(?<=\s)\(\?\)((?=$)|(?=\s))": "\u003F", |
| "(?<=\s)\(on\)((?=$)|(?=\s))": "\U0001F4A1", |
| "(?<=\s)\(off\)((?=$)|(?=\s))": "\U0001F4A1", |
| "(?<=\s)\(\*\)((?=$)|(?=\s))": "\u2B50", |
| "(?<=\s)\(\*r\)((?=$)|(?=\s))": "\u2B50", |
| "(?<=\s)\(\*g\)((?=$)|(?=\s))": "\u2B50", |
| "(?<=\s)\(\*b\)((?=$)|(?=\s))": "\u2B50", |
| "(?<=\s)\(flag\)((?=$)|(?=\s))": "\U0001F3F4", |
| "(?<=\s)\(flagoff\)((?=$)|(?=\s))": "\U0001F3F3" |
| } |
| |
| REGEX_CRLF = re.compile(r"\r\n") |
| REGEX_JIRA_KEY = re.compile(r"[^/]LUCENE-\d+") |
| REGEX_MENTION_ATMARK = re.compile(r"(^@[\w\s\.@_-]+?)|((?<=[\s\({\[\"',.])@[\w\s\.@_-]+?)($|(?=[\s\)}\]\"'\?!,,;:\..]))") # this regex may capture only "@" + "<username>" mentions |
| REGEX_MENION_TILDE = re.compile(r"(^\[~[\w\s\.@_-]+?\])|((?<=[\s\({\[\"',.])\[~[\w\s\.@_-]+?\])($|(?=[\s\)|\]\"'\?!,,;:\..]))") # this regex may capture only "[~" + "<username>" + "]" mentions |
| REGEX_LINK = re.compile(r"\[([^\]]+)\]\(([^\)]+)\)") |
| REGEX_GITHUB_ISSUE_LINK = re.compile(r"(\s)(#\d+)(\s)") |
| |
| |
| def extract_embedded_image_files(text: str, image_files: list[str]) -> set[str]: |
| """Extract embedded image files in the given text. |
| https://jira.atlassian.com/secure/WikiRendererHelpAction.jspa?section=images |
| """ |
| # capture candidates for embedded images |
| candidates = re.findall(REGEX_EMBEDDED_IMAGE, text) |
| embedded_image_files = set([]) |
| for x in candidates: |
| if x in image_files: |
| # !xyz.png! |
| embedded_image_files.add(x) |
| elif any(map(lambda s: x.startswith(s + "|"), image_files)): |
| # !xyz.png|styles! |
| embedded_image_files.add(x.split("|", 1)[0]) |
| return embedded_image_files |
| |
| |
| def convert_text(text: str, att_replace_map: dict[str, str] = {}, account_map: dict[str, str] = {}, jira_users: dict[str, str] = {}) -> str: |
| """Convert Jira markup to Markdown |
| """ |
| def repl_att(m: re.Match): |
| res = m.group(0) |
| for src, repl in att_replace_map.items(): |
| if m.group(2) == src: |
| res = f"[{m.group(1)}]({repl})" |
| return res |
| |
| def escape_gh_issue_link(m: re.Match): |
| # escape #NN by backticks to prevent creating an unintentional issue link |
| res = f"{m.group(1)}`{m.group(2)}`{m.group(3)}" |
| return res |
| |
| text = re.sub(REGEX_CRLF, "\n", text) # jira2markup does not support carriage return (?) |
| |
| # convert Jira special emojis into corresponding or similar Unicode characters |
| for emoji, unicode in JIRA_EMOJI_TO_UNICODE.items(): |
| text = re.sub(emoji, unicode, text) |
| |
| # convert @ mentions |
| mentions = re.findall(REGEX_MENTION_ATMARK, text) |
| if mentions: |
| mentions = set(filter(lambda x: x != '', itertools.chain.from_iterable(mentions))) |
| for m in mentions: |
| jira_id = m[1:] |
| disp_name = jira_users.get(jira_id) |
| gh_m = account_map.get(jira_id) |
| # replace Jira name with GitHub account or Jira display name if it is available, othewise show Jira name with `` to avoid unintentional mentions |
| mention = lambda: f"@{gh_m}" if gh_m else disp_name if disp_name else f"`@{jira_id}`" |
| text = text.replace(m, mention()) |
| |
| # convert ~ mentions |
| mentions = re.findall(REGEX_MENION_TILDE, text) |
| if mentions: |
| mentions = set(filter(lambda x: x != '', itertools.chain.from_iterable(mentions))) |
| for m in mentions: |
| jira_id = m[2:-1] |
| disp_name = jira_users.get(jira_id) |
| gh_m = account_map.get(jira_id) |
| # replace Jira name with GitHub account or Jira display name if it is available, othewise show Jira name with `` |
| mention = lambda: f"@{gh_m}" if gh_m else disp_name if disp_name else f"`@{jira_id}`" |
| text = text.replace(m, mention()) |
| |
| # escape tilde to avoid unintentional strike-throughs in Markdown |
| text = text.replace("~", "\~") |
| |
| # convert Jira markup into Markdown with customization |
| elements = MarkupElements() |
| elements.replace(UnorderedList, UnorderedTweakedList) |
| elements.replace(OrderedList, OrderedTweakedList) |
| elements.replace(BlockQuote, TweakedBlockQuote) |
| elements.replace(Quote, TweakedQuote) |
| elements.replace(Monospaced, TweakedMonospaced) |
| elements.insert_after(Ruler, LongRuler) |
| elements.append(EscapeHtmlTag) |
| text = jira2markdown.convert(text, elements=elements) |
| |
| # convert links to attachments |
| text = re.sub(REGEX_LINK, repl_att, text) |
| |
| # escape github style cross-issue link (#NN) |
| text = re.sub(REGEX_GITHUB_ISSUE_LINK, escape_gh_issue_link, text) |
| |
| return text |
| |
| |
| def embed_gh_issue_link(text: str, issue_id_map: dict[str, int], gh_number_self: int) -> str: |
| """Embed GitHub issue number |
| """ |
| def repl_simple(m: re.Match): |
| res = m.group(0) |
| gh_number = issue_id_map.get(m.group(2)) |
| if gh_number and gh_number != gh_number_self: |
| res = f"{m.group(1)}#{gh_number}{m.group(3)}" |
| return res |
| |
| def repl_paren(m: re.Match): |
| res = m.group(0) |
| gh_number = issue_id_map.get(m.group(2)) |
| if gh_number and gh_number != gh_number_self: |
| res = f"{m.group(1)}#{gh_number}{m.group(3)}" |
| return res |
| |
| def repl_bracket(m: re.Match): |
| res = m.group(0) |
| gh_number = issue_id_map.get(m.group(2)) |
| if gh_number and gh_number != gh_number_self: |
| res = f"#{gh_number}" |
| return res |
| |
| def repl_md_link(m: re.Match): |
| res = m.group(0) |
| gh_number = issue_id_map.get(m.group(1)) |
| if gh_number and gh_number != gh_number_self: |
| res = f"#{gh_number}" |
| # print(res) |
| return res |
| |
| text = re.sub(r"(\s)(LUCENE-\d+)([\s,;:\?\!\.])", repl_simple, text) |
| text = re.sub(r"(^)(LUCENE-\d+)([\s,;:\?\!\.])", repl_simple, text) |
| text = re.sub(r"(\s)https?://issues\.apache\.org/.+/(LUCENE-\d+)([\s,;:\?\!\.])", repl_simple, text) |
| text = re.sub(r"(^)https?://issues\.apache\.org/.+/(LUCENE-\d+)([\s,;:\?\!\.])", repl_simple, text) |
| text = re.sub(r"(\()(LUCENE-\d+)(\))", repl_paren, text) |
| text = re.sub(r"(\[)(LUCENE-\d+)(\])(?!\()", repl_bracket, text) |
| text = re.sub(r"\[(LUCENE-\d+)\]\(https?[^\)]+LUCENE-\d+\)", repl_md_link, text) |
| |
| return text |
| |
| |
| ASF_JIRA_BASE = "https://issues.apache.org/jira/browse/" |
| |
| def create_issue_links_outside_projects(text: str) -> str: |
| """Create links to outside ASF projects. |
| """ |
| |
| def repl_simple(m: re.Match): |
| prj = m.group(2).split("-")[0] |
| if prj not in ALL_JIRA_PROJECTS: |
| return m.group(0) |
| jira_link = ASF_JIRA_BASE + m.group(2) |
| return f"{m.group(1)}[{m.group(2)}]({jira_link}){m.group(3)}" |
| |
| def repl_paren(m: re.Match): |
| prj = m.group(2).split("-")[0] |
| if prj not in ALL_JIRA_PROJECTS: |
| return m.group(0) |
| jira_link = ASF_JIRA_BASE + m.group(2) |
| return f"{m.group(1)}[{m.group(2)}]({jira_link}){m.group(3)}" |
| |
| def repl_bracket(m: re.Match): |
| prj = m.group(2).split("-")[0] |
| if prj not in ALL_JIRA_PROJECTS: |
| return m.group(0) |
| jira_link = ASF_JIRA_BASE + m.group(2) |
| return jira_link |
| |
| text = re.sub(r"(\s)([A-Z0-9]{2,20}-\d+)([\s,;:\?\!\.])", repl_simple, text) |
| text = re.sub(r"(^)([A-Z0-9]{2,20}-\d+)([\s,;:\?\!\.])", repl_simple, text) |
| text = re.sub(r"(\()([A-Z0-9]{2,20}-\d+)(\))", repl_paren, text) |
| text = re.sub(r"(\[)([A-Z0-9]{2,20}-\d+)(\])(?!\()", repl_bracket, text) |
| |
| return text |
| |
| |
| ALL_JIRA_PROJECTS = set([ |
| "AAR", |
| "ABDERA", |
| "ACCUMULO", |
| "ACE", |
| "ACL", |
| "AMQ", |
| "AMQNET", |
| "APLO", |
| "ARTEMIS", |
| "AMQCPP", |
| "AMQCLI", |
| "OPENWIRE", |
| "BLAZE", |
| "ADDR", |
| "AGILA", |
| "AIRAVATA", |
| "ALOIS", |
| "ARMI", |
| "AMATERASU", |
| "AMBARI", |
| "AMBER", |
| "ANAKIA", |
| "AGE2", |
| "AGEOLD", |
| "ANY23", |
| "APEXCORE", |
| "APEXMALHAR", |
| "ARROW", |
| "ASTERIXDB", |
| "AVRO", |
| "AWF", |
| "BLUEMARLIN", |
| "BLUR", |
| "CHAINSAW", |
| "CMDA", |
| "COMMONSSITE", |
| "COMMONSRDF", |
| "TESTING", |
| "CONCERTED", |
| "CORAL", |
| "CB", |
| "CRAIL", |
| "CURATOR", |
| "DATALAB", |
| "DATASKETCHES", |
| "DIRECTMEMORY", |
| "DORIS", |
| "DRILL", |
| "DUBBO", |
| "ECHARTS", |
| "EVENTMESH", |
| "FINERACT", |
| "FLEX", |
| "FREEMARKER", |
| "GEARPUMP", |
| "GOBBLIN", |
| "GORA", |
| "HAWQ", |
| "HELIX", |
| "HOP", |
| "HORN", |
| "HUDI", |
| "INLONG", |
| "IOTDB", |
| "JENA", |
| "KNOX", |
| "LENS", |
| "LIMINAL", |
| "LINKIS", |
| "CLOWNFISH", |
| "MADLIB", |
| "MARVIN", |
| "MASFRES", |
| "METAMODEL", |
| "MXNET", |
| "NEMO", |
| "NETBEANSINFRA", |
| "NIFI", |
| "MINIFI", |
| "MINIFICPP", |
| "NUTTX", |
| "OLTU", |
| "ONAMI", |
| "CLIMATE", |
| "OPENAZ", |
| "HDDS", |
| "PEGASUS", |
| "PETRI", |
| "PINOT", |
| "PLC4X", |
| "QPIDIT", |
| "QUICKSTEP", |
| "RAT", |
| "RIPPLE", |
| "ROCKETMQ", |
| "ROL", |
| "S4", |
| "SDAP", |
| "SCIMPLE", |
| "SEDONA", |
| "SCB", |
| "STORM", |
| "SUBMARINE", |
| "TAVERNA", |
| "TENTACLES", |
| "TEZ", |
| "MTOMCAT", |
| "TRAFODION", |
| "TRAINING", |
| "TWILL", |
| "UNOMI", |
| "WAYANG", |
| "WHIRR", |
| "WHISKER", |
| "YUNIKORN", |
| "ZIPKIN", |
| "APISIX", |
| "MRM", |
| "ARIA", |
| "ARIES", |
| "ASYNCWEB", |
| "ATLAS", |
| "ATTIC", |
| "AURORA", |
| "AVALON", |
| "AVNSHARP", |
| "RUNTIME", |
| "STUDIO", |
| "CENTRAL", |
| "PLANET", |
| "TOOLS", |
| "PNIX", |
| "AXIOM", |
| "AXIS", |
| "AXISCPP", |
| "WSIF", |
| "AXIS2", |
| "TRANSPORTS", |
| "AXIS2C", |
| "BAHIR", |
| "BATCHEE", |
| "BATIK", |
| "BEAM", |
| "BEEHIVE", |
| "BIGTOP", |
| "BLUESKY", |
| "BOOKKEEPER", |
| "TM", |
| "BROOKLYN", |
| "BUILDR", |
| "BVAL", |
| "STDCXX", |
| "CACTUS", |
| "CALCITE", |
| "CAMEL", |
| "CARBONDATA", |
| "CASSANDRA", |
| "CAY", |
| "CELIX", |
| "CS", |
| "CMIS", |
| "CHUKWA", |
| "CLEREZZA", |
| "CLK", |
| "CLKE", |
| "CLOUDSTACK", |
| "COCOON", |
| "COCOON3", |
| "ATTRIBUTES", |
| "BCEL", |
| "BEANUTILS", |
| "BETWIXT", |
| "BSF", |
| "CHAIN", |
| "CLI", |
| "CODEC", |
| "COLLECTIONS", |
| "COMPRESS", |
| "CONFIGURATION", |
| "CRYPTO", |
| "CSV", |
| "DAEMON", |
| "DBCP", |
| "DBUTILS", |
| "DIGESTER", |
| "DISCOVERY", |
| "DORMANT", |
| "EL", |
| "EMAIL", |
| "EXEC", |
| "FEEDPARSER", |
| "FILEUPLOAD", |
| "FUNCTOR", |
| "GEOMETRY", |
| "IMAGING", |
| "IO", |
| "JCI", |
| "JCS", |
| "JELLY", |
| "JEXL", |
| "JXPATH", |
| "LANG", |
| "LAUNCHER", |
| "LOGGING", |
| "MATH", |
| "MODELER", |
| "NET", |
| "NUMBERS", |
| "OGNL", |
| "POOL", |
| "PRIMITIVES", |
| "PROXY", |
| "RESOURCES", |
| "RNG", |
| "SANDBOX", |
| "SANSELAN", |
| "SCXML", |
| "STATISTICS", |
| "TEXT", |
| "TRANSACTION", |
| "VALIDATOR", |
| "VFS", |
| "WEAVER", |
| "COMDEV", |
| "CONTINUUM", |
| "COR", |
| "COTTON", |
| "COUCHDB", |
| "CRUNCH", |
| "CTAKES", |
| "CUSTOS", |
| "CXF", |
| "DOSGI", |
| "CXFXJC", |
| "FEDIZ", |
| "DAFFODIL", |
| "DATAFU", |
| "DAYTRADER", |
| "DDLUTILS", |
| "DTACLOUD", |
| "DELTASPIKE", |
| "DEPOT", |
| "DERBY", |
| "DMAP", |
| "DIR", |
| "DIRSERVER", |
| "DIRAPI", |
| "DIRGROOVY", |
| "DIRKRB", |
| "DIRNAMING", |
| "DIRSHARED", |
| "DIRSTUDIO", |
| "DL", |
| "DI", |
| "DBF", |
| "DROIDS", |
| "DVSL", |
| "EAGLE", |
| "EASYANT", |
| "ECS", |
| "EDGENT", |
| "EMPIREDB", |
| "ESME", |
| "ESCIMO", |
| "ETCH", |
| "EWS", |
| "EXLBR", |
| "FORTRESS", |
| "FALCON", |
| "FELIX", |
| "FINCN", |
| "FLAGON", |
| "FLINK", |
| "FLUME", |
| "FOP", |
| "FOR", |
| "FC", |
| "FTPSERVER", |
| "GBUILD", |
| "GEODE", |
| "GERONIMO", |
| "GERONIMODEVTOOLS", |
| "GIRAPH", |
| "GOSSIP", |
| "GRFT", |
| "GRIFFIN", |
| "GROOVY", |
| "GSHELL", |
| "GUACAMOLE", |
| "GUMP", |
| "HADOOP", |
| "HDT", |
| "HDFS", |
| "MAPREDUCE", |
| "YARN", |
| "HAMA", |
| "HARMONY", |
| "HBASE", |
| "HCATALOG", |
| "HERALDRY", |
| "HISE", |
| "HIVE", |
| "HIVEMALL", |
| "HIVEMIND", |
| "HTRACE", |
| "HTTPASYNC", |
| "HTTPCLIENT", |
| "HTTPCORE", |
| "IBATISNET", |
| "IBATIS", |
| "RBATIS", |
| "IGNITE", |
| "IMPALA", |
| "IMPERIUS", |
| "INCUBATOR", |
| "INFRATEST3", |
| "INFRATEST987", |
| "INFRACLOUD1", |
| "INFRA", |
| "TEST6", |
| "IOTA", |
| "ISIS", |
| "IVY", |
| "IVYDE", |
| "JCR", |
| "JCRVLT", |
| "JCRBENCH", |
| "JCRCL", |
| "JCRSERVLET", |
| "JCRTCK", |
| "JCRRMI", |
| "OAK", |
| "OCM", |
| "JCRSITE", |
| "HUPA", |
| "IMAP", |
| "JDKIM", |
| "JSIEVE", |
| "JSPF", |
| "MAILBOX", |
| "MAILET", |
| "MIME4J", |
| "MPT", |
| "POSTAGE", |
| "PROTOCOLS", |
| "JAMES", |
| "JAXME", |
| "JCLOUDS", |
| "JDO", |
| "JS1", |
| "JS2", |
| "JOHNZON", |
| "JOSHUA", |
| "JSEC", |
| "JSPWIKI", |
| "JUDDI", |
| "JUNEAU", |
| "KAFKA", |
| "KALUMET", |
| "KAND", |
| "KARAF", |
| "KATO", |
| "KI", |
| "KITTY", |
| "KUDU", |
| "KYLIN", |
| "LABS", |
| "HTTPDRAFT", |
| "LEGAL", |
| "LIBCLOUD", |
| "LIVY", |
| "LOGCXX", |
| "LOG4J2", |
| "LOG4NET", |
| "LOG4PHP", |
| "LOKAHI", |
| "LUCENENET", |
| "LCN4C", |
| "LUCY", |
| "MAHOUT", |
| "CONNECTORS", |
| "MARMOTTA", |
| "MNG", |
| "MACR", |
| "MANT", |
| "MANTTASKS", |
| "MANTRUN", |
| "ARCHETYPE", |
| "MARCHETYPES", |
| "MARTIFACT", |
| "MASSEMBLY", |
| "MBUILDCACHE", |
| "MCHANGELOG", |
| "MCHANGES", |
| "MCHECKSTYLE", |
| "MCLEAN", |
| "MCOMPILER", |
| "MDEP", |
| "MDEPLOY", |
| "MDOAP", |
| "MDOCCK", |
| "DOXIA", |
| "DOXIASITETOOLS", |
| "DOXIATOOLS", |
| "MEAR", |
| "MECLIPSE", |
| "MEJB", |
| "MENFORCER", |
| "MGPG", |
| "MPH", |
| "MINDEXER", |
| "MINSTALL", |
| "MINVOKER", |
| "MJAR", |
| "MJARSIGNER", |
| "MJAVADOC", |
| "MJDEPRSCAN", |
| "MJDEPS", |
| "MJLINK", |
| "MJMOD", |
| "JXR", |
| "MLINKCHECK", |
| "MPATCH", |
| "MPDF", |
| "MPLUGINTESTING", |
| "MPLUGIN", |
| "MPMD", |
| "MPOM", |
| "MPIR", |
| "MNGSITE", |
| "MRAR", |
| "MRELEASE", |
| "MRRESOURCES", |
| "MREPOSITORY", |
| "MRESOLVER", |
| "MRESOURCES", |
| "SCM", |
| "MSCMPUB", |
| "MSCRIPTING", |
| "MSHADE", |
| "MSHARED", |
| "MSITE", |
| "MSKINS", |
| "MSOURCES", |
| "MSTAGE", |
| "SUREFIRE", |
| "MTOOLCHAINS", |
| "MVERIFIER", |
| "WAGON", |
| "MWAR", |
| "MWRAPPER", |
| "MAVIBOT", |
| "MEECROWAVE", |
| "MESOS", |
| "METRON", |
| "MILAGRO", |
| "DIRMINA", |
| "SSHD", |
| "MIRAE", |
| "MNEMONIC", |
| "MODPYTHON", |
| "MRQL", |
| "MRUNIT", |
| "MUSE", |
| "MXNETTEST", |
| "ADFFACES", |
| "EXTCDI", |
| "MFCOMMONS", |
| "MYFACES", |
| "EXTSCRIPT", |
| "EXTVAL", |
| "MFHTML5", |
| "ORCHESTRA", |
| "PORTLETBRIDGE", |
| "MYFACESTEST", |
| "TOBAGO", |
| "TOMAHAWK", |
| "TRINIDAD", |
| "MYNEWT", |
| "MYNEWTDOC", |
| "MYRIAD", |
| "NEETHI", |
| "NETBEANS", |
| "NIFIREG", |
| "NIFILIBS", |
| "NLPCRAFT", |
| "NPANDAY", |
| "NUTCH", |
| "NUVEM", |
| "ODE", |
| "JACOB", |
| "OWC", |
| "ODFTOOLKIT", |
| "OFBIZ", |
| "OJB", |
| "OLINGO", |
| "OLIO", |
| "OODT", |
| "OOZIE", |
| "ORP", |
| "OPENEJB", |
| "OEP", |
| "OPENJPA", |
| "OPENMEETINGS", |
| "OPENNLP", |
| "OPENOFFICE", |
| "OWB", |
| "ORC", |
| "PARQUET", |
| "PDFBOX", |
| "PHOENIX", |
| "OMID", |
| "TEPHRA", |
| "PHOTARK", |
| "PIG", |
| "PIRK", |
| "PIVOT", |
| "PLUTO", |
| "PODLINGNAMESEARCH", |
| "POLYGENE", |
| "PORTALS", |
| "APA", |
| "PB", |
| "PIO", |
| "PROVISIONR", |
| "PRC", |
| "HERMES", |
| "PULSAR", |
| "PYLUCENE", |
| "QPID", |
| "DISPATCH", |
| "QPIDJMS", |
| "PROTON", |
| "RAMPART", |
| "RAMPARTC", |
| "RANGER", |
| "RATIS", |
| "RAVE", |
| "REEF", |
| "RIVER", |
| "RYA", |
| "S2GRAPH", |
| "SAMOA", |
| "SAMZA", |
| "SAND", |
| "SANDESHA2", |
| "SANDESHA2C", |
| "SANTUARIO", |
| "SAVAN", |
| "SCOUT", |
| "SENTRY", |
| "SERF", |
| "SM", |
| "SMX4", |
| "SMXCOMP", |
| "SMX4KNL", |
| "SMX4NMR", |
| "SHALE", |
| "SHINDIG", |
| "SHIRO", |
| "CASSANDRASC", |
| "SINGA", |
| "SIRONA", |
| "SLIDER", |
| "SLING", |
| "SOAP", |
| "SOLR", |
| "SPARK", |
| "SIS", |
| "SPOT", |
| "SQOOP", |
| "STANBOL", |
| "STEVE", |
| "STOMP", |
| "STONEHENGE", |
| "STRATOS", |
| "STREAMPIPES", |
| "STREAMS", |
| "STR", |
| "WW", |
| "SB", |
| "SITE", |
| "SVN", |
| "SUPERSET", |
| "SYNAPSE", |
| "SYNCOPE", |
| "SYSTEMDS", |
| "TAJO", |
| "TAMAYA", |
| "TAPESTRY", |
| "TAP5", |
| "TASHI", |
| "TST", |
| "TEXEN", |
| "MMETRIC", |
| "THRIFT", |
| "TIKA", |
| "TILES", |
| "AUTOTAG", |
| "TEVAL", |
| "TREQ", |
| "TILESSB", |
| "TILESSHARED", |
| "TILESSHOW", |
| "TINKERPOP", |
| "TOMEE", |
| "TATPI", |
| "TOREE", |
| "TORQUE", |
| "TORQUEOLD", |
| "TC", |
| "TS", |
| "DIRTSEC", |
| "TRIPLES", |
| "TSIK", |
| "TRB", |
| "TUSCANY", |
| "TUWENI", |
| "UIMA", |
| "USERGRID", |
| "VCL", |
| "VELOCITY", |
| "VELOCITYSB", |
| "VELTOOLS", |
| "VXQUERY", |
| "VYSPER", |
| "WADI", |
| "WAVE", |
| "WEEX", |
| "WHIMSY", |
| "WICKET", |
| "WINK", |
| "WODEN", |
| "WOOKIE", |
| "WSCOMMONS", |
| "APOLLO", |
| "WSRP4J", |
| "WSS", |
| "ASFSITE", |
| "XALANC", |
| "XALANJ", |
| "XAP", |
| "XBEAN", |
| "XERCESC", |
| "XERCESP", |
| "XERCESJ", |
| "XMLCOMMONS", |
| "XMLRPC", |
| "XMLBEANS", |
| "XGC", |
| "XMLSCHEMA", |
| "XW", |
| "YETUS", |
| "YOKO", |
| "ZEPPELIN", |
| "ZETACOMP", |
| "ZOOKEEPER" |
| ]) |