/*******************************************************************************
* Copyright (c) 2017 Rogue Wave Software Inc. 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:
* Rogue Wave Software Inc. - initial implementation
*******************************************************************************/
package org.eclipse.php.internal.ui.wizards.types;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.operations.AbstractOperation;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.dltk.core.*;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.formatter.IContentFormatter;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.osgi.util.NLS;
import org.eclipse.php.core.compiler.PHPFlags;
import org.eclipse.php.internal.core.documentModel.parser.regions.IPHPScriptRegion;
import org.eclipse.php.internal.core.documentModel.parser.regions.PHPRegionTypes;
import org.eclipse.php.internal.core.language.LanguageModelInitializer;
import org.eclipse.php.internal.ui.Logger;
import org.eclipse.php.internal.ui.PHPUiPlugin;
import org.eclipse.php.internal.ui.preferences.includepath.IncludePathUtils;
import org.eclipse.php.internal.ui.util.CodeInjector;
import org.eclipse.php.internal.ui.wizards.PHPFileCreationWizard.FileCreator;
import org.eclipse.swt.SWT;
import org.eclipse.ui.INewWizard;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.ide.undo.CreateFolderOperation;
import org.eclipse.ui.ide.undo.WorkspaceUndoUtil;
import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
import org.eclipse.wst.sse.core.StructuredModelManager;
import org.eclipse.wst.sse.core.internal.model.ModelManagerImpl;
import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
import org.eclipse.wst.xml.core.internal.text.XMLStructuredDocumentRegion;
/**
* This is the base abstract class for new PHP types Wizards
*
*/
public abstract class NewPHPTypeWizard extends Wizard implements INewWizard {
private IStructuredSelection fSelection;
protected ArrayList<String> requiredNamesExcludeList = new ArrayList<>();
protected ArrayList<String> requiredToAdd = new ArrayList<>();
protected NewPHPTypePage page;
protected ISourceModule existingPHPFile = null;
protected String compilationResult;
protected IScriptProject currentProject;
public NewPHPTypeWizard() {
super();
setNeedsProgressMonitor(true);
}
@Override
public void init(final IWorkbench workbench, final IStructuredSelection selection) {
fSelection = selection;
}
protected boolean createNewFile(final String containerName, final String fileName, final String contents) {
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
IResource resource = root.findMember(new Path(containerName));
if (resource == null || !resource.exists() || !(resource instanceof IContainer)) {
createNewFolder(new Path(containerName));
}
final IFile file = root.getFile(new Path(containerName).append(fileName));
IRunnableWithProgress op = monitor -> {
try {
new FileCreator() {
@Override
protected void normalizeFile(IFile file) {
super.normalizeFile(file);
IContentFormatter formatter = PHPUiPlugin.getDefault().getActiveFormatter();
try {
IStructuredModel structuredModel = null;
structuredModel = StructuredModelManager.getModelManager().getModelForEdit(file);
if (structuredModel == null) {
return;
}
try {
// setup structuredModel
// Note: We are getting model for edit. Will
// save model if model
// changed.
IStructuredDocument structuredDocument = structuredModel.getStructuredDocument();
IRegion region = new Region(0, structuredDocument.getLength());
formatter.format(structuredDocument, region);
structuredModel.save();
} finally {
// release from model manager
if (structuredModel != null) {
structuredModel.releaseFromEdit();
}
}
currentProject.getProject().build(IncrementalProjectBuilder.INCREMENTAL_BUILD, null);
} catch (IOException | CoreException e) {
Logger.logException(e);
}
}
}.createFile(NewPHPTypeWizard.this, file, monitor, contents);
} catch (CoreException e) {
throw new InvocationTargetException(e);
} finally {
monitor.done();
}
};
try {
getContainer().run(true, false, op);
return true;
} catch (InterruptedException e) {
Logger.logException(e);
return false;
} catch (InvocationTargetException e) {
Logger.logException(e);
Throwable realException = e.getTargetException();
MessageDialog.openError(getShell(), "Error", //$NON-NLS-1$
realException.getMessage());
return false;
}
}
/**
* Processes this wizard's data after 'Finish' is clicked. This validator
* retrieves required information for generating code and validates it.
*
* @author yaronm
*/
class PostFinishValidator {
private ArrayList<String> warnings = new ArrayList<>();
public void addWarning(String warning) {
warnings.add(warning);
}
public boolean hasWarnings() {
return !warnings.isEmpty();
}
public String[] getWarnings() {
String[] result = new String[warnings.size()];
warnings.toArray(result);
return result;
}
public void packAndValidate() {
final IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
currentProject = DLTKCore.create(workspaceRoot.getProject(getProjectName(page.getSourceText())));
}
}
/**
* @return the project name the first section of the path
*/
protected String getProjectName(String sourcePath) {
int idx;
while ((idx = Math.max(sourcePath.lastIndexOf('\\'), sourcePath.lastIndexOf('/'))) > 1) {
sourcePath = sourcePath.substring(0, idx);
}
return sourcePath;
}
public IStructuredSelection getSelection() {
return fSelection;
}
public void showWarningsDialog(String[] warnings) {
StringBuilder buffer = new StringBuilder();
String elementType = ""; //$NON-NLS-1$
switch (page.fTypeKind) {
case NewPHPTypePage.CLASS_TYPE:
elementType = Messages.NewPHPTypeWizard_class;
break;
case NewPHPTypePage.INTERFACE_TYPE:
elementType = Messages.NewPHPTypeWizard_interface;
break;
case NewPHPTypePage.TRAIT_TYPE:
elementType = Messages.NewPHPTypeWizard_trait;
break;
}
buffer.append(elementType + Messages.NewPHPTypeWizard_creationWasSuccessful);
for (String element : warnings) {
buffer.append(element + "\n"); //$NON-NLS-1$
}
MessageDialog dialog = new MessageDialog(getShell(), "PHP Code Generator Warnings", null, buffer.toString(), //$NON-NLS-1$
MessageDialog.INFORMATION, new String[] { "OK" }, 0); //$NON-NLS-1$
dialog.open();
}
/**
* This function returns the start of first php block, if found, else -1
* returned
*
* @return injectOffset
*/
protected int findPHPBlockOffset() {
int injectOffset = -1;
IPHPScriptRegion scriptRegion = null;
ITextRegion[] subRegions = getStructuredDocumentsRegionis();
for (ITextRegion currentRegion : subRegions) {
if (currentRegion != null && currentRegion instanceof IPHPScriptRegion) {
scriptRegion = (IPHPScriptRegion) currentRegion;
if (scriptRegion.getType().equals(PHPRegionTypes.PHP_CONTENT)) {
injectOffset = scriptRegion.getEnd();
return injectOffset;
}
}
}
return injectOffset;
}
protected ITextRegion[] getStructuredDocumentsRegionis() {
IResource resource = existingPHPFile.getResource();
IStructuredModel existingModelForRead = null;
try {
IFile file = (IFile) resource;
existingModelForRead = ModelManagerImpl.getInstance().getExistingModelForRead(file);
if (existingModelForRead == null) {
existingModelForRead = ModelManagerImpl.getInstance().createUnManagedStructuredModelFor(file);
}
} catch (IOException | CoreException e) {
PHPUiPlugin.log(e);
return new ITextRegion[0];
}
IStructuredDocument structuredDocument = existingModelForRead.getStructuredDocument();
IStructuredDocumentRegion[] subRegions = structuredDocument.getStructuredDocumentRegions();
for (IStructuredDocumentRegion currentRegion : subRegions) {
if (currentRegion instanceof XMLStructuredDocumentRegion) {
return currentRegion.getRegions().toArray();
}
}
return new ITextRegion[0];
}
/**
* @param containerName
* @param fileName
* @param contents
*/
protected boolean createNewPHPFile(final String containerName, final String fileName, final String contents) {
if (!createNewFile(containerName, fileName, contents)) {
return false;
}
Path p = new Path(containerName + File.separatorChar + fileName);
IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(p);
// open file in IDE
IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
try {
IDE.openEditor(page, file, true);
} catch (Exception e) {
Logger.logException(e);
}
IStructuredModel model = null;
try {
model = StructuredModelManager.getModelManager().getModelForEdit(file);
} catch (Exception e) {
Logger.logException(e);
return false;
}
IStructuredDocument document = model.getStructuredDocument();
model.releaseFromEdit();
// TODO - Vadim fix the formatting
// format the code
CodeInjector injector = new CodeInjector();
injector.formatDocument(document, 0, document.getLength());
// save file
try {
model.save();
} catch (Exception e) {
Logger.logException(e);
}
return true;
}
/**
* @param templateEngine
*/
protected void injectCodeIntoExistingFile() {
// locate location for injection of :
// --------------------------------
// 1. Require list only when not in new block
if (page.isInFirstPHPBlock()) {
int injectOffset = findPHPBlockOffset();
int phpBlockEndOffset = -1;
if (injectOffset == -1) {
injectOffset = 0;
// create injector at offset
CodeInjector phpBlockInjector = new CodeInjector(existingPHPFile, injectOffset);
phpBlockInjector.inject("<?php", false, true);//$NON-NLS-1$
// find created php block end
injectOffset = findPHPBlockOffset();
if (injectOffset != -1) {
phpBlockEndOffset = injectOffset;
}
} else {
phpBlockEndOffset = injectOffset;
}
// if no PHP block and no include/require found, set offset at the
// start of the file
CodeInjector generatedCodeInjector = new CodeInjector(existingPHPFile, phpBlockEndOffset);
generatedCodeInjector.setOffset(phpBlockEndOffset);
generatedCodeInjector.inject("\n" + compilationResult, true, true); //$NON-NLS-1$
} else {
// isInFirstPHPBlock = false
CodeInjector injector = new CodeInjector(existingPHPFile);
injector.inject("\n" + compilationResult, true, true); //$NON-NLS-1$
}
}
protected void extractReqruiresInclude(IType superClassData) {
if (LanguageModelInitializer.isLanguageModelElement(superClassData)) {
return;
}
if (page.isInExistingPHPFile() && existingPHPFile.equals(superClassData.getSourceModule())) {
return;
}
// dependent source file in a current project
IPath superClassRelativeFilePath = IncludePathUtils.getRelativeLocationFromIncludePath(currentProject,
superClassData);
if (!superClassRelativeFilePath.isEmpty()) {
final String superclassFile = superClassRelativeFilePath.toString();
if (!requiredNamesExcludeList.contains(superclassFile)) {
requiredNamesExcludeList.add(superclassFile);
requiredToAdd.add(superclassFile);
}
}
}
protected void addImport(List<String> imports, IType type, List<String> existingImports) {
IModelElement parent = type.getParent();
if (parent instanceof IType) {
IType parentType = (IType) parent;
try {
if (PHPFlags.isNamespace(parentType.getFlags())) {
if (parent.getElementName().equals(page.getRealNamespace())) {
return;
}
String use = parent.getElementName() + '\\' + type.getElementName();
if (!existingImports.contains(use)) {
imports.add(use);
}
}
} catch (ModelException e) {
PHPUiPlugin.log(e);
}
}
}
protected List<String> getExistingImports() {
List<String> result = new ArrayList<>();
if (existingPHPFile != null && existingPHPFile.exists()) {
try {
IModelElement[] rootElements = existingPHPFile.getChildren();
for (IModelElement element : rootElements) {
if (!addImport(result, element) && element instanceof IType) {
IModelElement[] elementChildren = ((IType) element).getChildren();
for (IModelElement child : elementChildren) {
addImport(result, child);
}
}
}
} catch (ModelException e) {
PHPUiPlugin.log(e);
}
}
return result;
}
protected String[] getRequires() {
if (page.isCheckboxCreationChecked(NewPHPTypePage.REQUIRE_ONCE)) {
String[] requires = new String[requiredToAdd.size()];
requiredToAdd.toArray(requires);
return requires;
} else {
return new String[0];
}
}
private boolean addImport(List<String> result, IModelElement element) throws ModelException {
if (element instanceof IImportContainer) {
IImportContainer importContainer = (IImportContainer) element;
IImportDeclaration[] imports = importContainer.getImports();
for (IImportDeclaration iImportDeclaration : imports) {
result.add(iImportDeclaration.getElementName());
}
return true;
}
return false;
}
public IFolder createNewFolder(IPath newFolderPath) {
IFolder newFolder = null;
final IFolder newFolderHandle = createFolderHandle(newFolderPath);
final boolean createVirtualFolder = false;
IRunnableWithProgress op = monitor -> {
AbstractOperation op1;
op1 = new CreateFolderOperation(newFolderHandle, null, createVirtualFolder, null,
IDEWorkbenchMessages.WizardNewFolderCreationPage_title);
try {
// see bug
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=219901
// directly execute the operation so that the undo state is
// not preserved. Making this undoable can result in
// accidental
// folder (and file) deletions.
op1.execute(monitor, WorkspaceUndoUtil.getUIInfoAdapter(getShell()));
} catch (final ExecutionException e) {
getContainer().getShell().getDisplay().syncExec(() -> {
if (e.getCause() instanceof CoreException) {
ErrorDialog.openError(getContainer().getShell(), // Was
// Utilities.getFocusShell()
IDEWorkbenchMessages.WizardNewFolderCreationPage_errorTitle, null, // no
// special
// message
((CoreException) e.getCause()).getStatus());
} else {
IDEWorkbenchPlugin.log(getClass(), "createNewFolder()", e.getCause()); //$NON-NLS-1$
MessageDialog.openError(getContainer().getShell(),
IDEWorkbenchMessages.WizardNewFolderCreationPage_internalErrorTitle, NLS.bind(
IDEWorkbenchMessages.WizardNewFolder_internalError, e.getCause().getMessage()));
}
});
}
};
try {
getContainer().run(true, true, op);
} catch (InterruptedException e) {
return null;
} catch (InvocationTargetException e) {
// ExecutionExceptions are handled above, but unexpected runtime
// exceptions and errors may still occur.
IDEWorkbenchPlugin.log(getClass(), "createNewFolder()", e.getTargetException()); //$NON-NLS-1$
MessageDialog.open(MessageDialog.ERROR, getContainer().getShell(),
IDEWorkbenchMessages.WizardNewFolderCreationPage_internalErrorTitle,
NLS.bind(IDEWorkbenchMessages.WizardNewFolder_internalError, e.getTargetException().getMessage()),
SWT.SHEET);
return null;
}
return newFolder;
}
protected IFolder createFolderHandle(IPath folderPath) {
return IDEWorkbenchPlugin.getPluginWorkspace().getRoot().getFolder(folderPath);
}
}