/* * $Id$ * * SARL is an general-purpose agent programming language. * More details on http://www.sarl.io * * Copyright (C) 2014-2017 the original authors or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.sarl.eclipse.runtime; import java.io.File; import java.io.IOException; import java.lang.ref.SoftReference; import java.net.URL; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.jar.Attributes; import java.util.jar.JarFile; import java.util.jar.Manifest; import java.util.regex.Pattern; import com.google.common.base.Objects; import com.google.common.base.Strings; import com.google.common.collect.Maps; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Path; import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.internal.launching.RuntimeClasspathEntry; import org.eclipse.jdt.launching.IRuntimeClasspathEntry; import org.eclipse.jdt.launching.LibraryLocation; import org.eclipse.jdt.launching.PropertyChangeEvent; import org.osgi.framework.Version; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import io.sarl.eclipse.SARLEclipsePlugin; import io.sarl.eclipse.util.Utilities; /** * Standard SRE install. * * <p>The standard SRE install assumes: * <ul> * <li>The SRE is based on a single JAR file.</li> * <li>The main class of the SRE is defined in the Manifest field <code>"Main-Class"</code>.</li> * <li>The Manifest contains a section named <code>"SARL-Runtime-Environment"</code>. This section contains the following entries: * <ul> * <li>The version number of the SARL sepcifications that are supported by the SRE is defined in the Manifest field <code>"SARL-Spec-Version"</code>. * </li> * <li>The name of the SRE may be given by the field <code>"Name"</code>.</li> * <li>The VM arguments of the SRE may be given by the field <code>"VM-Arguments"</code>.</li> * <li>The program arguments of the SRE may be given by the field <code>"Program-Arguments"</code>.</li> * <li>The command line option for avoiding the logo is given by the field <code>"CLI-Hide-Logo"</code>.</li> * <li>The command line option for displaying the logo is given by the field <code>"CLI-Show-Logo"</code>.</li> * <li>The command line option for displaying the information messages is given by the field<code>"CLI-Show-Info"</code>.</li> * <li>The command line option for hiding the information messages is given by the field<code>"CLI-Hide-Info"</code>.</li> * <li>The command line option for using the default root context id is given by the field<code>"CLI-Default-Context-ID"</code>.</li> * <li>The command line option for using the random root context id is given by the field<code>"CLI-Random-Context-ID"</code>.</li> * <li>The command line option for using the agent-type-based root context id is given by the field<code>"CLI-BootAgent-Context-ID"</code>.</li> * </ul> * </li> * </ul> * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ public class StandardSREInstall extends AbstractSREInstall { private IPath jarFile; private String vmArguments = Utilities.EMPTY_STRING; private String programArguments = Utilities.EMPTY_STRING; private String cliLogoOff; private String cliLogoOn; private String cliShowInfo; private String cliHideInfo; private String cliDefaultContextID; private String cliRandomContextID; private String cliBootAgentContextID; private String cliSreOffline; private String cliNoMoreOption; private String cliEmbedded; private String manifestMainClass; private String manifestName; private transient SoftReference<Map<String, String>> optionBuffer; /** * Construct a SRE installation. * * @param id - the identifier of this SRE installation. */ public StandardSREInstall(String id) { super(id); } @Override public StandardSREInstall clone() { final StandardSREInstall clone = (StandardSREInstall) super.clone(); clone.jarFile = this.jarFile == null ? null : Path.fromPortableString(clone.jarFile.toPortableString()); return clone; } @Override public StandardSREInstall copy(String id) { return (StandardSREInstall) super.copy(id); } /** * Replies the path to the JAR file that is supporting this SRE installation. * * @return the path to the JAR file. Must not be <code>null</code>. */ public IPath getJarFile() { return this.jarFile; } /** * Change the path to the JAR file that is supporting this SRE installation. * * @param jarFile - the path to the JAR file. Must not be <code>null</code>. */ public void setJarFile(IPath jarFile) { if (!Objects.equal(jarFile, this.jarFile)) { final PropertyChangeEvent event = new PropertyChangeEvent(this, ISREInstallChangedListener.PROPERTY_JAR_FILE, this.jarFile, jarFile); this.jarFile = jarFile; setDirty(true); if (getNotify()) { SARLRuntime.fireSREChanged(event); } } } @Override public String getLocation() { final IPath iJarFile = getJarFile(); if (iJarFile == null) { return getName(); } return iJarFile.toOSString(); } @Override public String getName() { String nam = super.getName(); if (Strings.isNullOrEmpty(nam)) { final IPath path = getJarFile(); if (path != null) { nam = path.removeFileExtension().lastSegment(); } else { nam = getId(); } } return nam; } @Override public String getNameNoDefault() { return super.getName(); } @SuppressWarnings({ "checkstyle:cyclomaticcomplexity", "checkstyle:npathcomplexity" }) @Override protected void resolveDirtyFields(boolean forceSettings) { if (this.jarFile != null) { try (JarFile jFile = new JarFile(this.jarFile.toFile())) { final Manifest manifest = jFile.getManifest(); // // Main class this.manifestMainClass = manifest.getMainAttributes().getValue(SREConstants.MANIFEST_MAIN_CLASS); if (Strings.isNullOrEmpty(this.manifestMainClass)) { throw new SREException(MessageFormat.format(Messages.StandardSREInstall_0, getId())); } if (forceSettings || Strings.isNullOrEmpty(getMainClass())) { setMainClass(this.manifestMainClass); } // Get SARL section: final Attributes sarlSection = manifest.getAttributes(SREConstants.MANIFEST_SECTION_SRE); if (sarlSection == null) { throw new SREException(Messages.StandardSREInstall_1); } // // Stand-alone SRE final String strStandalone = sarlSection.getValue(SREConstants.MANIFEST_STANDALONE_SRE); boolean isStandalone = false; if (strStandalone != null && !strStandalone.isEmpty()) { isStandalone = Boolean.parseBoolean(strStandalone); } setStandalone(isStandalone); // // SARL version final String sarlVersion = sarlSection.getValue(SREConstants.MANIFEST_SARL_SPEC_VERSION); String minVersion = null; String maxVersion = null; if (!Strings.isNullOrEmpty(sarlVersion)) { try { final Version sarlVer = Version.parseVersion(sarlVersion); if (sarlVer != null) { minVersion = new Version(sarlVer.getMajor(), sarlVer.getMinor(), 0).toString(); maxVersion = new Version(sarlVer.getMajor(), sarlVer.getMinor() + 1, 0).toString(); } } catch (Throwable exception) { // } } if (forceSettings || Strings.isNullOrEmpty(getMinimalSARLVersion())) { setMinimalSARLVersion(minVersion); } if (forceSettings || Strings.isNullOrEmpty(getMaximalSARLVersion())) { setMaximalSARLVersion(maxVersion); } // // SRE Name this.manifestName = Strings.nullToEmpty(sarlSection.getValue(SREConstants.MANIFEST_SRE_NAME)); if (forceSettings || Strings.isNullOrEmpty(getNameNoDefault())) { setName(this.manifestName); } // // VM arguments final String vmArgs = Strings.nullToEmpty(sarlSection.getValue(SREConstants.MANIFEST_VM_ARGUMENTS)); if (!this.vmArguments.equals(vmArgs)) { final PropertyChangeEvent event = new PropertyChangeEvent(this, ISREInstallChangedListener.PROPERTY_VM_ARGUMENTS, this.vmArguments, Strings.nullToEmpty(vmArgs)); this.vmArguments = vmArgs; if (getNotify()) { SARLRuntime.fireSREChanged(event); } } // // VM-specific attributes setVMSpecificAttributesMap(null); // // Specific CLI Options this.optionBuffer = null; this.cliLogoOn = sarlSection.getValue(SREConstants.MANIFEST_CLI_SHOW_LOGO); this.cliLogoOff = sarlSection.getValue(SREConstants.MANIFEST_CLI_HIDE_LOGO); this.cliShowInfo = sarlSection.getValue(SREConstants.MANIFEST_CLI_SHOW_INFO); this.cliHideInfo = sarlSection.getValue(SREConstants.MANIFEST_CLI_HIDE_INFO); this.cliDefaultContextID = sarlSection.getValue(SREConstants.MANIFEST_CLI_DEFAULT_CONTEXT_ID); this.cliRandomContextID = sarlSection.getValue(SREConstants.MANIFEST_CLI_RANDOM_CONTEXT_ID); this.cliBootAgentContextID = sarlSection.getValue(SREConstants.MANIFEST_CLI_BOOT_AGENT_CONTEXT_ID); this.cliSreOffline = sarlSection.getValue(SREConstants.MANIFEST_CLI_SRE_OFFLINE); this.cliNoMoreOption = sarlSection.getValue(SREConstants.MANIFEST_CLI_NO_MORE_OPTION); this.cliEmbedded = sarlSection.getValue(SREConstants.MANIFEST_CLI_EMBEDDED); // // Program arguments final String programArgs = Strings.nullToEmpty(sarlSection.getValue(SREConstants.MANIFEST_PROGRAM_ARGUMENTS)); if (!this.programArguments.equals(programArgs)) { final PropertyChangeEvent event = new PropertyChangeEvent(this, ISREInstallChangedListener.PROPERTY_PROGRAM_ARGUMENTS, this.programArguments, programArgs); this.programArguments = programArgs; if (getNotify()) { SARLRuntime.fireSREChanged(event); } } // // Library Location if (forceSettings || getClassPathEntries().isEmpty()) { final List<IRuntimeClasspathEntry> classPath = new ArrayList<>(); LibraryLocation location = new LibraryLocation(this.jarFile, Path.EMPTY, Path.EMPTY); IClasspathEntry cpEntry = JavaCore.newLibraryEntry(location.getSystemLibraryPath(), location.getSystemLibrarySourcePath(), location.getPackageRootPath()); IRuntimeClasspathEntry rtcpEntry = new RuntimeClasspathEntry(cpEntry); // No more a bootstrap library for enabling it to be in the classpath (not the JVM bootstrap). // In fact you can have a single bootstrap library if you add a new one you remove the default one and it bugs rtcpEntry.setClasspathProperty(IRuntimeClasspathEntry.USER_CLASSES); classPath.add(rtcpEntry); // final String classPathStr = manifest.getMainAttributes().getValue(SREConstants.MANIFEST_CLASS_PATH); final IPath rootPath = this.jarFile.removeLastSegments(1); if (!Strings.isNullOrEmpty(classPathStr)) { for (final String cpElement : classPathStr.split(Pattern.quote(":"))) { //$NON-NLS-1$ final IPath path = parsePath(cpElement, Path.EMPTY, rootPath); location = new LibraryLocation(path, Path.EMPTY, Path.EMPTY); cpEntry = JavaCore.newLibraryEntry(location.getSystemLibraryPath(), location.getSystemLibrarySourcePath(), location.getPackageRootPath()); rtcpEntry = new RuntimeClasspathEntry(cpEntry); // No more a bootstrap library for enabling it to be in the classpath (not the JVM bootstrap). // In fact you can have a single bootstrap library if you add a new one you remove the default one and it bugs rtcpEntry.setClasspathProperty(IRuntimeClasspathEntry.USER_CLASSES); classPath.add(rtcpEntry); } } setClassPathEntries(classPath); } } catch (SREException e) { throw e; } catch (Throwable e) { throw new SREException(e); } } else { throw new SREException(Messages.StandardSREInstall_2); } } @Override public String getSREArguments() { if (isDirty()) { setDirty(false); resolveDirtyFields(true); } return this.programArguments; } @Override public String getJVMArguments() { if (isDirty()) { setDirty(false); resolveDirtyFields(true); } return this.vmArguments; } @Override public IStatus getValidity(int ignoreCauses) { if (isDirty()) { return revalidate(ignoreCauses); } try { final IPath path = getJarFile(); if (path == null && (ignoreCauses & CODE_SOURCE) == 0) { return SARLEclipsePlugin.getDefault().createStatus(IStatus.ERROR, CODE_SOURCE, Messages.StandardSREInstall_2); } final File file = (path == null) ? null : path.toFile(); if ((file == null || !file.canRead()) && (ignoreCauses & CODE_SOURCE) == 0) { return SARLEclipsePlugin.getDefault().createStatus(IStatus.ERROR, CODE_SOURCE, Messages.StandardSREInstall_3); } } catch (Throwable e) { if ((ignoreCauses & CODE_GENERAL) == 0) { return SARLEclipsePlugin.getDefault().createStatus(IStatus.ERROR, CODE_GENERAL, e); } } return super.getValidity(ignoreCauses); } @Override public void getAsXML(Document document, Element element) throws IOException { if (isDirty()) { setDirty(false); resolveDirtyFields(true); } final IPath path = getJarFile(); element.setAttribute(SREConstants.XML_STANDALONE_SRE, Boolean.toString(isStandalone())); element.setAttribute(SREConstants.XML_LIBRARY_PATH, path.toPortableString()); final String name = Strings.nullToEmpty(getName()); if (!name.equals(this.manifestName)) { element.setAttribute(SREConstants.XML_SRE_NAME, name); } final String mainClass = Strings.nullToEmpty(getMainClass()); if (!mainClass.equals(this.manifestMainClass)) { element.setAttribute(SREConstants.XML_MAIN_CLASS, mainClass); } final List<IRuntimeClasspathEntry> libraries = getClassPathEntries(); if (libraries.size() != 1 || !libraries.get(0).getClasspathEntry().getPath().equals(this.jarFile)) { final IPath rootPath = path.removeLastSegments(1); for (final IRuntimeClasspathEntry location : libraries) { final Element libraryNode = document.createElement(SREConstants.XML_LIBRARY_LOCATION); libraryNode.setAttribute(SREConstants.XML_SYSTEM_LIBRARY_PATH, makeRelativePath(location.getPath(), path, rootPath)); libraryNode.setAttribute(SREConstants.XML_PACKAGE_ROOT_PATH, makeRelativePath(location.getSourceAttachmentRootPath(), path, rootPath)); libraryNode.setAttribute(SREConstants.XML_SOURCE_PATH, makeRelativePath(location.getSourceAttachmentPath(), path, rootPath)); /* No javadoc path accessible from ClasspathEntry final URL javadoc = location.getJavadocLocation(); if (javadoc != null) { libraryNode.setAttribute(SREConstants.XML_JAVADOC_PATH, javadoc.toString()); } */ element.appendChild(libraryNode); } } } @Override public void setFromXML(Element element) throws IOException { final IPath path = parsePath( element.getAttribute(SREConstants.XML_LIBRARY_PATH), null, null); try { if (path != null) { setJarFile(path); final String strStandalone = element.getAttribute(SREConstants.XML_STANDALONE_SRE); boolean isStandalone = false; if (strStandalone != null && !strStandalone.isEmpty()) { isStandalone = Boolean.parseBoolean(strStandalone); } setStandalone(isStandalone); final String name = element.getAttribute(SREConstants.XML_SRE_NAME); if (!Strings.isNullOrEmpty(name)) { setName(name); } final String mainClass = element.getAttribute(SREConstants.XML_MAIN_CLASS); if (!Strings.isNullOrEmpty(mainClass)) { setMainClass(mainClass); } final List<IRuntimeClasspathEntry> locations = new ArrayList<>(); final NodeList children = element.getChildNodes(); final IPath rootPath = path.removeLastSegments(1); for (int i = 0; i < children.getLength(); ++i) { final Node node = children.item(i); if (node instanceof Element && SREConstants.XML_LIBRARY_LOCATION.equalsIgnoreCase(node.getNodeName())) { final Element libraryNode = (Element) node; final IPath systemLibraryPath = parsePath( libraryNode.getAttribute(SREConstants.XML_SYSTEM_LIBRARY_PATH), null, rootPath); if (systemLibraryPath != null) { final IPath packageRootPath = parsePath( libraryNode.getAttribute(SREConstants.XML_PACKAGE_ROOT_PATH), Path.EMPTY, rootPath); final IPath sourcePath = parsePath( libraryNode.getAttribute(SREConstants.XML_SOURCE_PATH), Path.EMPTY, rootPath); URL javadoc = null; try { final String urlTxt = libraryNode.getAttribute(SREConstants.XML_JAVADOC_PATH); javadoc = new URL(urlTxt); } catch (Throwable exception) { // } final LibraryLocation location = new LibraryLocation(systemLibraryPath, sourcePath, packageRootPath, javadoc); final IClasspathEntry cpEntry = JavaCore.newLibraryEntry( location.getSystemLibraryPath(), location.getSystemLibrarySourcePath(), location.getPackageRootPath()); final IRuntimeClasspathEntry rtcpEntry = new RuntimeClasspathEntry(cpEntry); // No more a bootstrap library for enabling it to be in the classpath (not the JVM bootstrap). // In fact you can have a single bootstrap library if you add a new one you remove the default one and it bugs rtcpEntry.setClasspathProperty(IRuntimeClasspathEntry.USER_CLASSES); locations.add(rtcpEntry); } else { SARLEclipsePlugin.getDefault() .logErrorMessage(MessageFormat.format(Messages.StandardSREInstall_4, getId())); } } } if (!locations.isEmpty()) { setClassPathEntries(locations); } return; } } catch (Throwable exception) { throw new IOException(MessageFormat.format(Messages.StandardSREInstall_5, getId()), exception); } throw new IOException(MessageFormat.format(Messages.StandardSREInstall_5, getId())); } /** Path the given string for extracting a path. * * @param path the string representation of the path to parse. * @param defaultPath the default path. * @param rootPath the root path to use is the given path is not absolute. * @return the absolute path. */ private static IPath parsePath(String path, IPath defaultPath, IPath rootPath) { if (!Strings.isNullOrEmpty(path)) { try { final IPath pathObject = Path.fromPortableString(path); if (pathObject != null) { if (rootPath != null && !pathObject.isAbsolute()) { return rootPath.append(pathObject); } return pathObject; } } catch (Throwable exception) { // } } return defaultPath; } private static String makeRelativePath(IPath pathToConvert, IPath jarPath, IPath rootPath) { if (pathToConvert == null) { return null; } if (!jarPath.equals(pathToConvert) && rootPath.isPrefixOf(pathToConvert)) { return pathToConvert.makeRelativeTo(rootPath).toPortableString(); } return pathToConvert.toPortableString(); } @Override public Map<String, String> getAvailableCommandLineOptions() { if (isDirty()) { setDirty(false); resolveDirtyFields(true); } Map<String, String> options = (this.optionBuffer == null) ? null : this.optionBuffer.get(); if (options == null) { options = Maps.newHashMap(); putIfNotempty(options, SREConstants.MANIFEST_CLI_SHOW_LOGO, this.cliLogoOn); putIfNotempty(options, SREConstants.MANIFEST_CLI_HIDE_LOGO, this.cliLogoOff); putIfNotempty(options, SREConstants.MANIFEST_CLI_SHOW_INFO, this.cliShowInfo); putIfNotempty(options, SREConstants.MANIFEST_CLI_HIDE_INFO, this.cliHideInfo); putIfNotempty(options, SREConstants.MANIFEST_CLI_DEFAULT_CONTEXT_ID, this.cliDefaultContextID); putIfNotempty(options, SREConstants.MANIFEST_CLI_RANDOM_CONTEXT_ID, this.cliRandomContextID); putIfNotempty(options, SREConstants.MANIFEST_CLI_BOOT_AGENT_CONTEXT_ID, this.cliBootAgentContextID); putIfNotempty(options, SREConstants.MANIFEST_CLI_SRE_OFFLINE, this.cliSreOffline); putIfNotempty(options, SREConstants.MANIFEST_CLI_NO_MORE_OPTION, this.cliNoMoreOption); putIfNotempty(options, SREConstants.MANIFEST_CLI_EMBEDDED, this.cliEmbedded); this.optionBuffer = new SoftReference<>(options); } return Collections.unmodifiableMap(options); } private static void putIfNotempty(Map<String, String> map, String key, String value) { if (!Strings.isNullOrEmpty(value)) { map.put(key, value); } } }