com.aptana.php.debug.ui.phpini.PHPIniValidator.java Source code

Java tutorial

Introduction

Here is the source code for com.aptana.php.debug.ui.phpini.PHPIniValidator.java

Source

/**
 * Aptana Studio
 * Copyright (c) 2005-2012 by Appcelerator, Inc. All Rights Reserved.
 * Licensed under the terms of the GNU Public License (GPL) v3 (with exceptions).
 * Please see the license.html included with this distribution for details.
 * Any modifications to this file must keep this entire header intact.
 */
package com.aptana.php.debug.ui.phpini;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.swt.widgets.Shell;

import com.aptana.core.logging.IdeLog;
import com.aptana.core.util.EclipseUtil;
import com.aptana.core.util.FileUtil;
import com.aptana.php.debug.IDebugScopes;
import com.aptana.php.debug.PHPDebugPlugin;
import com.aptana.php.debug.core.PHPDebugSupportManager;
import com.aptana.php.debug.core.util.FileUtils;
import com.aptana.php.debug.ui.phpini.PHPIniEntry.VALIDATION;

/**
 * A class that is used to validate the extensions directives in the php.ini. The class executes the PHP process and
 * parses the error output to determine which of the extensions is causing an error and which causes warnings. Every
 * error or warning is registered into the PHPIniEntrys that the INIFileModifier is holding.
 * 
 * @author sgibly@appcelerator.com
 */
// $codepro.audit.disable assignmentInCondition
public class PHPIniValidator {
    private static final String PHP = "PHP"; //$NON-NLS-1$
    private static final String DYLD = "dyld:"; //$NON-NLS-1$
    private static final String DLL_EXT = ".dll"; //$NON-NLS-1$
    private static final String SO_EXT = ".so"; //$NON-NLS-1$
    private static final String PHP_LOADING_ERROR_STRING = "unable to load"; //$NON-NLS-1$
    private final PHPIniContentProvider provider;
    private final String phpExePath;
    private String libraryPath;
    private File iniFile;
    private boolean isRunning;
    private Object lock = new Object();
    private List<PHPIniEntry> faultingExtensions;
    private Map<String, PHPIniEntry> mappedEntries;
    private boolean validationCanceled;
    private boolean validationCompleted;
    private final String debuggerID;

    /**
     * Constructs a new PHPIniValidator.
     * 
     * @param provider
     * @param phpExePath
     */
    public PHPIniValidator(PHPIniContentProvider provider, String phpExePath, String debuggerID) {
        this.provider = provider;
        this.phpExePath = phpExePath;
        this.debuggerID = debuggerID;
        File exePath = new File(phpExePath);
        libraryPath = exePath.getParent();
        iniFile = new File(provider.getFileName());
        faultingExtensions = new ArrayList<PHPIniEntry>(5);
        FileUtils.setExecutablePermissions(exePath);
        loadActiveExtensions();
    }

    /**
     * Map the extensions that are currently active in the php.ini
     */
    private void loadActiveExtensions() {
        mappedEntries = new HashMap<String, PHPIniEntry>();
        List<INIFileSection> sections = provider.getSections();
        for (INIFileSection section : sections) {
            List<PHPIniEntry> entries = section.getEntries();
            for (PHPIniEntry entry : entries) {
                if (entry.isExtensionEntry()) {
                    String extensionValue = entry.getValue();
                    if (extensionValue.endsWith(SO_EXT)) {
                        extensionValue = extensionValue.substring(0, extensionValue.length() - 3);
                    } else if (extensionValue.endsWith(DLL_EXT)) {
                        extensionValue = extensionValue.substring(0, extensionValue.length() - 4);
                    }
                    mappedEntries.put(extensionValue, entry);
                }
            }
        }
    }

    /**
     * Validate the php.ini directives.
     */
    public void validate() {
        synchronized (lock) {
            if (isRunning) {
                return;
            }
            isRunning = true;
        }
        List<PHPIniEntry> extensions = new ArrayList<PHPIniEntry>();
        extensions.addAll(mappedEntries.values());
        // Mark all entries as unknown.
        for (PHPIniEntry entry : extensions) {
            entry.setValidationState(VALIDATION.UNKNOWN, null);
        }
        Shell activeShell = PHPDebugPlugin.getDefault().getWorkbench().getActiveWorkbenchWindow().getShell();
        try {
            IRunnableWithProgress op = new ExtensionsValidatorRunnable(extensions);
            ProgressMonitorDialog progressMonitorDialog = new ProgressMonitorDialog(activeShell);
            progressMonitorDialog.run(true, true, op);
        } catch (InvocationTargetException e) {
            // handle exception
            IdeLog.logError(PHPDebugPlugin.getDefault(), "Error while validating the PHP extensions", e, //$NON-NLS-1$
                    IDebugScopes.DEBUG);
        } catch (InterruptedException e) // $codepro.audit.disable emptyCatchClause
        {
            // handle cancellation in the finally block

        } finally {
            // Revert the changes made to the ini
            for (PHPIniEntry entry : faultingExtensions) {
                provider.uncommentEntry(entry);
            }
            faultingExtensions.clear();
            // Mark all the unknown entries as resolved.
            for (PHPIniEntry entry : mappedEntries.values()) {
                if (validationCanceled) {
                    entry.setValidationState(VALIDATION.UNKNOWN, null);
                } else if (entry.getValidationState() == VALIDATION.UNKNOWN) {
                    entry.setValidationState(VALIDATION.OK, null);
                }
            }
            try {
                provider.save();
            } catch (IOException e) {
                IdeLog.logError(PHPDebugPlugin.getDefault(), "Error while saving the php.ini configuration.", e); //$NON-NLS-1$
            }
            synchronized (lock) {
                isRunning = false;
            }
        }
    }

