/*******************************************************************************
* Copyright (c) 2009,2014 IBM Corporation and others.
* 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:
* IBM Corporation - initial API and implementation
* Zend Technologies
*******************************************************************************/
package org.eclipse.php.internal.debug.core;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.MessageFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.php.internal.debug.core.launching.PHPLaunchUtilities;
import org.eclipse.php.internal.debug.core.phpIni.PHPINIUtil;
import org.eclipse.php.internal.debug.core.preferences.PHPexeItem;
import org.eclipse.php.internal.debug.core.preferences.PHPexes;
/**
* Class providing utility methods for PHP executables. Clients may use it to
* execute commands with use of PHP executables or get some basic info about
* provided PHP executable (name, version, SAPI type, etc.).
*
* @author Bartlomiej Laczkowski
*/
public final class PHPExeUtil {
/**
* Provides info about PHP executable (name, version, SAPI type, etc.).
*/
public static final class PHPExeInfo {
private String version;
private String sapiType;
private File execFile;
private String name;
private PHPExeInfo(String name, String version, String sapiType, File execFile) {
super();
this.name = name;
this.version = version;
this.sapiType = sapiType;
this.execFile = execFile;
}
public String getName() {
return name;
}
public String getVersion() {
return version;
}
public String getSapiType() {
return sapiType;
}
public File getExecFile() {
return execFile;
}
}
/**
* Provides basic info about module installed (only its name and name of a
* group to which it belongs) on PHP executable in pair with some INI file.
*/
public static final class PHPModuleInfo {
private String name;
private String groupName;
public PHPModuleInfo(String name, String groupName) {
super();
this.name = name;
this.groupName = groupName;
}
public String getName() {
return name;
}
public String getGroupName() {
return groupName;
}
}
public static final class PHPVersion {
private int major = 0;
private int minor = 0;
private int service = 0;
private PHPVersion(PHPexeItem phpExeItem) {
build(phpExeItem.getVersion());
}
private void build(String version) {
if (version == null)
return;
String[] parts = version.split("\\."); //$NON-NLS-1$
try {
major = Integer.valueOf(parts[0]);
minor = Integer.valueOf(parts[1]);
service = Integer.valueOf(parts[2]);
} catch (ArrayIndexOutOfBoundsException e) {
// skip
}
}
public int getMajor() {
return major;
}
public int getMinor() {
return minor;
}
public int getService() {
return service;
}
}
private static final Pattern PATTERN_PHP_VERSION = Pattern.compile("PHP (\\d\\.\\d\\.\\d+).*? \\((.*?)\\)"); //$NON-NLS-1$
private static final Map<File, PHPExeInfo> phpInfos = new HashMap<File, PHPExeInfo>();
private PHPExeUtil() {
};
/**
* Executes given PHP command and returns output.
*
* @param cmd
* Command array
* @throws IOException
*/
public static String exec(String... cmd) throws IOException {
String[] envParams = null;
String env = PHPLaunchUtilities.getLibrarySearchPathEnv(new File(cmd[0]).getParentFile());
if (env != null) {
envParams = new String[] { env };
}
cmd = filterNulls(cmd);
Process p = Runtime.getRuntime().exec(cmd, envParams);
BufferedReader r = new BufferedReader(new InputStreamReader(p.getInputStream()));
StringBuilder buf = new StringBuilder();
String l;
while ((l = r.readLine()) != null) {
buf.append(l).append('\n');
}
return buf.toString();
}
/**
* Creates and returns PHP executable info.
*
* @param executableFile
* @param loadDefaultINI
* @return PHP info
* @throws PHPExeException
*/
public synchronized static PHPExeInfo getPHPInfo(final File executableFile, boolean reload) throws PHPExeException {
PHPExeInfo phpInfo = phpInfos.get(executableFile);
if (phpInfo != null && !reload)
return phpInfo;
String version = null, sapiType = null, name = null;
String exePath = executableFile == null ? "<null>" //$NON-NLS-1$
: executableFile.getAbsolutePath();
// Simple pre-check
if (executableFile == null || !executableFile.exists()
|| !executableFile.getName().toLowerCase().contains("php") //$NON-NLS-1$
|| executableFile.isDirectory()) {
return null;
}
try {
PHPexes.changePermissions(executableFile);
Matcher m;
String output = fetchVersion(executableFile);
m = PATTERN_PHP_VERSION.matcher(output);
if (!m.find()) {
throw new PHPExeException(MessageFormat.format("Cannot determine version of the PHP executable ({0}).", //$NON-NLS-1$
exePath));
}
// Fetch version
version = m.group(1);
// Fetch SAPI type
String sType = m.group(2);
sapiType = null;
if (sType.startsWith("cgi")) { //$NON-NLS-1$
sapiType = PHPexeItem.SAPI_CGI;
} else if (sType.startsWith("cli")) { //$NON-NLS-1$
sapiType = PHPexeItem.SAPI_CLI;
} else {
throw new PHPExeException(MessageFormat.format("Cannot determine type of the PHP executable ({0}).", //$NON-NLS-1$
exePath));
}
// Fetch default name
name = "PHP " + version + " (" + sapiType + ")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
} catch (IOException e) {
throw new PHPExeException(MessageFormat.format("Invalid PHP executable: {0}.", exePath), e); //$NON-NLS-1$
}
phpInfo = new PHPExeInfo(name, version, sapiType, executableFile);
phpInfos.put(executableFile, phpInfo);
return phpInfo;
}
/**
* Returns info description by calling 'php -i' command in pair with the
* appropriate php configuration file location.
*
* @param exec
* @param tmpIni
* @param skipSystemIni
* @return PHP executable info
* @throws IOException
*/
public static String getInfo(PHPexeItem phpExeItem) {
PHPVersion phpVersion = new PHPVersion(phpExeItem);
File exec = phpExeItem.getExecutable();
try {
if (phpVersion.getMajor() >= 5)
return PHPExeUtil.exec(exec.getAbsolutePath(), phpExeItem.isLoadDefaultINI() ? null : "-n", "-c", //$NON-NLS-1$ //$NON-NLS-2$
getINIFile(exec).getAbsolutePath(), "-i"); //$NON-NLS-1$
else
return PHPExeUtil.exec(phpExeItem.getExecutable().getAbsolutePath(), "-c", //$NON-NLS-1$
getINIFile(exec).getAbsolutePath(), "-i"); //$NON-NLS-1$
} catch (IOException e) {
Logger.logException(e);
return "";
}
}
/**
* Checks and outputs list of modules installed on top of given PHP
* executable item.
*
* @param phpExeItem
* @return list of installed module names
*/
public static List<PHPModuleInfo> getModules(PHPexeItem phpExeItem) {
List<PHPModuleInfo> modules = new ArrayList<PHPExeUtil.PHPModuleInfo>();
String result;
PHPVersion phpVersion = new PHPVersion(phpExeItem);
File exec = phpExeItem.getExecutable();
try {
if (phpVersion.getMajor() >= 5)
result = PHPExeUtil.exec(exec.getAbsolutePath(), phpExeItem.isLoadDefaultINI() ? null : "-n", "-c", //$NON-NLS-1$ //$NON-NLS-2$
getINIFile(exec).getAbsolutePath(), "-m"); //$NON-NLS-1$
else
result = PHPExeUtil.exec(phpExeItem.getExecutable().getAbsolutePath(), "-c", //$NON-NLS-1$
getINIFile(exec).getAbsolutePath(), "-m"); //$NON-NLS-1$
} catch (IOException e) {
// empty list
return modules;
}
Scanner scanner = new Scanner(result);
String currentGroup = null;
while (scanner.hasNextLine()) {
String line = scanner.nextLine().trim();
if (line.startsWith("[")) { //$NON-NLS-1$
currentGroup = line;
continue;
}
if (!line.isEmpty())
modules.add(new PHPModuleInfo(line, currentGroup));
}
scanner.close();
return modules;
}
/**
* Checks if module with given name is installed on top of provided PHP
* executable item (group name for a module is not taken into account).
*
* @param phpExeItem
* @param moduleName
* @return <code>true</code> if installed, <code>false</code> otherwise
*/
public static boolean hasModule(PHPexeItem phpExeItem, String moduleName) {
List<PHPModuleInfo> modules = getModules(phpExeItem);
for (PHPModuleInfo module : modules)
if (module.getName().equalsIgnoreCase(moduleName))
return true;
return false;
}
/**
* Checks if a module with given name and belonging to specific group is
* installed on top of PHP executable item.
*
* @param phpExeItem
* @param moduleName
* @param groupName
* @return <code>true</code> if installed, <code>false</code> otherwise
*/
public static boolean hasModule(PHPexeItem phpExeItem, String moduleName, String groupName) {
List<PHPModuleInfo> modules = getModules(phpExeItem);
for (PHPModuleInfo module : modules)
if (module.getName().equalsIgnoreCase(moduleName) && module.getGroupName().equalsIgnoreCase(groupName))
return true;
return false;
}
/**
* Returns version description by calling 'php -v' command in pair with the
* appropriate php configuration file location.
*
* @param exec
* @param tmpIni
* @param skipSystemIni
* @return PHP executable version description
* @throws IOException
*/
public static String fetchVersion(File exec) throws IOException {
File emptyIni = PHPINIUtil.createTemporaryPHPINIFile();
return PHPExeUtil.exec(exec.getAbsolutePath(), "-c", //$NON-NLS-1$ //$NON-NLS-2$
emptyIni.getParentFile().getAbsolutePath(), "-v"); //$NON-NLS-1$
}
private static File getINIFile(File exec) {
return PHPINIUtil.createTemporaryPHPINIFile(PHPINIUtil.findPHPIni(exec.getAbsolutePath()));
}
/**
* OSX doesn't like empty argument, so due bug 472349 we allow and filter
* null values
*
* @param cmd
* @return
*/
private static String[] filterNulls(String[] cmd) {
ArrayList<String> result = new ArrayList<String>(cmd.length);
for (String el : cmd) {
if (el != null) {
result.add(el);
}
}
return result.toArray(new String[result.size()]);
}
}