You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

CommandCatalog.java 4.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. /*
  2. * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
  3. *
  4. * This program and the accompanying materials are made available under the
  5. * terms of the Eclipse Distribution License v. 1.0 which is available at
  6. * https://www.eclipse.org/org/documents/edl-v10.php.
  7. *
  8. * SPDX-License-Identifier: BSD-3-Clause
  9. */
  10. package org.eclipse.jgit.pgm;
  11. import static java.nio.charset.StandardCharsets.UTF_8;
  12. import java.io.BufferedReader;
  13. import java.io.IOException;
  14. import java.io.InputStreamReader;
  15. import java.net.URL;
  16. import java.util.ArrayList;
  17. import java.util.Arrays;
  18. import java.util.Collection;
  19. import java.util.Enumeration;
  20. import java.util.HashMap;
  21. import java.util.Map;
  22. import java.util.Vector;
  23. /**
  24. * List of all commands known by jgit's command line tools.
  25. * <p>
  26. * Commands are implementations of {@link org.eclipse.jgit.pgm.TextBuiltin},
  27. * with an optional {@link org.eclipse.jgit.pgm.Command} class annotation to
  28. * insert additional documentation or override the default command name (which
  29. * is guessed from the class name).
  30. * <p>
  31. * Commands may be registered by adding them to a services file in the same JAR
  32. * (or classes directory) as the command implementation. The service file name
  33. * is <code>META-INF/services/org.eclipse.jgit.pgm.TextBuiltin</code> and it
  34. * contains one concrete implementation class name per line.
  35. * <p>
  36. * Command registration is identical to Java 6's services, however the catalog
  37. * uses a lightweight wrapper to delay creating a command instance as much as
  38. * possible. This avoids initializing the AWT or SWT GUI toolkits even if the
  39. * command's constructor might require them.
  40. */
  41. public class CommandCatalog {
  42. private static final CommandCatalog INSTANCE = new CommandCatalog();
  43. /**
  44. * Locate a single command by its user friendly name.
  45. *
  46. * @param name
  47. * name of the command. Typically in dash-lower-case-form, which
  48. * was derived from the DashLowerCaseForm class name.
  49. * @return the command instance; null if no command exists by that name.
  50. */
  51. public static CommandRef get(String name) {
  52. return INSTANCE.commands.get(name);
  53. }
  54. /**
  55. * Get all commands sorted by their name
  56. *
  57. * @return all known commands, sorted by command name.
  58. */
  59. public static CommandRef[] all() {
  60. return toSortedArray(INSTANCE.commands.values());
  61. }
  62. /**
  63. * Get all common commands sorted by their name
  64. *
  65. * @return all common commands, sorted by command name.
  66. */
  67. public static CommandRef[] common() {
  68. final ArrayList<CommandRef> common = new ArrayList<>();
  69. for (CommandRef c : INSTANCE.commands.values())
  70. if (c.isCommon())
  71. common.add(c);
  72. return toSortedArray(common);
  73. }
  74. private static CommandRef[] toSortedArray(Collection<CommandRef> c) {
  75. final CommandRef[] r = c.toArray(new CommandRef[0]);
  76. Arrays.sort(r, (CommandRef o1, CommandRef o2) -> o1.getName()
  77. .compareTo(o2.getName()));
  78. return r;
  79. }
  80. private final ClassLoader ldr;
  81. private final Map<String, CommandRef> commands;
  82. private CommandCatalog() {
  83. ldr = Thread.currentThread().getContextClassLoader();
  84. commands = new HashMap<>();
  85. final Enumeration<URL> catalogs = catalogs();
  86. while (catalogs.hasMoreElements())
  87. scan(catalogs.nextElement());
  88. }
  89. private Enumeration<URL> catalogs() {
  90. try {
  91. final String pfx = "META-INF/services/"; //$NON-NLS-1$
  92. return ldr.getResources(pfx + TextBuiltin.class.getName());
  93. } catch (IOException err) {
  94. return new Vector<URL>().elements();
  95. }
  96. }
  97. private void scan(URL cUrl) {
  98. try (BufferedReader cIn = new BufferedReader(
  99. new InputStreamReader(cUrl.openStream(), UTF_8))) {
  100. String line;
  101. while ((line = cIn.readLine()) != null) {
  102. if (line.length() > 0 && !line.startsWith("#")) //$NON-NLS-1$
  103. load(line);
  104. }
  105. } catch (IOException e) {
  106. // Ignore errors
  107. }
  108. }
  109. private void load(String cn) {
  110. final Class<? extends TextBuiltin> clazz;
  111. try {
  112. clazz = Class.forName(cn, false, ldr).asSubclass(TextBuiltin.class);
  113. } catch (ClassNotFoundException | ClassCastException notBuiltin) {
  114. // Doesn't exist, even though the service entry is present.
  115. // Isn't really a builtin, even though its listed as such.
  116. return;
  117. }
  118. final CommandRef cr;
  119. final Command a = clazz.getAnnotation(Command.class);
  120. if (a != null)
  121. cr = new CommandRef(clazz, a);
  122. else
  123. cr = new CommandRef(clazz);
  124. commands.put(cr.getName(), cr);
  125. }
  126. }