diff options
18 files changed, 1981 insertions, 1082 deletions
diff --git a/lib/lombok/com.zwitserloot.cmdreader.jar b/lib/lombok/com.zwitserloot.cmdreader.jar Binary files differindex 411f2a1f..dade537a 100644 --- a/lib/lombok/com.zwitserloot.cmdreader.jar +++ b/lib/lombok/com.zwitserloot.cmdreader.jar diff --git a/src/core/lombok/core/runtimeDependencies/CreateLombokRuntimeApp.java b/src/core/lombok/core/runtimeDependencies/CreateLombokRuntimeApp.java index 3a83bea1..f30ff761 100644 --- a/src/core/lombok/core/runtimeDependencies/CreateLombokRuntimeApp.java +++ b/src/core/lombok/core/runtimeDependencies/CreateLombokRuntimeApp.java @@ -85,7 +85,6 @@ public class CreateLombokRuntimeApp implements LombokApp { @Requires("create") String output; - @Shorthand({"h", "?"}) @Description("Shows this help text") boolean help; } diff --git a/src/installer/lombok/installer/CorruptedIdeLocationException.java b/src/installer/lombok/installer/CorruptedIdeLocationException.java new file mode 100644 index 00000000..9b185d1e --- /dev/null +++ b/src/installer/lombok/installer/CorruptedIdeLocationException.java @@ -0,0 +1,26 @@ +package lombok.installer; + +import javax.swing.JFrame; +import javax.swing.JOptionPane; + +/** + * Represents an installation location problem. + * You should throw it upon creation of a {@code IdeLocation} class + * if the provided location looks like your kind of IDE but there's something wrong with it. + */ +public class CorruptedIdeLocationException extends Exception { + private final String ideType; + + public CorruptedIdeLocationException(String message, String ideType, Throwable cause) { + super(message, cause); + this.ideType = ideType; + } + + public String getIdeType() { + return ideType; + } + + void showDialog(JFrame appWindow) { + JOptionPane.showMessageDialog(appWindow, getMessage(), "Cannot configure " + ideType + " installation", JOptionPane.WARNING_MESSAGE); + } +} diff --git a/src/installer/lombok/installer/IdeFinder.java b/src/installer/lombok/installer/IdeFinder.java new file mode 100644 index 00000000..165d0768 --- /dev/null +++ b/src/installer/lombok/installer/IdeFinder.java @@ -0,0 +1,144 @@ +/* + * Copyright © 2009 Reinier Zwitserloot and Roel Spilker. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.installer; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +import lombok.core.Version; + +/** + * Implement and provide this class to add auto-finding a certain brand of IDEs to the lombok installer. + */ +public abstract class IdeFinder { + private static final AtomicBoolean windowsDriveInfoLibLoaded = new AtomicBoolean(false); + + private static void loadWindowsDriveInfoLib() throws IOException { + if (!windowsDriveInfoLibLoaded.compareAndSet(false, true)) return; + + final String prefix = "lombok-" + Version.getVersion() + "-"; + + File temp = File.createTempFile("lombok", ".mark"); + File dll1 = new File(temp.getParentFile(), prefix + "WindowsDriveInfo-i386.dll"); + File dll2 = new File(temp.getParentFile(), prefix + "WindowsDriveInfo-x86_64.dll"); + temp.delete(); + dll1.deleteOnExit(); + dll2.deleteOnExit(); + try { + if (unpackDLL("WindowsDriveInfo-i386.dll", dll1)) { + System.load(dll1.getAbsolutePath()); + return; + } + } catch (Throwable ignore) {} + + try { + if (unpackDLL("WindowsDriveInfo-x86_64.dll", dll2)) { + System.load(dll2.getAbsolutePath()); + } + } catch (Throwable ignore) {} + } + + private static boolean unpackDLL(String dllName, File target) throws IOException { + InputStream in = IdeFinder.class.getResourceAsStream(dllName); + try { + try { + FileOutputStream out = new FileOutputStream(target); + try { + byte[] b = new byte[32000]; + while (true) { + int r = in.read(b); + if (r == -1) break; + out.write(b, 0, r); + } + } finally { + out.close(); + } + } catch (IOException e) { + //Fall through - if there is a file named lombok-WindowsDriveInfo-arch.dll, we'll try it. + return target.exists() && target.canRead(); + } + } finally { + in.close(); + } + + return true; + } + + /** + * Returns all drive letters on windows that represent fixed disks. + * + * Floppy drives, optical drives, USB sticks, and network drives should all be excluded. + * + * @return A List of drive letters, such as ["C", "D", "X"]. + */ + public static List<String> getDrivesOnWindows() throws Throwable { + loadWindowsDriveInfoLib(); + + List<String> drives = new ArrayList<String>(); + + WindowsDriveInfo info = new WindowsDriveInfo(); + for (String drive : info.getLogicalDrives()) { + if (info.isFixedDisk(drive)) drives.add(drive); + } + + return drives; + } + + public static enum OS { + MAC_OS_X("\n"), WINDOWS("\r\n"), UNIX("\n"); + + private final String lineEnding; + + OS(String lineEnding) { + this.lineEnding = lineEnding; + } + + public String getLineEnding() { + return lineEnding; + } + } + + public static OS getOS() { + String prop = System.getProperty("os.name", "").toLowerCase(); + if (prop.matches("^.*\\bmac\\b.*$")) return OS.MAC_OS_X; + if (prop.matches("^.*\\bdarwin\\b.*$")) return OS.MAC_OS_X; + if (prop.matches("^.*\\bwin(dows)\\b.*$")) return OS.WINDOWS; + + return OS.UNIX; + } + + /** + * Look for installations of your IDE in the usual places. + * + * @param locations Add to this list any valid locations that you found. + * @param problems + * Add to this list any locations that look like installations, + * but have problems that prevent you from installing/uninstalling from them. DONT add to this list + * any common locations that have no installation at all - only add near misses. + */ + public abstract void findIdes(List<IdeLocation> locations, List<CorruptedIdeLocationException> problems); +} diff --git a/src/installer/lombok/installer/IdeLocation.java b/src/installer/lombok/installer/IdeLocation.java new file mode 100644 index 00000000..e23a0033 --- /dev/null +++ b/src/installer/lombok/installer/IdeLocation.java @@ -0,0 +1,74 @@ +/* + * Copyright © 2009 Reinier Zwitserloot and Roel Spilker. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.installer; + +import java.io.File; +import java.io.IOException; +import java.net.URL; + +import lombok.patcher.inject.LiveInjector; + +/** + * Represents a location that contains an IDE. + */ +public abstract class IdeLocation { + /** Toggling the 'selected' checkbox in the GUI is tracked via this boolean */ + boolean selected = true; + + public abstract String install() throws InstallException; + public abstract void uninstall() throws UninstallException; + public abstract String getName(); + public abstract boolean hasLombok(); + public abstract URL getIdeIcon(); + + /** + * Returns a File object pointing to our own jar file. Will obviously fail if the installer was started via + * a jar that wasn't accessed via the file-system, or if its started via e.g. unpacking the jar. + */ + public static File findOurJar() { + return new File(LiveInjector.findPathJar(IdeFinder.class)); + } + + /** + * Returns a full path to the provided file. + * Returns the canonical path, unless that is not available, in which cae it returns the absolute path. + */ + public static String canonical(File p) { + try { + return p.getCanonicalPath(); + } catch (IOException e) { + String x = p.getAbsolutePath(); + return x == null ? p.getPath() : x; + } + } + + private static final String LEGAL_PATH_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_/"; + public static String escapePath(String path) { + StringBuilder out = new StringBuilder(); + + for (char c : path.toCharArray()) { + if (LEGAL_PATH_CHARS.indexOf(c) == -1) out.append('\\'); + out.append(c); + } + return out.toString(); + } +} diff --git a/src/installer/lombok/installer/IdeLocationProvider.java b/src/installer/lombok/installer/IdeLocationProvider.java new file mode 100644 index 00000000..e98e199d --- /dev/null +++ b/src/installer/lombok/installer/IdeLocationProvider.java @@ -0,0 +1,40 @@ +/* + * Copyright © 2009 Reinier Zwitserloot and Roel Spilker. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.installer; + +import java.util.regex.Pattern; + +import lombok.installer.IdeFinder.OS; + +public interface IdeLocationProvider { + /** + * @throws CorruptedIdeLocationException + * Only throw this exception if the location seems like a proper installation except there's something wrong with it. + * Do not throw it (just return {@code null}) if there's nothing there or it looks absolutely nothing like your IDE. + */ + public abstract IdeLocation create(String path) throws CorruptedIdeLocationException; + + /** + * Return the usual name of the IDE executable or other obvious marker of an IDE installation on the provided platform. + */ + public abstract Pattern getLocationSelectors(OS os); +} diff --git a/src/installer/lombok/installer/InstallException.java b/src/installer/lombok/installer/InstallException.java new file mode 100644 index 00000000..41879604 --- /dev/null +++ b/src/installer/lombok/installer/InstallException.java @@ -0,0 +1,10 @@ +package lombok.installer; + +/** + * Thrown when installation of lombok into an IDE fails. + */ +public class InstallException extends Exception { + public InstallException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/installer/lombok/installer/Installer.java b/src/installer/lombok/installer/Installer.java index d4b31251..9ed9e93a 100644 --- a/src/installer/lombok/installer/Installer.java +++ b/src/installer/lombok/installer/Installer.java @@ -21,55 +21,23 @@ */ package lombok.installer; -import java.awt.Color; -import java.awt.Component; -import java.awt.Container; -import java.awt.Cursor; -import java.awt.Dimension; -import java.awt.FileDialog; -import java.awt.FlowLayout; -import java.awt.Font; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; import java.awt.HeadlessException; -import java.awt.Insets; -import java.awt.Rectangle; -import java.awt.Toolkit; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.font.TextAttribute; -import java.io.File; -import java.io.FilenameFilter; +import java.io.IOException; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Pattern; -import javax.swing.Box; -import javax.swing.BoxLayout; -import javax.swing.ImageIcon; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JComponent; -import javax.swing.JFileChooser; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.Scrollable; import javax.swing.SwingUtilities; import javax.swing.UIManager; -import javax.swing.filechooser.FileFilter; +import lombok.Lombok; import lombok.core.LombokApp; +import lombok.core.SpiLoadUtil; import lombok.core.Version; -import lombok.installer.EclipseFinder.OS; -import lombok.installer.EclipseLocation.InstallException; -import lombok.installer.EclipseLocation.NotAnEclipseException; -import lombok.installer.EclipseLocation.UninstallException; +import lombok.installer.IdeFinder.OS; import org.mangosdk.spi.ProviderFor; @@ -77,6 +45,7 @@ import com.zwitserloot.cmdreader.CmdReader; import com.zwitserloot.cmdreader.Description; import com.zwitserloot.cmdreader.InvalidCommandLineException; import com.zwitserloot.cmdreader.Parameterized; +import com.zwitserloot.cmdreader.Sequential; import com.zwitserloot.cmdreader.Shorthand; /** @@ -86,24 +55,49 @@ import com.zwitserloot.cmdreader.Shorthand; * and looks in some common places on Mac OS X, Linux and Windows. */ public class Installer { - private static final URI ABOUT_LOMBOK_URL = URI.create("http://projectlombok.org"); - private static final AtomicReference<Integer> exitMarker = new AtomicReference<Integer>(); + static final URI ABOUT_LOMBOK_URL = URI.create("http://projectlombok.org"); + static final List<IdeLocationProvider> locationProviders; - private JFrame appWindow; + static { + List<IdeLocationProvider> list = new ArrayList<IdeLocationProvider>(); + try { + for (IdeLocationProvider provider : SpiLoadUtil.findServices(IdeLocationProvider.class)) { + list.add(provider); + } + } catch (IOException e) { + throw Lombok.sneakyThrow(e); + } + locationProviders = Collections.unmodifiableList(list); + } - private JComponent loadingExpl; - - private Component javacArea; - private Component eclipseArea; - private Component uninstallArea; - private Component howIWorkArea; - - private Box uninstallBox; - private List<EclipseLocation> toUninstall; + static List<Pattern> getIdeExecutableNames() { + OS os = IdeFinder.getOS(); + List<Pattern> list = new ArrayList<Pattern>(); + for (IdeLocationProvider provider : locationProviders) { + Pattern p = provider.getLocationSelectors(os); + if (p != null) list.add(p); + } + return list; + } + + static IdeLocation tryAllProviders(String location) throws CorruptedIdeLocationException { + for (IdeLocationProvider provider : locationProviders) { + IdeLocation loc = provider.create(location); + if (loc != null) return loc; + } + + return null; + } - private JHyperLink uninstallButton; - private JLabel uninstallPlaceholder; - private JButton installButton; + static void autoDiscover(List<IdeLocation> locations, List<CorruptedIdeLocationException> problems) { + try { + for (IdeFinder finder : SpiLoadUtil.findServices(IdeFinder.class)) { + finder.findIdes(locations, problems); + } + } catch (IOException e) { + throw Lombok.sneakyThrow(e); + } + } @ProviderFor(LombokApp.class) public static class GraphicalInstallerApp implements LombokApp { @@ -163,7 +157,7 @@ public class Installer { } private static int guiInstaller() { - if (EclipseFinder.getOS() == OS.MAC_OS_X) { + if (IdeFinder.getOS() == OS.MAC_OS_X) { System.setProperty("com.apple.mrj.application.apple.menu.about.name", "Lombok Installer"); System.setProperty("com.apple.macos.use-file-dialog-packages", "true"); } @@ -176,22 +170,22 @@ public class Installer { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception ignore) {} - new Installer().show(); + new InstallerGUI().show(); } catch (HeadlessException e) { printHeadlessInfo(); } } }); - synchronized (exitMarker) { - while (!Thread.interrupted() && exitMarker.get() == null) { + synchronized (InstallerGUI.exitMarker) { + while (!Thread.interrupted() && InstallerGUI.exitMarker.get() == null) { try { - exitMarker.wait(); + InstallerGUI.exitMarker.wait(); } catch (InterruptedException e) { return 1; } } - Integer errCode = exitMarker.get(); + Integer errCode = InstallerGUI.exitMarker.get(); return errCode == null ? 1 : errCode; } } catch (HeadlessException e) { @@ -201,12 +195,12 @@ public class Installer { } private static class CmdArgs { - @Shorthand("e") - @Description("Specify a path to an eclipse location to install/uninstall. Use 'auto' to apply to all automatically discoverable eclipse installations.") + @Description("Specify paths to a location to install/uninstall. Use 'auto' to apply to all automatically discoverable installations.") @Parameterized - List<String> eclipse = new ArrayList<String>(); + @Sequential + List<String> path = new ArrayList<String>(); - @Shorthand({"?", "h"}) + @Shorthand({"h", "?"}) @Description("Shows this help text") boolean help; } @@ -228,30 +222,33 @@ public class Installer { return 0; } - if (args.eclipse.isEmpty()) { + if (args.path.isEmpty()) { System.err.println("ERROR: Nothing to do!"); System.err.println("--------------------------"); System.err.println(generateCliHelp(uninstall, reader)); return 1; } - final List<EclipseLocation> locations = new ArrayList<EclipseLocation>(); - final List<NotAnEclipseException> problems = new ArrayList<NotAnEclipseException>(); + final List<IdeLocation> locations = new ArrayList<IdeLocation>(); + final List<CorruptedIdeLocationException> problems = new ArrayList<CorruptedIdeLocationException>(); + + if (args.path.contains("auto")) autoDiscover(locations, problems); - for (String rawPath : args.eclipse) { - if (rawPath.equals("auto")) { - EclipseFinder.findEclipses(locations, problems); - } else { + for (String rawPath : args.path) { + if (!rawPath.equals("auto")) { try { - locations.add(EclipseLocation.create(rawPath)); - } catch (NotAnEclipseException e) { + IdeLocation loc = tryAllProviders(rawPath); + if (loc != null) locations.add(loc); + else problems.add(new CorruptedIdeLocationException("Can't find any IDE at: " + rawPath, null, null)); + locations.add(tryAllProviders(rawPath)); + } catch (CorruptedIdeLocationException e) { problems.add(e); } } } int validLocations = locations.size(); - for (EclipseLocation loc : locations) { + for (IdeLocation loc : locations) { try { if (uninstall) { loc.uninstall(); @@ -270,7 +267,7 @@ public class Installer { } } - for (NotAnEclipseException problem : problems) { + for (CorruptedIdeLocationException problem : problems) { System.err.println("WARNING: " + problem.getMessage()); } @@ -301,695 +298,4 @@ public class Installer { " java -cp lombok.jar MyCode.java\n", Version.getVersion(), ABOUT_LOMBOK_URL); } - - /** - * Creates a new installer that starts out invisible. - * Call the {@link #show()} method on a freshly created installer to render it. - */ - public Installer() { - appWindow = new JFrame(String.format("Project Lombok v%s - Installer", Version.getVersion())); - - appWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - appWindow.setResizable(false); - appWindow.setIconImage(Toolkit.getDefaultToolkit().getImage(Installer.class.getResource("lombokIcon.png"))); - - try { - javacArea = buildJavacArea(); - eclipseArea = buildEclipseArea(); - uninstallArea = buildUninstallArea(); - uninstallArea.setVisible(false); - howIWorkArea = buildHowIWorkArea(); - howIWorkArea.setVisible(false); - buildChrome(appWindow.getContentPane()); - appWindow.pack(); - } catch (Throwable t) { - handleException(t); - } - } - - private void handleException(final Throwable t) { - SwingUtilities.invokeLater(new Runnable() { - @Override public void run() { - JOptionPane.showMessageDialog(appWindow, "There was a problem during the installation process:\n" + t, "Uh Oh!", JOptionPane.ERROR_MESSAGE); - t.printStackTrace(); - System.exit(1); - } - }); - } - - private Component buildHowIWorkArea() { - JPanel container = new JPanel(); - - container.setLayout(new GridBagLayout()); - GridBagConstraints constraints = new GridBagConstraints(); - constraints.anchor = GridBagConstraints.WEST; - - container.add(new JLabel(HOW_I_WORK_TITLE), constraints); - - constraints.gridy = 1; - constraints.insets = new Insets(8, 0, 0, 16); - container.add(new JLabel(String.format(HOW_I_WORK_EXPLANATION, File.pathSeparator)), constraints); - - Box buttonBar = Box.createHorizontalBox(); - JButton backButton = new JButton("Okay - Good to know!"); - buttonBar.add(Box.createHorizontalGlue()); - buttonBar.add(backButton); - - backButton.addActionListener(new ActionListener() { - @Override public void actionPerformed(ActionEvent e) { - howIWorkArea.setVisible(false); - javacArea.setVisible(true); - eclipseArea.setVisible(true); - appWindow.pack(); - } - }); - - constraints.gridy = 2; - container.add(buttonBar, constraints); - - return container; - } - - private Component buildUninstallArea() { - JPanel container = new JPanel(); - - container.setLayout(new GridBagLayout()); - GridBagConstraints constraints = new GridBagConstraints(); - constraints.anchor = GridBagConstraints.WEST; - - container.add(new JLabel(UNINSTALL_TITLE), constraints); - - constraints.gridy = 1; - constraints.insets = new Insets(8, 0, 0, 16); - container.add(new JLabel(UNINSTALL_EXPLANATION), constraints); - - uninstallBox = Box.createVerticalBox(); - constraints.gridy = 2; - constraints.fill = GridBagConstraints.HORIZONTAL; - container.add(uninstallBox, constraints); - - constraints.fill = GridBagConstraints.HORIZONTAL; - constraints.gridy = 3; - container.add(new JLabel("Are you sure?"), constraints); - - Box buttonBar = Box.createHorizontalBox(); - JButton noButton = new JButton("No - Don't uninstall"); - buttonBar.add(noButton); - buttonBar.add(Box.createHorizontalGlue()); - JButton yesButton = new JButton("Yes - uninstall Lombok"); - buttonBar.add(yesButton); - - noButton.addActionListener(new ActionListener() { - @Override public void actionPerformed(ActionEvent e) { - uninstallArea.setVisible(false); - javacArea.setVisible(true); - eclipseArea.setVisible(true); - appWindow.pack(); - } - }); - - yesButton.addActionListener(new ActionListener() { - @Override public void actionPerformed(ActionEvent e) { - doUninstall(); - } - }); - - constraints.gridy = 4; - container.add(buttonBar, constraints); - - return container; - } - - private Component buildJavacArea() { - JPanel container = new JPanel(); - - container.setLayout(new GridBagLayout()); - GridBagConstraints constraints = new GridBagConstraints(); - constraints.anchor = GridBagConstraints.WEST; - constraints.insets = new Insets(8, 0, 0, 16); - - container.add(new JLabel(JAVAC_TITLE), constraints); - - constraints.gridy = 1; - constraints.weightx = 1.0; - constraints.fill = GridBagConstraints.HORIZONTAL; - container.add(new JLabel(JAVAC_EXPLANATION), constraints); - - JLabel example = new JLabel(JAVAC_EXAMPLE); - - constraints.gridy = 2; - container.add(example, constraints); - return container; - } - - private Component buildEclipseArea() { - JPanel container = new JPanel(); - - container.setLayout(new GridBagLayout()); - GridBagConstraints constraints = new GridBagConstraints(); - constraints.anchor = GridBagConstraints.WEST; - - constraints.insets = new Insets(8, 0, 0, 16); - container.add(new JLabel(ECLIPSE_TITLE), constraints); - - constraints.gridy = 1; - container.add(new JLabel(ECLIPSE_EXPLANATION), constraints); - - constraints.gridy = 2; - loadingExpl = Box.createHorizontalBox(); - loadingExpl.add(new JLabel(new ImageIcon(Installer.class.getResource("/lombok/installer/loading.gif")))); - loadingExpl.add(new JLabel(ECLIPSE_LOADING_EXPLANATION)); - container.add(loadingExpl, constraints); - - constraints.weightx = 1.0; - constraints.gridy = 3; - constraints.fill = GridBagConstraints.HORIZONTAL; - eclipsesList = new EclipsesList(); - - JScrollPane eclipsesListScroll = new JScrollPane(eclipsesList); - eclipsesListScroll.setBackground(Color.WHITE); - eclipsesListScroll.getViewport().setBackground(Color.WHITE); - container.add(eclipsesListScroll, constraints); - - Thread findEclipsesThread = new Thread() { - @Override public void run() { - try { - final List<EclipseLocation> locations = new ArrayList<EclipseLocation>(); - final List<NotAnEclipseException> problems = new ArrayList<NotAnEclipseException>(); - EclipseFinder.findEclipses(locations, problems); - - SwingUtilities.invokeLater(new Runnable() { - @Override public void run() { - for (EclipseLocation location : locations) { - try { - eclipsesList.addEclipse(location); - } catch (Throwable t) { - handleException(t); - } - } - - for (NotAnEclipseException problem : problems) { - problem.showDialog(appWindow); - } - - loadingExpl.setVisible(false); - - if (locations.size() + problems.size() == 0) { - JOptionPane.showMessageDialog(appWindow, - "I don't know how to automatically find Eclipse installations on this platform.\n" + - "Please use the 'Specify Eclipse Location...' button to manually point out the\n" + - "location of your Eclipse installation to me. Thanks!", "Can't find Eclipse", JOptionPane.INFORMATION_MESSAGE); - } - } - }); - } catch (Throwable t) { - handleException(t); - } - } - }; - - findEclipsesThread.start(); - - Box buttonBar = Box.createHorizontalBox(); - JButton specifyEclipseLocationButton = new JButton("Specify Eclipse location..."); - buttonBar.add(specifyEclipseLocationButton); - specifyEclipseLocationButton.addActionListener(new ActionListener() { - @Override public void actionPerformed(ActionEvent event) { - final String exeName = EclipseFinder.getEclipseExecutableName(); - String file = null; - - if (EclipseFinder.getOS() == OS.MAC_OS_X) { - FileDialog chooser = new FileDialog(appWindow); - chooser.setMode(FileDialog.LOAD); - chooser.setFilenameFilter(new FilenameFilter() { - @Override public boolean accept(File dir, String fileName) { - if (exeName.equalsIgnoreCase(fileName)) return true; - if (new File(dir, fileName).isDirectory()) return true; - return false; - } - }); - - chooser.setVisible(true); - file = new File(chooser.getDirectory(), chooser.getFile()).getAbsolutePath(); - } else { - JFileChooser chooser = new JFileChooser(); - - chooser.setAcceptAllFileFilterUsed(false); - chooser.setFileSelectionMode(JFileChooser.FILES_ONLY); - chooser.setFileFilter(new FileFilter() { - @Override public boolean accept(File f) { - if (f.getName().equalsIgnoreCase(exeName)) return true; - if (f.getName().equalsIgnoreCase("eclipse.ini")) return true; - if (f.isDirectory()) return true; - - return false; - } - - @Override public String getDescription() { - return "Eclipse Installation"; - } - }); - - switch (chooser.showDialog(appWindow, "Select")) { - case JFileChooser.APPROVE_OPTION: - file = chooser.getSelectedFile().getAbsolutePath(); - } - } - - if (file != null) { - try { - eclipsesList.addEclipse(EclipseLocation.create(file)); - } catch (NotAnEclipseException e) { - e.showDialog(appWindow); - } catch (Throwable t) { - handleException(t); - } - } - } - }); - - buttonBar.add(Box.createHorizontalGlue()); - installButton = new JButton("Install / Update"); - buttonBar.add(installButton); - - installButton.addActionListener(new ActionListener() { - @Override public void actionPerformed(ActionEvent e) { - List<EclipseLocation> locationsToInstall = new ArrayList<EclipseLocation>(eclipsesList.getSelectedEclipses()); - if (locationsToInstall.isEmpty()) { - JOptionPane.showMessageDialog(appWindow, "You haven't selected any Eclipse installations!.", "No Selection", JOptionPane.WARNING_MESSAGE); - return; - } - - install(locationsToInstall); - } - }); - - constraints.gridy = 4; - constraints.weightx = 0; - container.add(buttonBar, constraints); - - constraints.gridy = 5; - constraints.fill = GridBagConstraints.NONE; - JHyperLink showMe = new JHyperLink("Show me what this installer will do to my Eclipse installation."); - container.add(showMe, constraints); - showMe.addActionListener(new ActionListener() { - @Override public void actionPerformed(ActionEvent e) { - showWhatIDo(); - } - }); - - constraints.gridy = 6; - uninstallButton = new JHyperLink("Uninstall lombok from selected Eclipse installations."); - uninstallPlaceholder = new JLabel("<html> </html>"); - uninstallButton.addActionListener(new ActionListener() { - @Override public void actionPerformed(ActionEvent e) { - List<EclipseLocation> locationsToUninstall = new ArrayList<EclipseLocation>(); - for (EclipseLocation location : eclipsesList.getSelectedEclipses()) { - if (location.hasLombok()) locationsToUninstall.add(location); - } - - if (locationsToUninstall.isEmpty()) { - JOptionPane.showMessageDialog(appWindow, "You haven't selected any Eclipse installations that have been lombok-enabled.", "No Selection", JOptionPane.WARNING_MESSAGE); - return; - } - - - uninstall(locationsToUninstall); - } - }); - container.add(uninstallButton, constraints); - uninstallPlaceholder.setVisible(false); - container.add(uninstallPlaceholder, constraints); - - - return container; - } - - private void showWhatIDo() { - javacArea.setVisible(false); - eclipseArea.setVisible(false); - howIWorkArea.setVisible(true); - appWindow.pack(); - } - - private void uninstall(List<EclipseLocation> locations) { - javacArea.setVisible(false); - eclipseArea.setVisible(false); - - uninstallBox.removeAll(); - uninstallBox.add(Box.createRigidArea(new Dimension(1, 16))); - for (EclipseLocation location : locations) { - JLabel label = new JLabel(location.getName()); - label.setFont(label.getFont().deriveFont(Font.BOLD)); - uninstallBox.add(label); - } - uninstallBox.add(Box.createRigidArea(new Dimension(1, 16))); - - toUninstall = locations; - uninstallArea.setVisible(true); - appWindow.pack(); - } - - private void install(final List<EclipseLocation> toInstall) { - JPanel spinner = new JPanel(); - spinner.setOpaque(true); - spinner.setLayout(new FlowLayout()); - spinner.add(new JLabel(new ImageIcon(Installer.class.getResource("/lombok/installer/loading.gif")))); - appWindow.setContentPane(spinner); - - final AtomicReference<Boolean> success = new AtomicReference<Boolean>(true); - - new Thread() { - @Override public void run() { - for (EclipseLocation loc : toInstall) { - try { - loc.install(); - } catch (final InstallException e) { - success.set(false); - try { - SwingUtilities.invokeAndWait(new Runnable() { - @Override public void run() { - JOptionPane.showMessageDialog(appWindow, - e.getMessage(), "Install Problem", JOptionPane.ERROR_MESSAGE); - } - }); - } catch (Exception e2) { - //Shouldn't happen. - throw new RuntimeException(e2); - } - } - } - - if (success.get()) SwingUtilities.invokeLater(new Runnable() { - @Override public void run() { - JOptionPane.showMessageDialog(appWindow, - "<html>Lombok has been installed on the selected Eclipse installations.<br>" + - "Don't forget to add <code>lombok.jar</code> to your projects, and restart your eclipse!<br>" + - "If you start eclipse with a custom -vm parameter, you'll need to add:<br>" + - "<code>-vmargs -Xbootclasspath/a:lombok.jar -javaagent:lombok.jar</code><br>" + - "as parameter as well.</html>", "Install successful", - JOptionPane.INFORMATION_MESSAGE); - appWindow.setVisible(false); - synchronized (exitMarker) { - exitMarker.set(0); - exitMarker.notifyAll(); - } - } - }); - - if (!success.get()) SwingUtilities.invokeLater(new Runnable() { - @Override public void run() { - synchronized (exitMarker) { - exitMarker.set(1); - exitMarker.notifyAll(); - } - } - }); - } - }.start(); - } - - private void doUninstall() { - JPanel spinner = new JPanel(); - spinner.setOpaque(true); - spinner.setLayout(new FlowLayout()); - spinner.add(new JLabel(new ImageIcon(Installer.class.getResource("/lombok/installer/loading.gif")))); - - appWindow.setContentPane(spinner); - - final AtomicReference<Boolean> success = new AtomicReference<Boolean>(true); - new Thread() { - @Override public void run() { - for (EclipseLocation loc : toUninstall) { - try { - loc.uninstall(); - } catch (final UninstallException e) { - success.set(false); - try { - SwingUtilities.invokeAndWait(new Runnable() { - @Override public void run() { - JOptionPane.showMessageDialog(appWindow, - e.getMessage(), "Uninstall Problem", JOptionPane.ERROR_MESSAGE); - } - }); - } catch (Exception e2) { - //Shouldn't happen. - throw new RuntimeException(e2); - } - } - } - - if (success.get()) SwingUtilities.invokeLater(new Runnable() { - @Override public void run() { - JOptionPane.showMessageDialog(appWindow, "Lombok has been removed from the selected Eclipse installations.", "Uninstall successful", JOptionPane.INFORMATION_MESSAGE); - appWindow.setVisible(false); - System.exit(0); - } - }); - } - }.start(); - } - - private EclipsesList eclipsesList = new EclipsesList(); - - private static class JHyperLink extends JButton { - private static final long serialVersionUID = 1L; - - public JHyperLink(String text) { - super(); - setFont(getFont().deriveFont(Collections.singletonMap(TextAttribute.UNDERLINE, 1))); - setText(text); - setBorder(null); - setContentAreaFilled(false); - setForeground(Color.BLUE); - setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - setMargin(new Insets(0, 0, 0, 0)); - } - } - - void selectedLomboksChanged(List<EclipseLocation> selectedEclipses) { - boolean uninstallAvailable = false; - boolean installAvailable = false; - for (EclipseLocation loc : selectedEclipses) { - if (loc.hasLombok()) uninstallAvailable = true; - installAvailable = true; - } - - uninstallButton.setVisible(uninstallAvailable); - uninstallPlaceholder.setVisible(!uninstallAvailable); - installButton.setEnabled(installAvailable); - } - - private class EclipsesList extends JPanel implements Scrollable { - private static final long serialVersionUID = 1L; - - List<EclipseLocation> locations = new ArrayList<EclipseLocation>(); - - EclipsesList() { - setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); - setBackground(Color.WHITE); - } - - List<EclipseLocation> getSelectedEclipses() { - List<EclipseLocation> list = new ArrayList<EclipseLocation>(); - for (EclipseLocation loc : locations) if (loc.selected) list.add(loc); - return list; - } - - void fireSelectionChange() { - selectedLomboksChanged(getSelectedEclipses()); - } - - void addEclipse(final EclipseLocation location) { - if (locations.contains(location)) return; - Box box = Box.createHorizontalBox(); - box.setBackground(Color.WHITE); - final JCheckBox checkbox = new JCheckBox(location.getName()); - checkbox.setBackground(Color.WHITE); - box.add(checkbox); - checkbox.setSelected(true); - checkbox.addActionListener(new ActionListener() { - @Override public void actionPerformed(ActionEvent e) { - location.selected = checkbox.isSelected(); - fireSelectionChange(); - } - }); - - if (location.hasLombok()) { - box.add(new JLabel(new ImageIcon(Installer.class.getResource("/lombok/installer/lombokIcon.png")))); - } - box.add(Box.createHorizontalGlue()); - locations.add(location); - add(box); - getParent().doLayout(); - fireSelectionChange(); - } - - @Override public Dimension getPreferredScrollableViewportSize() { - return new Dimension(1, 100); - } - - @Override public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { - return 12; - } - - @Override public boolean getScrollableTracksViewportHeight() { - return false; - } - - @Override public boolean getScrollableTracksViewportWidth() { - return true; - } - - @Override public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { - return 1; - } - } - - private void buildChrome(Container appWindowContainer) { - JLabel leftGraphic = new JLabel(new ImageIcon(Installer.class.getResource("/lombok/installer/lombok.png"))); - - GridBagConstraints constraints = new GridBagConstraints(); - - appWindowContainer.setLayout(new GridBagLayout()); - - constraints.gridheight = 3; - constraints.gridwidth = 1; - constraints.gridx = 0; - constraints.gridy = 0; - constraints.insets = new Insets(8, 8, 8, 8); - appWindowContainer.add(leftGraphic, constraints); - constraints.insets = new Insets(0, 0, 0, 0); - - constraints.gridx++; - constraints.gridy++; - constraints.gridheight = 1; - constraints.fill = GridBagConstraints.HORIZONTAL; - constraints.ipadx = 16; - constraints.ipady = 14; - appWindowContainer.add(javacArea, constraints); - - constraints.gridy++; - appWindowContainer.add(eclipseArea, constraints); - - appWindowContainer.add(uninstallArea, constraints); - - appWindowContainer.add(howIWorkArea, constraints); - - constraints.gridy++; - constraints.gridwidth = 2; - constraints.gridx = 0; - constraints.weightx = 0; - constraints.weighty = 0; - constraints.ipadx = 0; - constraints.ipady = 0; - constraints.fill = GridBagConstraints.HORIZONTAL; - constraints.anchor = GridBagConstraints.SOUTHEAST; - constraints.insets = new Insets(0, 16, 8, 8); - Box buttonBar = Box.createHorizontalBox(); - JButton quitButton = new JButton("Quit Installer"); - quitButton.addActionListener(new ActionListener() { - @Override public void actionPerformed(ActionEvent e) { - appWindow.setVisible(false); - System.exit(0); - } - }); - final JHyperLink hyperlink = new JHyperLink(ABOUT_LOMBOK_URL.toString()); - hyperlink.addActionListener(new ActionListener() { - @Override public void actionPerformed(ActionEvent event) { - hyperlink.setForeground(new Color(85, 145, 90)); - try { - //java.awt.Desktop doesn't exist in 1.5. - Object desktop = Class.forName("java.awt.Desktop").getMethod("getDesktop").invoke(null); - Class.forName("java.awt.Desktop").getMethod("browse", URI.class).invoke(desktop, ABOUT_LOMBOK_URL); - } catch (Exception e) { - Runtime rt = Runtime.getRuntime(); - try { - switch (EclipseFinder.getOS()) { - case WINDOWS: - String[] cmd = new String[4]; - cmd[0] = "cmd.exe"; - cmd[1] = "/C"; - cmd[2] = "start"; - cmd[3] = ABOUT_LOMBOK_URL.toString(); - rt.exec(cmd); - break; - case MAC_OS_X: - rt.exec("open " + ABOUT_LOMBOK_URL.toString()); - break; - default: - case UNIX: - rt.exec("firefox " + ABOUT_LOMBOK_URL.toString()); - break; - } - } catch (Exception e2) { - JOptionPane.showMessageDialog(appWindow, - "Well, this is embarrassing. I don't know how to open a webbrowser.\n" + - "I guess you'll have to open it. Browse to:\n" + - "http://projectlombok.org for more information about Lombok.", - "I'm embarrassed", JOptionPane.INFORMATION_MESSAGE); - } - } - } - }); - buttonBar.add(hyperlink); - buttonBar.add(Box.createRigidArea(new Dimension(16, 1))); - buttonBar.add(new JLabel("<html><font size=\"-1\">v" + Version.getVersion() + "</font></html>")); - - buttonBar.add(Box.createHorizontalGlue()); - buttonBar.add(quitButton); - appWindow.add(buttonBar, constraints); - } - - /** - * Makes the installer window visible. - */ - public void show() { - appWindow.setVisible(true); - if (EclipseFinder.getOS() == OS.MAC_OS_X) { - try { - AppleNativeLook.go(); - } catch (Throwable ignore) { - //We're just prettying up the app. If it fails, meh. - } - } - } - - private static final String ECLIPSE_TITLE = - "<html><font size=\"+1\"><b><i>Eclipse</i></b></font></html>"; - - private static final String ECLIPSE_EXPLANATION = - "<html>Lombok can update your Eclipse to fully support all Lombok features.<br>" + - "Select Eclipse installations below and hit 'Install/Update'.</html>"; - - private static final String ECLIPSE_LOADING_EXPLANATION = - "Scanning your drives for Eclipse installations..."; - - private static final String JAVAC_TITLE = - "<html><font size=\"+1\"><b><i>Javac</i></b></font> (and tools that invoke javac such as <i>ant</i> and <i>maven</i>)</html>"; - - private static final String JAVAC_EXPLANATION = - "<html>Lombok works 'out of the box' with javac.<br>Just make sure the lombok.jar is in your classpath when you compile."; - - private static final String JAVAC_EXAMPLE = - "<html>Example: <code>javac -cp lombok.jar MyCode.java</code></html>"; - - private static final String UNINSTALL_TITLE = - "<html><font size=\"+1\"><b><i>Uninstall</i></b></font></html>"; - - private static final String UNINSTALL_EXPLANATION = - "<html>Uninstall Lombok from the following Eclipse Installations?</html>"; - - private static final String HOW_I_WORK_TITLE = - "<html><font size=\"+1\"><b><i>What this installer does</i></b></font></html>"; - - private static final String HOW_I_WORK_EXPLANATION = - "<html><ol>" + - "<li>First, I copy myself (lombok.jar) to your Eclipse install directory.</li>" + - "<li>Then, I edit the eclipse.ini file to add the following two entries:<br>" + - "<pre>-Xbootclasspath/a:lombok.jar<br>" + - "-javaagent:lombok.jar</pre></li></ol>" + - "<br>" + - "That's all there is to it. Note that on Mac OS X, eclipse.ini is hidden in<br>" + - "<code>Eclipse.app%1$sContents%1$sMacOS</code> so that's where I place the jar files.</html>"; } diff --git a/src/installer/lombok/installer/InstallerGUI.java b/src/installer/lombok/installer/InstallerGUI.java new file mode 100644 index 00000000..7d266546 --- /dev/null +++ b/src/installer/lombok/installer/InstallerGUI.java @@ -0,0 +1,796 @@ +/* + * Copyright © 2009 Reinier Zwitserloot and Roel Spilker. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.installer; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Cursor; +import java.awt.Dimension; +import java.awt.FileDialog; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.Rectangle; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.font.TextAttribute; +import java.io.File; +import java.io.FilenameFilter; +import java.net.URI; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Pattern; + +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.Scrollable; +import javax.swing.SwingUtilities; +import javax.swing.filechooser.FileFilter; + +import lombok.core.Version; +import lombok.installer.IdeFinder.OS; + +/** + * The lombok GUI installer. + * Uses swing to show a simple GUI that can add and remove the java agent to IDE installations. + * Also offers info on what this installer does in case people want to instrument their IDE manually. + */ +public class InstallerGUI { + static final AtomicReference<Integer> exitMarker = new AtomicReference<Integer>(); + + private JFrame appWindow; + + private JComponent loadingExpl; + + private Component javacArea; + private Component ideArea; + private Component uninstallArea; + private Component howIWorkArea; + + private Box uninstallBox; + private JHyperLink uninstallButton; + private JLabel uninstallPlaceholder; + private JButton installButton; + + private List<IdeLocation> toUninstall; + private final Set<String> installSpecificMessages = new LinkedHashSet<String>(); + + /** + * Creates a new installer that starts out invisible. + * Call the {@link #show()} method on a freshly created installer to render it. + */ + public InstallerGUI() { + appWindow = new JFrame(String.format("Project Lombok v%s - Installer", Version.getVersion())); + + appWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + appWindow.setResizable(false); + appWindow.setIconImage(Toolkit.getDefaultToolkit().getImage(Installer.class.getResource("lombokIcon.png"))); + + try { + javacArea = buildJavacArea(); + ideArea = buildIdeArea(); + uninstallArea = buildUninstallArea(); + uninstallArea.setVisible(false); + howIWorkArea = buildHowIWorkArea(); + howIWorkArea.setVisible(false); + buildChrome(appWindow.getContentPane()); + appWindow.pack(); + } catch (Throwable t) { + handleException(t); + } + } + + private void handleException(final Throwable t) { + SwingUtilities.invokeLater(new Runnable() { + @Override public void run() { + JOptionPane.showMessageDialog(appWindow, "There was a problem during the installation process:\n" + t, "Uh Oh!", JOptionPane.ERROR_MESSAGE); + t.printStackTrace(); + System.exit(1); + } + }); + } + + private Component buildHowIWorkArea() { + JPanel container = new JPanel(); + + container.setLayout(new GridBagLayout()); + GridBagConstraints constraints = new GridBagConstraints(); + constraints.anchor = GridBagConstraints.WEST; + + container.add(new JLabel(HOW_I_WORK_TITLE), constraints); + + constraints.gridy = 1; + constraints.insets = new Insets(8, 0, 0, 16); + container.add(new JLabel(String.format(HOW_I_WORK_EXPLANATION, File.separator)), constraints); + + Box buttonBar = Box.createHorizontalBox(); + JButton backButton = new JButton("Okay - Good to know!"); + buttonBar.add(Box.createHorizontalGlue()); + buttonBar.add(backButton); + + backButton.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { + howIWorkArea.setVisible(false); + javacArea.setVisible(true); + ideArea.setVisible(true); + appWindow.pack(); + } + }); + + constraints.gridy = 2; + container.add(buttonBar, constraints); + + return container; + } + + private Component buildUninstallArea() { + JPanel container = new JPanel(); + + container.setLayout(new GridBagLayout()); + GridBagConstraints constraints = new GridBagConstraints(); + constraints.anchor = GridBagConstraints.WEST; + + container.add(new JLabel(UNINSTALL_TITLE), constraints); + + constraints.gridy = 1; + constraints.insets = new Insets(8, 0, 0, 16); + container.add(new JLabel(UNINSTALL_EXPLANATION), constraints); + + uninstallBox = Box.createVerticalBox(); + constraints.gridy = 2; + constraints.fill = GridBagConstraints.HORIZONTAL; + container.add(uninstallBox, constraints); + + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.gridy = 3; + container.add(new JLabel("Are you sure?"), constraints); + + Box buttonBar = Box.createHorizontalBox(); + JButton noButton = new JButton("No - Don't uninstall"); + buttonBar.add(noButton); + buttonBar.add(Box.createHorizontalGlue()); + JButton yesButton = new JButton("Yes - uninstall Lombok"); + buttonBar.add(yesButton); + + noButton.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { + uninstallArea.setVisible(false); + javacArea.setVisible(true); + ideArea.setVisible(true); + appWindow.pack(); + } + }); + + yesButton.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { + doUninstall(); + } + }); + + constraints.gridy = 4; + container.add(buttonBar, constraints); + + return container; + } + + private Component buildJavacArea() { + JPanel container = new JPanel(); + + container.setLayout(new GridBagLayout()); + GridBagConstraints constraints = new GridBagConstraints(); + constraints.anchor = GridBagConstraints.WEST; + constraints.insets = new Insets(8, 0, 0, 16); + + container.add(new JLabel(JAVAC_TITLE), constraints); + + constraints.gridy = 1; + constraints.weightx = 1.0; + constraints.fill = GridBagConstraints.HORIZONTAL; + container.add(new JLabel(JAVAC_EXPLANATION), constraints); + + JLabel example = new JLabel(JAVAC_EXAMPLE); + + constraints.gridy = 2; + container.add(example, constraints); + return container; + } + + private Component buildIdeArea() { + JPanel container = new JPanel(); + + container.setLayout(new GridBagLayout()); + GridBagConstraints constraints = new GridBagConstraints(); + constraints.anchor = GridBagConstraints.WEST; + + constraints.insets = new Insets(8, 0, 0, 16); + container.add(new JLabel(IDE_TITLE), constraints); + + constraints.gridy = 1; + container.add(new JLabel(IDE_EXPLANATION), constraints); + + constraints.gridy = 2; + loadingExpl = Box.createHorizontalBox(); + loadingExpl.add(new JLabel(new ImageIcon(Installer.class.getResource("loading.gif")))); + loadingExpl.add(new JLabel(IDE_LOADING_EXPLANATION)); + container.add(loadingExpl, constraints); + + constraints.weightx = 1.0; + constraints.gridy = 3; + constraints.fill = GridBagConstraints.HORIZONTAL; + idesList = new IdesList(); + + JScrollPane idesListScroll = new JScrollPane(idesList); + idesListScroll.setBackground(Color.WHITE); + idesListScroll.getViewport().setBackground(Color.WHITE); + container.add(idesListScroll, constraints); + + Thread findIdesThread = new Thread() { + @Override public void run() { + try { + final List<IdeLocation> locations = new ArrayList<IdeLocation>(); + final List<CorruptedIdeLocationException> problems = new ArrayList<CorruptedIdeLocationException>(); + Installer.autoDiscover(locations, problems); + + SwingUtilities.invokeLater(new Runnable() { + @Override public void run() { + for (IdeLocation location : locations) { + try { + idesList.addLocation(location); + } catch (Throwable t) { + handleException(t); + } + } + + for (CorruptedIdeLocationException problem : problems) { + problem.showDialog(appWindow); + } + + loadingExpl.setVisible(false); + + if (locations.size() + problems.size() == 0) { + JOptionPane.showMessageDialog(appWindow, + "I can't find any IDEs on your computer.\n" + + "If you have IDEs installed on this computer, please use the " + + "'Specify Location...' button to manually point out the \n" + + "location of your IDE installation to me. Thanks!", + "Can't find IDE", JOptionPane.INFORMATION_MESSAGE); + } + } + }); + } catch (Throwable t) { + handleException(t); + } + } + }; + + findIdesThread.start(); + + Box buttonBar = Box.createHorizontalBox(); + JButton specifyIdeLocationButton = new JButton("Specify location..."); + buttonBar.add(specifyIdeLocationButton); + specifyIdeLocationButton.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent event) { + final List<Pattern> exeNames = Installer.getIdeExecutableNames(); + String file = null; + + if (IdeFinder.getOS() == OS.MAC_OS_X) { + FileDialog chooser = new FileDialog(appWindow); + chooser.setMode(FileDialog.LOAD); + chooser.setFilenameFilter(new FilenameFilter() { + @Override public boolean accept(File dir, String fileName) { + for (Pattern exeName : exeNames) if (exeName.matcher(fileName).matches()) return true; + if (new File(dir, fileName).isDirectory()) return true; + return false; + } + }); + + chooser.setVisible(true); + file = new File(chooser.getDirectory(), chooser.getFile()).getAbsolutePath(); + } else { + JFileChooser chooser = new JFileChooser(); + + chooser.setAcceptAllFileFilterUsed(false); + chooser.setFileSelectionMode(JFileChooser.FILES_ONLY); + chooser.setFileFilter(new FileFilter() { + @Override public boolean accept(File f) { + for (Pattern exeName : exeNames) if (exeName.matcher(f.getName()).matches()) return true; + if (f.isDirectory()) return true; + + return false; + } + + @Override public String getDescription() { + return "IDE Installation"; + } + }); + + switch (chooser.showDialog(appWindow, "Select")) { + case JFileChooser.APPROVE_OPTION: + file = chooser.getSelectedFile().getAbsolutePath(); + } + } + + if (file != null) { + try { + IdeLocation loc = Installer.tryAllProviders(file); + if (loc != null) idesList.addLocation(loc); + else JOptionPane.showMessageDialog(appWindow, "I can't find any IDE that lombok supports at location: " + file, "No IDE found", JOptionPane.WARNING_MESSAGE); + } catch (CorruptedIdeLocationException e) { + e.showDialog(appWindow); + } catch (Throwable t) { + handleException(t); + } + } + } + }); + + buttonBar.add(Box.createHorizontalGlue()); + installButton = new JButton("Install / Update"); + buttonBar.add(installButton); + + installButton.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { + List<IdeLocation> locationsToInstall = new ArrayList<IdeLocation>(idesList.getSelectedIdes()); + if (locationsToInstall.isEmpty()) { + JOptionPane.showMessageDialog(appWindow, "You haven't selected any IDE installations!.", "No Selection", JOptionPane.WARNING_MESSAGE); + return; + } + + install(locationsToInstall); + } + }); + + constraints.gridy = 4; + constraints.weightx = 0; + container.add(buttonBar, constraints); + + constraints.gridy = 5; + constraints.fill = GridBagConstraints.NONE; + JHyperLink showMe = new JHyperLink("Show me what this installer will do to my IDE installation."); + container.add(showMe, constraints); + showMe.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { + showWhatIDo(); + } + }); + + constraints.gridy = 6; + uninstallButton = new JHyperLink("Uninstall lombok from selected IDE installations."); + uninstallPlaceholder = new JLabel("<html> </html>"); + uninstallButton.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { + List<IdeLocation> locationsToUninstall = new ArrayList<IdeLocation>(); + for (IdeLocation location : idesList.getSelectedIdes()) { + if (location.hasLombok()) locationsToUninstall.add(location); + } + + if (locationsToUninstall.isEmpty()) { + JOptionPane.showMessageDialog(appWindow, "You haven't selected any IDE installations that have been lombok-enabled.", "No Selection", JOptionPane.WARNING_MESSAGE); + return; + } + + + uninstall(locationsToUninstall); + } + }); + container.add(uninstallButton, constraints); + uninstallPlaceholder.setVisible(false); + container.add(uninstallPlaceholder, constraints); + + + return container; + } + + private void showWhatIDo() { + javacArea.setVisible(false); + ideArea.setVisible(false); + howIWorkArea.setVisible(true); + appWindow.pack(); + } + + private void uninstall(List<IdeLocation> locations) { + javacArea.setVisible(false); + ideArea.setVisible(false); + + uninstallBox.removeAll(); + uninstallBox.add(Box.createRigidArea(new Dimension(1, 16))); + for (IdeLocation location : locations) { + JLabel label = new JLabel(location.getName()); + label.setFont(label.getFont().deriveFont(Font.BOLD)); + uninstallBox.add(label); + } + uninstallBox.add(Box.createRigidArea(new Dimension(1, 16))); + + toUninstall = locations; + uninstallArea.setVisible(true); + appWindow.pack(); + } + + private void install(final List<IdeLocation> toInstall) { + JPanel spinner = new JPanel(); + spinner.setOpaque(true); + spinner.setLayout(new FlowLayout()); + spinner.add(new JLabel(new ImageIcon(Installer.class.getResource("loading.gif")))); + appWindow.setContentPane(spinner); + + final AtomicReference<Boolean> success = new AtomicReference<Boolean>(true); + + new Thread() { + @Override public void run() { + for (IdeLocation loc : toInstall) { + try { + installSpecificMessages.add(loc.install()); + } catch (final InstallException e) { + success.set(false); + try { + SwingUtilities.invokeAndWait(new Runnable() { + @Override public void run() { + JOptionPane.showMessageDialog(appWindow, + e.getMessage(), "Install Problem", JOptionPane.ERROR_MESSAGE); + } + }); + } catch (Exception e2) { + //Shouldn't happen. + throw new RuntimeException(e2); + } + } + } + + if (success.get()) SwingUtilities.invokeLater(new Runnable() { + @Override public void run() { + StringBuilder installSpecific = new StringBuilder(); + for (String installSpecificMessage : installSpecificMessages) { + installSpecific.append("<br>").append(installSpecificMessage); + } + JOptionPane.showMessageDialog(appWindow, + "<html>Lombok has been installed on the selected IDE installations.<br>" + + "Don't forget to add <code>lombok.jar</code> to your projects, and restart your IDE!" + installSpecific.toString() + "</html>", + "Install successful", + JOptionPane.INFORMATION_MESSAGE); + appWindow.setVisible(false); + synchronized (exitMarker) { + exitMarker.set(0); + exitMarker.notifyAll(); + } + } + }); + + if (!success.get()) SwingUtilities.invokeLater(new Runnable() { + @Override public void run() { + synchronized (exitMarker) { + exitMarker.set(1); + exitMarker.notifyAll(); + } + } + }); + } + }.start(); + } + + private void doUninstall() { + JPanel spinner = new JPanel(); + spinner.setOpaque(true); + spinner.setLayout(new FlowLayout()); + spinner.add(new JLabel(new ImageIcon(Installer.class.getResource("/lombok/installer/loading.gif")))); + + appWindow.setContentPane(spinner); + + final AtomicReference<Boolean> success = new AtomicReference<Boolean>(true); + new Thread() { + @Override public void run() { + for (IdeLocation loc : toUninstall) { + try { + loc.uninstall(); + } catch (final UninstallException e) { + success.set(false); + try { + SwingUtilities.invokeAndWait(new Runnable() { + @Override public void run() { + JOptionPane.showMessageDialog(appWindow, + e.getMessage(), "Uninstall Problem", JOptionPane.ERROR_MESSAGE); + } + }); + } catch (Exception e2) { + //Shouldn't happen. + throw new RuntimeException(e2); + } + } + } + + if (success.get()) SwingUtilities.invokeLater(new Runnable() { + @Override public void run() { + JOptionPane.showMessageDialog(appWindow, "Lombok has been removed from the selected IDE installations.", "Uninstall successful", JOptionPane.INFORMATION_MESSAGE); + appWindow.setVisible(false); + System.exit(0); + } + }); + } + }.start(); + } + + private IdesList idesList = new IdesList(); + + void selectedLomboksChanged(List<IdeLocation> selectedIdes) { + boolean uninstallAvailable = false; + boolean installAvailable = false; + for (IdeLocation loc : selectedIdes) { + if (loc.hasLombok()) uninstallAvailable = true; + installAvailable = true; + } + + uninstallButton.setVisible(uninstallAvailable); + uninstallPlaceholder.setVisible(!uninstallAvailable); + installButton.setEnabled(installAvailable); + } + + private class IdesList extends JPanel implements Scrollable { + private static final long serialVersionUID = 1L; + + List<IdeLocation> locations = new ArrayList<IdeLocation>(); + + IdesList() { + setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + setBackground(Color.WHITE); + } + + List<IdeLocation> getSelectedIdes() { + List<IdeLocation> list = new ArrayList<IdeLocation>(); + for (IdeLocation loc : locations) if (loc.selected) list.add(loc); + return list; + } + + void fireSelectionChange() { + selectedLomboksChanged(getSelectedIdes()); + } + + void addLocation(final IdeLocation location) { + if (locations.contains(location)) return; + Box box = Box.createHorizontalBox(); + box.setBackground(Color.WHITE); + final JCheckBox checkbox = new JCheckBox(location.getName()); + checkbox.setBackground(Color.WHITE); + box.add(new JLabel(new ImageIcon(location.getIdeIcon()))); + box.add(checkbox); + checkbox.setSelected(true); + checkbox.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { + location.selected = checkbox.isSelected(); + fireSelectionChange(); + } + }); + + if (location.hasLombok()) { + box.add(new JLabel(new ImageIcon(Installer.class.getResource("lombokIcon.png")))); + } + box.add(Box.createHorizontalGlue()); + locations.add(location); + add(box); + getParent().doLayout(); + fireSelectionChange(); + } + + @Override public Dimension getPreferredScrollableViewportSize() { + return new Dimension(1, 100); + } + + @Override public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { + return 12; + } + + @Override public boolean getScrollableTracksViewportHeight() { + return false; + } + + @Override public boolean getScrollableTracksViewportWidth() { + return true; + } + + @Override public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { + return 1; + } + } + + private void buildChrome(Container appWindowContainer) { + JLabel leftGraphic = new JLabel(new ImageIcon(Installer.class.getResource("lombok.png"))); + + GridBagConstraints constraints = new GridBagConstraints(); + + appWindowContainer.setLayout(new GridBagLayout()); + + constraints.gridheight = 3; + constraints.gridwidth = 1; + constraints.gridx = 0; + constraints.gridy = 0; + constraints.insets = new Insets(8, 8, 8, 8); + appWindowContainer.add(leftGraphic, constraints); + constraints.insets = new Insets(0, 0, 0, 0); + + constraints.gridx++; + constraints.gridy++; + constraints.gridheight = 1; + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.ipadx = 16; + constraints.ipady = 14; + appWindowContainer.add(javacArea, constraints); + + constraints.gridy++; + appWindowContainer.add(ideArea, constraints); + + appWindowContainer.add(uninstallArea, constraints); + + appWindowContainer.add(howIWorkArea, constraints); + + constraints.gridy++; + constraints.gridwidth = 2; + constraints.gridx = 0; + constraints.weightx = 0; + constraints.weighty = 0; + constraints.ipadx = 0; + constraints.ipady = 0; + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.anchor = GridBagConstraints.SOUTHEAST; + constraints.insets = new Insets(0, 16, 8, 8); + Box buttonBar = Box.createHorizontalBox(); + JButton quitButton = new JButton("Quit Installer"); + quitButton.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { + appWindow.setVisible(false); + System.exit(0); + } + }); + final JHyperLink hyperlink = new JHyperLink(Installer.ABOUT_LOMBOK_URL.toString()); + hyperlink.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent event) { + hyperlink.setForeground(new Color(85, 145, 90)); + try { + //java.awt.Desktop doesn't exist in 1.5. + Object desktop = Class.forName("java.awt.Desktop").getMethod("getDesktop").invoke(null); + Class.forName("java.awt.Desktop").getMethod("browse", URI.class).invoke(desktop, Installer.ABOUT_LOMBOK_URL); + } catch (Exception e) { + Runtime rt = Runtime.getRuntime(); + try { + switch (IdeFinder.getOS()) { + case WINDOWS: + String[] cmd = new String[4]; + cmd[0] = "cmd.exe"; + cmd[1] = "/C"; + cmd[2] = "start"; + cmd[3] = Installer.ABOUT_LOMBOK_URL.toString(); + rt.exec(cmd); + break; + case MAC_OS_X: + rt.exec("open " + Installer.ABOUT_LOMBOK_URL.toString()); + break; + default: + case UNIX: + rt.exec("firefox " + Installer.ABOUT_LOMBOK_URL.toString()); + break; + } + } catch (Exception e2) { + JOptionPane.showMessageDialog(appWindow, + "Well, this is embarrassing. I don't know how to open a webbrowser.\n" + + "I guess you'll have to open it. Browse to:\n" + Installer.ABOUT_LOMBOK_URL + + " for more information about Lombok.", + "I'm embarrassed", JOptionPane.INFORMATION_MESSAGE); + } + } + } + }); + buttonBar.add(hyperlink); + buttonBar.add(Box.createRigidArea(new Dimension(16, 1))); + buttonBar.add(new JLabel("<html><font size=\"-1\">v" + Version.getVersion() + "</font></html>")); + + buttonBar.add(Box.createHorizontalGlue()); + buttonBar.add(quitButton); + appWindow.add(buttonBar, constraints); + } + + /** + * Makes the installer window visible. + */ + public void show() { + appWindow.setVisible(true); + if (IdeFinder.getOS() == OS.MAC_OS_X) { + try { + AppleNativeLook.go(); + } catch (Throwable ignore) { + //We're just prettying up the app. If it fails, meh. + } + } + } + + private static final String IDE_TITLE = + "<html><font size=\"+1\"><b><i>IDEs</i></b></font></html>"; + + private static final String IDE_EXPLANATION = + "<html>Lombok can update your Eclipse and Netbeans to fully support all Lombok features.<br>" + + "Select IDE installations below and hit 'Install/Update'.</html>"; + + private static final String IDE_LOADING_EXPLANATION = + "Scanning your drives for IDE installations..."; + + private static final String JAVAC_TITLE = + "<html><font size=\"+1\"><b><i>Javac</i></b></font> (and tools that invoke javac such as <i>ant</i> and <i>maven</i>)</html>"; + + private static final String JAVAC_EXPLANATION = + "<html>Lombok works 'out of the box' with javac.<br>Just make sure the lombok.jar is in your classpath when you compile."; + + private static final String JAVAC_EXAMPLE = + "<html>Example: <code>javac -cp lombok.jar MyCode.java</code></html>"; + + private static final String UNINSTALL_TITLE = + "<html><font size=\"+1\"><b><i>Uninstall</i></b></font></html>"; + + private static final String UNINSTALL_EXPLANATION = + "<html>Uninstall Lombok from the following IDE Installations?</html>"; + + private static final String HOW_I_WORK_TITLE = + "<html><font size=\"+1\"><b><i>What this installer does</i></b></font></html>"; + + private static final String HOW_I_WORK_EXPLANATION = + "<html><h2>Eclipse</h2><ol>" + + "<li>First, I copy myself (lombok.jar) to your Eclipse install directory.</li>" + + "<li>Then, I edit the <i>eclipse.ini</i> file to add the following two entries:<br>" + + "<pre>-Xbootclasspath/a:lombok.jar<br>" + + "-javaagent:lombok.jar</pre></li></ol>" + + "On Mac OS X, eclipse.ini is hidden in<br>" + + "<code>Eclipse.app/Contents/MacOS</code> so that's where I place the jar files.<p>" + + "<h2>Netbeans</h2><ol>" + + "<li>First, I copy myself (lombok.jar) to your Netbeans install directory.</li>" + + "<lI>Then, I edit <i>etc%1$snetbeans.conf</i> to add the following argument to<br><b>netbeans_default_options</b>:<br>" + + "<pre>-J-javaagent:lombok.jar</pre></li></ol>" + + "On Mac OS X, your netbeans directory is hidden in<br>" + + "<code>NetBeans.app/Contents/Resources/NetBeans</code></html>"; + + private static class JHyperLink extends JButton { + private static final long serialVersionUID = 1L; + + public JHyperLink(String text) { + super(); + setFont(getFont().deriveFont(Collections.singletonMap(TextAttribute.UNDERLINE, 1))); + setText(text); + setBorder(null); + setContentAreaFilled(false); + setForeground(Color.BLUE); + setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + setMargin(new Insets(0, 0, 0, 0)); + } + } +} diff --git a/src/installer/lombok/installer/UninstallException.java b/src/installer/lombok/installer/UninstallException.java new file mode 100644 index 00000000..dda6c143 --- /dev/null +++ b/src/installer/lombok/installer/UninstallException.java @@ -0,0 +1,10 @@ +package lombok.installer; + +/** + * Thrown when uninstallation of lombok into an IDE fails. + */ +public class UninstallException extends Exception { + public UninstallException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/installer/lombok/installer/EclipseFinder.java b/src/installer/lombok/installer/eclipse/EclipseFinder.java index 8e45852c..256e6221 100644 --- a/src/installer/lombok/installer/EclipseFinder.java +++ b/src/installer/lombok/installer/eclipse/EclipseFinder.java @@ -19,120 +19,22 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package lombok.installer; +package lombok.installer.eclipse; import static java.util.Arrays.asList; import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.net.URLDecoder; -import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import lombok.Lombok; -import lombok.core.Version; -import lombok.installer.EclipseLocation.NotAnEclipseException; +import lombok.installer.IdeFinder; +import lombok.installer.IdeLocation; +import lombok.installer.CorruptedIdeLocationException; -/** Utility class for doing various OS-specific operations related to finding Eclipse installations. */ -class EclipseFinder { - private EclipseFinder() { - //Prevent instantiation. - } - - /** - * Returns a File object pointing to our own jar file. Will obviously fail if the installer was started via - * a jar that wasn't accessed via the file-system, or if its started via e.g. unpacking the jar. - */ - static File findOurJar() { - try { - URI uri = EclipseFinder.class.getResource("/" + EclipseFinder.class.getName().replace('.', '/') + ".class").toURI(); - Pattern p = Pattern.compile("^jar:file:([^\\!]+)\\!.*\\.class$"); - Matcher m = p.matcher(uri.toString()); - if (!m.matches()) return new File("lombok.jar"); - String rawUri = m.group(1); - return new File(URLDecoder.decode(rawUri, Charset.defaultCharset().name())); - } catch (Exception e) { - throw Lombok.sneakyThrow(e); - } - } - - private static final AtomicBoolean windowsDriveInfoLibLoaded = new AtomicBoolean(false); - private static void loadWindowsDriveInfoLib() throws IOException { - if (!windowsDriveInfoLibLoaded.compareAndSet(false, true)) return; - - final String prefix = "lombok-" + Version.getVersion() + "-"; - - File temp = File.createTempFile("lombok", ".mark"); - File dll1 = new File(temp.getParentFile(), prefix + "WindowsDriveInfo-i386.dll"); - File dll2 = new File(temp.getParentFile(), prefix + "WindowsDriveInfo-x86_64.dll"); - temp.delete(); - dll1.deleteOnExit(); - dll2.deleteOnExit(); - try { - if (unpackDLL("WindowsDriveInfo-i386.dll", dll1)) { - System.load(dll1.getAbsolutePath()); - return; - } - } catch (Throwable ignore) {} - - try { - if (unpackDLL("WindowsDriveInfo-x86_64.dll", dll2)) { - System.load(dll2.getAbsolutePath()); - } - } catch (Throwable ignore) {} - } - - private static boolean unpackDLL(String dllName, File target) throws IOException { - InputStream in = EclipseFinder.class.getResourceAsStream(dllName); - try { - try { - FileOutputStream out = new FileOutputStream(target); - try { - byte[] b = new byte[32000]; - while (true) { - int r = in.read(b); - if (r == -1) break; - out.write(b, 0, r); - } - } finally { - out.close(); - } - } catch (IOException e) { - //Fall through - if there is a file named lombok-WindowsDriveInfo-arch.dll, we'll try it. - return target.exists() && target.canRead(); - } - } finally { - in.close(); - } - - return true; - } - - /** - * Returns all drive letters on windows, regardless of what kind of drive is represented. - * - * @return A List of drive letters, such as ["A", "C", "D", "X"]. - */ - static List<String> getDrivesOnWindows() throws Throwable { - loadWindowsDriveInfoLib(); - - List<String> drives = new ArrayList<String>(); - - WindowsDriveInfo info = new WindowsDriveInfo(); - for (String drive : info.getLogicalDrives()) { - if (info.isFixedDisk(drive)) drives.add(drive); - } - - return drives; - } - +import org.mangosdk.spi.ProviderFor; + +@ProviderFor(IdeFinder.class) +public class EclipseFinder extends IdeFinder { /** * Returns a list of paths of Eclipse installations. * Eclipse installations are found by checking for the existence of 'eclipse.exe' in the following locations: @@ -144,7 +46,7 @@ class EclipseFinder { * Where 'X' is tried for all local disk drives, unless there's a problem calling fsutil, in which case only * C: is tried. */ - private static void findEclipseOnWindows(List<EclipseLocation> locations, List<NotAnEclipseException> problems) { + private static void findEclipseOnWindows(List<IdeLocation> locations, List<CorruptedIdeLocationException> problems) { List<String> driveLetters = asList("C"); try { driveLetters = getDrivesOnWindows(); @@ -165,8 +67,8 @@ class EclipseFinder { String eclipseLocation = findEclipseOnWindows1(dir); if (eclipseLocation != null) { try { - locations.add(EclipseLocation.create(eclipseLocation)); - } catch (NotAnEclipseException e) { + locations.add(EclipseLocationProvider.create0(eclipseLocation)); + } catch (CorruptedIdeLocationException e) { problems.add(e); } } @@ -177,12 +79,12 @@ class EclipseFinder { if (dir.getName().toLowerCase().contains("program files")) { for (File dir2 : dir.listFiles()) { if (!dir2.isDirectory()) continue; - if (dir.getName().toLowerCase().contains("eclipse")) { - String eclipseLocation = findEclipseOnWindows1(dir); + if (dir2.getName().toLowerCase().contains("eclipse")) { + String eclipseLocation = findEclipseOnWindows1(dir2); if (eclipseLocation != null) { try { - locations.add(EclipseLocation.create(eclipseLocation)); - } catch (NotAnEclipseException e) { + locations.add(EclipseLocationProvider.create0(eclipseLocation)); + } catch (CorruptedIdeLocationException e) { problems.add(e); } } @@ -213,7 +115,8 @@ class EclipseFinder { * eclipses that can't be installed. Provide an empty list; this * method will fill it. */ - static void findEclipses(List<EclipseLocation> locations, List<NotAnEclipseException> problems) { + @Override + public void findIdes(List<IdeLocation> locations, List<CorruptedIdeLocationException> problems) { switch (getOS()) { case WINDOWS: findEclipseOnWindows(locations, problems); @@ -228,38 +131,8 @@ class EclipseFinder { } } - static enum OS { - MAC_OS_X, WINDOWS, UNIX; - } - - static OS getOS() { - String prop = System.getProperty("os.name", "").toLowerCase(); - if (prop.matches("^.*\\bmac\\b.*$")) return OS.MAC_OS_X; - if (prop.matches("^.*\\bdarwin\\b.*$")) return OS.MAC_OS_X; - if (prop.matches("^.*\\bwin(dows)\\b.*$")) return OS.WINDOWS; - - return OS.UNIX; - } - - /** - * Returns the proper name of the executable for the local OS. - * - * @return 'Eclipse.app' on OS X, 'eclipse.exe' on Windows, and 'eclipse' on other OSes. - */ - static String getEclipseExecutableName() { - switch (getOS()) { - case WINDOWS: - return "eclipse.exe"; - case MAC_OS_X: - return "Eclipse.app"; - default: - case UNIX: - return "eclipse"; - } - } - /** Scans a couple of likely locations on linux. */ - private static void findEclipseOnUnix(List<EclipseLocation> locations, List<NotAnEclipseException> problems) { + private void findEclipseOnUnix(List<IdeLocation> locations, List<CorruptedIdeLocationException> problems) { List<String> guesses = new ArrayList<String>(); File d; @@ -278,14 +151,14 @@ class EclipseFinder { for (String guess : guesses) { try { - locations.add(EclipseLocation.create(guess)); - } catch (NotAnEclipseException e) { + locations.add(EclipseLocationProvider.create0(guess)); + } catch (CorruptedIdeLocationException e) { problems.add(e); } } } - private static void findEclipseInSubDir(String dir, List<String> guesses) { + private void findEclipseInSubDir(String dir, List<String> guesses) { File d = new File(dir); if (!d.isDirectory()) return; for (File f : d.listFiles()) { @@ -299,23 +172,23 @@ class EclipseFinder { /** * Scans /Applications for any folder named 'Eclipse' */ - private static void findEclipseOnMac(List<EclipseLocation> locations, List<NotAnEclipseException> problems) { + private void findEclipseOnMac(List<IdeLocation> locations, List<CorruptedIdeLocationException> problems) { for (File dir : new File("/Applications").listFiles()) { if (!dir.isDirectory()) continue; if (dir.getName().toLowerCase().equals("eclipse.app")) { //This would be kind of an unorthodox Eclipse installation, but if Eclipse ever //moves to this more maclike installation concept, our installer can still handle it. try { - locations.add(EclipseLocation.create("/Applications")); - } catch (NotAnEclipseException e) { + locations.add(EclipseLocationProvider.create0("/Applications")); + } catch (CorruptedIdeLocationException e) { problems.add(e); } } if (dir.getName().toLowerCase().contains("eclipse")) { if (new File(dir, "Eclipse.app").exists()) { try { - locations.add(EclipseLocation.create(dir.toString())); - } catch (NotAnEclipseException e) { + locations.add(EclipseLocationProvider.create0(dir.toString())); + } catch (CorruptedIdeLocationException e) { problems.add(e); } } diff --git a/src/installer/lombok/installer/EclipseLocation.java b/src/installer/lombok/installer/eclipse/EclipseLocation.java index c43c5042..7b3a5536 100644 --- a/src/installer/lombok/installer/EclipseLocation.java +++ b/src/installer/lombok/installer/eclipse/EclipseLocation.java @@ -19,7 +19,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package lombok.installer; +package lombok.installer.eclipse; import java.io.BufferedReader; import java.io.File; @@ -28,162 +28,39 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.net.URL; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.swing.JFrame; -import javax.swing.JOptionPane; +import lombok.installer.CorruptedIdeLocationException; +import lombok.installer.IdeFinder; +import lombok.installer.IdeLocation; +import lombok.installer.InstallException; +import lombok.installer.UninstallException; /** * Represents an Eclipse installation. * An instance can figure out if an Eclipse installation has been lombok-ified, and can * install and uninstall lombok from the Eclipse installation. */ -final class EclipseLocation { - private static final String OS_NEWLINE; - - static { - String os = System.getProperty("os.name", ""); - - if ("Mac OS".equals(os)) OS_NEWLINE = "\r"; - else if (os.toLowerCase().contains("windows")) OS_NEWLINE = "\r\n"; - else OS_NEWLINE = "\n"; - } - +public final class EclipseLocation extends IdeLocation { private final String name; private final File eclipseIniPath; private volatile boolean hasLombok; - /** Toggling the 'selected' checkbox in the GUI is tracked via this boolean */ - boolean selected = true; + private static final String OS_NEWLINE = IdeFinder.getOS().getLineEnding(); - /** - * Thrown when creating a new EclipseLocation with a path object that doesn't, in fact, - * point at an Eclipse installation. - */ - static final class NotAnEclipseException extends Exception { - private static final long serialVersionUID = 1L; - - public NotAnEclipseException(String message, Throwable cause) { - super(message, cause); - } - - /** - * Renders a message dialog with information about what went wrong. - */ - void showDialog(JFrame appWindow) { - JOptionPane.showMessageDialog(appWindow, getMessage(), "Cannot configure Eclipse installation", JOptionPane.WARNING_MESSAGE); - } - } - - private EclipseLocation(String nameOfLocation, File pathToEclipseIni) throws NotAnEclipseException { + EclipseLocation(String nameOfLocation, File pathToEclipseIni) throws CorruptedIdeLocationException { this.name = nameOfLocation; this.eclipseIniPath = pathToEclipseIni; try { this.hasLombok = checkForLombok(eclipseIniPath); } catch (IOException e) { - throw new NotAnEclipseException( + throw new CorruptedIdeLocationException( "I can't read the configuration file of the Eclipse installed at " + name + "\n" + - "You may need to run this installer with root privileges if you want to modify that Eclipse.", e); - } - } - - private static final List<String> eclipseExecutableNames = Collections.unmodifiableList(Arrays.asList( - "eclipse.app", "eclipse.exe", "eclipse")); - - /** - * Create a new EclipseLocation by pointing at either the directory contain the Eclipse executable, or the executable itself, - * or an eclipse.ini file. - * - * @throws NotAnEclipseException - * If this isn't an Eclipse executable or a directory with an - * Eclipse executable. - */ - public static EclipseLocation create(String path) throws NotAnEclipseException { - if (path == null) throw new NullPointerException("path"); - File p = new File(path); - - if (!p.exists()) throw new NotAnEclipseException("File does not exist: " + path, null); - if (p.isDirectory()) { - for (String possibleExeName : eclipseExecutableNames) { - File f = new File(p, possibleExeName); - if (f.exists()) return findEclipseIniFromExe(f, 0); - } - - File f = new File(p, "eclipse.ini"); - if (f.exists()) return new EclipseLocation(getFilePath(p), f); - } - - if (p.isFile()) { - if (p.getName().equalsIgnoreCase("eclipse.ini")) { - return new EclipseLocation(getFilePath(p.getParentFile()), p); - } - - if (eclipseExecutableNames.contains(p.getName().toLowerCase())) { - return findEclipseIniFromExe(p, 0); - } - } - - throw new NotAnEclipseException("This path does not appear to contain an Eclipse installation: " + p, null); - } - - private static EclipseLocation findEclipseIniFromExe(File exePath, int loopCounter) throws NotAnEclipseException { - /* Try looking for eclipse.ini as sibling to the executable */ { - File ini = new File(exePath.getParentFile(), "eclipse.ini"); - if (ini.isFile()) return new EclipseLocation(getFilePath(exePath), ini); - } - - /* Try looking for Eclipse/app/Contents/MacOS/eclipse.ini as sibling to executable; this works on Mac OS X. */ { - File ini = new File(exePath.getParentFile(), "Eclipse.app/Contents/MacOS/eclipse.ini"); - if (ini.isFile()) return new EclipseLocation(getFilePath(exePath), ini); - } - - /* If executable is a soft link, follow it and retry. */ { - if (loopCounter < 50) { - try { - String oPath = exePath.getAbsolutePath(); - String nPath = exePath.getCanonicalPath(); - if (!oPath.equals(nPath)) try { - return findEclipseIniFromExe(new File(nPath), loopCounter + 1); - } catch (NotAnEclipseException ignore) { - // Unlinking didn't help find an eclipse, so continue. - } - } catch (IOException ignore) { /* okay, that didn't work, assume it isn't a soft link then. */ } - } - } - - /* If executable is a linux LSB-style path, then look in the usual places that package managers like apt-get use.*/ { - String path = exePath.getAbsolutePath(); - try { - path = exePath.getCanonicalPath(); - } catch (IOException ignore) { /* We'll stick with getAbsolutePath()'s result then. */ } - - if (path.equals("/usr/bin/eclipse") || path.equals("/bin/eclipse") || path.equals("/usr/local/bin/eclipse")) { - File ini = new File("/usr/lib/eclipse/eclipse.ini"); - if (ini.isFile()) return new EclipseLocation(path, ini); - ini = new File("/usr/local/lib/eclipse/eclipse.ini"); - if (ini.isFile()) return new EclipseLocation(path, ini); - ini = new File("/usr/local/etc/eclipse/eclipse.ini"); - if (ini.isFile()) return new EclipseLocation(path, ini); - ini = new File("/etc/eclipse.ini"); - if (ini.isFile()) return new EclipseLocation(path, ini); - } - } - - /* If we get this far, we lose. */ - throw new NotAnEclipseException("This path does not appear to contain an eclipse installation: " + exePath, null); - } - - public static String getFilePath(File p) { - try { - return p.getCanonicalPath(); - } catch (IOException e) { - String x = p.getAbsolutePath(); - return x == null ? p.getPath() : x; + "You may need to run this installer with root privileges if you want to modify that Eclipse.", "eclipse", e); } } @@ -201,14 +78,16 @@ final class EclipseLocation { * * Executables: "eclipse.exe" (Windows), "Eclipse.app" (Mac OS X), "eclipse" (Linux and other unixes). */ - String getName() { + @Override + public String getName() { return name; } /** * @return true if the Eclipse installation has been instrumented with lombok. */ - boolean hasLombok() { + @Override + public boolean hasLombok() { return hasLombok; } @@ -234,15 +113,6 @@ final class EclipseLocation { } } - /** Thrown when uninstalling lombok fails. */ - static class UninstallException extends Exception { - private static final long serialVersionUID = 1L; - - public UninstallException(String message, Throwable cause) { - super(message, cause); - } - } - /** Returns directories that may contain lombok.jar files that need to be deleted. */ private List<File> getUninstallDirs() { List<File> result = new ArrayList<File>(); @@ -263,7 +133,8 @@ final class EclipseLocation { * installation. bugs in the uninstall code will probably throw * other exceptions; this is intentional. */ - void uninstall() throws UninstallException { + @Override + public void uninstall() throws UninstallException { for (File dir : getUninstallDirs()) { File lombokJar = new File(dir, "lombok.jar"); if (lombokJar.exists()) { @@ -327,19 +198,10 @@ final class EclipseLocation { } } - /** Thrown when installing lombok fails. */ - static class InstallException extends Exception { - private static final long serialVersionUID = 1L; - - public InstallException(String message, Throwable cause) { - super(message, cause); - } - } - private static String generateWriteErrorMessage() { String osSpecificError; - switch (EclipseFinder.getOS()) { + switch (IdeFinder.getOS()) { default: case MAC_OS_X: case UNIX: @@ -363,21 +225,20 @@ final class EclipseLocation { * installation. bugs in the install code will probably throw * other exceptions; this is intentional. */ - void install() throws InstallException { + @Override + public String install() throws InstallException { // For whatever reason, relative paths in your eclipse.ini file don't work on linux, but only for -javaagent. // If someone knows how to fix this, please do so, as this current hack solution (putting the absolute path // to the jar files in your eclipse.ini) means you can't move your eclipse around on linux without lombok // breaking it. NB: rerunning lombok.jar installer and hitting 'update' will fix it if you do that. - boolean fullPathRequired = EclipseFinder.getOS() == EclipseFinder.OS.UNIX; + boolean fullPathRequired = IdeFinder.getOS() == EclipseFinder.OS.UNIX; boolean installSucceeded = false; StringBuilder newContents = new StringBuilder(); - //If 'installSucceeded' is true here, something very weird is going on, but instrumenting all of them - //is no less bad than aborting, and this situation should be rare to the point of non-existence. File lombokJar = new File(eclipseIniPath.getParentFile(), "lombok.jar"); - File ourJar = EclipseFinder.findOurJar(); + File ourJar = findOurJar(); byte[] b = new byte[524288]; boolean readSucceeded = true; try { @@ -448,9 +309,9 @@ final class EclipseLocation { String fullPathToLombok = fullPathRequired ? (lombokJar.getParentFile().getCanonicalPath() + File.separator) : ""; newContents.append(String.format( - "-javaagent:%slombok.jar", fullPathToLombok)).append(OS_NEWLINE); + "-javaagent:%s", escapePath(fullPathToLombok + "lombok.jar"))).append(OS_NEWLINE); newContents.append(String.format( - "-Xbootclasspath/a:%slombok.jar", fullPathToLombok)).append(OS_NEWLINE); + "-Xbootclasspath/a:%s", escapePath(fullPathToLombok + "lombok.jar"))).append(OS_NEWLINE); FileOutputStream fos = new FileOutputStream(eclipseIniPath); try { @@ -470,5 +331,13 @@ final class EclipseLocation { if (!installSucceeded) { throw new InstallException("I can't find the eclipse.ini file. Is this a real Eclipse installation?", null); } + + return "If you start eclipse with a custom -vm parameter, you'll need to add:<br>" + + "<code>-vmargs -Xbootclasspath/a:lombok.jar -javaagent:lombok.jar</code><br>" + + "as parameter as well."; + } + + @Override public URL getIdeIcon() { + return EclipseLocation.class.getResource("eclipse.png"); } } diff --git a/src/installer/lombok/installer/eclipse/EclipseLocationProvider.java b/src/installer/lombok/installer/eclipse/EclipseLocationProvider.java new file mode 100644 index 00000000..0cef59f4 --- /dev/null +++ b/src/installer/lombok/installer/eclipse/EclipseLocationProvider.java @@ -0,0 +1,144 @@ +/* + * Copyright © 2009 Reinier Zwitserloot and Roel Spilker. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.installer.eclipse; + +import static lombok.installer.IdeLocation.canonical; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.regex.Pattern; + +import lombok.installer.IdeLocation; +import lombok.installer.IdeLocationProvider; +import lombok.installer.CorruptedIdeLocationException; +import lombok.installer.IdeFinder.OS; + +import org.mangosdk.spi.ProviderFor; + +@ProviderFor(IdeLocationProvider.class) +public class EclipseLocationProvider implements IdeLocationProvider { + @Override public IdeLocation create(String path) throws CorruptedIdeLocationException { + return create0(path); + } + + private static final List<String> eclipseExecutableNames = Collections.unmodifiableList(Arrays.asList( + "eclipse.app", "eclipse.exe", "eclipse")); + + /** + * Create a new EclipseLocation by pointing at either the directory contains the Eclipse executable, or the executable itself, + * or an eclipse.ini file. + * + * @throws NotAnIdeLocationException + * If this isn't an Eclipse executable or a directory with an + * Eclipse executable. + */ + static EclipseLocation create0(String path) throws CorruptedIdeLocationException { + if (path == null) throw new NullPointerException("path"); + File p = new File(path); + + if (!p.exists()) return null; + if (p.isDirectory()) { + for (String possibleExeName : eclipseExecutableNames) { + File f = new File(p, possibleExeName); + if (f.exists()) return findEclipseIniFromExe(f, 0); + } + + File f = new File(p, "eclipse.ini"); + if (f.exists()) return new EclipseLocation(canonical(p), f); + } + + if (p.isFile()) { + if (p.getName().equalsIgnoreCase("eclipse.ini")) { + return new EclipseLocation(canonical(p.getParentFile()), p); + } + + if (eclipseExecutableNames.contains(p.getName().toLowerCase())) { + return findEclipseIniFromExe(p, 0); + } + } + + return null; + } + + private static EclipseLocation findEclipseIniFromExe(File exePath, int loopCounter) throws CorruptedIdeLocationException { + /* Try looking for eclipse.ini as sibling to the executable */ { + File ini = new File(exePath.getParentFile(), "eclipse.ini"); + if (ini.isFile()) return new EclipseLocation(canonical(exePath), ini); + } + + /* Try looking for Eclipse/app/Contents/MacOS/eclipse.ini as sibling to executable; this works on Mac OS X. */ { + File ini = new File(exePath.getParentFile(), "Eclipse.app/Contents/MacOS/eclipse.ini"); + if (ini.isFile()) return new EclipseLocation(canonical(exePath), ini); + } + + /* If executable is a soft link, follow it and retry. */ { + if (loopCounter < 50) { + try { + String oPath = exePath.getAbsolutePath(); + String nPath = exePath.getCanonicalPath(); + if (!oPath.equals(nPath)) try { + EclipseLocation loc = findEclipseIniFromExe(new File(nPath), loopCounter + 1); + if (loc != null) return loc; + } catch (CorruptedIdeLocationException ignore) { + // Unlinking didn't help find an eclipse, so continue. + } + } catch (IOException ignore) { /* okay, that didn't work, assume it isn't a soft link then. */ } + } + } + + /* If executable is a linux LSB-style path, then look in the usual places that package managers like apt-get use.*/ { + String path = exePath.getAbsolutePath(); + try { + path = exePath.getCanonicalPath(); + } catch (IOException ignore) { /* We'll stick with getAbsolutePath()'s result then. */ } + + if (path.equals("/usr/bin/eclipse") || path.equals("/bin/eclipse") || path.equals("/usr/local/bin/eclipse")) { + File ini = new File("/usr/lib/eclipse/eclipse.ini"); + if (ini.isFile()) return new EclipseLocation(path, ini); + ini = new File("/usr/local/lib/eclipse/eclipse.ini"); + if (ini.isFile()) return new EclipseLocation(path, ini); + ini = new File("/usr/local/etc/eclipse/eclipse.ini"); + if (ini.isFile()) return new EclipseLocation(path, ini); + ini = new File("/etc/eclipse.ini"); + if (ini.isFile()) return new EclipseLocation(path, ini); + } + } + + /* If we get this far, we lose. */ + return null; + } + + @Override public Pattern getLocationSelectors(OS os) { + switch (os) { + case MAC_OS_X: + return Pattern.compile("^(eclipse|eclipse\\.ini|eclipse\\.app)$", Pattern.CASE_INSENSITIVE); + case WINDOWS: + return Pattern.compile("^(eclipse\\.exe|eclipse\\.ini)$", Pattern.CASE_INSENSITIVE); + default: + case UNIX: + return Pattern.compile("^(eclipse|eclipse\\.ini)$", Pattern.CASE_INSENSITIVE); + } + } +} diff --git a/src/installer/lombok/installer/eclipse/eclipse.png b/src/installer/lombok/installer/eclipse/eclipse.png Binary files differnew file mode 100644 index 00000000..ba652d33 --- /dev/null +++ b/src/installer/lombok/installer/eclipse/eclipse.png diff --git a/src/installer/lombok/installer/netbeans/NetbeansFinder.java b/src/installer/lombok/installer/netbeans/NetbeansFinder.java new file mode 100644 index 00000000..5186d639 --- /dev/null +++ b/src/installer/lombok/installer/netbeans/NetbeansFinder.java @@ -0,0 +1,201 @@ +/* + * Copyright © 2009 Reinier Zwitserloot and Roel Spilker. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.installer.netbeans; + +import static java.util.Arrays.asList; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import lombok.installer.CorruptedIdeLocationException; +import lombok.installer.IdeFinder; +import lombok.installer.IdeLocation; + +import org.mangosdk.spi.ProviderFor; + +/** Utility class for doing various OS-specific operations related to finding Netbeans installations. */ +@ProviderFor(IdeFinder.class) +public class NetbeansFinder extends IdeFinder { + /** + * Returns a list of paths of Netbeans installations. + * + * Netbeans installations are found by checking for the existence of 'netbeans.exe' in the following locations: + * <ul> + * <li>X:\*Program Files*\*NetBeans*</li> + * <li>X:\*NetBeans*</li> + * </ul> + * + * Where 'X' is tried for all local disk drives, unless there's a problem calling fsutil, in which case only + * C: is tried. + */ + private void findNetbeansOnWindows(List<IdeLocation> locations, List<CorruptedIdeLocationException> problems) { + List<String> driveLetters = asList("C"); + try { + driveLetters = getDrivesOnWindows(); + } catch (Throwable ignore) { + ignore.printStackTrace(); + } + + //Various try/catch/ignore statements are in this for loop. Weird conditions on the disk can cause exceptions, + //such as an unformatted drive causing a NullPointerException on listFiles. Best action is almost invariably to just + //continue onwards. + for (String letter : driveLetters) { + try { + File f = new File(letter + ":\\"); + for (File dir : f.listFiles()) { + if (!dir.isDirectory()) continue; + try { + if (dir.getName().toLowerCase().contains("netbeans")) { + String netbeansLocation = findNetbeansOnWindows1(dir); + if (netbeansLocation != null) { + try { + locations.add(NetbeansLocationProvider.create0(netbeansLocation)); + } catch (CorruptedIdeLocationException e) { + problems.add(e); + } + } + } + } catch (Exception ignore) {} + + try { + if (dir.getName().toLowerCase().contains("program files")) { + for (File dir2 : dir.listFiles()) { + if (!dir2.isDirectory()) continue; + if (dir2.getName().toLowerCase().contains("netbeans")) { + String netbeansLocation = findNetbeansOnWindows1(dir2); + if (netbeansLocation != null) { + try { + locations.add(NetbeansLocationProvider.create0(netbeansLocation)); + } catch (CorruptedIdeLocationException e) { + problems.add(e); + } + } + } + } + } + } catch (Exception ignore) {} + } + } catch (Exception ignore) {} + } + } + + /** Checks if the provided directory contains 'netbeans.exe', and if so, returns the directory, otherwise null. */ + private String findNetbeansOnWindows1(File dir) { + if (new File(dir, "netbeans.exe").isFile()) return dir.getAbsolutePath(); + return null; + } + + /** + * Calls the OS-dependent 'find Netbeans' routine. If the local OS doesn't have a routine written for it, + * null is returned. + * + * @param locations + * List of valid netbeans locations - provide an empty list; this + * method will fill it. + * @param problems + * List of netbeans locations that seem to contain half-baked + * netbeanses that can't be installed. Provide an empty list; this + * method will fill it. + */ + @Override + public void findIdes(List<IdeLocation> locations, List<CorruptedIdeLocationException> problems) { + switch (getOS()) { + case WINDOWS: + findNetbeansOnWindows(locations, problems); + break; + case MAC_OS_X: + findNetbeansOnMac(locations, problems); + break; + default: + case UNIX: + findNetbeansOnUnix(locations, problems); + break; + } + } + + /** Scans a couple of likely locations on linux. */ + private void findNetbeansOnUnix(List<IdeLocation> locations, List<CorruptedIdeLocationException> problems) { + List<String> guesses = new ArrayList<String>(); + + File d; + + d = new File("/usr/bin/netbeans"); + if (d.exists()) guesses.add(d.getPath()); + d = new File("/usr/local/bin/netbeans"); + if (d.exists()) guesses.add(d.getPath()); + d = new File(System.getProperty("user.home", "."), "bin/netbeans"); + if (d.exists()) guesses.add(d.getPath()); + + findNetbeansInSubDir("/usr/local/share", guesses); + findNetbeansInSubDir("/usr/local", guesses); + findNetbeansInSubDir("/usr/share", guesses); + findNetbeansInSubDir(System.getProperty("user.home", "."), guesses); + + for (String guess : guesses) { + try { + locations.add(NetbeansLocationProvider.create0(guess)); + } catch (CorruptedIdeLocationException e) { + problems.add(e); + } + } + } + + private static void findNetbeansInSubDir(String dir, List<String> guesses) { + File d = new File(dir); + if (!d.isDirectory()) return; + for (File f : d.listFiles()) { + if (f.isDirectory() && f.getName().toLowerCase().contains("netbeans")) { + File possible = new File(f, "bin/netbeans.exe"); + if (possible.exists()) guesses.add(possible.getAbsolutePath()); + } + } + } + + /** + * Scans /Applications for any folder named 'Eclipse' + */ + private static void findNetbeansOnMac(List<IdeLocation> locations, List<CorruptedIdeLocationException> problems) { + for (File dir : new File("/Applications").listFiles()) { + if (!dir.isDirectory()) continue; + if (dir.getName().toLowerCase().startsWith("netbeans") && dir.getName().toLowerCase().endsWith(".app")) { + try { + locations.add(NetbeansLocationProvider.create0(dir.getAbsolutePath())); + } catch (CorruptedIdeLocationException e) { + problems.add(e); + } + } + if (dir.getName().toLowerCase().contains("netbeans")) { + for (File dir2 : dir.listFiles()) { + if (!dir2.isDirectory()) continue; + if (dir2.getName().toLowerCase().startsWith("netbeans") && dir2.getName().toLowerCase().endsWith(".app")) { + try { + locations.add(NetbeansLocationProvider.create0(dir2.getAbsolutePath())); + } catch (CorruptedIdeLocationException e) { + problems.add(e); + } + } + } + } + } + } +} diff --git a/src/installer/lombok/installer/netbeans/NetbeansLocation.java b/src/installer/lombok/installer/netbeans/NetbeansLocation.java new file mode 100644 index 00000000..51acef3d --- /dev/null +++ b/src/installer/lombok/installer/netbeans/NetbeansLocation.java @@ -0,0 +1,282 @@ +/* + * Copyright © 2009 Reinier Zwitserloot and Roel Spilker. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.installer.netbeans; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import lombok.installer.CorruptedIdeLocationException; +import lombok.installer.IdeFinder; +import lombok.installer.IdeLocation; +import lombok.installer.InstallException; +import lombok.installer.UninstallException; + +public class NetbeansLocation extends IdeLocation { + private final String name; + private final File netbeansConfPath; + private volatile boolean hasLombok; + + private static final String OS_NEWLINE = IdeFinder.getOS().getLineEnding(); + + NetbeansLocation(String nameOfLocation, File pathToNetbeansConf) throws CorruptedIdeLocationException { + this.name = nameOfLocation; + this.netbeansConfPath = pathToNetbeansConf; + try { + this.hasLombok = checkForLombok(netbeansConfPath); + } catch (IOException e) { + throw new CorruptedIdeLocationException( + "I can't read the configuration file of the Netbeans installed at " + name + "\n" + + "You may need to run this installer with root privileges if you want to modify that Netbeans.", "netbeans", e); + } + } + + @Override public int hashCode() { + return netbeansConfPath.hashCode(); + } + + @Override public boolean equals(Object o) { + if (!(o instanceof NetbeansLocation)) return false; + return ((NetbeansLocation)o).netbeansConfPath.equals(netbeansConfPath); + } + + /** + * Returns the name of this location; generally the path to the netbeans executable. + */ + @Override + public String getName() { + return name; + } + + /** + * @return true if the Netbeans installation has been instrumented with lombok. + */ + @Override + public boolean hasLombok() { + return hasLombok; + } + + private final String ID_CHARS = "(?:\\\\.|[^\"\\\\])*"; + private final String ID_CHARS_NS = "(?:\\\\.|[^\"\\\\ ])*"; + private final Pattern JAVA_AGENT_LINE_MATCHER = Pattern.compile( + "^\\s*netbeans_default_options\\s*=\\s*\"\\s*" + ID_CHARS + "(?<=[ \"])(-J-javaagent:" + ID_CHARS_NS + "lombok" + ID_CHARS_NS + "\\.jar)(?=[ \"])" + ID_CHARS +"\\s*\"\\s*(?:#.*)?$", Pattern.CASE_INSENSITIVE); + + private final Pattern OPTIONS_LINE_MATCHER = Pattern.compile( + "^\\s*netbeans_default_options\\s*=\\s*\"\\s*" + ID_CHARS + "\\s*(\")\\s*(?:#.*)?$", Pattern.CASE_INSENSITIVE); + + private boolean checkForLombok(File iniFile) throws IOException { + if (!iniFile.exists()) return false; + FileInputStream fis = new FileInputStream(iniFile); + try { + BufferedReader br = new BufferedReader(new InputStreamReader(fis)); + String line; + while ((line = br.readLine()) != null) { + if (JAVA_AGENT_LINE_MATCHER.matcher(line.trim()).matches()) return true; + } + + return false; + } finally { + fis.close(); + } + } + + /** + * Uninstalls lombok from this location. + * It's a no-op if lombok wasn't there in the first place, + * and it will remove a half-succeeded lombok installation as well. + * + * @throws UninstallException + * If there's an obvious I/O problem that is preventing + * installation. bugs in the uninstall code will probably throw + * other exceptions; this is intentional. + */ + @Override + public void uninstall() throws UninstallException { + File dir = netbeansConfPath.getParentFile(); + File lombokJar = new File(dir, "lombok.jar"); + if (lombokJar.exists()) { + if (!lombokJar.delete()) throw new UninstallException( + "Can't delete " + lombokJar.getAbsolutePath() + generateWriteErrorMessage(), null); + } + + StringBuilder newContents = new StringBuilder(); + if (netbeansConfPath.exists()) { + try { + FileInputStream fis = new FileInputStream(netbeansConfPath); + try { + BufferedReader br = new BufferedReader(new InputStreamReader(fis)); + String line; + while ((line = br.readLine()) != null) { + Matcher m = JAVA_AGENT_LINE_MATCHER.matcher(line); + if (m.matches()) { + newContents.append(line.substring(0, m.start(1)) + line.substring(m.end(1))); + } else { + newContents.append(line); + } + newContents.append(OS_NEWLINE); + } + } finally { + fis.close(); + } + + FileOutputStream fos = new FileOutputStream(netbeansConfPath); + try { + fos.write(newContents.toString().getBytes()); + } finally { + fos.close(); + } + } catch (IOException e) { + throw new UninstallException("Cannot uninstall lombok from " + name + generateWriteErrorMessage(), e); + } + } + } + + private static String generateWriteErrorMessage() { + String osSpecificError; + + switch (IdeFinder.getOS()) { + default: + case MAC_OS_X: + case UNIX: + osSpecificError = ":\nStart terminal, go to the directory with lombok.jar, and run: sudo java -jar lombok.jar"; + break; + case WINDOWS: + osSpecificError = ":\nStart a new cmd (dos box) with admin privileges, go to the directory with lombok.jar, and run: java -jar lombok.jar"; + break; + } + + return ", probably because this installer does not have the access rights.\n" + + "Try re-running the installer with administrative privileges" + osSpecificError; + } + + /** + * Install lombok into the Netbeans at this location. + * If lombok is already there, it is overwritten neatly (upgrade mode). + * + * @throws InstallException + * If there's an obvious I/O problem that is preventing + * installation. bugs in the install code will probably throw + * other exceptions; this is intentional. + */ + @Override + public String install() throws InstallException { + boolean installSucceeded = false; + StringBuilder newContents = new StringBuilder(); + + File lombokJar = new File(netbeansConfPath.getParentFile(), "lombok.jar"); + + File ourJar = findOurJar(); + byte[] b = new byte[524288]; + boolean readSucceeded = true; + try { + FileOutputStream out = new FileOutputStream(lombokJar); + try { + readSucceeded = false; + InputStream in = new FileInputStream(ourJar); + try { + while (true) { + int r = in.read(b); + if (r == -1) break; + if (r > 0) readSucceeded = true; + out.write(b, 0, r); + } + } finally { + in.close(); + } + } finally { + out.close(); + } + } catch (IOException e) { + try { + lombokJar.delete(); + } catch (Throwable ignore) { /* Nothing we can do about that. */ } + if (!readSucceeded) throw new InstallException( + "I can't read my own jar file. I think you've found a bug in this installer!\nI suggest you restart it " + + "and use the 'what do I do' link, to manually install lombok. Also, tell us about this at:\n" + + "http://groups.google.com/group/project-lombok - Thanks!", e); + throw new InstallException("I can't write to your Netbeans directory at " + name + generateWriteErrorMessage(), e); + } + + try { + FileInputStream fis = new FileInputStream(netbeansConfPath); + try { + BufferedReader br = new BufferedReader(new InputStreamReader(fis)); + String line; + while ((line = br.readLine()) != null) { + Matcher m = JAVA_AGENT_LINE_MATCHER.matcher(line); + if (m.matches()) { + newContents.append(line.substring(0, m.start(1))); + newContents.append("-J-javaagent:" + escapePath(canonical(lombokJar))); + newContents.append(line.substring(m.end(1))); + newContents.append(OS_NEWLINE); + continue; + } + + m = OPTIONS_LINE_MATCHER.matcher(line); + if (m.matches()) { + newContents.append(line.substring(0, m.start(1))); + newContents.append(" ").append("-J-javaagent:" + escapePath(canonical(lombokJar)) +"\""); + newContents.append(line.substring(m.end(1))).append(OS_NEWLINE); + continue; + } + + newContents.append(line).append(OS_NEWLINE); + } + } finally { + fis.close(); + } + + FileOutputStream fos = new FileOutputStream(netbeansConfPath); + try { + fos.write(newContents.toString().getBytes()); + } finally { + fos.close(); + } + installSucceeded = true; + } catch (IOException e) { + throw new InstallException("Cannot install lombok at " + name + generateWriteErrorMessage(), e); + } finally { + if (!installSucceeded) try { + lombokJar.delete(); + } catch (Throwable ignore) {} + } + + if (!installSucceeded) { + throw new InstallException("I can't find the netbeans.conf file. Is this a real Netbeans installation?", null); + } + + return "If you start netbeans with custom parameters, you'll need to add:<br>" + + "<code>-J-javaagent:" + escapePath(canonical(lombokJar)) + "</code><br>" + + "as parameter as well."; + } + + @Override public URL getIdeIcon() { + return NetbeansLocation.class.getResource("netbeans.png"); + } +} diff --git a/src/installer/lombok/installer/netbeans/NetbeansLocationProvider.java b/src/installer/lombok/installer/netbeans/NetbeansLocationProvider.java new file mode 100644 index 00000000..1bc699f1 --- /dev/null +++ b/src/installer/lombok/installer/netbeans/NetbeansLocationProvider.java @@ -0,0 +1,125 @@ +/* + * Copyright © 2009 Reinier Zwitserloot and Roel Spilker. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.installer.netbeans; + +import static lombok.installer.IdeLocation.canonical; + +import java.io.File; +import java.io.IOException; +import java.util.regex.Pattern; + +import lombok.installer.CorruptedIdeLocationException; +import lombok.installer.IdeLocation; +import lombok.installer.IdeLocationProvider; +import lombok.installer.IdeFinder.OS; + +import org.mangosdk.spi.ProviderFor; + +@ProviderFor(IdeLocationProvider.class) +public class NetbeansLocationProvider implements IdeLocationProvider { + + @Override public IdeLocation create(String path) throws CorruptedIdeLocationException { + return create0(path); + } + + /** + * Create a new NetbeansLocation by pointing at either the directory containing the executable, or the executable itself, + * or a netbeans.conf file. + * + * @throws NotAnIdeLocationException + * If this isn't an Eclipse executable or a directory with an + * Eclipse executable. + */ + static NetbeansLocation create0(String path) throws CorruptedIdeLocationException { + if (path == null) throw new NullPointerException("path"); + File p = new File(path); + + if (!p.exists()) return null; + if (p.isDirectory()) { + String name = p.getName().toLowerCase(); + if (name.endsWith(".app") && name.startsWith("netbeans")) { + File conf = new File(p, "Contents/Resources/NetBeans/etc/netbeans.conf"); + if (conf.exists()) return new NetbeansLocation(path, conf); + } + + File f = new File(p, "netbeans"); + if (f.isFile()) return findNetbeansConfFromExe(f, 0); + f = new File(p, "netbeans.exe"); + if (f.isFile()) return findNetbeansConfFromExe(f, 0); + f = new File(p, "netbeans.conf"); + if (f.isFile()) return new NetbeansLocation(canonical(f.getParentFile().getParentFile()), f); + } + + if (p.isFile()) { + if (p.getName().equalsIgnoreCase("netbeans.conf")) { + return new NetbeansLocation(canonical(p.getParentFile().getParentFile()), p); + } + + if (p.getName().equalsIgnoreCase("netbeans") || p.getName().equalsIgnoreCase("netbeans.exe")) { + return findNetbeansConfFromExe(p, 0); + } + } + + return null; + } + + private static NetbeansLocation findNetbeansConfFromExe(File exePath, int loopCounter) throws CorruptedIdeLocationException { + /* Try looking for netbeans.conf as etc/netbeans.conf */ { + File conf = new File(exePath.getParentFile(), "etc/netbeans.conf"); + if (conf.isFile()) return new NetbeansLocation(canonical(exePath), conf); + } + + /* Try looking for netbeans.conf as ../etc/netbeans.conf */ { + File conf = new File(exePath.getParentFile().getParentFile(), "etc/netbeans.conf"); + if (conf.isFile()) return new NetbeansLocation(canonical(exePath), conf); + } + + /* If executable is a soft link, follow it and retry. */ { + if (loopCounter < 50) { + try { + String oPath = exePath.getAbsolutePath(); + String nPath = exePath.getCanonicalPath(); + if (!oPath.equals(nPath)) try { + return findNetbeansConfFromExe(new File(nPath), loopCounter + 1); + } catch (CorruptedIdeLocationException ignore) { + // Unlinking didn't help find a netbeans, so continue. + } + } catch (IOException ignore) { /* okay, that didn't work, assume it isn't a soft link then. */ } + } + } + + /* If we get this far, we lose. */ + return null; + } + + @Override public Pattern getLocationSelectors(OS os) { + switch (os) { + case MAC_OS_X: + return Pattern.compile("^(netbeans|netbeans\\.conf|NetBeans.*\\.app)$", Pattern.CASE_INSENSITIVE); + case WINDOWS: + return Pattern.compile("^(netbeans\\.exe|netbeans\\.ini)$", Pattern.CASE_INSENSITIVE); + default: + case UNIX: + return Pattern.compile("^(netbeans|netbeans\\.conf)$", Pattern.CASE_INSENSITIVE); + } + } +} diff --git a/src/installer/lombok/installer/netbeans/netbeans.png b/src/installer/lombok/installer/netbeans/netbeans.png Binary files differnew file mode 100644 index 00000000..43fe63cc --- /dev/null +++ b/src/installer/lombok/installer/netbeans/netbeans.png |