diff options
Diffstat (limited to 'src/lombok/installer')
-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 |
7 files changed, 1224 insertions, 25 deletions
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" |