diff options
author | Reinier Zwitserloot <reinier@tipit.to> | 2009-07-05 00:49:54 +0200 |
---|---|---|
committer | Reinier Zwitserloot <reinier@tipit.to> | 2009-07-05 00:49:54 +0200 |
commit | e7492d444cace44a39d94853512c31a9b95d930d (patch) | |
tree | c245e78ba3b328f255c1584f0702c04251d6dd4f /src/lombok | |
parent | 938d4bb9f231722b2f55b71a05ecf9c3878119aa (diff) | |
download | lombok-e7492d444cace44a39d94853512c31a9b95d930d.tar.gz lombok-e7492d444cace44a39d94853512c31a9b95d930d.tar.bz2 lombok-e7492d444cace44a39d94853512c31a9b95d930d.zip |
W00t - written an installer that should work on linux (sort of), windows, and Mac OS X. Bumped the version number to celebrate, and changed build to roll the agent.jar into the main jar, and
change the executable class from the minimal help that was there to the installer. That minimal help thing (ShowUserHelp.java) is now gone.
Diffstat (limited to 'src/lombok')
-rw-r--r-- | src/lombok/core/ShowUserHelp.java | 32 | ||||
-rw-r--r-- | src/lombok/core/Version.java | 2 | ||||
-rw-r--r-- | src/lombok/installer/EclipseFinder.java | 146 | ||||
-rw-r--r-- | src/lombok/installer/EclipseLocation.java | 313 | ||||
-rw-r--r-- | src/lombok/installer/InstallerWindow.java | 732 | ||||
-rw-r--r-- | src/lombok/installer/loading.gif | bin | 0 -> 2248 bytes | |||
-rw-r--r-- | src/lombok/installer/lombokIcon.png | bin | 0 -> 863 bytes | |||
-rw-r--r-- | src/lombok/installer/lombokText.png | bin | 0 -> 3055 bytes | |||
-rw-r--r-- | src/lombok/installer/lombokText.svg | 58 |
9 files changed, 1225 insertions, 58 deletions
diff --git a/src/lombok/core/ShowUserHelp.java b/src/lombok/core/ShowUserHelp.java deleted file mode 100644 index 443de833..00000000 --- a/src/lombok/core/ShowUserHelp.java +++ /dev/null @@ -1,32 +0,0 @@ -package lombok.core; - -import java.net.URI; - -/** - * This class is used as main class for the lombok jar; this way, if the jar is run as java app, the user is pointed - * at documentation about lombok. - */ -public class ShowUserHelp { - private static final URI ABOUT_LOMBOK_URL = URI.create("http://wiki.github.com/rzwitserloot/lombok"); - - public static void main(String[] args) { - boolean browserOpened = false; - try { - //java.awt.Desktop doesn't exist in 1.5, and for IDE's sakes, we may want to work in java1.5 someday, so... - 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); - browserOpened = true; - } catch ( Exception ignore ) {} - - String version = Version.getVersion(); - final String nextStep = browserOpened ? "See your browser window" : - String.format("Browse to %s", ABOUT_LOMBOK_URL); - - System.out.printf("About lombok v%s\n" + - "Lombok makes java better by providing very spicy additions to the Java programming language," + - "such as using @Getter to automatically generate a getter method for any field.\n\n%s" + - " for more information about the lombok project, and how to" + - "install it into your programming environment. If you are just using javac (the java compiler)," + - "just use this jar, no further steps needed.", version, nextStep); - } -} diff --git a/src/lombok/core/Version.java b/src/lombok/core/Version.java index eff6b151..cb349aaf 100644 --- a/src/lombok/core/Version.java +++ b/src/lombok/core/Version.java @@ -1,7 +1,7 @@ package lombok.core; public class Version { - private static final String VERSION = "0.3.5"; + private static final String VERSION = "0.4"; private Version() { //Prevent instantiation diff --git a/src/lombok/installer/EclipseFinder.java b/src/lombok/installer/EclipseFinder.java new file mode 100644 index 00000000..1ca82440 --- /dev/null +++ b/src/lombok/installer/EclipseFinder.java @@ -0,0 +1,146 @@ +package lombok.installer; + +import static java.util.Arrays.asList; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URI; +import java.net.URLDecoder; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import lombok.Lombok; + +public class EclipseFinder { + public static File findOurJar() { + try { + URI uri = EclipseFinder.class.getResource("/" + EclipseFinder.class.getName().replace('.', '/') + ".class").toURI(); + Pattern p = Pattern.compile("^jar:file:([^\\!]+)\\!.*\\.class$"); + System.out.println(uri.toString()); + 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); + } + } + + public static List<String> getDrivesOnWindows() throws IOException { + ProcessBuilder builder = new ProcessBuilder("c:\\windows\\system32\\fsutil.exe", "fsinfo", "drives"); + builder.redirectErrorStream(true); + Process process = builder.start(); + InputStream in = process.getInputStream(); + BufferedReader br = new BufferedReader(new InputStreamReader(in, "ISO-8859-1")); + + List<String> drives = new ArrayList<String>(); + + String line; + while ( (line = br.readLine()) != null ) { + if ( line.startsWith("Drives: ") ) { + line = line.substring(8); + for ( String driveLetter : line.split("\\:\\\\\\s*") ) drives.add(driveLetter.trim()); + } + } + + Iterator<String> it = drives.iterator(); + while ( it.hasNext() ) { + if ( !isLocalDriveOnWindows(it.next()) ) it.remove(); + } + + return drives; + } + + public static boolean isLocalDriveOnWindows(String driveLetter) { + if ( driveLetter == null || driveLetter.length() == 0 ) return false; + try { + ProcessBuilder builder = new ProcessBuilder("c:\\windows\\system32\\fsutil.exe", "fsinfo", "drivetype", driveLetter + ":"); + builder.redirectErrorStream(true); + Process process = builder.start(); + InputStream in = process.getInputStream(); + BufferedReader br = new BufferedReader(new InputStreamReader(in, "ISO-8859-1")); + + String line; + while ( (line = br.readLine()) != null ) { + if ( line.substring(5).equalsIgnoreCase("Fixed Drive") ) return true; + } + + return false; + } catch ( Exception e ) { + return false; + } + } + + public static List<String> findEclipseOnWindows() { + List<String> eclipses = new ArrayList<String>(); + List<String> driveLetters = asList("C"); + + try { + driveLetters = getDrivesOnWindows(); + } catch ( IOException ignore ) {} + + for ( String letter : driveLetters ) { + File f = new File(letter + ":\\"); + for ( File dir : f.listFiles() ) { + if ( !dir.isDirectory() ) continue; + if ( dir.getName().toLowerCase().contains("eclipse") ) { + String eclipseLocation = findEclipseOnWindows1(dir); + if ( eclipseLocation != null ) eclipses.add(eclipseLocation); + } + 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 ( eclipseLocation != null ) eclipses.add(eclipseLocation); + } + } + } + } + } + + return eclipses; + } + + private static String findEclipseOnWindows1(File dir) { + if ( new File(dir, "eclipse.exe").isFile() ) return dir.toString(); + return null; + } + + public static void main(String[] args) throws Exception { + System.out.println(findEclipses()); + } + + public static List<String> findEclipses() { + String prop = System.getProperty("os.name", ""); + if ( prop.equalsIgnoreCase("Mac OS X") ) return findEclipseOnMac(); + if ( prop.equalsIgnoreCase("Windows XP") ) return findEclipseOnWindows(); + + return null; + } + + public static String getEclipseExecutableName() { + String prop = System.getProperty("os.name", ""); + if ( prop.equalsIgnoreCase("Mac OS X") ) return "Eclipse.app"; + if ( prop.equalsIgnoreCase("Windows XP") ) return "eclipse.exe"; + + return "eclipse"; + } + + public static List<String> findEclipseOnMac() { + List<String> eclipses = new ArrayList<String>(); + for ( File dir : new File("/Applications").listFiles() ) { + if ( dir.getName().toLowerCase().contains("eclipse") ) { + if ( new File(dir, "Eclipse.app").exists() ) eclipses.add(dir.toString()); + } + } + return eclipses; + } +} diff --git a/src/lombok/installer/EclipseLocation.java b/src/lombok/installer/EclipseLocation.java new file mode 100644 index 00000000..400b5606 --- /dev/null +++ b/src/lombok/installer/EclipseLocation.java @@ -0,0 +1,313 @@ +package lombok.installer; + +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.util.Arrays; +import java.util.List; +import java.util.jar.JarFile; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.zip.ZipEntry; + +import javax.swing.JFrame; +import javax.swing.JOptionPane; + +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"; + } + + private final File path; + private volatile boolean hasLombok; + boolean selected = true; + + final class NotAnEclipseException extends Exception { + private static final long serialVersionUID = 1L; + + public NotAnEclipseException(String message, Throwable cause) { + super(message, cause); + } + + public void showDialog(JFrame appWindow) { + JOptionPane.showMessageDialog(appWindow, getMessage(), "Cannot configure eclipse installation", JOptionPane.WARNING_MESSAGE); + } + } + + EclipseLocation(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); + + final String execName = EclipseFinder.getEclipseExecutableName(); + if ( p.isDirectory() ) { + for ( File f : p.listFiles() ) { + if ( f.getName().equalsIgnoreCase(execName) ) { + p = f; + break; + } + } + } + + this.path = p; + try { + this.hasLombok = checkForLombok(); + } catch ( IOException e ) { + throw new NotAnEclipseException( + "I can't read the configuration file of the eclipse installed at " + this.path.getAbsolutePath() + "\n" + + "You may need to run this installer with root privileges if you want to modify that eclipse.", e); + } + } + + public int hashCode() { + return path.hashCode(); + } + + public boolean equals(Object o) { + if ( !(o instanceof EclipseLocation) ) return false; + return ((EclipseLocation)o).path.equals(path); + } + + public String getPath() { + return path.getAbsolutePath(); + } + + public boolean hasLombok() { + return hasLombok; + } + + private List<File> getTargetDirs() { + return Arrays.asList(path.getParentFile(), new File(new File(path, "Contents"), "MacOS")); + } + + private boolean checkForLombok() throws IOException { + for ( File targetDir : getTargetDirs() ) { + if ( checkForLombok0(targetDir) ) return true; + } + + return false; + } + + private final Pattern JAVA_AGENT_LINE_MATCHER = Pattern.compile( + "^\\-javaagent\\:.*lombok.*\\.jar$", Pattern.CASE_INSENSITIVE); + + private final Pattern BOOTCLASSPATH_LINE_MATCHER = Pattern.compile( + "^\\-Xbootclasspath\\/a\\:(.*lombok.*\\.jar.*)$", Pattern.CASE_INSENSITIVE); + + private boolean checkForLombok0(File dir) throws IOException { + File iniFile = new File(dir, "eclipse.ini"); + 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(); + } + } + + public class UninstallException extends Exception { + private static final long serialVersionUID = 1L; + + public UninstallException(String message, Throwable cause) { + super(message, cause); + } + } + + public void uninstall() throws UninstallException { + for ( File dir : getTargetDirs() ) { + File lombokJar = new File(dir, "lombok.jar"); + if ( lombokJar.exists() ) { + if ( !lombokJar.delete() ) throw new UninstallException( + "Can't delete " + lombokJar.getAbsolutePath() + + " - perhaps the installer does not have the access rights to do so.", + null); + } + + File agentJar = new File(dir, "lombok.eclipse.agent.jar"); + if ( agentJar.exists() ) { + if ( !agentJar.delete() ) throw new UninstallException( + "Can't delete " + agentJar.getAbsolutePath() + + " - perhaps the installer does not have the access rights to do so.", + null); + } + + File iniFile = new File(dir, "eclipse.ini"); + StringBuilder newContents = new StringBuilder(); + if ( iniFile.exists() ) { + try { + 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).matches() ) continue; + Matcher m = BOOTCLASSPATH_LINE_MATCHER.matcher(line); + if ( m.matches() ) { + StringBuilder elemBuilder = new StringBuilder(); + elemBuilder.append("-Xbootclasspath/a:"); + boolean first = true; + for ( String elem : m.group(1).split(Pattern.quote(File.pathSeparator)) ) { + if ( elem.toLowerCase().endsWith("lombok.jar") ) continue; + if ( elem.toLowerCase().endsWith("lombok.eclipse.agent.jar") ) continue; + if ( first ) first = false; + else elemBuilder.append(File.pathSeparator); + elemBuilder.append(elem); + } + if ( !first ) newContents.append(elemBuilder.toString()).append(OS_NEWLINE); + continue; + } + + newContents.append(line).append(OS_NEWLINE); + } + + } finally { + fis.close(); + } + + FileOutputStream fos = new FileOutputStream(iniFile); + try { + fos.write(newContents.toString().getBytes()); + } finally { + fos.close(); + } + } catch ( IOException e ) { + throw new UninstallException("Cannot uninstall lombok from " + path.getAbsolutePath() + + " probably because this installer does not have the access rights to do so.", e); + } + } + } + } + + public class InstallException extends Exception { + private static final long serialVersionUID = 1L; + + public InstallException(String message, Throwable cause) { + super(message, cause); + } + } + + public void install() throws InstallException { + for ( File dir : getTargetDirs() ) { + File iniFile = new File(dir, "eclipse.ini"); + StringBuilder newContents = new StringBuilder(); + if ( iniFile.exists() ) { + File lombokJar = new File(iniFile.getParentFile(), "lombok.jar"); + File agentJar = new File(iniFile.getParentFile(), "lombok.eclipse.agent.jar"); + + File ourJar = EclipseFinder.findOurJar(); + byte[] b = new byte[524288]; + boolean readSucceeded = false; + boolean installSucceeded = false; + try { + JarFile jar = new JarFile(ourJar); + + try { + ZipEntry entry = jar.getEntry("lombok.eclipse.agent.jar"); + InputStream in = jar.getInputStream(entry); + FileOutputStream out = new FileOutputStream(agentJar); + try { + while ( true ) { + int r = in.read(b); + if ( r == -1 ) break; + readSucceeded = true; + out.write(b, 0, r); + } + } finally { + out.close(); + } + } finally { + jar.close(); + + } + + FileOutputStream out = new FileOutputStream(lombokJar); + InputStream in = new FileInputStream(ourJar); + try { + while ( true ) { + int r = in.read(b); + if ( r == -1 ) break; + out.write(b, 0, r); + } + } finally { + out.close(); + } + } catch ( IOException e ) { + try { + lombokJar.delete(); + agentJar.delete(); + } catch ( Throwable ignore ) {} + if ( !readSucceeded ) throw new InstallException("I can't read my own jar file. I think you've found a bug in this installer! I suggest you restart it " + + "and use the 'what do I do' link, to manually install lombok. And tell us about this. Thanks!", e); + throw new InstallException("I can't write to your eclipse directory, probably because this installer does not have the access rights.", e); + } + + try { + 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).matches() ) continue; + Matcher m = BOOTCLASSPATH_LINE_MATCHER.matcher(line); + if ( m.matches() ) { + StringBuilder elemBuilder = new StringBuilder(); + elemBuilder.append("-Xbootclasspath/a:"); + boolean first = true; + for ( String elem : m.group(1).split(Pattern.quote(File.pathSeparator)) ) { + if ( elem.toLowerCase().endsWith("lombok.jar") ) continue; + if ( elem.toLowerCase().endsWith("lombok.eclipse.agent.jar") ) continue; + if ( first ) first = false; + else elemBuilder.append(File.pathSeparator); + elemBuilder.append(elem); + } + if ( !first ) newContents.append(elemBuilder.toString()).append(OS_NEWLINE); + continue; + } + + newContents.append(line).append(OS_NEWLINE); + } + + } finally { + fis.close(); + } + + newContents.append("-javaagent:lombok.jar").append(OS_NEWLINE); + newContents.append("-Xbootclasspath/a:lombok.jar" + File.pathSeparator + "lombok.eclipse.agent.jar").append(OS_NEWLINE); + + FileOutputStream fos = new FileOutputStream(iniFile); + try { + fos.write(newContents.toString().getBytes()); + } finally { + fos.close(); + } + installSucceeded = true; + + } catch ( IOException e ) { + throw new InstallException("Cannot install lombok at " + path.getAbsolutePath() + + " probably because this installer does not have the access rights to do so.", e); + } finally { + if ( !installSucceeded ) try { + lombokJar.delete(); + agentJar.delete(); + } catch ( Throwable ignore ) {} + } + } + } + } +} diff --git a/src/lombok/installer/InstallerWindow.java b/src/lombok/installer/InstallerWindow.java index 7db465be..a0911c26 100644 --- a/src/lombok/installer/InstallerWindow.java +++ b/src/lombok/installer/InstallerWindow.java @@ -1,48 +1,740 @@ package lombok.installer; -import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Cursor; +import java.awt.Dimension; +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.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.font.TextAttribute; +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +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.EclipseLocation.InstallException; +import lombok.installer.EclipseLocation.NotAnEclipseException; +import lombok.installer.EclipseLocation.UninstallException; public class InstallerWindow { - private JFrame jFrame; - private JLabel mainText; - private JLabel leftGraphic, topGraphic; + private static final URI ABOUT_LOMBOK_URL = URI.create("http://projectlombok.org"); + + private JFrame appWindow; + + private JComponent loadingExpl; + + private Component javacArea; + private Component eclipseArea; + private Component uninstallArea; + private Component howIWorkArea; + + private Box uninstallBox; + + private List<EclipseLocation> toUninstall; public static void main(String[] args) { + try { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + try { + new InstallerWindow().show(); + } catch ( HeadlessException e ) { + printHeadlessInfo(); + } + } + }); + } catch ( HeadlessException e ) { + printHeadlessInfo(); + } + } + + private static void printHeadlessInfo() { + System.out.printf("About lombok v%s\n" + + "Lombok makes java better by providing very spicy additions to the Java programming language," + + "such as using @Getter to automatically generate a getter method for any field.\n\n" + + "Browse to %s for more information. To install lombok on eclipse, re-run this jar file on a " + + "graphical computer system - this message is being shown because your terminal is not graphics capable." + + "If you are just using 'javac' or a tool that calls on javac, no installation is neccessary; just " + + "make sure lombok.jar is in the classpath when you compile. Example:\n\n" + + " java -cp lombok.jar MyCode.java", Version.getVersion(), ABOUT_LOMBOK_URL); + } + + public InstallerWindow() { + appWindow = new JFrame(String.format("Project Lombok v%s - Installer", Version.getVersion())); + + appWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + appWindow.setResizable(false); + + 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() { - public void run() { - new InstallerWindow().show(); + @Override public void run() { + JOptionPane.showMessageDialog(appWindow, "There was a problem during the installation process:\n" + t, "Uh Oh!", JOptionPane.ERROR_MESSAGE); + System.exit(1); } }); } - public InstallerWindow() { - jFrame = new JFrame(String.format("Project Lombok v%s - Installer", Version.getVersion())); + 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); - //We want to offer an undo feature when the user cancels in the middle of an operation. - jFrame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + constraints.gridy = 1; + constraints.insets = new Insets(8, 0, 0, 16); + container.add(new JLabel(HOW_I_WORK_EXPLANATION), constraints); - leftGraphic = new JLabel(new ImageIcon(InstallerWindow.class.getResource("/lombok/installer/lombok.png"))); - topGraphic = new JLabel(new ImageIcon(InstallerWindow.class.getResource("/lombok/installer/lombokText.png"))); - mainText = new JLabel("Explanatory stuff goes here."); + Box buttonBar = Box.createHorizontalBox(); + JButton backButton = new JButton("Okay - Good to know!"); + buttonBar.add(Box.createHorizontalGlue()); + buttonBar.add(backButton); - jFrame.setLayout(new BorderLayout()); - jFrame.add(leftGraphic, BorderLayout.WEST); - jFrame.add(topGraphic, BorderLayout.NORTH); - jFrame.add(mainText, BorderLayout.CENTER); + 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); - jFrame.pack(); - System.out.println("WE SHOULD BE UP"); + 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; + + container.add(new JLabel(JAVAC_TITLE), constraints); + + constraints.gridy = 1; + constraints.insets = new Insets(8, 0, 0, 16); + container.add(new JLabel(JAVAC_EXPLANATION), constraints); + + JLabel example = new JLabel(JAVAC_EXAMPLE); + + constraints.gridy = 2; + constraints.insets = new Insets(8, 0, 0, 16); + container.add(example, constraints); + return container; + } + + private Component buildEclipseArea() throws IOException { + // "Or:" [I'll tell you where eclipse is] [Tell me how to install lombok manually] + + //Mode 2 (manual): + // Replace the entirety of the content (javac+eclipse) with an explanation about what to do: + // - copy lombok.jar to your eclipse directory. + // - jar xvf lombok.jar lombok.eclipse.agent.jar + // - edit eclipse.ini with: + // -javaagent:../../../lombok.eclipse.agent.jar + // -Xbootclasspath/a:../../../lombok.eclipse.agent.jar:../../../lombok.jar + + //Mode 3 (let me choose): + // pop up a file chooser. Make sure we don't care what you pick - eclipse.ini, eclipse.exe, eclipse.app, or dir. + // empty the list, remove the spinner and the [let me find eclipse on my own] button, and put the chosen + // eclipse in the list. + + JPanel container = new JPanel(); + + container.setLayout(new GridBagLayout()); + GridBagConstraints constraints = new GridBagConstraints(); + constraints.anchor = GridBagConstraints.WEST; + + container.add(new JLabel(ECLIPSE_TITLE), constraints); + + constraints.gridy = 1; + constraints.insets = new Insets(8, 0, 0, 16); + container.add(new JLabel(ECLIPSE_EXPLANATION), constraints); + + constraints.gridy = 2; + loadingExpl = Box.createHorizontalBox(); + loadingExpl.add(new JLabel(new ImageIcon(InstallerWindow.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); + container.add(eclipsesListScroll, constraints); + + Thread findEclipsesThread = new Thread() { + @Override public void run() { + try { + final List<String> eclipses = EclipseFinder.findEclipses(); + final List<EclipseLocation> locations = new ArrayList<EclipseLocation>(); + final List<NotAnEclipseException> problems = new ArrayList<NotAnEclipseException>(); + + for ( String eclipse : eclipses ) try { + locations.add(new EclipseLocation(eclipse)); + } catch ( NotAnEclipseException e ) { + problems.add(e); + } + + 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); + } + }); + } 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) { + JFileChooser chooser = new JFileChooser(); + + chooser.setAcceptAllFileFilterUsed(false); + chooser.setFileSelectionMode(JFileChooser.FILES_ONLY); + chooser.setFileFilter(new FileFilter() { + private final String name = EclipseFinder.getEclipseExecutableName(); + @Override public boolean accept(File f) { + if ( f.getName().equalsIgnoreCase(name) ) 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: + try { + try { + eclipsesList.addEclipse(new EclipseLocation(chooser.getSelectedFile().getAbsolutePath())); + } 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; + uninstallButton = new JHyperLink("Uninstall lombok from selected eclipse installations."); + 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); + + constraints.gridy = 6; + 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(); + } + }); + + 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.getPath()); + 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(InstallerWindow.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, "Lombok has been installed on the selected eclipse installations.", "Install successful", JOptionPane.INFORMATION_MESSAGE); + appWindow.setVisible(false); + System.exit(0); + } + }); + } + }.start(); + } + + private void doUninstall() { + JPanel spinner = new JPanel(); + spinner.setOpaque(true); + spinner.setLayout(new FlowLayout()); + spinner.add(new JLabel(new ImageIcon(InstallerWindow.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); + 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.getPath()); + 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(InstallerWindow.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(InstallerWindow.class.getResource("/lombok/installer/lombok.png"))); + JLabel topGraphic = new JLabel(new ImageIcon(InstallerWindow.class.getResource("/lombok/installer/lombokText.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.gridx++; + constraints.gridheight = 1; + constraints.fill = GridBagConstraints.NONE; + constraints.ipadx = 40; + constraints.ipady = 64; + appWindowContainer.add(topGraphic, constraints); + + constraints.gridy++; + constraints.ipadx = 16; + constraints.ipady = 16; + appWindowContainer.add(javacArea, constraints); + + constraints.gridy++; + constraints.weightx = 1; + constraints.weighty = 1; + constraints.fill = GridBagConstraints.BOTH; + 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 ) { + String os = System.getProperty("os.name").toLowerCase(); + Runtime rt = Runtime.getRuntime(); + try { + if ( os.indexOf( "win" ) > -1 ) { + String[] cmd = new String[4]; + cmd[0] = "cmd.exe"; + cmd[1] = "/C"; + cmd[2] = "start"; + cmd[3] = ABOUT_LOMBOK_URL.toString(); + rt.exec(cmd); + } else if ( os.indexOf( "mac" ) >= 0 ) { + rt.exec( "open " + ABOUT_LOMBOK_URL.toString()); + } else { + rt.exec("firefox " + ABOUT_LOMBOK_URL.toString()); + } + } 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); } public void show() { - jFrame.setVisible(true); + appWindow.setVisible(true); } + + 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 unpack lombok.eclipse.agent.jar like so:<br>" + + "<pre>jar xvf lombok.jar lombok.eclipse.agent.jar</pre></li>" + + "<li>Then, I edit the eclipse.ini file to add the following two entries:<br>" + + "<pre>-Xbootclasspath/a:lombok.jar:lombok.eclipse.agent.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/Contents/MacOS</code> so that's where I place the jar files.</html>"; + + private JHyperLink uninstallButton; + + private JButton installButton; } diff --git a/src/lombok/installer/loading.gif b/src/lombok/installer/loading.gif Binary files differnew file mode 100644 index 00000000..b9fc304a --- /dev/null +++ b/src/lombok/installer/loading.gif diff --git a/src/lombok/installer/lombokIcon.png b/src/lombok/installer/lombokIcon.png Binary files differnew file mode 100644 index 00000000..b19beee2 --- /dev/null +++ b/src/lombok/installer/lombokIcon.png diff --git a/src/lombok/installer/lombokText.png b/src/lombok/installer/lombokText.png Binary files differnew file mode 100644 index 00000000..279746cb --- /dev/null +++ b/src/lombok/installer/lombokText.png diff --git a/src/lombok/installer/lombokText.svg b/src/lombok/installer/lombokText.svg index 24e387e9..9fd2f73b 100644 --- a/src/lombok/installer/lombokText.svg +++ b/src/lombok/installer/lombokText.svg @@ -1,16 +1,64 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> <!-- Created with Inkscape (http://www.inkscape.org/) --> <svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" version="1.0" - width="500" - height="200" - id="svg2"> + width="425.0675" + height="56.760002" + id="svg2" + sodipodi:version="0.32" + inkscape:version="0.46" + sodipodi:docname="lombokText.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape"> + <metadata + id="metadata2527"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <sodipodi:namedview + inkscape:window-height="713" + inkscape:window-width="640" + inkscape:pageshadow="2" + inkscape:pageopacity="0.0" + guidetolerance="10.0" + gridtolerance="10.0" + objecttolerance="10.0" + borderopacity="1.0" + bordercolor="#666666" + pagecolor="#ffffff" + id="base" + showgrid="false" + inkscape:zoom="1" + inkscape:cx="194.15432" + inkscape:cy="205" + inkscape:window-x="365" + inkscape:window-y="58" + inkscape:current-layer="svg2" /> <defs - id="defs4" /> + id="defs4"> + <inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 100 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="500 : 100 : 1" + inkscape:persp3d-origin="250 : 66.666667 : 1" + id="perspective2529" /> + </defs> <g - id="layer1"> + id="layer1" + transform="translate(-31.89625,-66.299366)"> <path d="M 31.89625,123.05937 L 31.89625,66.299366 L 41.35625,66.299366 L 41.35625,115.13937 L 84.36625,115.13937 L 84.36625,123.05937 L 31.89625,123.05937 M 98.808906,87.529366 C 98.808897,83.276072 99.615562,80.379409 101.22891,78.839366 C 102.84223,77.226078 105.84889,76.419413 110.24891,76.419366 L 143.90891,76.419366 C 148.30885,76.419413 151.31551,77.226078 152.92891,78.839366 C 154.54217,80.379409 155.34884,83.276072 155.34891,87.529366 L 155.34891,112.05937 C 155.34884,116.31271 154.54217,119.20937 152.92891,120.74937 C 151.31551,122.28937 148.30885,123.05937 143.90891,123.05937 L 110.24891,123.05937 C 105.77556,123.05937 102.73223,122.32603 101.11891,120.85937 C 99.578896,119.31937 98.808897,116.38604 98.808906,112.05937 L 98.808906,87.529366 M 107.60891,116.01937 L 146.54891,116.01937 L 146.54891,83.349366 L 107.60891,83.349366 L 107.60891,116.01937 M 182.78875,85.109366 L 183.00875,91.599366 L 183.00875,123.05937 L 175.74875,123.05937 L 175.74875,76.419366 L 183.00875,76.419366 L 206.21875,106.66937 L 227.55875,76.419366 L 235.69875,76.419366 L 235.69875,123.05937 L 227.55875,123.05937 L 227.55875,91.599366 C 227.55869,89.326066 227.74202,87.162735 228.10875,85.109366 C 227.08202,88.042734 226.05536,90.206065 225.02875,91.599366 L 207.09875,115.68937 L 204.78875,115.68937 L 186.19875,91.819366 C 184.8054,90.059399 183.66873,87.822734 182.78875,85.109366 M 257.38937,123.05937 L 257.38937,76.419366 L 296.76938,76.419366 C 303.58932,76.419413 306.99931,79.462743 306.99937,85.549366 L 306.99937,90.939366 C 306.99931,95.632727 304.28598,98.309391 298.85938,98.969366 C 304.50598,99.556056 307.32931,102.56272 307.32938,107.98937 L 307.32938,113.92937 C 307.32931,120.01604 302.81932,123.05937 293.79937,123.05937 L 257.38937,123.05937 M 298.19938,91.489366 L 298.19938,87.089366 C 298.19932,84.522738 296.54933,83.239406 293.24937,83.239366 L 266.18937,83.239366 L 266.18937,95.229366 L 293.24937,95.229366 C 295.08266,95.229394 296.36599,95.009394 297.09938,94.569366 C 297.83266,94.056062 298.19932,93.029396 298.19938,91.489366 M 266.18937,102.04937 L 266.18937,116.23937 L 293.24937,116.23937 C 295.22933,116.23937 296.58599,115.87271 297.31937,115.13937 C 298.12599,114.33271 298.52932,112.93938 298.52938,110.95937 L 298.52938,107.32937 C 298.52932,103.80939 296.76932,102.04939 293.24937,102.04937 L 266.18937,102.04937 M 325.68391,87.529366 C 325.6839,83.276072 326.49056,80.379409 328.10391,78.839366 C 329.71723,77.226078 332.72389,76.419413 337.12391,76.419366 L 370.78391,76.419366 C 375.18385,76.419413 378.19051,77.226078 379.80391,78.839366 C 381.41717,80.379409 382.22384,83.276072 382.22391,87.529366 L 382.22391,112.05937 C 382.22384,116.31271 381.41717,119.20937 379.80391,120.74937 C 378.19051,122.28937 375.18385,123.05937 370.78391,123.05937 L 337.12391,123.05937 C 332.65056,123.05937 329.60723,122.32603 327.99391,120.85937 C 326.4539,119.31937 325.6839,116.38604 325.68391,112.05937 L 325.68391,87.529366 M 334.48391,116.01937 L 373.42391,116.01937 L 373.42391,83.349366 L 334.48391,83.349366 L 334.48391,116.01937 M 402.62375,123.05937 L 402.62375,76.419366 L 411.42375,76.419366 L 411.42375,96.439366 L 440.90375,76.419366 L 453.11375,76.419366 L 418.68375,98.969366 L 456.96375,123.05937 L 442.55375,123.05937 L 411.42375,102.15937 L 411.42375,123.05937 L 402.62375,123.05937" id="flowRoot2383" |