package com.anjlab.eclipse.tapestry5.actions;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IImportDeclaration;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.ArrayInitializer;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ImportDeclaration;
import org.eclipse.jdt.core.dom.MarkerAnnotation;
import org.eclipse.jdt.core.dom.MemberValuePair;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.jface.viewers.TreeSelection;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.dialogs.WizardNewFileCreationPage;
import com.anjlab.eclipse.tapestry5.Activator;
import com.anjlab.eclipse.tapestry5.EclipseUtils;
import com.anjlab.eclipse.tapestry5.EclipseUtils.EditorCallback;
import com.anjlab.eclipse.tapestry5.templates.TapestryTemplates;
import com.anjlab.eclipse.tapestry5.LocalFile;
import com.anjlab.eclipse.tapestry5.SetEditorCaretPositionOffsetLength;
import com.anjlab.eclipse.tapestry5.TapestryContext;
import com.anjlab.eclipse.tapestry5.TapestryUtils;
public class NewFileWizardAction extends Action
{
private static final String CARET_SNIPPET = "$Caret$";
private TapestryContext tapestryContext;
private IProject project;
private String folder;
private String fileName;
private Shell shell;
private IWorkbenchWindow window;
public NewFileWizardAction(IProject project, TapestryContext tapestryContext, Shell shell, IWorkbenchWindow window)
{
this.project = project;
this.tapestryContext = tapestryContext;
this.shell = shell;
this.window = window;
}
public void run()
{
WizardDialog dialog = new WizardDialog(shell, new Wizard()
{
private WizardNewFileCreationPage fileCreationPage;
private Stack<IFolder> preCreatedFolders = new Stack<IFolder>();
@Override
public boolean performCancel()
{
deletePreCreatedFolders(null);
return super.performCancel();
}
private void deletePreCreatedFolders(IContainer existingFolder)
{
while (!preCreatedFolders.isEmpty())
{
try
{
IFolder folder = preCreatedFolders.pop();
if (isEmpty(folder))
{
folder.delete(false, null);
}
}
catch (CoreException e)
{
Activator.getDefault().logWarning("Unable to delete pre-created folder during cleanup", e);
}
}
}
private boolean isEmpty(IFolder folder) throws CoreException
{
return folder.members().length == 0;
}
private void preCreateFolder(IFolder folder) throws CoreException
{
if (!folder.exists())
{
IContainer parent = folder.getParent();
if (parent instanceof IFolder)
{
preCreateFolder((IFolder) parent);
}
folder.create(false, true, null);
preCreatedFolders.add(folder);
}
}
@Override
public void addPages()
{
super.addPages();
setWindowTitle("Creating file for Tapestry Context");
List<Object> segments = new ArrayList<Object>();
segments.add(project);
if (folder != null)
{
IFolder segment = project.getFolder(folder);
if (!segment.exists())
{
try
{
preCreateFolder(segment);
}
catch (CoreException e)
{
Activator.getDefault().logWarning("Unable to pre-create folder for new file", e);
}
}
segments.add(segment);
}
IStructuredSelection selection = new TreeSelection(new TreePath(segments.toArray()));
fileCreationPage = new WizardNewFileCreationPage("", selection)
{
@Override
protected InputStream getInitialContents()
{
caretPosition = -1;
IPath newFile = Path.fromPortableString(fileCreationPage.getFileName());
String templateName = null;
String contextName;
if (tapestryContext != null)
{
contextName = tapestryContext.getName();
if (tapestryContext.isPage())
{
templateName = "page";
}
else if (tapestryContext.isComponent())
{
templateName = "component";
}
else if (tapestryContext.isMixin())
{
templateName = "mixin";
}
}
else
{
contextName = TapestryUtils.getDefaultContextNameFromFileName(
newFile.removeFileExtension().lastSegment());
}
TapestryTemplates templates = TapestryTemplates.get(
Activator.getDefault().getTapestryProject(window));
InputStream stream = templates.openTemplate(
fileCreationPage.getContainerFullPath(),
StringUtils.defaultIfEmpty(templateName, "template"),
newFile.getFileExtension());
if (stream != null)
{
String content = TapestryUtils.readToEnd(stream);
content = content.replace("$ContextName$", contextName);
caretPosition = content.indexOf(CARET_SNIPPET);
if (caretPosition != -1)
{
content = content.replace(CARET_SNIPPET, "");
}
return new ByteArrayInputStream(content.getBytes());
}
return super.getInitialContents();
}
};
fileCreationPage.setFileName(fileName);
fileCreationPage.setTitle("New file");
addPage(fileCreationPage);
}
private int caretPosition;
@Override
public boolean performFinish()
{
IFile file = fileCreationPage.createNewFile();
deletePreCreatedFolders(file.getParent());
try
{
String resourceType = getImportResourceType(file);
if (resourceType == null || tapestryContext == null)
{
return true;
}
addImport(file, resourceType);
return true;
}
finally
{
EclipseUtils.openFile(window, file, new SetEditorCaretPositionOffsetLength(caretPosition, 0));
}
}
private void addImport(final IFile file, final String resourceType)
{
final IFile javaFile = ((LocalFile) tapestryContext.getJavaFile()).getFile();
EclipseUtils.ensureFileIsOpenedInEditor(window, javaFile, new EditorCallback()
{
@Override
public void editorOpened(IEditorPart editorPart)
{
addImport(file, resourceType, javaFile);
}
});
}
private void addImport(IFile file, String resourceType, IFile javaFile)
{
IJavaElement javaElement = JavaCore.create(javaFile);
final ICompilationUnit compilationUnit = (ICompilationUnit) javaElement;
CompilationUnit unit = (CompilationUnit) EclipseUtils.parse(compilationUnit, ASTParser.K_COMPILATION_UNIT);
AnnotationLookupContext rewriteContext = new AnnotationLookupContext();
findTapestryImportAnnotation(unit, rewriteContext);
NormalAnnotation newNormalAnnotation = null;
try
{
ASTRewrite rewrite = ASTRewrite.create(unit.getAST());
boolean tapestryImportAnnotationInImports = isTapestryImportAnnotationInImports(compilationUnit);
if (!tapestryImportAnnotationInImports && isSafeToImportTapestryAnnotationClass(compilationUnit))
{
ImportDeclaration importDeclaration = unit.getAST().newImportDeclaration();
importDeclaration.setName(unit.getAST().newName("org.apache.tapestry5.annotations.Import"));
ListRewrite listRewrite = rewrite.getListRewrite(unit, CompilationUnit.IMPORTS_PROPERTY);
listRewrite.insertLast(importDeclaration, null);
tapestryImportAnnotationInImports = true;
}
if (rewriteContext.annotation == null)
{
newNormalAnnotation = newAnnotation(unit.getAST(), tapestryImportAnnotationInImports);
addImport(newNormalAnnotation, file, resourceType, rewrite);
ListRewrite listRewrite = rewrite.getListRewrite(rewriteContext.typeNode, TypeDeclaration.MODIFIERS2_PROPERTY);
listRewrite.insertFirst(newNormalAnnotation, null);
}
else if (rewriteContext.annotation instanceof MarkerAnnotation)
{
newNormalAnnotation = newAnnotation(unit.getAST(), tapestryImportAnnotationInImports);
addImport(newNormalAnnotation, file, resourceType, rewrite);
rewrite.replace(rewriteContext.annotation, newNormalAnnotation, null);
}
else if (rewriteContext.annotation instanceof NormalAnnotation)
{
newNormalAnnotation = newAnnotation(unit.getAST(), tapestryImportAnnotationInImports);
addImport(rewriteContext.annotation, file, resourceType, rewrite);
}
Document document = new Document(compilationUnit.getSource());
rewrite.rewriteAST(document, null).apply(document);
compilationUnit.getBuffer().setContents(document.get());
}
catch (Exception e)
{
Activator.getDefault().logError("Error modifying compilation unit", e);
}
}
private void addImport(Annotation annotation, IFile file, String resourceType, ASTRewrite rewrite)
{
AST ast = rewrite.getAST();
ListRewrite listRewrite = rewrite.getListRewrite(annotation, NormalAnnotation.VALUES_PROPERTY);
List<?> values = listRewrite.getOriginalList();
MemberValuePair originalResources = null;
for (Object object : values)
{
if (object instanceof MemberValuePair)
{
MemberValuePair pair = (MemberValuePair) object;
if (resourceType.equals(pair.getName().getIdentifier()))
{
originalResources = pair;
break;
}
}
}
MemberValuePair newResources = ast.newMemberValuePair();
newResources.setName(ast.newSimpleName(resourceType));
Expression expression = null;
if (originalResources != null)
{
if (originalResources.getValue() instanceof StringLiteral)
{
expression = ast.newArrayInitializer();
@SuppressWarnings("unchecked")
List<Expression> imports = ((ArrayInitializer) expression).expressions();
imports.add((Expression) ASTNode.copySubtree(ast, originalResources.getValue()));
imports.add(newFileImport(file, ast));
}
else if (originalResources.getValue() instanceof ArrayInitializer)
{
expression = (Expression) ASTNode.copySubtree(ast, originalResources.getValue());
@SuppressWarnings("unchecked")
List<Expression> imports = ((ArrayInitializer) expression).expressions();
imports.add(newFileImport(file, ast));
}
}
else
{
expression = newFileImport(file, ast);
}
newResources.setValue(expression);
if (originalResources != null)
{
listRewrite.replace(originalResources, newResources, null);
}
else
{
listRewrite.insertLast(newResources, null);
}
}
private String getImportResourceType(IFile file)
{
String resourceType = TapestryUtils.isStyleSheetFile(file.getProjectRelativePath())
? "stylesheet"
: TapestryUtils.isJavaScriptFile(file.getProjectRelativePath())
? "library"
: null;
return resourceType;
}
private Expression newFileImport(IFile file, AST ast)
{
StringLiteral literal = ast.newStringLiteral();
String location;
IContainer root = TapestryUtils.getRoot(file);
if (TapestryUtils.isWebApp(root))
{
location = "context:" + TapestryUtils.getRelativeFileName(file, root);
}
else
{
assert tapestryContext != null;
location = getRelativePath(tapestryContext.getPackageName(),
TapestryUtils.pathToPackageName(
TapestryUtils.getRelativeFileName(file.getParent(), root), false))
+ file.getName();
}
literal.setLiteralValue(location);
return literal;
}
private String getRelativePath(String contextPackageName, String newFilePackageName)
{
String[] contextPackageParts = contextPackageName.split("\\.");
String[] newFilePackageParts = newFilePackageName.split("\\.");
int i = 0;
while (i < newFilePackageParts.length
&& i < contextPackageParts.length
&& contextPackageParts[i].equals(newFilePackageParts[i]))
{
i++;
}
if (i == newFilePackageParts.length)
{
return "";
}
StringBuilder builder = new StringBuilder();
for (int j = i; j < contextPackageParts.length; j++)
{
builder.append("../");
}
for (int j = i; j < newFilePackageParts.length; j++)
{
builder.append(newFilePackageParts[j]).append("/");
}
return builder.toString();
}
private boolean isSafeToImportTapestryAnnotationClass(final ICompilationUnit compilationUnit)
throws JavaModelException
{
for (IImportDeclaration importDeclaration : compilationUnit.getImports())
{
if (importDeclaration.getElementName().endsWith(".Import"))
{
// Some other class named as 'Import' present in imports
// -- have to use FQN for the @Import annotation
return false;
}
}
return true;
}
private boolean isTapestryImportAnnotationInImports(final ICompilationUnit compilationUnit) throws JavaModelException
{
for (IImportDeclaration importDeclaration : compilationUnit.getImports())
{
if ("org.apache.tapestry5.annotations.Import".equals(importDeclaration.getElementName()))
{
return true;
}
}
return false;
}
class AnnotationLookupContext
{
private ASTNode typeNode;
private Annotation annotation;
}
private void findTapestryImportAnnotation(CompilationUnit unit, final AnnotationLookupContext annotationContext)
{
unit.accept(new ASTVisitor()
{
@Override
public boolean visit(TypeDeclaration node)
{
if (annotationContext.typeNode == null)
{
annotationContext.typeNode = node;
}
return super.visit(node);
}
@Override
public boolean visit(MarkerAnnotation node)
{
if (TapestryUtils.isTapestryImportAnnotation(node))
{
annotationContext.annotation = node;
}
return super.visit(node);
}
@Override
public boolean visit(NormalAnnotation node)
{
if (TapestryUtils.isTapestryImportAnnotation(node))
{
annotationContext.annotation = node;
}
return super.visit(node);
}
});
}
private NormalAnnotation newAnnotation(final AST ast, boolean useSimpleName)
{
NormalAnnotation newNormalAnnotation;
newNormalAnnotation = ast.newNormalAnnotation();
if (useSimpleName)
{
newNormalAnnotation.setTypeName(ast.newSimpleName("Import"));
}
else
{
newNormalAnnotation.setTypeName(ast.newQualifiedName(
ast.newName("org.apache.tapestry5.annotations"), ast.newSimpleName("Import")));
}
return newNormalAnnotation;
}
});
dialog.open();
}
public String getFileName()
{
return fileName;
}
public void setFileName(String fileName)
{
this.fileName = fileName;
}
public String getFolder()
{
return folder;
}
public void setFolder(String folder)
{
this.folder = folder;
}
}