    private class ExtensionsValidatorRunnable implements IRunnableWithProgress {

        private final List<PHPIniEntry> extensions;

        ExtensionsValidatorRunnable(List<PHPIniEntry> extensions) {
            this.extensions = extensions;
        }

        public void run(IProgressMonitor monitor) {
            monitor.beginTask(Messages.PHPIniValidator_validatingExtensionTaskName, extensions.size());

            try {
                innerValidate(extensions, monitor);
            } catch (IOException e) {
                monitor.done();
                IdeLog.logError(PHPDebugPlugin.getDefault(), "Error while validating the PHP extensions", e, //$NON-NLS-1$
                        IDebugScopes.DEBUG);
                MessageDialog.openError(null, Messages.PHPIniValidator_errorTitle,
                        Messages.PHPIniValidator_extensionValidationErrorMessage);
            } finally {
                monitor.done();
                validationCompleted = true;
            }
        }
    }

    /*
     * Do the validation in a recursively until the process is loaded without any fatal errors.
     */
    private void innerValidate(List<PHPIniEntry> extensions, IProgressMonitor monitor) throws IOException {
        validationCanceled = false;
        validationCompleted = false;
        List<String> processExecutionResults = getProcessExecutionResults(monitor);
        if (processExecutionResults == null) {
            // the user canceled, or we had an error validating.
            return;
        }
        if (processExecutionResults.isEmpty()) {
            // no errors / warnings
            monitor.worked(extensions.size());
        } else {
            List<String> fatalErrors = extractFatalErrors(processExecutionResults);
            if (fatalErrors.isEmpty()) {
                // we only have warnings and can move on
                markEntriesWithWarning(processExecutionResults);
                monitor.worked(extensions.size());
            } else {
                // remove the faulting extension, and try again.
                // this process can take several times until we get no fatal errors.
                monitor.worked(1);
                if (!monitor.isCanceled()) {
                    removeFaultingEntries(fatalErrors, extensions, monitor);
                }
            }
        }
    }

    /**
     * Mark the PHPIniEntries with a warning
     * 
     * @param processExecutionResults
     */
    private void markEntriesWithWarning(List<String> warnings) {
        markMatchingEntries(warnings, false, null);
    }

    private void removeFaultingEntries(List<String> fatalErrors, List<PHPIniEntry> extensions,
            IProgressMonitor monitor) throws IOException {
        markMatchingEntries(fatalErrors, true, extensions);
        provider.save();
        innerValidate(extensions, monitor);
    }

    /*
     * Search for any reference for the PHPIniEntries in the error/warning lines and mark them with error/warning. (In
     * case of errors, the entry is commented out) @param processOutputLines @param isError If true, mark the
     * PHPIniEntries as errors; Otherwise, mark them as warnings. @param extensions When isError==true this parameter
     * must not be null.
     */
    private void markMatchingEntries(List<String> processOutputLines, boolean isError,
            List<PHPIniEntry> extensions) {
        // Check for each of the .so/.dll entries in the ini
        for (String line : processOutputLines) {
            for (String entryValue : mappedEntries.keySet()) {
                String testEntry = entryValue;
                if (testEntry.toLowerCase().startsWith("php_")) //$NON-NLS-1$ 
                {
                    testEntry = testEntry.substring(4);
                }
                if (line.indexOf(testEntry) > -1 || line.indexOf(entryValue) > -1) {
                    PHPIniEntry iniEntry = mappedEntries.get(entryValue);
                    if (isError) {
                        iniEntry.setValidationState(VALIDATION.ERROR, line);
                        faultingExtensions.add(iniEntry);
                        extensions.remove(iniEntry);
                        provider.commentEntry(iniEntry);
                    } else {
                        // Some of the warnings can also indicate errors which are not fatal to the process.
                        // Mark these warnings as errors.
                        if (line.toLowerCase().indexOf(PHP_LOADING_ERROR_STRING) > -1) {
                            iniEntry.setValidationState(VALIDATION.ERROR, line);
                        } else {
                            iniEntry.setValidationState(VALIDATION.WARNING, line);
                        }
                    }
                    break;
                }
            }
        }
    }

