From 23e82f9bc83bdf3f30b44354d3096c02a8473abc Mon Sep 17 00:00:00 2001 From: Ivan Dubrov Date: Mon, 28 Apr 2014 09:21:47 -0700 Subject: [PATCH] Moving installer from the separate branch --- installer/build.gradle | 68 +++++ .../dcevm/installer/AddDirectoryAction.java | 72 +++++ .../dcevm/installer/ConfigurationInfo.java | 255 ++++++++++++++++++ .../installer/InstallUninstallAction.java | 102 +++++++ .../github/dcevm/installer/Installation.java | 128 +++++++++ .../InstallationTableCellRenderer.java | 76 ++++++ .../installer/InstallationsTableModel.java | 70 +++++ .../com/github/dcevm/installer/Installer.java | 138 ++++++++++ .../java/com/github/dcevm/installer/Main.java | 55 ++++ .../github/dcevm/installer/MainWindow.java | 156 +++++++++++ .../com/github/dcevm/installer/splash.png | Bin 0 -> 21587 bytes 11 files changed, 1120 insertions(+) create mode 100644 installer/build.gradle create mode 100644 installer/src/main/java/com/github/dcevm/installer/AddDirectoryAction.java create mode 100644 installer/src/main/java/com/github/dcevm/installer/ConfigurationInfo.java create mode 100644 installer/src/main/java/com/github/dcevm/installer/InstallUninstallAction.java create mode 100644 installer/src/main/java/com/github/dcevm/installer/Installation.java create mode 100644 installer/src/main/java/com/github/dcevm/installer/InstallationTableCellRenderer.java create mode 100644 installer/src/main/java/com/github/dcevm/installer/InstallationsTableModel.java create mode 100644 installer/src/main/java/com/github/dcevm/installer/Installer.java create mode 100644 installer/src/main/java/com/github/dcevm/installer/Main.java create mode 100644 installer/src/main/java/com/github/dcevm/installer/MainWindow.java create mode 100644 installer/src/main/resources/com/github/dcevm/installer/splash.png diff --git a/installer/build.gradle b/installer/build.gradle new file mode 100644 index 00000000..f49ba255 --- /dev/null +++ b/installer/build.gradle @@ -0,0 +1,68 @@ +import java.nio.file.FileVisitResult +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths +import java.nio.file.SimpleFileVisitor +import java.nio.file.StandardCopyOption +import java.nio.file.attribute.BasicFileAttributes + +apply plugin: 'java' +apply plugin: 'idea' + +jar { + manifest { + attributes("Main-Class": "com.github.dcevm.installer.Main") + } +} + +project.ext { + processedData = Paths.get("$buildDir/data") + // Should be populated by build server from the DCEVM upstream job + dataSource = Paths.get("$buildDir/rawdata") +} + +sourceSets { + main { + output.dir(processedData.toFile(), builtBy: 'copyData') + } +} + +task copyData << { + Files.createDirectories(processedData) + Files.walkFileTree(dataSource, new CopyDataVisitor(project)); +} + +class CopyDataVisitor extends SimpleFileVisitor { + def project + + CopyDataVisitor(prj) { + project = prj + } + + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + Path rel = project.dataSource.relativize(dir) + if (rel.nameCount > 4) { + rel = rel.subpath(4, rel.nameCount); + if (rel.fileName.toString() == 'fastdebug') { + // Do not copy fastdebug versions + return FileVisitResult.SKIP_SUBTREE; + } + def targetPath = project.processedData.resolve(rel); + if(!Files.exists(targetPath)){ + Files.createDirectory(targetPath); + } + } + return FileVisitResult.CONTINUE; + } + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Path rel = project.dataSource.relativize(file) + if (rel.nameCount > 4) { + rel = rel.subpath(4, rel.nameCount); + def targetPath = project.processedData.resolve(rel); + Files.copy(file, targetPath, StandardCopyOption.REPLACE_EXISTING); + } + return FileVisitResult.CONTINUE; + } +} diff --git a/installer/src/main/java/com/github/dcevm/installer/AddDirectoryAction.java b/installer/src/main/java/com/github/dcevm/installer/AddDirectoryAction.java new file mode 100644 index 00000000..94af7852 --- /dev/null +++ b/installer/src/main/java/com/github/dcevm/installer/AddDirectoryAction.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +package com.github.dcevm.installer; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.prefs.Preferences; + +/** + * @author Kerstin Breiteneder + * @author Christoph Wimberger + * @author Ivan Dubrov + */ +class AddDirectoryAction extends AbstractAction { + + private final Component parent; + private final InstallationsTableModel installations; + private final ConfigurationInfo config; + + public AddDirectoryAction(Component parent, InstallationsTableModel inst, ConfigurationInfo config) { + super("Add installation directory..."); + this.parent = parent; + this.installations = inst; + this.config = config; + } + + public void actionPerformed(ActionEvent e) { + JFileChooser fc = new JFileChooser(); + fc.setDialogTitle("Select a Java installation directory..."); + fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + fc.setAcceptAllFileFilterUsed(false); + Preferences p = Preferences.userNodeForPackage(Installer.class); + final String prefID = "defaultDirectory"; + fc.setCurrentDirectory(new File(p.get(prefID, "."))); + + if (fc.showOpenDialog(parent) == JFileChooser.APPROVE_OPTION) { + + Path dir = fc.getSelectedFile().toPath(); + p.put(prefID, dir.getParent().toString()); + try { + installations.add(new Installation(config, dir)); + } catch (IOException ex) { + MainWindow.showInstallerException(ex, parent); + } + } + } +} diff --git a/installer/src/main/java/com/github/dcevm/installer/ConfigurationInfo.java b/installer/src/main/java/com/github/dcevm/installer/ConfigurationInfo.java new file mode 100644 index 00000000..cb28045f --- /dev/null +++ b/installer/src/main/java/com/github/dcevm/installer/ConfigurationInfo.java @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +package com.github.dcevm.installer; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author Kerstin Breiteneder + * @author Christoph Wimberger + * @author Ivan Dubrov + */ +public enum ConfigurationInfo { + + // Note: 32-bit is not supported on Mac OS X + MAC_OS(null, "bsd_amd64_compiler2", + "lib/client", "lib/server", "lib/server", + "bin/java", "libjvm.dylib"), + LINUX("linux_i486_compiler2", "linux_amd64_compiler2", + "lib/i386/client", "lib/i386/server", "lib/amd64/server", + "bin/java", "libjvm.so") { + @Override + public String[] paths() { + return new String[]{"/usr/java", "/usr/lib/jvm"}; + } + }, + WINDOWS("windows_i486_compiler2", "windows_amd64_compiler2", + "bin/client", "bin/server", "bin/server", + "bin/java.exe", "jvm.dll") { + @Override + public String[] paths() { + return new String[]{ + System.getenv("JAVA_HOME") + "/..", + System.getenv("PROGRAMW6432") + "/JAVA", + System.getenv("PROGRAMFILES") + "/JAVA", + System.getenv("SYSTEMDRIVE") + "/JAVA"}; + } + }; + + private final String resourcePath32; + private final String resourcePath64; + + private final String clientPath; + private final String server32Path; + private final String server64Path; + + private final String javaExecutable; + private final String libraryName; + + private ConfigurationInfo(String resourcePath32, String resourcePath64, + String clientPath, String server32Path, String server64Path, + String javaExecutable, String libraryName) { + this.resourcePath32 = resourcePath32; + this.resourcePath64 = resourcePath64; + this.clientPath = clientPath; + this.server32Path = server32Path; + this.server64Path = server64Path; + this.javaExecutable = javaExecutable; + this.libraryName = libraryName; + } + + public String getResourcePath(boolean bit64) { + return bit64 ? resourcePath64 : resourcePath32; + } + + public String getResourcePath32() { + return resourcePath32; + } + + public String getResourcePath64() { + return resourcePath64; + } + + public String getClientPath() { + return clientPath; + } + + public String getServerPath(boolean bit64) { + return bit64 ? server64Path : server32Path; + } + + public String getServer32Path() { + return server32Path; + } + + public String getServer64Path() { + return server64Path; + } + + public String getJavaExecutable() { + return javaExecutable; + } + + public String getLibraryName() { + return libraryName; + } + + public String getBackupLibraryName() { + return libraryName + ".backup"; + } + + public String[] paths() { + return new String[0]; + } + + public static ConfigurationInfo current() { + String os = System.getProperty("os.name").toLowerCase(); + + if (os.contains("windows")) { + return ConfigurationInfo.WINDOWS; + } else if (os.contains("mac") || os.contains("darwin")) { + return ConfigurationInfo.MAC_OS; + } else if (os.contains("unix") || os.contains("linux")) { + return ConfigurationInfo.LINUX; + } + throw new IllegalStateException("OS is unsupported: " + os); + } + + // Utility methods to query installation directories + public boolean isJRE(Path directory) { + if (Files.isDirectory(directory) && directory.getFileName().toString().startsWith("jre")) { + if (!Files.exists(directory.resolve(getJavaExecutable()))) { + return false; + } + + Path client = directory.resolve(getClientPath()); + if (Files.exists(client)) { + if (!Files.exists(client.resolve(getLibraryName()))) { + return Files.exists(client.resolve(getBackupLibraryName())); + } + } + + Path server = directory.resolve(getServer64Path()); + if (Files.exists(server)) { + if (!Files.exists(server.resolve(getLibraryName()))) { + return Files.exists(server.resolve(getBackupLibraryName())); + } + } + return true; + } + return false; + } + + public boolean isJDK(Path directory) { + if (Files.isDirectory(directory)) { + Path jreDir = directory.resolve(getJREDirectory()); + return isJRE(jreDir); + } + return false; + } + + public String executeJava(Path jreDir, String... params) throws IOException { + Path executable = jreDir.resolve(getJavaExecutable()); + String[] commands = new String[params.length + 1]; + System.arraycopy(params, 0, commands, 1, params.length); + commands[0] = executable.toAbsolutePath().toString(); + Process p = Runtime.getRuntime().exec(commands); + + StringBuilder result = new StringBuilder(); + try (InputStream in = p.getErrorStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(in))) { + String line; + while ((line = reader.readLine()) != null) { + result.append(line); + result.append('\n'); + } + } + return result.toString(); + } + + public boolean isDCEInstalled(Path dir) throws IOException { + Path jreDir; + if (isJDK(dir)) { + jreDir = dir.resolve("jre"); + } else { + jreDir = dir; + } + Path clientPath = jreDir.resolve(getClientPath()); + Path clientBackup = clientPath.resolve(getBackupLibraryName()); + + Path serverPath = jreDir.resolve(getServer32Path()); + if (!Files.exists(serverPath)) { + serverPath = jreDir.resolve(getServer64Path()); + } + Path serverBackup = serverPath.resolve(getBackupLibraryName()); + + if (Files.exists(clientPath) && Files.exists(serverPath)) { + if (Files.exists(clientBackup) != Files.exists(serverBackup)) { + throw new IllegalStateException(jreDir.toAbsolutePath() + " has invalid state."); + } + } + return Files.exists(clientBackup) || Files.exists(serverBackup); + } + + public String getVersionString(Path jreDir) throws IOException { + return executeJava(jreDir, "-version"); + } + + public boolean is64Bit(Path jreDir) throws IOException { + return getVersionString(jreDir).contains("64-Bit"); + } + + public String getJavaVersion(Path jreDir) throws IOException { + return getVersionHelper(jreDir, ".*java version.*\"(.*)\".*", true); + } + + final public String getDCEVersion(Path jreDir) throws IOException { + return getVersionHelper(jreDir, ".*Dynamic Code Evolution.*build ([^,]+),.*", false); + } + + private String getVersionHelper(Path jreDir, String regex, boolean javaVersion) throws IOException { + String version = getVersionString(jreDir); + version = version.replaceAll("\n", ""); + Matcher matcher = Pattern.compile(regex).matcher(version); + + if (!matcher.matches()) { + throw new IllegalArgumentException("Could not get " + (javaVersion ? "java" : "dce") + + "version of " + jreDir.toAbsolutePath() + "."); + } + + version = matcher.replaceFirst("$1"); + return version; + } + + public String getJREDirectory() { + return "jre"; + } +} diff --git a/installer/src/main/java/com/github/dcevm/installer/InstallUninstallAction.java b/installer/src/main/java/com/github/dcevm/installer/InstallUninstallAction.java new file mode 100644 index 00000000..78cb8698 --- /dev/null +++ b/installer/src/main/java/com/github/dcevm/installer/InstallUninstallAction.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +package com.github.dcevm.installer; + +import javax.swing.*; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import java.awt.event.ActionEvent; +import java.io.IOException; +import java.util.Observable; +import java.util.Observer; + +/** + * @author Kerstin Breiteneder + * @author Christoph Wimberger + * @author Ivan Dubrov + */ +class InstallUninstallAction extends AbstractAction implements ListSelectionListener, Observer { + + private final JTable table; + private final boolean install; + private Installation installation; + + public InstallUninstallAction(boolean install, JTable t) { + super(install ? "Install" : "Uninstall"); + this.install = install; + setEnabled(false); + table = t; + t.getSelectionModel().addListSelectionListener(this); + } + + private Installation getSelectedInstallation() { + InstallationsTableModel itm = (InstallationsTableModel) table.getModel(); + int sel = table.getSelectedRow(); + if (sel < 0) { + return null; + } else { + return itm.getInstallationAt(sel); + } + } + + public void actionPerformed(ActionEvent e) { + try { + if (install) { + getSelectedInstallation().installDCE(); + } else { + getSelectedInstallation().uninstallDCE(); + } + } catch (IOException ex) { + MainWindow.showInstallerException(ex, table); + } + } + + private void setCurrentInstallation(Installation i) { + if (installation != null) { + installation.deleteObserver(this); + } + installation = i; + if (installation != null) { + installation.addObserver(this); + } + update(); + } + + public void valueChanged(ListSelectionEvent e) { + Installation i = getSelectedInstallation(); + setCurrentInstallation(i); + } + + private void update() { + if (install) { + setEnabled(installation != null && !installation.isDCEInstalled()); + } else { + setEnabled(installation != null && installation.isDCEInstalled()); + } + } + + public void update(Observable o, Object arg) { + update(); + } +} diff --git a/installer/src/main/java/com/github/dcevm/installer/Installation.java b/installer/src/main/java/com/github/dcevm/installer/Installation.java new file mode 100644 index 00000000..133e5a1c --- /dev/null +++ b/installer/src/main/java/com/github/dcevm/installer/Installation.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +package com.github.dcevm.installer; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Observable; + +/** + * @author Kerstin Breiteneder + * @author Christoph Wimberger + * @author Ivan Dubrov + */ +public class Installation extends Observable { + + private final Path file; + private final ConfigurationInfo config; + + private final boolean isJDK; + private boolean installed; + private String version; + private String dceVersion; + private boolean is64Bit; + + public Installation(ConfigurationInfo config, Path path) throws IOException { + this.config = config; + try { + file = path.toRealPath(); + } catch (IOException ex) { + throw new IllegalArgumentException(path.toAbsolutePath() + " is no JRE or JDK-directory."); + } + isJDK = config.isJDK(file); + if (!isJDK && !config.isJRE(file)) { + throw new IllegalArgumentException(path.toAbsolutePath() + " is no JRE or JDK-directory."); + } + + version = config.getJavaVersion(file); + update(); + } + + final public void update() throws IOException { + installed = config.isDCEInstalled(file); + if (installed) { + dceVersion = config.getDCEVersion(file); + } + is64Bit = config.is64Bit(file); + } + + public Path getPath() { + return file; + } + + public String getVersion() { + return version; + } + + public String getDCEVersion() { + return dceVersion; + } + + public boolean isJDK() { + return isJDK; + } + + public boolean is64Bit() { + return is64Bit; + } + + public void installDCE() throws IOException { + new Installer(config).install(file, is64Bit); + installed = true; + update(); + setChanged(); + notifyObservers(); + } + + public void uninstallDCE() throws IOException { + new Installer(config).uninstall(file, is64Bit); + installed = false; + update(); + setChanged(); + notifyObservers(); + } + + public boolean isDCEInstalled() { + return installed; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Installation other = (Installation) obj; + return !(this.file != other.file && (this.file == null || !this.file.equals(other.file))); + } + + @Override + public int hashCode() { + int hash = 5; + hash = 97 * hash + (this.file != null ? this.file.hashCode() : 0); + return hash; + } +} diff --git a/installer/src/main/java/com/github/dcevm/installer/InstallationTableCellRenderer.java b/installer/src/main/java/com/github/dcevm/installer/InstallationTableCellRenderer.java new file mode 100644 index 00000000..c15fe688 --- /dev/null +++ b/installer/src/main/java/com/github/dcevm/installer/InstallationTableCellRenderer.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +package com.github.dcevm.installer; + +import javax.swing.*; +import javax.swing.table.DefaultTableCellRenderer; +import java.awt.*; + +/** + * @author Kerstin Breiteneder + * @author Christoph Wimberger + * @author Ivan Dubrov + */ +class InstallationTableCellRenderer extends DefaultTableCellRenderer { + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + + if (c instanceof JLabel && value instanceof Installation) { + JLabel l = (JLabel) c; + Installation inst = (Installation) value; + + switch (column) { + case 0: + l.setText(inst.getPath().toAbsolutePath().toString()); + break; + case 1: + l.setText(inst.getVersion()); + break; + case 2: + l.setText(inst.isJDK() ? "JDK" : "JRE"); + if (inst.is64Bit()) { + l.setText(l.getText() + " (64 Bit)"); + } + break; + case 3: + if (inst.isDCEInstalled()) { + l.setText("Yes (" + inst.getDCEVersion() + ")"); + } else { + l.setText("No"); + } + break; + } + } + + return c; + } + + @Override + public void setText(String text) { + super.setText(text); + setToolTipText(text); + } +} diff --git a/installer/src/main/java/com/github/dcevm/installer/InstallationsTableModel.java b/installer/src/main/java/com/github/dcevm/installer/InstallationsTableModel.java new file mode 100644 index 00000000..c5014627 --- /dev/null +++ b/installer/src/main/java/com/github/dcevm/installer/InstallationsTableModel.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +package com.github.dcevm.installer; + +import javax.swing.table.AbstractTableModel; +import java.util.ArrayList; +import java.util.Observable; +import java.util.Observer; + +/** + * @author Kerstin Breiteneder + * @author Christoph Wimberger + * @author Ivan Dubrov + */ +class InstallationsTableModel extends AbstractTableModel implements Observer { + + private final ArrayList installations; + + public InstallationsTableModel() { + installations = new ArrayList(); + } + + public int getRowCount() { + return installations.size(); + } + + public int getColumnCount() { + return 4; + } + + public Object getValueAt(int rowIndex, int columnIndex) { + return installations.get(rowIndex); + } + + public Installation getInstallationAt(int row) { + return installations.get(row); + } + + public void add(Installation i) { + installations.add(i); + i.addObserver(this); + fireTableDataChanged(); + } + + public void update(Observable o, Object arg) { + int row = installations.indexOf(o); + fireTableRowsUpdated(row, row); + } +} diff --git a/installer/src/main/java/com/github/dcevm/installer/Installer.java b/installer/src/main/java/com/github/dcevm/installer/Installer.java new file mode 100644 index 00000000..93399365 --- /dev/null +++ b/installer/src/main/java/com/github/dcevm/installer/Installer.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +package com.github.dcevm.installer; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.*; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Kerstin Breiteneder + * @author Christoph Wimberger + * @author Ivan Dubrov + */ +public class Installer { + + private ConfigurationInfo config; + + public Installer(ConfigurationInfo config) { + this.config = config; + } + + public void install(Path dir, boolean bit64) throws IOException { + if (config.isJDK(dir)) { + dir = dir.resolve(config.getJREDirectory()); + } + + Path serverPath = dir.resolve(config.getServerPath(bit64)); + if (Files.exists(serverPath)) { + installClientServer(serverPath, bit64); + } + + Path clientPath = dir.resolve(config.getClientPath()); + if (Files.exists(clientPath) && !bit64) { + installClientServer(clientPath, false); + } + } + + public void uninstall(Path dir, boolean bit64) throws IOException { + if (config.isJDK(dir)) { + dir = dir.resolve(config.getJREDirectory()); + } + + Path serverPath = dir.resolve(config.getServerPath(bit64)); + if (Files.exists(serverPath)) { + uninstallClientServer(serverPath); + } + + Path clientPath = dir.resolve(config.getClientPath()); + if (Files.exists(clientPath) && !bit64) { + uninstallClientServer(clientPath); + } + } + + public List listInstallations() { + return scanPaths(config.paths()); + } + + public ConfigurationInfo getConfiguration() { + return config; + } + + private void installClientServer(Path path, boolean bit64) throws IOException { + String resource = config.getResourcePath(bit64) + "/product/" + config.getLibraryName(); + + Path library = path.resolve(config.getLibraryName()); + Path backup = path.resolve(config.getBackupLibraryName()); + + + Files.move(library, backup); + try { + try (InputStream in = getClass().getClassLoader().getResourceAsStream(resource)) { + Files.copy(in, library); + } + } catch (IOException e) { + Files.move(backup, library, StandardCopyOption.REPLACE_EXISTING); + throw e; + } + } + + private void uninstallClientServer(Path path) throws IOException { + Path library = path.resolve(config.getLibraryName()); + Path backup = path.resolve(config.getBackupLibraryName()); + + Files.delete(library); + Files.move(backup, library); // FIXME: if fails, JRE is inconsistent! + } + + private List scanPaths(String... dirPaths) { + List installations = new ArrayList<>(); + for (String dirPath : dirPaths) { + Path dir = Paths.get(dirPath); + if (Files.isDirectory(dir)) { + try (DirectoryStream stream = Files.newDirectoryStream(dir)) { + scanDirectory(stream, installations); + } catch (Exception ex) { + // Ignore, try different directory + } + } + } + return installations; + } + + private void scanDirectory(DirectoryStream stream, List installations) { + for (Path path : stream) { + try { + Installation inst = new Installation(config, path); + if (!installations.contains(inst)) { + installations.add(inst); + } + } catch (Exception e) { + // FIXME: just ignore the installation for now.. + } + } + } +} diff --git a/installer/src/main/java/com/github/dcevm/installer/Main.java b/installer/src/main/java/com/github/dcevm/installer/Main.java new file mode 100644 index 00000000..4d360f91 --- /dev/null +++ b/installer/src/main/java/com/github/dcevm/installer/Main.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +package com.github.dcevm.installer; + +import javax.swing.JOptionPane; +import javax.swing.UIManager; +import javax.swing.UnsupportedLookAndFeelException; + +/** + * @author Kerstin Breiteneder + * @author Christoph Wimberger + * @author Thomas Wuerthinger + */ +public class Main { + + public static void main(String[] args) { + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (Exception e) { + // Ignore everything + } + + MainWindow w; + try { + w = new MainWindow(); + w.setLocationRelativeTo(null); + w.setVisible(true); + } catch (Exception ex) { + ex.printStackTrace(); + JOptionPane.showMessageDialog(null, ex.getMessage(), ex.getMessage(), JOptionPane.ERROR_MESSAGE); + } + + } +} diff --git a/installer/src/main/java/com/github/dcevm/installer/MainWindow.java b/installer/src/main/java/com/github/dcevm/installer/MainWindow.java new file mode 100644 index 00000000..d253b44d --- /dev/null +++ b/installer/src/main/java/com/github/dcevm/installer/MainWindow.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ +package com.github.dcevm.installer; + +import javax.imageio.ImageIO; +import javax.swing.*; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.IOException; + +/** + * @author Kerstin Breiteneder + * @author Christoph Wimberger + * @author Ivan Dubrov + */ +public class MainWindow extends JFrame { + + private final InstallationsTableModel installations; + private JTable table; + + public MainWindow() { + super("Dynamic Code Evolution VM Installer"); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + Installer installer = new Installer(ConfigurationInfo.current()); + installations = new InstallationsTableModel(); + for (Installation i : installer.listInstallations()) { + installations.add(i); + } + add(getBanner(), BorderLayout.NORTH); + add(getCenterPanel(), BorderLayout.CENTER); + add(getBottomPanel(), BorderLayout.SOUTH); + + if (table.getRowCount() > 0) { + table.setRowSelectionInterval(0, 0); + } + + pack(); + setMinimumSize(getSize()); + } + + static void showInstallerException(Exception ex, Component parent) { + Throwable e = ex; + String error = e.getMessage(); + e = e.getCause(); + + while (e != null) { + error += "\n" + e.getMessage(); + e = e.getCause(); + } + + ex.printStackTrace(); + + error += "\nPlease ensure that no other Java applications are running and you have sufficient permissions."; + JOptionPane.showMessageDialog(parent, error, ex.getMessage(), JOptionPane.ERROR_MESSAGE); + } + + private JComponent getBanner() { + try { + BufferedImage img = ImageIO.read(getClass().getResource("splash.png")); + JLabel title = new JLabel(new ImageIcon(img)); + title.setPreferredSize(new Dimension(img.getWidth() + 10, img.getHeight())); + title.setOpaque(true); + title.setBackground(new Color(238, 238, 255)); + return title; + } catch (IOException ex) { + } + return new JLabel(); + } + + private JComponent getCenterPanel() { + JPanel center = new JPanel(new BorderLayout()); + center.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + center.setBackground(Color.WHITE); + JTextArea license = new javax.swing.JTextArea(); + license.setLineWrap(true); + license.setWrapStyleWord(true); + license.setEditable(false); + license.setFont(license.getFont().deriveFont(11.0f)); + StringBuilder licenseText = new StringBuilder(); + licenseText.append("This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 only, as published by the Free Software Foundation.\n\nThis code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License version 2 for more details (a copy is included in the LICENSE file that accompanied this code).\n\nYou should have received a copy of the GNU General Public License version 2 along with this work; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA."); + licenseText.append("\n\n\nASM LICENSE TEXT:\nCopyright (c) 2000-2005 INRIA, France Telecom\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.\n\n3. Neither the name of the copyright holders nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."); + license.setText(licenseText.toString()); + JScrollPane licenses = new JScrollPane(license, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + licenses.setPreferredSize(new Dimension(150, 150)); + center.add(licenses, BorderLayout.NORTH); + center.add(getChooserPanel(), BorderLayout.CENTER); + return center; + } + + private JComponent getChooserPanel() { + JPanel p = new JPanel(new BorderLayout()); + p.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0)); + p.setOpaque(false); + + JLabel l = new JLabel("Please choose installation directory:"); + l.setVerticalAlignment(JLabel.NORTH); + l.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 10)); + p.add(l, BorderLayout.WEST); + + table = new JTable(installations); + table.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + table.setColumnSelectionAllowed(false); + table.setDefaultRenderer(Object.class, new InstallationTableCellRenderer()); + table.getColumnModel().getColumn(0).setHeaderValue("Directory"); + table.getColumnModel().getColumn(0).setPreferredWidth(300); + table.getColumnModel().getColumn(1).setHeaderValue("Java Version"); + table.getColumnModel().getColumn(2).setHeaderValue("Type"); + table.getColumnModel().getColumn(3).setHeaderValue("DCE"); + JScrollPane lists = new JScrollPane(table); + lists.setPreferredSize(new Dimension(200, 200)); + p.add(lists, BorderLayout.CENTER); + + return p; + } + + private JComponent getBottomPanel() { + + JPanel left = new JPanel(new FlowLayout()); + left.add(new JButton(new AddDirectoryAction(this, installations, ConfigurationInfo.current()))); + + JPanel right = new JPanel(new FlowLayout()); + //right.add(new JButton(new TestAction(table, installer))); + right.add(new JButton(new InstallUninstallAction(false, table))); + right.add(new JButton(new InstallUninstallAction(true, table))); + + JPanel bottom = new JPanel(new BorderLayout()); + bottom.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + bottom.add(left, BorderLayout.WEST); + bottom.add(right, BorderLayout.EAST); + + return bottom; + } +} + diff --git a/installer/src/main/resources/com/github/dcevm/installer/splash.png b/installer/src/main/resources/com/github/dcevm/installer/splash.png new file mode 100644 index 0000000000000000000000000000000000000000..fd49c0a42b965a7c0d781c4e96ca08174a89c09c GIT binary patch literal 21587 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¬r9lz zXGt`%ySdKUfD_B$Xq#3Pb=ZkGT2$G|m9B}}Uz?cf9H2EuFff15k`L}cmkgXC`~BPI zw+*@h0=*;V+1#d@yCp<11)8brLDMs-2{U@O(-%%+prZRg>@%{R8(GOLBIL-o{oiGP zXVJm!WpjN^)~pQYupW;|65$QH&S>C4d5ae^{uRoF0_v~Rb`|cAnkSG_bdVrNx(0gi z-FXr}`h&n4Kdu8+V_a&obJ*@(nxmy8>_ba(@`1}h0;vjOy7cFOiVArP3mux|!khs3 z&;&Vot2?R2UP=^)GczyRS{g7q_u#Iy!`vQWy;mCJ3)#ZUqcH)vW#ys;Z1Qa3CAW%_ddgY+6fOC6ub-`RRPHI+^6FM9h(9{ly{& zf3??50=dk~Q*}ZO3}b}rPi;nEzx>pBOQzE9UAh*h>HABOw}^Ul=O9Rtn8S8D>}5Iq zK5RVZHWOd|cJcx@wabl9?eRO`$=x)5x(?U1(e*aZWTNM)MZAr}<{DsVJ((&q;}<`T zM_in@|~Kaz}BzP^&u^XfGfj~e}p>-IG2^{w3oxcz*37QO(XW(F@oGq^oNAmM&4 zg12I>A!FC7`bu@V?QGflQvIk+!F$MXDKt}{vX-@XoVmB;27{S=^P#QA{WdlCkS`q# zAMt=S?d9S3h>n=~k$qidUW%sA;i_j?Lvpxx^LMXgY+J?H)#T4Nk&P|+Hd+!ZHbt~t zXzK;4#A+MC^1T*<{)X0|j5Gls#jwLs4oQ^Nz1i_$;*ZvYTFY8$VliUjUB&ZI(W}IZ@7X=S^W>e@?4l%2wr2G|oF>00WAIiw-+K*^xi1#r zp2`~5jRT#`I=v0+Z+^%c-lT|=%vS1H4}#@0X<~Gq+)5PnSr{)5t6>>l^!irV^j5e5 z-D=Zqo!G5UW!}1c6kfFn#`A6FkK!+LVX03N z9UDS2{zt+$_Xkq)>v2nb0=oT0kUc8>;O=F4LB{IcquHl+4y#@QX?<<46i-5&N7iij zEN@+amdnN0Y)8RSSe8mHLI0+DZw7Q=4q;4xr!&pX&eY*GwJIcPcsx?fs zJaa}R@!{fwvOvnzeD=iE5{pxmGsrl~Da7akS^yy`Iww|O?cDtEry3o(NT1M-jqYmW zr4=B#Ykq?Dr>!Ym*nH-%DO1s=paX9w(Im|f8$aks?~Ry>k=Jgmu*s+s8x?zHtBZYK zkk^Xyj}lEP^-CxyQEY1FIzrm~U>5@Xl(&P4MXr4#*62=HzN{MD@l%U%SC=HNKkY|L zHw3@j$DQmZyTcsLC}mw!tI~U3#C~&tjs(Pvj5d<>l^CDpmaz_>S>;`~Ohp#6C=NKn zjSjrvzmK|GEtdz^!*%1Z{3EDI{!y^}a*e$}dn4r8<=SzoSQL030o6U8iPZ}O@kHl3`ZT#DbZ`ynSjxTpv*L+_Z$ zJZddk>}O5h!PUKqHbJ?bY{K_5;b0Hg*!3*E7ib6{i5s5>6daB7mC4kpw=0?QUP?Q>ObjCfv6xT$7kBW3+|b)Z_#=H98pA1^T+i`MM*X;vxUyBVf#9&_GWb0hTAK3y}J^2qaq(&cVm=V>ZD{3l@6%c zBrKi`Iyr5KD3F(dPGR?6zjS*J1Cw#D@Fwp3S_dq$T?-0ZIAEiz%X4&U20p&OUV&~$ zdxV4UqK_yQHLRp%Z^W1~*I=kct7M~^s(xfvv+(+0UN)=slP<{^#=3oF>;?}m`_I$G zigu~|O>OK~Vz#bbM|$0+&Wo*rSWJwq;NalRef3}V5hv4#%#JsCLKDff?0jKX&yGTHiu!svvCTv@u*Wx*@Se^%-ye>_S386y0eqRdZ5AVXcrje?nafr_T-RA>w<;X31Vf9sxx$S;A(FT=jRCpcVwKu;!Tu;_|bD>#hmNR(?-PqtV=b5Ju%RYM* z#iS`DWXP2jwDZpi{J1Zjs!$%q@aF1{-S48+;l9FNmcFx;1&hh@HB3vBC{YYmcvvPm z?^x5oC+TP6Zat<&z;HI*9yX`4WE`AY8!TGOnWIUP#Wq2s&jyHI#*R$M4cKTrKQ2bm zHaoe=>%Mowz1{LpPsyMYEVGzhC=i)@kuphjQ)imp2z(A=+D%=McccBiSb+S#U|+izNAX$aDAH-sp~)`Y{0TN?H4B*hx%5L! zax$P8geE4_B-BQMsXo+!4|ln{J<8^VR0Nls$B<;fZ@JXon|+=fvqP3hlet`mv6BK# zipChO)owf(%bfUFeJ^j3IfzXP&Jwc}_zW1naOQ`Lf&r1p4u>Tg+1+yo)+|(ZaB_*~Oh6j~ zCS(FZ*okg+0DnW=p?qmu-nQc7_;YK+?c>T!$8)5bywV5150)^kdh%03v81ucP*8mk z`RoB6X~8^tf!5ZaP=tdXvf#4^_lL4dvns?`YJzu0;E+eJIxMrzDDQ=eu@5>n)P;7R zAT?zPv%d_|Pg-Np!pPyV%x|WrQ0yG&f?xQ8S;SH?1z1+L?X*~OFLohw>DXmmUHYd2 z^n`ihUzsdc5^u9CP;tz(Gc$gK^)Rb@N5erHHnyeUds!U8d@|NIP)dAna=(*6NXVz} za5Y!JF?2)xdaQ+`<}J~$YQgoTn5w@@M_or?;pBuqvOqh9(ba@eeYkiZ2>Iblc~5c0 z#S?A9uXgE6pmyhX#msPotcc7Tuh;Vg{X>GgRW~%6@(i^sGo!|v$iL4~~q?01_kqJuu464DB(V@^BpBp864;cU^LM=mr|;S;bQKWJYHm zl7%O6ZqD%E@p&$b&k<;AkH^gI)3bh8f_6N)XAV?lK_dX;1Fm24KEXcz*rGB5Gl2C* z*ByoBXGh#wsy93*FqP4~%z5tg2aFGSm}$*T@ixF8gk-F;mshkyB~9%$4x3Ymbg^D! zWxVWX(jE`HMw&}2i|?ngEzAlb+l8-{3sbl^RGx^8flgYmFpaxPu%*o;5OjmxSIyq~nfYG*7~ z_X$8@b+t^`Bg(7h$+rF>8tDQ#po8rjkdU+W0LTkB+yGkQ{lUkv6x_4s<-?TtU{yB`mi z?WW9%5xbq4P?KZGzhd0%$oBn!3YN!0h~wUMu}s&rJ}95QX_2oP-jm3$Q#7iK2#{qVDmNmd#@Wmg>PW`CI~Pv0of>x7@bWh~NRly0&6iV?8y5vKUj za_daK7fJ)V*5skG8P67@>EYI>C1*Dbz@BLNNv!JWX#}))i(B(8`$xF;mE5wr=fM<# z9gUZK8}NJ&sRGgHglgi>#SgE)M%~+ln{t%Fn{maqb7_|U@oM-s_>ojDNgHsS@Y+fB zT#U@%)gHo^__-s0(Hub-Nwxp~U$}g~N5*<2HV>3>f}B*f?Ko zy+XisQ`SD$v=})gcd`}wD~w;$skA8P;3hB=GrWM_;bE$uP~AHZnUme#Pn*4`|eRJHbgP(kCz3TGM}g2)74mAdXcOZ&mo*|lHj#!#*R?_!4zg;UVp zECc^Si}AHJpp2erE=9n_EZ`JziI^N1%E7vuy3U1)7g0e7NoW+&ig+FG^uGGHZwmppC7N`Va|{~eZped`HhrF7Zw>w zo@$B8$0KGjHYq1rPP{4el9jB8#7F3T74@lBC<@OZjmO5tM59IANn>;>{L}nC0}09U zRAUul)6mQFu{bU0c4KYjTFr}l1#Mx7r(I%4r2p4+b&mR|(RtwrRoRSTIbDrWzV1ez ze6Lr>(z5ft-|s=X=90VJ!fQ)b%C{>qWk;>I)j6)qBv!B)8Aw0+hdz@kf^6JxAui?Vjm_O_HuHYNf)h6E>IWae_;37(Sc_cNmDYRSz zlEJ=cDbteFmzRQH5CfmFz~^W7lkw$FJB@gF+1WOxW4|Mm(e=LANi-`ERMG!X{=rJ% zwXuVn@%zLpVKN6D`c<1Lp*E1Y;%SSmk*cOxxd&hW&PsCg<*N8m&a~>qsE`}jcZCAm zP}s(Etz#tYc)U&MbvWK3r4EHJ-U@tPm2C|`7?AaPdfQ|rxrb&6G4HfOT$}y;iNy#w zK97JuxDoDxSY5c{aKb1)2fM7cS%bVLqmf<%arFsjKd5DvD{j(?y+2HbSe31tqjv|C zy>`qT8?@^0#g8j{YF^cim?gLv1)REt_g-Dt!q-FH+|>m!=NdNoyhpGn?5N4ko{`w( zRi?oqB;+u<`;Ocd6pqJM($ezO;p?65(*M9m(L|Z4pBsAR#z*HcMp3&ILRGEh7!VZZ zG%h&cyA=9#FsU8NaLUmt@HDW_!_8(n%j@g}g9E=P4^NujB*1u|pepd>c2Th5Ul_FD zw$kb8PM4w%fb#4uDv!_^>~TCa+=0*Jj`yo`6gq9b;7Xz|d#{HSRxoW*qR=TT~%r?$#B(YYY|H$22=nyz%k6m4` zvRo{CRJjj-7@B{4T!=sX18M`~q61!>-P>IEMov%Qx_>t8C;TU)r{+i6Lj7qjSF+F` z$2B`*8-21WRCwbCDCT57#BXb7!BEUS^ri13a({jBQTs3%1m#IiPEJNfc5^iEbfPRD z7zlN`SifkgUgd^3d+0nBuhtv!iPTO(rs(+EYq7=@Jiubep{ZKDKSv{S zG;$4tCIUQfcFx}#p%ax}iVPhvV+kOKUnQIm%ouq)z7Nkwz^6aR6ps$esatdlmCxkr zCPf9;B%nD66}EZu#Dyl!5ZTP~cZzqnrjCW4n)Fk{W=B_$)Dn-LjlE-V?-i)A5_NE> z(*uh-*k4}3p50p9(Kgba%Khr^P}?JzDl&gSc`H-3%C}65ImGz9w>keoI_(5Syt+qi zzLd6)PCexm;M2}$I$D$IJfBW(5+6tW>79?$S9u!nXxfYCCIgMYV?)hPJW1#ng`~Jt zj<>=jGHHz4!`V#I@Q=mrF6U0~-}~y9>tv75*bMY+J5F0&m#CKR3?-xDFL#Zr6PlRn z&DB_L8b|S+Y;TKx{s<@AvD=<|8$dY2--eFyRx)$c{XKikPk6&m7>{3(@&`8>g8Hq3Iw+U{ z8I9IfJ0d1`-l+7*6$*3)-1-Or5MF^!|2S^4axNIEG~IEb)f_NjI$0_W2PfmcJ(|H5 zR~btu_bdDNko!Y+hxxFZ)$xLUn`SL_UG=?Z7v=~B*^PvZj8*?qmbe9s?y!gQEVAKL z@9%<)9%Z!g9)?XBuT!cQp~9A5#l9yK`?<{ed;$!?yQeV^>aDG(I9`|Vf70nS$h0@v zf}KLdJEO@g`q{DYkmSv?J{@Zfb8khtk#gF$-Rxez9{{WTc$O)p>XUY>BZBmA)lA2D zv%|~FWn>Y4$?U9F`548?d;H{1N&|P8#c9w50lNh$E^N6j2zD%fy+zc^^``CDJow`6 z7ggyum4CF3k!Q)MM!S6ufi&*rm6+e10bUX#+cF=8k0cBa(-smFFF->Jwz*SwbzpnQ1K z(DCfs!_TV2!b=c^f*qe{fQ~V;5F9ka8vhL?tOv_@T2Pih&&V>}F?;)Z z-zeUc{Y-T|J^GDcaZu0rcFF ztOB4|Qu{ay1n%S_u|B20(ZBP7zyfL|Xyr~H;8(SZ`UR1OlV$@t<53g+EuHcFh{hYa zTUQiQ^;<`>@>c_oZCC@8=va~}HZ*?1T-K!j&Vv41&-hL~#N9!0J*xdFN3|4>TQOJh z&>2TSf0bP3^Zi4E8jVUSyDeHscVvdPh7R-D`$;IS;TYU6f-m{6eTd~FmYt3Hfz0Zb zJ`!tbNq>v$-N%tNG)(R$t21pE_U#DlP%M3Z3&x>rtuFF-9BfgPl$NC0j}t6kJDl@{ z?6?}5?$R$~$ZG$7h{M`FU2y(dsveolh=E_)@8-{>!?#a-9vOP!JQMlz-lgMh)xG98=&T8l$#8nr$8~ZTCo693C)u#;LaI9vcn%6+BC1iM(Z$Qz zavM$6-E5Xld+hDKZ@$VC@41=Uh8hr#(T3cXbG2tBALQg6?O%1`N2xE}YxBIaPd*g( zU_(kjOtpUdy7quW znUSu4va|X-Sii-3Q(q5);1u)3aJ)cesh{=Rlr*AL)(l>2i8g|9n=#%zg(Lso!Lss^1Sq^u8Dl z1*kt~TM%d#(n#`TIF7`Rh7M)Udc;wYD1xi^Oez&^cd_s#?h`d>$b?z0fcuT)> zFhc;%$|wfC@9s#b?F7B_>dKo>JJHKEjx78gH#!yoZTD!;rUU6N^@`nI8nqWn*`apb z-mfp0uT|R-I|LVv_i>`DuV=^kmlR0P=UpKE)|dGknn=ASdR8YN%0uQm2jK;p*$OSe z{P>qgmre9WW>nGZ#R@M313Muoe%IB7RRT_JTh&uJsDWgQU2()ZvRJ@p;PuPy#AB|L9 z>1SD}G`iQnqwv3MK|5svpQy1PI+ZtzIbf9|fiD(UI%b?uN);?;7MPx@zTv0`#wX02 z<*-9enwtj5PS&&|6N!0S*qwU~(|nf9<)xHTU3beyt@ek}wmrY`bo=M#2@zX<-FIDA zNyw+MIkSuN+qh50oAfivKWz7!h{_S{vo*Eix*uQ__sw1EhPE8S6AtGqX*oV;n&gAJ z?q|MQe5dg)@7n}=+8%sG#B_h&JdKuq@uq1Pz&@X$K~k)+dOHV`u;4HnJ9y>)9>ry7 zyV`BD;p;s=F^jYf=Fdf-sN13>2O71o;h1XeQaWZT$zskzq}GKs4dD@dQQb957Md*l z6|}P#VV#x4=EhTGFwUoHq9+9HTMN*)E@GUc3{BdUHyf%MV_LUIJy54fy-q9_X(ApZ zT~sdCOyz8Xn>@G7!qP5W$o8ldh((yaf~pE*3rm~0<5W3FQ6Rvvpi7;}gYfB@3(sIJ zf3%%^%Un_=z7-H1Ej3>6b2*(vWKr=qf61q|=?{yl&^?cliZUdoK}uxzZogIguJXfG zyZ7?sp|9vnITf^nEXHhc^*9>I;jCh^e&W1JFiI(&ksHj%;`!|ZQt9q@7_1B8QM|Xp z6CX^%i&xia_8|kE)}x**eipIEyf0v~D8%mmi!FiIucJgHKa<80%Noj$&ksMAx8ymg$6#*tdn_kiJAJl8tzFu2Uf#iMoDD}Thx zCgG=YXG*!W?tuGJwv+yNN&GBW= zn;Hi3-iC5MRCQRr*9TYwlPmP9E{udnxf-421l z084p)d&{e zQjghstUq&=8CJy1(5!R{!@RZvT6AjTLDd=Bromotwvu_w8Nos8O?)upGx`inBG*J7$5{OP zcS#vT-`iaIAZJVc-_TUI?$mw(=I=yb0 zf=n*K3vP8TnSeGl*Azdf5&E4G6p*wFj5s`I^!uw+L zHnG`zaXHs&vw6_2#@nPn)iXck=vv31UBb}c;m)Hpc#6x;lIiDD70}=G z&OcbzFei93dt|ZbZM+Asbx+t!+9j`dGh552MuTY|w zbWq7hMF=;ldmSxpD`jTj3DJRQl$2m!&6lm`nS>#??r|>Di?wkAwo;^3-=`C9xrDr$TJsqnD6cdt4`~|k$EZ7ZC^Kf z*C}g$2yYw<3ID1|(XM+wjh_oS&B3!^W#s|j4On>@_k?3-UF*VcqjCV$#BNT*?d*o% zYmXnC_$8p3#nT`dK9uf_IJRcj|^Q%M}jNiCJII zj_MM}68!X*(ZW0(vq(&VnVMnK%e|hRR#2wN_XWmAJRH!Sw(7lfTn_Gtj|aN?wELAQ z&ARi>X|%(^BZMy88O$W(h3Vesc$LJoJa)S96RoFv?Ne(?#-E$hz3YNShj zWHsjwp)yW2&)xIBRXUF`QJ(4iI1*`x(zL$NGCQ82E_x;7k@n`uI}oKn~mHe10! z!&A`sBrb>{X_`&KLS=ADtjl4eh^=*-d{8^Kml zG`7ckPlZ5u>(~mKPbF`MS3m1wc+?|a~Rd^ZQfgnI?<6~VLy=6_O2$=fh zkQ%W|XNUR$mHjFZIQ z%6tkXYc}!|{-zNqlkTy$I}!qGS@+GmT$2dKVl^L&dkFFG|E6Anc>WBft-%_X zUtquktpuhtI*;)h3Y9Dj%qI;ewP$98mJ>6 zUjZG2BnEbD)S-05=C`8d#s~|L)0s3$%o<0>UjYh|#j4mopU_PuVqHU>!VR|i1Ia91 zgDS^Y3X-TU81}7>Ax=#6$RW5P!#Fh)&~xV6rfB^I73o>vxS^W+Be%jftag~uL@l}X z;xnUjY2;YuT|P>ELRT+G$yoLzV_Q$fS5-gk%x|n zs+&a0XAL{;bM8rvOfnUEGRGAdg*9expy6};@L&u!sM1^y2%1LHbYau~%;&n%<=_?( z6cr{Om9cX|XHpMZ4|sTxpf5`Wq;r#6=(AMJe?{Q-QR9W9r6*#Q_izd2h$ ziA2@Qum{zqq=ku@D$bKG8B^O30$IlG4tq#lRY>&u=^mQqN4AWGKqViaANrmUo+_9) zum}kV*bKhx30cvrZU3i?>kNmhYrE1!k_RCs20?U2OM>Xa5G{HqMvWT5Bn(kzlwp+U zCVH9Zy^Y@Mh!TQmQ64>Nh!$+Q}oqsg#0)x|) zS>S_~9hxdLKSl2)TDxSbInxEq#*>~?yzj>Ze$t@M(&U~7Z94VXFr(9_`-_vdwy+tI zQMy(hYZZgu%*h81K^*!qrtdU9>tQv5W$lGFX91n%82)E8`bTqh_YlbiBo8L~{o6ZF zn)W~8I$z{blQ~ZAtTY|{|20 z#+sBm2El#sC!`A11$iIQN7C|gGsc-+T z2{l-=cg%PRL&r?iPy(h7XO4bvy6H8Yx9Ue6?sD;WSGZSDL3+3r$Tc1O2$ZIZVyl~O zAD)Ah9>Tt6LimP(gZElSg_?uwR6?H~7S{P$B!uVIdCyh5e%_isNxz=x_aw<9c1F3= z9WysHX{7V|+_$Z^=pjkU@g$;ts8?sqah{;^bRl&ss=t$fhG=jG zNUGsYM7pe2O)0x81l*XACwslC1D$o+v`KZ;;dhHKe(0kxtu~TIFBe;Crb;gW;1ZXb zfdBsIm)XPpkmz-e=sSb`0O!(>29-sH5@t<@L$5=jHR#EWTXdxTI&H?^gS$QMAG{YH zy0N)V1q=c8a3dZp1c~#RG+|@7Z@P-gH*oX%7t>F$2hp~bw|(Tey|i3>2A8bvRq^;y z|8;Pq7V6uuCLc;u(Nj7@1~TC1LrInDa@`FO4!x<)u)IoncMbxT$nct6u&goOPw+o^ z#rO3ZL$cV(4s=vEZcs0y+u2p#B?;y@6VO9mcm4HFHj8w<~PqJBo~8|lh50bZT6N`dG?DMP}Q+Ssc9Q@y>H0~PCmmJQnZr1#ou zBpokhA)(G1JIaGNlv4w^%=uJi%OK=4X4=XO%N}4J)98N;=ra{T!k|vnpTv{$=Ez*l z-38))K^u$~B9J)CRlD%w1+eO}!x`f($>XIEH);)QpAv~ty7!K0g5&#(X^f4H10!2z z2G!$_tTotbX||?qw2GKE$Dw_G#da^64eNs1m(m?q1K{&D9i{!>XFcDD11L0CfWvnS zLTz%z%jOowR{mVGSW86XgveYU3nyidGK{r)+!t`%CmvmcRCCrWyb6tMNA;~EmguPv zJ%|V;CjoVSvE8%kdZ&0b657UBYtkRuGqeun&qg$*i>xWa;QD+qNa-e(HYD?^JNHT@}OddLA@@8#FsV-y6Fe!oup4Q0nJkGn6x?=$J5DRI`%G-?%B?*EzlPQsBQzqoA0 zZr3gMt+4wQhYUX{6>rEbbZ;HxHT{6` zz>6i6N4uY2Y}!o9V8mjS7Dub3msVJG_jPf(^;d|H)YIFPo_Q6@HD^n{LTzEfs}6q_ zfMfToTgAnLh-;(i9EN15Squ9v9TW(ZUBU4D`wcR(D#)p{pQ<6b5%0FFzYh17G)mbV@F31P?avpjc|X>;?zVOwh3C`s*?Y=i;~Y* zkE=!PaPVo5Qu+A0$o(%7Rg%J;NoR)v3AeQifgfLH2gAU&siq1615tz9EttlJvEqsN zy;ccwoC#0FAFBG9aC6N+aE$242T9>CQ5M$co6d#OMpGmOD-^-lBjL$P#d23=0?I0w zh*ke3%ImMWqun$k68{gP+3qW9=QQ6D6-}sUo+X8#ys-|%kT2RpXNqeKs5oU-StZ*- zXnh2_iC*DFwKN~TC^`H^A(YkU`rz>RP9vIKfVS8&Cse|Jx#`rmx|)3P84;0m0h@rp z;lLX@odzvxsZhV>DC;K$oH4WIc%`h8w@h9+k~=OaXGD&QB0;vd;lr}CV|!c+hpSG^ z>nzUmzx6F7l!k4;y)v$kG}_l!K@60?t*m$Jm&h(W81HSzrq$2X#~!GkXM*%?vg{bT z85w~8u7Kueyj3pb%S3~6B#m~*Pckb!#VhUPqghEszPzEf;zcv8N--G|TGjXRWvtI?@Z_BheH9`6UQ>Cz{8D zo{4|V9+0VFKg7)dw=f#jq2lUvlqi!^JYwBbgX6?6{c#e50yPplkUklNzHz8yJ`4^I zIR4Dtu@{m=DE*^cU~&3uPtp+#Fj(qC&I>EXLOD!o!clUCo$-@0lN zPq@mB6I$H_Wp(#bGp{IH?d`G-ER(h_W)<@5{yX(AS1*LoM7GQXDg&knKb z;Dt_;$nDNMB(Ys$se6RNvk(lz_3*yu_1jz-W|v9CYQ>A+U{UqQ-myA=(HK+CW_eZo z#$O|fK{+1S?JoOS`H43BOPxBADvAM9yYkqVi{nr4fhdtg2<(412nx0le0ftCZ#Tp! zX1uZ0rwOva{{-jzY|4{?;Wz)hjDzj+DMh z9j5BQBMFp(VQz&p$Hcu0|0JUH)x@s7&rTsAyGg?;AFJB#$zQK7cnt;K3GcbBM3Ew{=ad3VS?YJj+1z| zwIRx?Fk*D}@eRUQy*IrhOew?m(J|21g6ml_0X?D?CpIg=Q zN-uicR~APwLty1mevzM#t^;Lh7HcrOE&5Gy1K258sBUZXC4=$`TSN^sW#;Ct)i<7c zeTBjt@t?JA_A{fz8Vj+^)&G*E;bg)S5^5Ejwx53*9|x8x<_^&oL+Yboymv&Ix%|QM zsZqj}TC>i5t(?F=0rR0%Yy$!S8Q-|ikKdhSDzHDK77k`T>Wi^BppYg_KzxK_g#S89OFHDNOrB8hm#1XS*5*jYHN!XUOPj~OjfBGCGZ-0s8Un97 zvkB%`ACoU{vt`2l1Y5R{t-p*uKOO=?~0>dL)- zrqXM(tE&!lOF@6R&t}3?>N?PIaXC4r3O0>L2jx*LI8dzel{9hN?(J?eO^_CU0cfDN z5uK7!@$bY+n&Do9nvG&@GINZs~;DXs$3tZv4}jI_^IyQA6(tl|@L;P%hCMsO1=D{IH|?oLtQ@kUB4 zGPY;uolWFQ6ZtY;?b4+`9cS59IutlTOyq;Qi z33v4TC=ZUva)t#hVR~vPn1k^b*CIN0Ni_Cfkcl&knFo5w?$m>GKCu>C)IAhO*(Rrk3CYpj=W24rU^Bn}03)H-x&QzG literal 0 HcmV?d00001 -- 2.39.5