1 /* 2 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> 3 * and other copyright owners as documented in the project's IP log. 4 * 5 * This program and the accompanying materials are made available 6 * under the terms of the Eclipse Distribution License v1.0 which 7 * accompanies this distribution, is reproduced below, and is 8 * available at http://www.eclipse.org/org/documents/edl-v10.php 9 * 10 * All rights reserved. 11 * 12 * Redistribution and use in source and binary forms, with or 13 * without modification, are permitted provided that the following 14 * conditions are met: 15 * 16 * - Redistributions of source code must retain the above copyright 17 * notice, this list of conditions and the following disclaimer. 18 * 19 * - Redistributions in binary form must reproduce the above 20 * copyright notice, this list of conditions and the following 21 * disclaimer in the documentation and/or other materials provided 22 * with the distribution. 23 * 24 * - Neither the name of the Eclipse Foundation, Inc. nor the 25 * names of its contributors may be used to endorse or promote 26 * products derived from this software without specific prior 27 * written permission. 28 * 29 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 30 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 31 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 32 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 33 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 34 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 35 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 36 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 37 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 38 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 39 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 40 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 41 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 42 */ 43 44 package org.eclipse.jgit.pgm; 45 46 import java.io.BufferedReader; 47 import java.io.IOException; 48 import java.io.InputStream; 49 import java.io.InputStreamReader; 50 import java.net.URL; 51 import java.util.ArrayList; 52 import java.util.Arrays; 53 import java.util.Collection; 54 import java.util.Comparator; 55 import java.util.Enumeration; 56 import java.util.HashMap; 57 import java.util.Map; 58 import java.util.Vector; 59 60 /** 61 * List of all commands known by jgit's command line tools. 62 * <p> 63 * Commands are implementations of {@link TextBuiltin}, with an optional 64 * {@link Command} class annotation to insert additional documentation or 65 * override the default command name (which is guessed from the class name). 66 * <p> 67 * Commands may be registered by adding them to a services file in the same JAR 68 * (or classes directory) as the command implementation. The service file name 69 * is <code>META-INF/services/org.eclipse.jgit.pgm.TextBuiltin</code> and it 70 * contains one concrete implementation class name per line. 71 * <p> 72 * Command registration is identical to Java 6's services, however the catalog 73 * uses a lightweight wrapper to delay creating a command instance as much as 74 * possible. This avoids initializing the AWT or SWT GUI toolkits even if the 75 * command's constructor might require them. 76 */ 77 public class CommandCatalog { 78 private static final CommandCatalog INSTANCE = new CommandCatalog(); 79 80 /** 81 * Locate a single command by its user friendly name. 82 * 83 * @param name 84 * name of the command. Typically in dash-lower-case-form, which 85 * was derived from the DashLowerCaseForm class name. 86 * @return the command instance; null if no command exists by that name. 87 */ 88 public static CommandRef get(final String name) { 89 return INSTANCE.commands.get(name); 90 } 91 92 /** 93 * @return all known commands, sorted by command name. 94 */ 95 public static CommandRef[] all() { 96 return toSortedArray(INSTANCE.commands.values()); 97 } 98 99 /** 100 * @return all common commands, sorted by command name. 101 */ 102 public static CommandRef[] common() { 103 final ArrayList<CommandRef> common = new ArrayList<CommandRef>(); 104 for (final CommandRef c : INSTANCE.commands.values()) 105 if (c.isCommon()) 106 common.add(c); 107 return toSortedArray(common); 108 } 109 110 private static CommandRef[] toSortedArray(final Collection<CommandRef> c) { 111 final CommandRef[] r = c.toArray(new CommandRef[c.size()]); 112 Arrays.sort(r, new Comparator<CommandRef>() { 113 public int compare(final CommandRef o1, final CommandRef o2) { 114 return o1.getName().compareTo(o2.getName()); 115 } 116 }); 117 return r; 118 } 119 120 private final ClassLoader ldr; 121 122 private final Map<String, CommandRef> commands; 123 124 private CommandCatalog() { 125 ldr = Thread.currentThread().getContextClassLoader(); 126 commands = new HashMap<String, CommandRef>(); 127 128 final Enumeration<URL> catalogs = catalogs(); 129 while (catalogs.hasMoreElements()) 130 scan(catalogs.nextElement()); 131 } 132 133 private Enumeration<URL> catalogs() { 134 try { 135 final String pfx = "META-INF/services/"; //$NON-NLS-1$ 136 return ldr.getResources(pfx + TextBuiltin.class.getName()); 137 } catch (IOException err) { 138 return new Vector<URL>().elements(); 139 } 140 } 141 142 private void scan(final URL cUrl) { 143 final BufferedReader cIn; 144 try { 145 final InputStream in = cUrl.openStream(); 146 cIn = new BufferedReader(new InputStreamReader(in, "UTF-8")); //$NON-NLS-1$ 147 } catch (IOException err) { 148 // If we cannot read from the service list, go to the next. 149 // 150 return; 151 } 152 153 try { 154 String line; 155 while ((line = cIn.readLine()) != null) { 156 if (line.length() > 0 && !line.startsWith("#")) //$NON-NLS-1$ 157 load(line); 158 } 159 } catch (IOException err) { 160 // If we failed during a read, ignore the error. 161 // 162 } finally { 163 try { 164 cIn.close(); 165 } catch (IOException e) { 166 // Ignore the close error; we are only reading. 167 } 168 } 169 } 170 171 private void load(final String cn) { 172 final Class<? extends TextBuiltin> clazz; 173 try { 174 clazz = Class.forName(cn, false, ldr).asSubclass(TextBuiltin.class); 175 } catch (ClassNotFoundException notBuiltin) { 176 // Doesn't exist, even though the service entry is present. 177 // 178 return; 179 } catch (ClassCastException notBuiltin) { 180 // Isn't really a builtin, even though its listed as such. 181 // 182 return; 183 } 184 185 final CommandRef cr; 186 final Command a = clazz.getAnnotation(Command.class); 187 if (a != null) 188 cr = new CommandRef(clazz, a); 189 else 190 cr = new CommandRef(clazz); 191 192 commands.put(cr.getName(), cr); 193 } 194 }