/********************************************************************** * Copyright (c) 2005-2009 ant4eclipse project team. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Nils Hartmann, Daniel Kasmeroglu, Gerd Wuetherich **********************************************************************/ package org.ant4eclipse.lib.pydt.internal.model.pyre; import org.ant4eclipse.lib.core.Assure; import org.ant4eclipse.lib.core.Lifecycle; import org.ant4eclipse.lib.core.data.Version; import org.ant4eclipse.lib.core.exception.Ant4EclipseException; import org.ant4eclipse.lib.core.logging.A4ELogging; import org.ant4eclipse.lib.core.util.StringMap; import org.ant4eclipse.lib.core.util.Utilities; import org.ant4eclipse.lib.pydt.PydtExceptionCode; import org.ant4eclipse.lib.pydt.model.PythonInterpreter; import org.ant4eclipse.lib.pydt.model.pyre.PythonRuntime; import org.ant4eclipse.lib.pydt.model.pyre.PythonRuntimeRegistry; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.StringReader; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Hashtable; import java.util.List; import java.util.Map; /** * Implementation of a registry for {@link PythonRuntime} instances. * * @author Daniel Kasmeroglu (Daniel.Kasmeroglu@Kasisoft.net) */ public class PythonRuntimeRegistryImpl implements PythonRuntimeRegistry, Lifecycle { private static final String PROP_INTERPRETER = "interpreter."; private static final String MSG_FAILEDTOREADOUTPUT = "Failed to read the output. Cause: %s"; private static final String MSG_INVALIDOUTPUT = "The executable '%s' produced invalid output.\nOutput:\n%sError:\n%s"; private static final String MSG_REGISTEREDRUNTIME = "Registered runtime with id '%s' for the location '%s'."; private static final String MSG_REPEATEDREGISTRATION = "A python runtime with the id '%s' and the location '%s' has been registered multiple times !"; private static final String MARKER_BEGIN = "ANT4ECLIPSE-BEGIN"; private static final String MARKER_END = "ANT4ECLIPSE-END"; private static final String NAME_SITEPACKAGES = "site-packages"; private Map<String, PythonRuntime> _runtimes = new Hashtable<String, PythonRuntime>(); private String _defaultid = null; private File _pythonlister = null; private File _listerdir = null; private File _currentdir = null; private PythonInterpreter[] _interpreters = null; private boolean _initialised = false; /** * Tries to determine the location of a python interpreter. * * @param location * The location of a python installation. Not <code>null</code>. * * @return The interpreter or <code>null</code> if none could be found. */ private PythonInterpreter lookupInterpreter(File location) { for (PythonInterpreter interpreter : this._interpreters) { File result = interpreter.lookup(location); if (result != null) { return interpreter; } } return null; } /** * {@inheritDoc} */ public boolean hasRuntime(String id) { Assure.nonEmpty("id", id); return this._runtimes.containsKey(id); } /** * {@inheritDoc} */ public void registerRuntime(String id, File location, boolean sitepackages) { Assure.nonEmpty("id", id); Assure.notNull("location", location); location = Utilities.getCanonicalFile(location); PythonRuntime existing = getRuntime(id); if (existing != null) { // check the current setting if (!location.equals(existing.getLocation())) { // same id for different locations is not allowed throw new Ant4EclipseException(PydtExceptionCode.DUPLICATERUNTIME, id, existing.getLocation(), location); } else { // same record, so skip this registration while creating a message only A4ELogging.debug(MSG_REPEATEDREGISTRATION, id, location); return; } } // register the new runtime but we need to identify the corresponding libraries PythonInterpreter python = lookupInterpreter(location); if (python == null) { throw new Ant4EclipseException(PydtExceptionCode.UNSUPPORTEDRUNTIME, id, location); } File interpreter = python.lookup(location); // launch the python lister script to access the python path StringBuffer output = new StringBuffer(); StringBuffer error = new StringBuffer(); Utilities.execute(interpreter, output, error, this._pythonlister.getAbsolutePath()); String[] extraction = extractOutput(output.toString(), sitepackages); if (extraction == null) { // returncode is 0 A4ELogging.debug(MSG_INVALIDOUTPUT, interpreter, output, error); throw new Ant4EclipseException(PydtExceptionCode.UNSUPPORTEDRUNTIME, id, location); } // load version number and library records Version version = Version.newStandardVersion(extraction[0]); File[] libs = new File[extraction.length - 1]; for (int i = 0; i < libs.length; i++) { libs[i] = new File(extraction[i + 1]); } PythonRuntime newruntime = new PythonRuntimeImpl(id, location, version, libs, python); A4ELogging.debug(MSG_REGISTEREDRUNTIME, id, location); this._runtimes.put(id, newruntime); } /** * This function parses the output and returns the output as a list of strings. * * @param content * The output that has to be parsed. Neither <code>null</code> nor empty. * @param sitepackages * <code>true</code> <=> Enable support for site packages on the runtime. * * @return A list of strings containing the runtime information or <code>null</code> in case of a failure. */ private String[] extractOutput(String content, boolean sitepackages) { List<String> list = new ArrayList<String>(); boolean collect = false; boolean first = true; BufferedReader reader = new BufferedReader(new StringReader(content)); try { String line = reader.readLine(); while (line != null) { line = line.trim(); if (MARKER_BEGIN.equals(line)) { collect = true; } else if (MARKER_END.equals(line)) { collect = true; } else if (collect) { if (first) { // the first line provides the versioning information list.add(line); first = false; } else { // the brackets are just a security precaution just for the case that a path // starts or ends with whitespace characters int open = line.indexOf('['); int close = line.lastIndexOf(']'); line = line.substring(open + 1, close); if (!isHiddenDir(line, sitepackages)) { list.add(line); } } } line = reader.readLine(); } if (list.size() < 2) { // there must be at least the version information and one directory return null; } else { return list.toArray(new String[list.size()]); } } catch (IOException ex) { A4ELogging.debug(MSG_FAILEDTOREADOUTPUT, ex.getMessage()); return null; } } /** * Returns <code>true</code> if the supplied directory path is considered to be hidden. This is the case when the path * is just a symbol (f.e. __classpath__ under jython), the path is the working directory of our python script or the * path is the current directory. * * @param dir * The potential directory used to be tested. Neither <code>null</code> nor empty. * @param sitepackages * <code>true</code> <=> Enable support for site packages on the runtime. * * @return <code>true</code> <=> The supplied path is not a valid part of the runtime and shall be hidden for that * reason. * * @throws IOException * Path calculation failed for some reason. */ private boolean isHiddenDir(String dir, boolean sitepackages) throws IOException { File file = new File(dir); if (!file.exists()) { // this directory does not exist (f.e. __classpath__ symbols provided by jython) return true; } if ((!sitepackages) && NAME_SITEPACKAGES.equals(file.getName())) { return true; } file = file.getCanonicalFile(); return this._listerdir.equals(file) || this._currentdir.equals(file); } /** * {@inheritDoc} */ public void setDefaultRuntime(String id) { Assure.nonEmpty("id", id); if (!hasRuntime(id)) { throw new Ant4EclipseException(PydtExceptionCode.INVALIDDEFAULTID, id); } this._defaultid = id; } /** * {@inheritDoc} */ public PythonRuntime getRuntime() { if (this._defaultid != null) { return this._runtimes.get(this._defaultid); } else if (this._runtimes.size() == 1) { return this._runtimes.values().iterator().next(); } throw new Ant4EclipseException(PydtExceptionCode.NODEFAULTRUNTIME); } /** * {@inheritDoc} */ public PythonRuntime getRuntime(String id) { Assure.nonEmpty("id", id); return this._runtimes.get(id); } /** * {@inheritDoc} */ public PythonInterpreter[] getSupportedInterpreters() { return this._interpreters; } /** * {@inheritDoc} */ public void dispose() { this._runtimes.clear(); Utilities.delete(this._pythonlister); this._defaultid = null; this._pythonlister = null; this._listerdir = null; this._currentdir = null; this._interpreters = null; this._initialised = false; } /** * {@inheritDoc} */ public void initialize() { // export the python lister script, so it can be executed in order to access the pythonpath this._pythonlister = Utilities.exportResource("/org/ant4eclipse/lib/pydt/lister.py"); if (!this._pythonlister.isAbsolute()) { this._pythonlister = this._pythonlister.getAbsoluteFile(); } this._listerdir = this._pythonlister.getParentFile(); this._currentdir = new File("."); this._listerdir = Utilities.getCanonicalFile(this._listerdir); this._currentdir = Utilities.getCanonicalFile(this._currentdir); // load the python interpreter configurations URL cfgurl = getClass().getResource("/org/ant4eclipse/lib/pydt/python.properties"); if (cfgurl == null) { throw new Ant4EclipseException(PydtExceptionCode.MISSINGPYTHONPROPERTIES); } StringMap props = new StringMap(cfgurl); List<PythonInterpreter> interpreters = new ArrayList<PythonInterpreter>(); for (Map.Entry<String, String> entry : props.entrySet()) { if (entry.getKey().startsWith(PROP_INTERPRETER)) { String name = entry.getKey().substring(PROP_INTERPRETER.length()); String[] exes = Utilities.cleanup(entry.getValue().split(",")); if (exes == null) { throw new Ant4EclipseException(PydtExceptionCode.MISSINGEXECUTABLES, entry.getKey()); } Arrays.sort(exes); interpreters.add(new PythonInterpreter(name, exes)); } } this._interpreters = interpreters.toArray(new PythonInterpreter[interpreters.size()]); Arrays.sort(this._interpreters); this._initialised = true; } /** * {@inheritDoc} */ public boolean isInitialized() { return this._initialised; } } /* ENDCLASS */