    /*
     * Execute the PHP process and collect the errors and the warnings into an array of strings.
     */
    private List<String> getProcessExecutionResults(final IProgressMonitor monitor) throws IOException {
        File tempIniFile = PHPDebugSupportManager.getLaunchSupport().generatePhpIni(this.iniFile, phpExePath, null,
                debuggerID);

        ProcessBuilder builder = new ProcessBuilder(phpExePath, "-c", tempIniFile.getAbsolutePath(), "-i"); //$NON-NLS-1$ //$NON-NLS-2$
        // builder.environment().put("PHPRC", tempIniFile.getParent()); //$NON-NLS-1$
        if (!Platform.OS_WIN32.equals(Platform.getOS())) {
            if (Platform.OS_MACOSX.equals(Platform.getOS())) {
                builder.environment().put("DYLD_LIBRARY_PATH", libraryPath); //$NON-NLS-1$
            } else {
                builder.environment().put("LD_LIBRARY_PATH", libraryPath); //$NON-NLS-1$
            }
        }
        builder.directory(new File(phpExePath).getParentFile());

        final Process process = builder.start();

        Job monitorCancelListener = new Job("Validation Cancel Listener") //$NON-NLS-1$ (system)
        {
            protected IStatus run(IProgressMonitor monitor2) {
                int timeLimit = 60; // time limit for 1 minute
                while (--timeLimit > 0) {
                    try {
                        Thread.sleep(1000); // $codepro.audit.disable disallowSleepInsideWhile
                    } catch (InterruptedException e) // $codepro.audit.disable emptyCatchClause
                    {
                        // ignore
                    }
                    if (validationCompleted) {
                        return Status.CANCEL_STATUS;
                    }
                    if (monitor.isCanceled()) {
                        try {
                            process.exitValue();
                        } catch (IllegalThreadStateException itse) {
                            process.destroy();
                            validationCanceled = true;
                            return Status.OK_STATUS;
                        }
                        return Status.CANCEL_STATUS;
                    }
                }
                return Status.CANCEL_STATUS;
            }
        };
        monitorCancelListener.setSystem(EclipseUtil.showSystemJobs());
        monitorCancelListener.setProgressGroup(monitor, IProgressMonitor.UNKNOWN);
        monitorCancelListener.schedule();
        // Important - First read the output, then read the errors.
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String line = null;
            while (!monitor.isCanceled() && (line = reader.readLine()) != null) {
                if (PHPDebugPlugin.DEBUG) {
                    // $codepro.audit.disable debuggingCode
                    System.out.println(line);
                }
            }
        } finally {
            if (reader != null) {
                reader.close();
            }
        }
        if (monitor.isCanceled()) {
            return null;
        }
        // Read the errors
        return getProcessErrorLines(process.getErrorStream());
    }

    /**
     * Reads the process error stream and extract the PHP warnings and errors.
     * 
     * @param errorStream
     * @return A string list of all the PHP-related warnings and errors from the error stream
     * @throws IOException
     */
    public static List<String> getProcessErrorLines(InputStream errorStream) throws IOException {
        List<String> lines = new ArrayList<String>();
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new InputStreamReader(errorStream));
            String line = null;
            while ((line = reader.readLine()) != null) {
                if (PHPDebugPlugin.DEBUG) {
                    // $codepro.audit.disable debuggingCode
                    System.err.println(line);
                }
                if (line.startsWith(PHP)) {
                    lines.add(line);
                } else if (line.startsWith(DYLD)) {
                    StringBuilder stringBuilder = new StringBuilder();
                    // read the next 2 or 3 lines
                    int linesCount = (line.indexOf("error", 4) > -1) ? 3 : 2; //$NON-NLS-1$
                    if (linesCount < 3) {
                        lines.add(line);
                    }
                    while ((line = reader.readLine()) != null && linesCount-- > 0) {
                        if (PHPDebugPlugin.DEBUG) {
                            // $codepro.audit.disable debuggingCode
                            System.err.println(line);
                        }
                        // just to make sure
                        if (line.startsWith(PHP)) {
                            lines.add(line);
                            break;
                        }
                        stringBuilder.append(FileUtil.NEW_LINE);
                        stringBuilder.append(line);
                        if (line.toLowerCase().startsWith("reason")) //$NON-NLS-1$
                            break; // just to make sure
                    }
                    lines.add(stringBuilder.toString());
                }
            }
        } finally {
            if (reader != null) {
                reader.close();
            }
        }
        return lines;
    }

    /**
     * Extract the fatal errors from the given list. The fatal errors are removed from the given list and returned in a
     * new list.
     * 
     * @param processExecutionResults
     *            The PHP process error and warning lines
     * @return A list containing only the fatal errors.
     */
    public static List<String> extractFatalErrors(List<String> processExecutionResults) {
        List<String> errors = new ArrayList<String>(processExecutionResults.size());
        List<String> warnings = new ArrayList<String>(processExecutionResults.size());
        for (String str : processExecutionResults) {
            String lowerCaseStr = str.toLowerCase();
            if (lowerCaseStr.indexOf(" fatal ") > -1 || lowerCaseStr.indexOf(DYLD) > -1) //$NON-NLS-1$ // $NON-NLS-2$
            {
                errors.add(str);
            } else {
                warnings.add(str);
            }
        }
        // update the warnings
        processExecutionResults.clear();
        processExecutionResults.addAll(warnings);
        return errors;
    }
}