diff --git a/README.md b/README.md index 9b638b9..58a3826 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,20 @@ Yet another Design/Code switcher for Tapestry5 applications. ![Screenshot](https://f.cloud.github.com/assets/76579/1050715/4503627e-10be-11e3-8bf2-b9e6ccc2628b.png) +### Tapestry Context View + +Go to *Window* -> *Show View* -> *Other...*, filter by "Tapestry" and select "Tapestry Context". + +Tapestry Context View will appear (top right on this screenshot): + + - Provides context for opened Tapestry file + - Includes code/template files of selected page/component, as well as list of properties files and @Import'ed assets + - Click on the file in a view to open this file + - Simple validation implemented to highlight assets that couldn't be resolved (supports default or 'context:' binding prefixes) + +![Screenshot](https://f.cloud.github.com/assets/76579/1105085/c106e906-1918-11e3-9525-68839dcc89b2.png) + + ### Install In Eclipse go to *Help* -> *Install New Software...* -> *Add...* diff --git a/com.anjlab.eclipse.tapestry5.feature/feature.xml b/com.anjlab.eclipse.tapestry5.feature/feature.xml index b5acddb..bbb5807 100644 --- a/com.anjlab.eclipse.tapestry5.feature/feature.xml +++ b/com.anjlab.eclipse.tapestry5.feature/feature.xml @@ -2,7 +2,7 @@ diff --git a/com.anjlab.eclipse.tapestry5/META-INF/MANIFEST.MF b/com.anjlab.eclipse.tapestry5/META-INF/MANIFEST.MF index 7ef79ef..3b0e787 100644 --- a/com.anjlab.eclipse.tapestry5/META-INF/MANIFEST.MF +++ b/com.anjlab.eclipse.tapestry5/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Eclipse Integration for Tapestry5 Bundle-SymbolicName: com.anjlab.eclipse.tapestry5;singleton:=true -Bundle-Version: 1.0.2 +Bundle-Version: 1.1.0 Bundle-Activator: com.anjlab.eclipse.tapestry5.Activator Bundle-Vendor: AnjLab Team Require-Bundle: org.eclipse.ui, @@ -12,7 +12,9 @@ Require-Bundle: org.eclipse.ui, org.eclipse.ui, org.eclipse.core.runtime, org.eclipse.ui.ide, - org.eclipse.jdt.core + org.eclipse.jdt.core, + org.eclipse.ui.workbench.texteditor;bundle-version="3.8.100", + org.eclipse.jdt.ui;bundle-version="3.9.0" Bundle-RequiredExecutionEnvironment: JavaSE-1.6 Bundle-ActivationPolicy: lazy Eclipse-LazyStart: true diff --git a/com.anjlab.eclipse.tapestry5/plugin.xml b/com.anjlab.eclipse.tapestry5/plugin.xml index 56bdbcd..0feafd1 100644 --- a/com.anjlab.eclipse.tapestry5/plugin.xml +++ b/com.anjlab.eclipse.tapestry5/plugin.xml @@ -45,5 +45,31 @@ + + + + + + + + + + + + diff --git a/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/AssetException.java b/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/AssetException.java new file mode 100644 index 0000000..d997fe5 --- /dev/null +++ b/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/AssetException.java @@ -0,0 +1,16 @@ +package com.anjlab.eclipse.tapestry5; + +public class AssetException extends Exception +{ + private static final long serialVersionUID = 1L; + + public AssetException(String message) + { + super(message); + } + + public AssetException(String message, Throwable cause) + { + super(message, cause); + } +} diff --git a/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/AssetPath.java b/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/AssetPath.java new file mode 100644 index 0000000..d34efac --- /dev/null +++ b/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/AssetPath.java @@ -0,0 +1,944 @@ +package com.anjlab.eclipse.tapestry5; + +import java.io.InputStream; +import java.io.Reader; +import java.net.URI; +import java.util.Map; + +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFileState; +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IPathVariableManager; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IProjectDescription; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IResourceProxy; +import org.eclipse.core.resources.IResourceProxyVisitor; +import org.eclipse.core.resources.IResourceVisitor; +import org.eclipse.core.resources.IWorkspace; +import org.eclipse.core.resources.ResourceAttributes; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.QualifiedName; +import org.eclipse.core.runtime.content.IContentDescription; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.jdt.core.ISourceRange; +import org.eclipse.ui.IContributorResourceAdapter; + +public class AssetPath implements IFile +{ + public static final String ASSET_PATH_MARKER_ATTRIBUTE = "AssetPath"; + + private IFile javaFile; + private String assetPath; + private ISourceRange sourceRange; + + public AssetPath(IFile javaFile, ISourceRange sourceRange, String assetPath) + { + this.javaFile = javaFile; + this.sourceRange = sourceRange; + this.assetPath = assetPath.replace('\\', '/'); + } + + @Override + public int hashCode() + { + return javaFile.hashCode() + assetPath.hashCode(); + } + + @Override + public boolean equals(Object obj) + { + if (obj == null) + { + return false; + } + + if (obj instanceof AssetPath) + { + AssetPath other = (AssetPath) obj; + + return javaFile.equals(other.javaFile) && assetPath.equals(other.assetPath); + } + + if (obj instanceof IFile) + { + try + { + return obj.equals(resolveFile(false)); + } + catch (AssetException e) + { + } + } + + return false; + } + + public String getAssetPath() + { + return assetPath; + } + + public ISourceRange getSourceRange() + { + return sourceRange; + } + + public IFile getJavaFile() + { + return javaFile; + } + + private static class Asset + { + private final String bindingPrefix; + private final String path; + + public Asset(String assetPath) + { + String path = assetPath; + + String bindingPrefix = "default"; + + int colonIndex = assetPath.indexOf(":"); + if (colonIndex > 0) + { + bindingPrefix = assetPath.substring(0, colonIndex); + + path = assetPath.substring(colonIndex + 1); + } + + this.path = path; + this.bindingPrefix = bindingPrefix; + } + } + + public IFile resolveFile(boolean updateMarker) throws AssetException + { + Asset asset = new Asset(assetPath); + + AssetResolver assetResolver = TapestryUtils.createAssetResolver(asset.bindingPrefix, asset.path); + + if (assetResolver == null) + { + throw new AssetException("Binding prefix '" + asset.bindingPrefix + "' not supported"); + } + + try + { + IFile resolvedFile = assetResolver.resolve(asset.path, javaFile); + + if (updateMarker) + { + try + { + deleteMarker(); + } + catch (CoreException e) + { + Activator.getDefault().logError("Error deleting problem marker", e); + } + } + + return resolvedFile; + } + catch (AssetException e) + { + if (updateMarker) + { + try + { + createMarker(e); + } + catch (CoreException e2) + { + Activator.getDefault().logError("Error creating problem marker", e2); + } + } + + throw e; + } + } + + @Override + public String getName() + { + Asset asset = new Asset(assetPath); + + int separatorIndex = asset.path.lastIndexOf('/'); + + if (separatorIndex < 0) + { + return asset.path; + } + return asset.path.substring(separatorIndex + 1); + } + + @Override + public void accept(IResourceVisitor arg0) throws CoreException + { + // TODO Auto-generated method stub + + } + + @Override + public void accept(IResourceProxyVisitor arg0, int arg1) + throws CoreException + { + // TODO Auto-generated method stub + + } + + @Override + public void accept(IResourceProxyVisitor arg0, int arg1, int arg2) + throws CoreException + { + // TODO Auto-generated method stub + + } + + @Override + public void accept(IResourceVisitor arg0, int arg1, boolean arg2) + throws CoreException + { + // TODO Auto-generated method stub + + } + + @Override + public void accept(IResourceVisitor arg0, int arg1, int arg2) + throws CoreException + { + // TODO Auto-generated method stub + + } + + @Override + public void clearHistory(IProgressMonitor arg0) throws CoreException + { + // TODO Auto-generated method stub + + } + + @Override + public void copy(IPath arg0, boolean arg1, IProgressMonitor arg2) + throws CoreException + { + // TODO Auto-generated method stub + + } + + @Override + public void copy(IPath arg0, int arg1, IProgressMonitor arg2) + throws CoreException + { + // TODO Auto-generated method stub + + } + + @Override + public void copy(IProjectDescription arg0, boolean arg1, + IProgressMonitor arg2) throws CoreException + { + // TODO Auto-generated method stub + + } + + @Override + public void copy(IProjectDescription arg0, int arg1, IProgressMonitor arg2) + throws CoreException + { + // TODO Auto-generated method stub + + } + + @Override + public IMarker createMarker(String arg0) throws CoreException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public IResourceProxy createProxy() + { + // TODO Auto-generated method stub + return null; + } + + @Override + public void delete(boolean arg0, IProgressMonitor arg1) + throws CoreException + { + // TODO Auto-generated method stub + + } + + @Override + public void delete(int arg0, IProgressMonitor arg1) throws CoreException + { + // TODO Auto-generated method stub + + } + + @Override + public void deleteMarkers(String arg0, boolean arg1, int arg2) + throws CoreException + { + // TODO Auto-generated method stub + + } + + @Override + public boolean exists() + { + // TODO Auto-generated method stub + return false; + } + + @Override + public IMarker findMarker(long arg0) throws CoreException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public IMarker[] findMarkers(String arg0, boolean arg1, int arg2) + throws CoreException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public int findMaxProblemSeverity(String arg0, boolean arg1, int arg2) + throws CoreException + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public String getFileExtension() + { + // TODO Auto-generated method stub + return null; + } + + @Override + public long getLocalTimeStamp() + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public IPath getLocation() + { + // TODO Auto-generated method stub + return null; + } + + @Override + public URI getLocationURI() + { + // TODO Auto-generated method stub + return null; + } + + @Override + public IMarker getMarker(long arg0) + { + // TODO Auto-generated method stub + return null; + } + + @Override + public long getModificationStamp() + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public IContainer getParent() + { + // TODO Auto-generated method stub + return null; + } + + @Override + public IPathVariableManager getPathVariableManager() + { + // TODO Auto-generated method stub + return null; + } + + @Override + public Map getPersistentProperties() + throws CoreException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getPersistentProperty(QualifiedName arg0) + throws CoreException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public IProject getProject() + { + return null; + } + + @Override + public IPath getProjectRelativePath() + { + // TODO Auto-generated method stub + return null; + } + + @Override + public IPath getRawLocation() + { + // TODO Auto-generated method stub + return null; + } + + @Override + public URI getRawLocationURI() + { + // TODO Auto-generated method stub + return null; + } + + @Override + public ResourceAttributes getResourceAttributes() + { + // TODO Auto-generated method stub + return null; + } + + @Override + public Map getSessionProperties() + throws CoreException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public Object getSessionProperty(QualifiedName arg0) throws CoreException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public int getType() + { + return IFile.FILE; + } + + @Override + public IWorkspace getWorkspace() + { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean isAccessible() + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean isDerived() + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean isDerived(int arg0) + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean isHidden() + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean isHidden(int arg0) + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean isLinked() + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean isLinked(int arg0) + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean isLocal(int arg0) + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean isPhantom() + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean isSynchronized(int arg0) + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean isTeamPrivateMember() + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean isTeamPrivateMember(int arg0) + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean isVirtual() + { + // TODO Auto-generated method stub + return false; + } + + @Override + public void move(IPath arg0, boolean arg1, IProgressMonitor arg2) + throws CoreException + { + // TODO Auto-generated method stub + + } + + @Override + public void move(IPath arg0, int arg1, IProgressMonitor arg2) + throws CoreException + { + // TODO Auto-generated method stub + + } + + @Override + public void move(IProjectDescription arg0, int arg1, IProgressMonitor arg2) + throws CoreException + { + // TODO Auto-generated method stub + + } + + @Override + public void move(IProjectDescription arg0, boolean arg1, boolean arg2, + IProgressMonitor arg3) throws CoreException + { + // TODO Auto-generated method stub + + } + + @Override + public void refreshLocal(int arg0, IProgressMonitor arg1) + throws CoreException + { + // TODO Auto-generated method stub + + } + + @Override + public void revertModificationStamp(long arg0) throws CoreException + { + // TODO Auto-generated method stub + + } + + @Override + public void setDerived(boolean arg0) throws CoreException + { + // TODO Auto-generated method stub + + } + + @Override + public void setDerived(boolean arg0, IProgressMonitor arg1) + throws CoreException + { + // TODO Auto-generated method stub + + } + + @Override + public void setHidden(boolean arg0) throws CoreException + { + // TODO Auto-generated method stub + + } + + @Override + public void setLocal(boolean arg0, int arg1, IProgressMonitor arg2) + throws CoreException + { + // TODO Auto-generated method stub + + } + + @Override + public long setLocalTimeStamp(long arg0) throws CoreException + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public void setPersistentProperty(QualifiedName arg0, String arg1) + throws CoreException + { + // TODO Auto-generated method stub + + } + + @Override + public void setReadOnly(boolean arg0) + { + // TODO Auto-generated method stub + + } + + @Override + public void setResourceAttributes(ResourceAttributes arg0) + throws CoreException + { + // TODO Auto-generated method stub + + } + + @Override + public void setSessionProperty(QualifiedName arg0, Object arg1) + throws CoreException + { + // TODO Auto-generated method stub + + } + + @Override + public void setTeamPrivateMember(boolean arg0) throws CoreException + { + // TODO Auto-generated method stub + + } + + @Override + public void touch(IProgressMonitor arg0) throws CoreException + { + // TODO Auto-generated method stub + + } + + @SuppressWarnings("rawtypes") + @Override + public Object getAdapter(Class clazz) + { + if (clazz.equals(IContributorResourceAdapter.class)) + { + return new IContributorResourceAdapter() + { + @Override + public IResource getAdaptedResource(IAdaptable adaptable) + { + return AssetPath.this; + } + }; + } + return null; + } + + @Override + public boolean contains(ISchedulingRule arg0) + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean isConflicting(ISchedulingRule arg0) + { + // TODO Auto-generated method stub + return false; + } + + @Override + public void appendContents(InputStream arg0, int arg1, IProgressMonitor arg2) + throws CoreException + { + // TODO Auto-generated method stub + + } + + @Override + public void appendContents(InputStream arg0, boolean arg1, boolean arg2, + IProgressMonitor arg3) throws CoreException + { + // TODO Auto-generated method stub + + } + + @Override + public void create(InputStream arg0, boolean arg1, IProgressMonitor arg2) + throws CoreException + { + // TODO Auto-generated method stub + + } + + @Override + public void create(InputStream arg0, int arg1, IProgressMonitor arg2) + throws CoreException + { + // TODO Auto-generated method stub + + } + + @Override + public void createLink(IPath arg0, int arg1, IProgressMonitor arg2) + throws CoreException + { + // TODO Auto-generated method stub + + } + + @Override + public void createLink(URI arg0, int arg1, IProgressMonitor arg2) + throws CoreException + { + // TODO Auto-generated method stub + + } + + @Override + public void delete(boolean arg0, boolean arg1, IProgressMonitor arg2) + throws CoreException + { + // TODO Auto-generated method stub + + } + + @Override + public String getCharset() throws CoreException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getCharset(boolean arg0) throws CoreException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getCharsetFor(Reader arg0) throws CoreException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public IContentDescription getContentDescription() throws CoreException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public InputStream getContents() throws CoreException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public InputStream getContents(boolean arg0) throws CoreException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public int getEncoding() throws CoreException + { + // TODO Auto-generated method stub + return 0; + } + + @Override + public IPath getFullPath() + { + // TODO Auto-generated method stub + return null; + } + + @Override + public IFileState[] getHistory(IProgressMonitor arg0) throws CoreException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean isReadOnly() + { + // TODO Auto-generated method stub + return false; + } + + @Override + public void move(IPath arg0, boolean arg1, boolean arg2, + IProgressMonitor arg3) throws CoreException + { + // TODO Auto-generated method stub + + } + + @Override + public void setCharset(String arg0) throws CoreException + { + // TODO Auto-generated method stub + + } + + @Override + public void setCharset(String arg0, IProgressMonitor arg1) + throws CoreException + { + // TODO Auto-generated method stub + + } + + @Override + public void setContents(InputStream arg0, int arg1, IProgressMonitor arg2) + throws CoreException + { + // TODO Auto-generated method stub + + } + + @Override + public void setContents(IFileState arg0, int arg1, IProgressMonitor arg2) + throws CoreException + { + // TODO Auto-generated method stub + + } + + @Override + public void setContents(InputStream arg0, boolean arg1, boolean arg2, + IProgressMonitor arg3) throws CoreException + { + // TODO Auto-generated method stub + + } + + @Override + public void setContents(IFileState arg0, boolean arg1, boolean arg2, + IProgressMonitor arg3) throws CoreException + { + // TODO Auto-generated method stub + + } + + + public void createMarker(Throwable t) throws CoreException + { + IMarker marker = findMarker(); + + if (marker == null) + { + marker = javaFile.createMarker(IMarker.PROBLEM); + + marker.setAttribute(IMarker.MESSAGE, t.getLocalizedMessage()); + marker.setAttribute(IMarker.PRIORITY, IMarker.PRIORITY_HIGH); + marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_WARNING); + + if (sourceRange != null) + { + marker.setAttribute(IMarker.CHAR_START, sourceRange.getOffset()); + marker.setAttribute(IMarker.CHAR_END, sourceRange.getOffset() + sourceRange.getLength()); + } + + marker.setAttribute(ASSET_PATH_MARKER_ATTRIBUTE, assetPath); + } + } + + public IMarker findMarker() throws CoreException + { + IMarker[] markers = javaFile.findMarkers(IMarker.PROBLEM, false, IResource.DEPTH_ZERO); + + IMarker assetPathMarker = null; + + for (IMarker marker : markers) + { + Object markerPath = marker.getAttribute(ASSET_PATH_MARKER_ATTRIBUTE); + + if (markerPath != null && markerPath.equals(assetPath)) + { + assetPathMarker = marker; + break; + } + } + return assetPathMarker; + } + + public void deleteMarker() throws CoreException + { + IMarker marker = findMarker(); + + if (marker != null) + { + marker.delete(); + } + } +} diff --git a/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/AssetResolver.java b/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/AssetResolver.java new file mode 100644 index 0000000..5f1e5ed --- /dev/null +++ b/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/AssetResolver.java @@ -0,0 +1,10 @@ +package com.anjlab.eclipse.tapestry5; + +import org.eclipse.core.resources.IFile; + +public interface AssetResolver +{ + + IFile resolve(String path, IFile relativeTo) throws AssetException; + +} diff --git a/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/ContextAssetResolver.java b/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/ContextAssetResolver.java new file mode 100644 index 0000000..27e2acf --- /dev/null +++ b/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/ContextAssetResolver.java @@ -0,0 +1,28 @@ +package com.anjlab.eclipse.tapestry5; + +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IFile; + +public class ContextAssetResolver implements AssetResolver +{ + @Override + public IFile resolve(String path, IFile relativeTo) throws AssetException + { + IContainer webapp = TapestryUtils.findWebapp(relativeTo.getProject()); + + if (webapp != null) + { + IFile file = (IFile) webapp.findMember(path); + + if (file == null) + { + throw new AssetException("File not found '" + + webapp.getProjectRelativePath().toPortableString() + "/" + path + "'"); + } + + return file; + } + + throw new AssetException("Couldn't find context folder ('src/main/webapp')"); + } +} diff --git a/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/DefaultAssetResolver.java b/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/DefaultAssetResolver.java new file mode 100644 index 0000000..a434ef5 --- /dev/null +++ b/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/DefaultAssetResolver.java @@ -0,0 +1,30 @@ +package com.anjlab.eclipse.tapestry5; + +import java.util.List; + +import org.eclipse.core.resources.IFile; + +import com.anjlab.eclipse.tapestry5.TapestryUtils.FileNameBuilder; + +public class DefaultAssetResolver implements AssetResolver +{ + @Override + public IFile resolve(final String path, IFile relativeTo) throws AssetException + { + List files = TapestryUtils.findTapestryFiles(relativeTo, true, new FileNameBuilder() + { + @Override + public String getFileName(String fileName, String fileExtension) + { + return path; + } + }); + + if (!files.isEmpty()) + { + return files.get(0); + } + + throw new AssetException("Couldn't resolve asset from path '" + path + "'"); + } +} diff --git a/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/EclipseUtils.java b/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/EclipseUtils.java new file mode 100644 index 0000000..301e843 --- /dev/null +++ b/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/EclipseUtils.java @@ -0,0 +1,151 @@ +package com.anjlab.eclipse.tapestry5; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IResourceDelta; +import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.core.runtime.Platform; +import org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.ITreeSelection; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IFileEditorInput; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.ide.IDE; + +public class EclipseUtils +{ + + public static IFile getFileFromPage(IWorkbenchPage page) + { + if (page == null) + { + return null; + } + + IEditorPart activeEditor = page.getActiveEditor(); + + if (activeEditor == null) + { + return null; + } + + IEditorInput editorInput = activeEditor.getEditorInput(); + + if (editorInput instanceof IFileEditorInput) + { + IFileEditorInput fileEditorInput = (IFileEditorInput) editorInput; + + return fileEditorInput.getFile(); + } + + return null; + } + + public static IFile getFileFromSelection(ISelection selection) throws JavaModelException + { + return selection instanceof IStructuredSelection + ? getFileFromSelectionElement(((IStructuredSelection) selection).getFirstElement()) + : null; + } + + private static IFile getFileFromSelectionElement(Object firstElement) throws JavaModelException + { + if (firstElement == null) + { + return null; + } + + if (firstElement instanceof ICompilationUnit) + { + ICompilationUnit compilationUnit = ((ICompilationUnit) firstElement); + + return (IFile) compilationUnit.getCorrespondingResource().getAdapter(IFile.class); + } + + if (firstElement instanceof ITreeSelection) + { + ITreeSelection treeSelection = (ITreeSelection) firstElement; + + return getFileFromSelectionElement(treeSelection.getFirstElement()); + } + + IFile file = (IFile) Platform.getAdapterManager().getAdapter(firstElement, IFile.class); + + if (file == null) + { + if (firstElement instanceof IAdaptable) + { + file = (IFile) ((IAdaptable) firstElement).getAdapter(IFile.class); + } + } + + return file; + } + + public static void openFile(final IWorkbenchWindow window, final IFile file) + { + window.getShell().getDisplay().asyncExec(new Runnable() + { + public void run() + { + try + { + IDE.openEditor(window.getActivePage(), file, true); + } + catch (Exception e) + { + Activator.getDefault().logError("Unable to open editor", e); + + openError(window, "Unable to open editor: " + e.getLocalizedMessage()); + } + } + }); + } + + public static void openError(final IWorkbenchWindow window, String message) + { + MessageDialog.openError( + window.getShell(), + "Eclipse Integration for Tapestry5", + message); + } + + public static List getAllAffectedResources(IResourceDelta delta, Class clazz) + { + return getAllAffectedResources(delta, clazz, 0xFFFFFFFF); + } + + @SuppressWarnings("unchecked") + public static List getAllAffectedResources(IResourceDelta delta, Class clazz, int deltaKind) + { + List files = new ArrayList(); + + for (IResourceDelta child : delta.getAffectedChildren()) + { + IResource resource = child.getResource(); + + if (resource != null && clazz.isAssignableFrom(resource.getClass())) + { + if ((child.getKind() & deltaKind) != 0) + { + files.add((T) resource); + } + } + else + { + files.addAll(getAllAffectedResources(child, clazz, deltaKind)); + } + } + return files; + } + +} diff --git a/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/TapestryContext.java b/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/TapestryContext.java new file mode 100644 index 0000000..f172f8c --- /dev/null +++ b/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/TapestryContext.java @@ -0,0 +1,279 @@ +package com.anjlab.eclipse.tapestry5; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jdt.core.IAnnotation; +import org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.IMemberValuePair; +import org.eclipse.jdt.core.ISourceRange; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.JavaModelException; + +import com.anjlab.eclipse.tapestry5.TapestryUtils.FileNameBuilder; + +public class TapestryContext +{ + private List files; + + public TapestryContext(IFile file) + { + this.files = new ArrayList(); + + if (TapestryUtils.isJavaFile(file) || TapestryUtils.isTemplateFile(file)) + { + initFromJavaOrTemplateFile(file); + } + else if (TapestryUtils.isPropertiesFile(file)) + { + initFromPropertiesFile(file); + } + + IFile javaFile = getJavaFile(); + + if (javaFile != null) + { + try + { + ICompilationUnit compilationUnit = (ICompilationUnit) JavaCore.create(javaFile); + + for (IType type : compilationUnit.getAllTypes()) + { + for (IAnnotation annotation : type.getAnnotations()) + { + if ("org.apache.tapestry5.annotations.Import".equals(annotation.getElementName()) + || "Import".equals(annotation.getElementName())) + { + IMemberValuePair[] pairs = annotation.getMemberValuePairs(); + for (IMemberValuePair pair : pairs) + { + if ("library".equals(pair.getMemberName()) + || "stylesheet".equals(pair.getMemberName())) + { + processImport(annotation, pair.getMemberName(), pair.getValue()); + } + } + } + } + } + } + catch (JavaModelException e) + { + Activator.getDefault().logError("Error inspecting compilation unit", e); + } + } + } + + private void processImport(IAnnotation annotation, String type, Object value) + { + if (value instanceof Object[]) + { + for (Object item : (Object[])value) + { + processImportedFile(annotation, type, (String) item); + } + } + else if (value instanceof String) + { + processImportedFile(annotation, type, (String) value); + } + } + + private void processImportedFile(IAnnotation annotation, String type, String fileName) + { + ISourceRange sourceRange = null; + try + { + sourceRange = annotation.getSourceRange(); + } + catch (JavaModelException e) + { + Activator.getDefault().logError("Error getting annotation location", e); + } + files.add(new AssetPath(getJavaFile(), sourceRange, fileName)); + } + + public List getFiles() + { + return Collections.unmodifiableList(files); + } + + public IFile getJavaFile() + { + for (IFile file : files) + { + if (TapestryUtils.isJavaFile(file)) + { + return file; + } + } + return null; + } + + private void initFromPropertiesFile(IFile file) + { + List files = TapestryUtils.findTapestryFiles(file, true, new FileNameBuilder() + { + @Override + public String getFileName(String fileName, String fileExtension) + { + Matcher matcher = getLocalizedPropertiesPattern().matcher(fileName); + if (matcher.find()) + { + return matcher.group(1) + ".java"; + } + return fileName.substring(0, fileName.lastIndexOf(fileExtension)) + "java"; + } + + }); + + if (!files.isEmpty()) + { + IFile javaFile = files.get(0); + addWithComplementFile(javaFile); + } + + addPropertiesFiles(file); + } + + private void initFromJavaOrTemplateFile(IFile file) + { + addWithComplementFile(file); + addPropertiesFiles(file); + } + + private void addWithComplementFile(IFile file) + { + this.files.add(file); + IFile complement = TapestryUtils.findComplementFile(file); + if (complement != null) + { + this.files.add(complement); + } + } + + private void addPropertiesFiles(IFile file) + { + List propertiesFiles = TapestryUtils.findTapestryFiles(file, false, new FileNameBuilder() + { + @Override + public String getFileName(String fileName, String fileExtension) + { + Matcher matcher = getLocalizedPropertiesPattern().matcher(fileName); + if (matcher.find()) + { + return matcher.group(1) + ".*\\.properties"; + } + return fileName.substring(0, fileName.lastIndexOf(fileExtension) - 1) + ".*\\.properties"; + } + }); + + for (IFile properties : propertiesFiles) + { + this.files.add(properties); + } + } + + private Pattern getLocalizedPropertiesPattern() + { + return Pattern.compile("([^_]*)(_.*)+\\.properties"); + } + + public boolean contains(IFile file) + { + if (file == null) + { + return false; + } + + for (IFile f : files) + { + if (f.equals(file)) + { + return true; + } + if (f instanceof AssetPath && !(file instanceof AssetPath)) + { + try + { + IFile resolvedAsset = ((AssetPath) f).resolveFile(false); + + if (resolvedAsset.equals(file)) + { + return true; + } + } + catch (AssetException e) + { + // Ignore + } + } + } + return false; + } + + public void validate() + { + for (IFile file : files) + { + if (file instanceof AssetPath) + { + try + { + ((AssetPath) file).resolveFile(true); + } + catch (AssetException e) + { + // Ignore + } + } + } + } + + public void deleteMarkers() + { + deleteMarkers(getProject()); + } + + public static void deleteMarkers(IResource project) + { + try + { + IMarker[] markers = project.findMarkers(IMarker.PROBLEM, false, IResource.DEPTH_INFINITE); + + for (IMarker marker : markers) + { + if (marker.getAttribute(AssetPath.ASSET_PATH_MARKER_ATTRIBUTE) != null) + { + marker.delete(); + } + } + } + catch (CoreException e) + { + Activator.getDefault().logError("Error deleting asset problem markers", e); + } + } + + public IProject getProject() + { + for (IFile file : files) + { + if (!(file instanceof AssetPath)) + { + return file.getProject(); + } + } + return null; + } + +} \ No newline at end of file diff --git a/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/TapestryUtils.java b/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/TapestryUtils.java new file mode 100644 index 0000000..c35dd6d --- /dev/null +++ b/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/TapestryUtils.java @@ -0,0 +1,287 @@ +package com.anjlab.eclipse.tapestry5; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.regex.Pattern; + +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.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IPackageFragmentRoot; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.JavaModelException; + +public class TapestryUtils +{ + public static IFile findComplementFile(IFile forFile) + { + List files = findTapestryFiles(forFile, true, new FileNameBuilder() + { + @Override + public String getFileName(String fileName, String fileExtension) + { + String complementExtension = "tml".equals(fileExtension) ? "java" : "java".equals(fileExtension) ? "tml" : null; + + if (complementExtension == null) + { + throw new IllegalArgumentException(); + } + + return fileName.substring(0, fileName.lastIndexOf(fileExtension)) + complementExtension; + } + }); + + return !files.isEmpty() ? files.get(0) : null; + } + + public static interface FileNameBuilder + { + String getFileName(String fileName, String fileExtension); + } + + public static List findTapestryFiles(IFile forFile, boolean findFirst, FileNameBuilder fileNameBuilder) + { + try + { + String complementFileName = null; + + // Check if the file is in the web application context + + boolean fromWebapp = false; + + IProject project = forFile.getProject(); + + IContainer webapp = findWebapp(project); + + if (isTemplateFile(forFile)) + { + if (forFile.getParent().equals(webapp)) + { + String relativeFileName = getRelativeFileName(forFile, webapp); + + fromWebapp = relativeFileName != null; + + if (fromWebapp) + { + complementFileName = fileNameBuilder.getFileName(relativeFileName, forFile.getFileExtension()); + } + } + } + + List resources = new ArrayList(); + + if (!fromWebapp) + { + complementFileName = fileNameBuilder.getFileName(forFile.getName(), forFile.getFileExtension()); + + // Try searching in the same folder first + + resources = findMembers(forFile.getParent(), complementFileName); + + if (findFirst && !resources.isEmpty()) + { + return resources; + } + } + + // Look in the source folders + + if (!forFile.getProject().hasNature(JavaCore.NATURE_ID)) + { + Activator.getDefault().logError("'" + forFile.getProject() + "' is not a Java project"); + + return Collections.emptyList(); + } + + IJavaProject javaProject = JavaCore.create(forFile.getProject()); + + IContainer container = null; + + if (!fromWebapp) + { + IContainer adaptedProject = (IContainer) javaProject.getCorrespondingResource().getAdapter(IContainer.class); + + IResource adaptedFile = adaptedProject.findMember(forFile.getProjectRelativePath()); + + container = adaptedFile.getParent(); + + while (container != null && !isSourceFolder(container)) + { + container = container.getParent(); + } + + if (container == null) + { + Activator.getDefault().logWarning("Unable to find source folder for file: " + forFile.getFullPath()); + + return Collections.emptyList(); + } + + // Get the file name relative to source folder + String relativeFileName = getRelativeFileName(forFile, container); + + complementFileName = fileNameBuilder.getFileName(relativeFileName, forFile.getFileExtension()); + } + + for (IPackageFragmentRoot root : javaProject.getAllPackageFragmentRoots()) + { + if (!isSourceFolder(root)) + { + continue; + } + + IContainer resourceContainer = (IContainer) root.getCorrespondingResource().getAdapter(IContainer.class); + + if (container != null && resourceContainer.getFullPath().equals(container.getFullPath())) + { + continue; + } + + List resources2 = findMembers(resourceContainer, complementFileName); + + if (findFirst && !resources2.isEmpty()) + { + return resources2; + } + + resources.addAll(resources2); + } + + // Look for TML files in web application context + // https://github.com/anjlab/eclipse-tapestry5-plugin/issues/2 + + if (complementFileName.endsWith(".tml")) + { + if (webapp != null) + { + IResource file = webapp.findMember(complementFileName); + + if (file instanceof IFile) + { + resources.add((IFile) file); + } + } + } + + return resources; + } + catch (CoreException e) + { + Activator.getDefault().logError("Error finding complement file", e); + + return Collections.emptyList(); + } + } + + private static String getRelativeFileName(IFile file, IContainer ancestor) + { + return file.getProjectRelativePath().toPortableString().substring( + ancestor.getProjectRelativePath().toPortableString().length()); + } + + private static List findMembers(IContainer container, String path) + { + List resources = new ArrayList(); + + if (path.contains("*")) + { + // Find files by mask + int slashIndex = path.lastIndexOf("/"); + + String parentPath = slashIndex < 0 ? "/" : path.substring(0, slashIndex); + String mask = slashIndex < 0 ? path : path.substring(slashIndex + 1); + + Pattern pattern = Pattern.compile(mask); + + IResource resource = container.findMember(parentPath); + + if (resource instanceof IFolder) + { + try + { + IResource[] members = ((IFolder) resource).members(); + + for (IResource member : members) + { + if (pattern.matcher(member.getName()).matches()) + { + resources.add((IFile) member); + } + } + } + catch (CoreException e) + { + Activator.getDefault().logError("Error finding files by mask", e); + } + } + } + else + { + // Exact match + IResource resource = container.findMember(path); + if (resource != null && resource instanceof IFile) + { + resources.add((IFile) resource); + } + } + return resources; + } + + private static boolean isSourceFolder(IContainer container) throws JavaModelException + { + return isSourceFolder((IJavaElement) container.getAdapter(IJavaElement.class)); + } + + private static boolean isSourceFolder(IJavaElement javaElement) throws JavaModelException + { + return javaElement != null + && (javaElement instanceof IPackageFragmentRoot) + && (((IPackageFragmentRoot) javaElement).getKind() == IPackageFragmentRoot.K_SOURCE); + } + + public static boolean isTemplateFile(IFile file) + { + return "tml".equals(file.getFileExtension()); + } + + public static boolean isJavaFile(IFile file) + { + return "java".equals(file.getFileExtension()); + } + + public static boolean isPropertiesFile(IFile file) + { + return "properties".equals(file.getFileExtension()); + } + + public static TapestryContext createTapestryContext(IFile forFile) + { + return new TapestryContext(forFile); + } + + public static AssetResolver createAssetResolver(String bindingPrefix, String assetPath) + { + if ("default".equals(bindingPrefix)) + { + return new DefaultAssetResolver(); + } + else if ("context".equals(bindingPrefix)) + { + return new ContextAssetResolver(); + } + return null; + } + + public static IContainer findWebapp(IProject project) + { + // TODO Support non-default locations? + + return (IContainer) project.findMember("src/main/webapp"); + } +} diff --git a/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/handlers/SwitchHandler.java b/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/handlers/SwitchHandler.java index 60854a0..97b4e92 100644 --- a/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/handlers/SwitchHandler.java +++ b/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/handlers/SwitchHandler.java @@ -3,32 +3,14 @@ import org.eclipse.core.commands.AbstractHandler; import org.eclipse.core.commands.ExecutionEvent; import org.eclipse.core.commands.ExecutionException; -import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IAdaptable; -import org.eclipse.core.runtime.Platform; -import org.eclipse.jdt.core.ICompilationUnit; -import org.eclipse.jdt.core.IJavaElement; -import org.eclipse.jdt.core.IJavaProject; -import org.eclipse.jdt.core.IPackageFragmentRoot; -import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.viewers.ITreeSelection; -import org.eclipse.ui.IEditorInput; -import org.eclipse.ui.IEditorPart; -import org.eclipse.ui.IFileEditorInput; -import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchWindow; -import org.eclipse.ui.PartInitException; import org.eclipse.ui.handlers.HandlerUtil; -import org.eclipse.ui.ide.IDE; -import com.anjlab.eclipse.tapestry5.Activator; +import com.anjlab.eclipse.tapestry5.EclipseUtils; +import com.anjlab.eclipse.tapestry5.TapestryUtils; /** * Our sample handler extends AbstractHandler, an IHandler base class. @@ -52,221 +34,46 @@ public Object execute(ExecutionEvent event) throws ExecutionException { IWorkbenchWindow window = HandlerUtil.getActiveWorkbenchWindowChecked(event); - ISelection selection = window.getSelectionService().getSelection(); - - if (selection instanceof IStructuredSelection) + try { - Object firstElement = ((IStructuredSelection) selection).getFirstElement(); + IFile file = EclipseUtils.getFileFromSelection(window.getSelectionService().getSelection()); + + if (file == null) + { + file = EclipseUtils.getFileFromPage(window.getActivePage()); + } - if (firstElement != null) + if (file != null) { - try + if (!TapestryUtils.isTemplateFile(file) && !TapestryUtils.isJavaFile(file)) { - IFile file = getFileFromSelection(selection); - - if (file != null) - { - openComplementFile(window, file); - - return null; - } + throw new ExecutionException("This feature only works for *.java and *.tml files"); } - catch (JavaModelException e) + + IFile complementFile = TapestryUtils.findComplementFile(file); + + if (complementFile == null) { - throw new ExecutionException("Unable to get file from selection", e); + throw new ExecutionException("Complement file not found for " + + file.getProjectRelativePath().toPortableString()); } + + EclipseUtils.openFile(window, complementFile); } } - - IWorkbenchPage activePage = window.getActivePage(); - - if (activePage == null) + catch (JavaModelException e) { - return null; + throw new ExecutionException("Error switching file", e); } - - IEditorPart activeEditor = activePage.getActiveEditor(); - - if (activeEditor == null) + catch (ExecutionException e) { - return null; + MessageDialog.openError( + window.getShell(), + "Eclipse Integration for Tapestry5", + e.getLocalizedMessage()); } - - IEditorInput editorInput = activeEditor.getEditorInput(); - - if (editorInput instanceof IFileEditorInput) - { - IFileEditorInput fileEditorInput = (IFileEditorInput) editorInput; - - openComplementFile(window, fileEditorInput.getFile()); - } - return null; } + - private IFile getFileFromSelection(Object firstElement) throws JavaModelException - { - if (firstElement == null) - { - return null; - } - - if (firstElement instanceof ICompilationUnit) - { - ICompilationUnit compilationUnit = ((ICompilationUnit) firstElement); - - return (IFile) compilationUnit.getCorrespondingResource().getAdapter(IFile.class); - } - - if (firstElement instanceof ITreeSelection) - { - ITreeSelection treeSelection = (ITreeSelection) firstElement; - - return getFileFromSelection(treeSelection.getFirstElement()); - } - - IFile file = (IFile) Platform.getAdapterManager().getAdapter(firstElement, IFile.class); - - if (file == null) - { - if (firstElement instanceof IAdaptable) - { - file = (IFile) ((IAdaptable) firstElement).getAdapter(IFile.class); - } - } - - return file; - } - - private void openComplementFile(final IWorkbenchWindow window, final IFile file) - { - if (file == null) - { - return; - } - - window.getShell().getDisplay().asyncExec(new Runnable() - { - public void run() - { - IFile complementFile = null; - - try - { - if (getComplementFileExtension(file.getFileExtension()) == null) - { - throw new PartInitException("This feature only works for *.java and *.tml files"); - } - - complementFile = findComplementFile(file); - - if (complementFile == null) - { - throw new PartInitException("Complement file not found for " - + file.getProjectRelativePath().toPortableString()); - } - - IDE.openEditor(window.getActivePage(), complementFile, true); - } - catch (Exception e) - { - Activator.getDefault().logError("Unable to open editor", e); - - MessageDialog.openError( - window.getShell(), - "Eclipse Integration for Tapestry5", - "Unable to open editor: " + e.getLocalizedMessage()); - } - } - - private IFile findComplementFile(IFile file) throws CoreException - { - // Try searching in the same folder first - - IResource resource = file.getParent().findMember(getComplementFileName(file.getName(), file.getFileExtension())); - - if (resource instanceof IFile) - { - return (IFile) resource; - } - - // Look in the source folders - - if (!file.getProject().hasNature(JavaCore.NATURE_ID)) - { - Activator.getDefault().logError("Project '" + file.getProject() + "' does not a Java nature"); - - return null; - } - - IJavaProject project = JavaCore.create(file.getProject()); - - IContainer adaptedProject = (IContainer) project.getCorrespondingResource().getAdapter(IContainer.class); - - IResource adaptedFile = adaptedProject.findMember(file.getProjectRelativePath()); - - IContainer container = adaptedFile.getParent(); - - while (container != null && !isSourceFolder(container)) - { - container = container.getParent(); - } - - if (container == null) - { - Activator.getDefault().logWarning("Unable to find source folder for file: " + file.getFullPath()); - - return null; - } - - // Get the file name relative to source folder - String fileName = file.getProjectRelativePath().toPortableString().substring( - container.getProjectRelativePath().toPortableString().length()); - - String complementFileName = getComplementFileName(fileName, file.getFileExtension()); - - for (IPackageFragmentRoot root : project.getAllPackageFragmentRoots()) - { - if (!isSourceFolder(root)) - { - continue; - } - - IContainer resourceContainer = (IContainer) root.getCorrespondingResource().getAdapter(IContainer.class); - - resource = resourceContainer.findMember(complementFileName); - - if (resource instanceof IFile) - { - return (IFile) resource; - } - } - - return null; - } - - private boolean isSourceFolder(IContainer container) throws JavaModelException - { - return isSourceFolder((IJavaElement) container.getAdapter(IJavaElement.class)); - } - - private boolean isSourceFolder(IJavaElement javaElement) throws JavaModelException - { - return javaElement != null - && (javaElement instanceof IPackageFragmentRoot) - && (((IPackageFragmentRoot) javaElement).getKind() == IPackageFragmentRoot.K_SOURCE); - } - - private String getComplementFileName(String fileName, String originalExtension) - { - return fileName.substring(0, fileName.lastIndexOf(originalExtension)) - + getComplementFileExtension(originalExtension); - } - - private String getComplementFileExtension(String extension) - { - return "tml".equals(extension) ? "java" : "java".equals(extension) ? "tml" : null; - } - }); - } } diff --git a/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/views/NameSorter.java b/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/views/NameSorter.java new file mode 100644 index 0000000..6a42d2c --- /dev/null +++ b/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/views/NameSorter.java @@ -0,0 +1,8 @@ +package com.anjlab.eclipse.tapestry5.views; + +import org.eclipse.jface.viewers.ViewerSorter; + +public class NameSorter extends ViewerSorter +{ + +} \ No newline at end of file diff --git a/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/views/TapestryContextView.java b/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/views/TapestryContextView.java new file mode 100644 index 0000000..291c87b --- /dev/null +++ b/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/views/TapestryContextView.java @@ -0,0 +1,257 @@ +package com.anjlab.eclipse.tapestry5.views; + +import java.util.List; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResourceChangeEvent; +import org.eclipse.core.resources.IResourceChangeListener; +import org.eclipse.core.resources.IResourceDelta; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.IDoubleClickListener; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.TreePath; +import org.eclipse.jface.viewers.TreeSelection; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.ISelectionListener; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchPart; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.part.ViewPart; + +import com.anjlab.eclipse.tapestry5.AssetException; +import com.anjlab.eclipse.tapestry5.AssetPath; +import com.anjlab.eclipse.tapestry5.EclipseUtils; +import com.anjlab.eclipse.tapestry5.TapestryContext; +import com.anjlab.eclipse.tapestry5.TapestryUtils; + +/** + * This sample class demonstrates how to plug-in a new workbench view. The view + * shows data obtained from the model. The sample creates a dummy model on the + * fly, but a real implementation would connect to the model available either in + * this or another plug-in (e.g. the workspace). The view is connected to the + * model using a content provider. + *

+ * The view uses a label provider to define how model objects should be + * presented in the view. Each view can present the same model objects using + * different labels and icons, if needed. Alternatively, a single label provider + * can be shared between views in order to ensure that objects of the same type + * are presented in the same way everywhere. + *

+ */ + +public class TapestryContextView extends ViewPart +{ + /** + * The ID of the view as specified by the extension. + */ + public static final String ID = "com.anjlab.eclipse.tapestry5.views.TapestryOutlineView"; + + private TreeViewer viewer; + private ViewContentProvider contentProvider; + private ISelectionListener selectionListener; + private IResourceChangeListener postBuildListener; + private IResourceChangeListener postChangeListener; + + /** + * This is a callback that will allow us to create the viewer and initialize it. + */ + public void createPartControl(Composite parent) + { + /* + * TODO Add 'Validate' toolbar icon: + * [x] markers when asset files couldn't be resolved + * [ ] markers when some properties are not localized (present in one file, but not in the others) + * + * TODO Create Java/TML/JS/CSS files + * [ ] Quick fix to create missing imports + * [ ] Create complement file if absent + * [ ] Add simple naming convention for JS/CSS files. For example: + * - files should be in the same folder and have the same name as the Java file + * - or be in lower case with dashes instead of Pascal-casing + * + * TODO [ ] Support Tapestry 5.4 assets + * + * TODO [ ] Context menu for asset files (JS/CSS) -- 'Find references' in project + */ + + viewer = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL); + viewer.setContentProvider(new ViewContentProvider(getViewSite(), (IFile) null)); + viewer.setLabelProvider(new ViewLabelProvider()); + viewer.setSorter(new NameSorter()); + viewer.setInput(getViewSite()); + viewer.addDoubleClickListener(new IDoubleClickListener() + { + public void doubleClick(DoubleClickEvent event) + { + ISelection selection = viewer.getSelection(); + + Object obj = ((IStructuredSelection) selection).getFirstElement(); + + if (obj instanceof TreeObject) + { + IFile file = (IFile) ((TreeObject) obj).getData(); + + if (file instanceof AssetPath) + { + AssetPath assetPath = (AssetPath) file; + try + { + file = assetPath.resolveFile(false); + } + catch (AssetException e) + { + EclipseUtils.openError(getViewSite().getWorkbenchWindow(), + "Unable to resolve asset '" + assetPath.getAssetPath() + "': " + + e.getLocalizedMessage()); + + return; + } + } + + EclipseUtils.openFile(getViewSite().getWorkbenchWindow(), file); + } + } + }); + + postChangeListener = new IResourceChangeListener() + { + @Override + public void resourceChanged(IResourceChangeEvent event) + { + if (contentProvider == null) + { + return; + } + + List changedFiles = EclipseUtils.getAllAffectedResources( + event.getDelta(), IFile.class, IResourceDelta.CHANGED); + + for (IFile changedFile : changedFiles) + { + if (!TapestryUtils.isJavaFile(changedFile)) + { + continue; + } + + if (contentProvider.getContext().contains(changedFile)) + { + // Some @Imports may have changed + updateContext(changedFile); + + break; + } + } + } + }; + + ResourcesPlugin.getWorkspace().addResourceChangeListener(postChangeListener, IResourceChangeEvent.POST_CHANGE); + + postBuildListener = new IResourceChangeListener() + { + @Override + public void resourceChanged(IResourceChangeEvent event) + { + if (contentProvider != null) + { + List projects = EclipseUtils.getAllAffectedResources(event.getDelta(), IProject.class); + + for (IProject project : projects) + { + TapestryContext.deleteMarkers(project); + } + + contentProvider.getContext().validate(); + } + } + }; + ResourcesPlugin.getWorkspace().addResourceChangeListener(postBuildListener, IResourceChangeEvent.POST_BUILD); + + selectionListener = new ISelectionListener() + { + private IFile activeFile; + + @Override + public void selectionChanged(IWorkbenchPart part, ISelection selection) + { + IWorkbenchWindow window = part.getSite().getWorkbenchWindow(); + + IWorkbenchPage activePage = window.getActivePage(); + + IFile file = EclipseUtils.getFileFromPage(activePage); + + if (file != null) + { + if (activeFile != file) + { + activeFile = file; + + updateContext(file); + + viewer.setSelection( + new TreeSelection( + new TreePath( + new Object[] { new TreeObject(file.getName(), file) }))); + } + } + else + { + // Keep outline view presenting information about previous activeFile + } + } + }; + + getSite().getPage().addSelectionListener(selectionListener); + } + + private void updateContext(IFile file) + { + ViewContentProvider provider = new ViewContentProvider(getViewSite(), file); + + if (!provider.hasElements() && contentProvider != null + && contentProvider.getContext().contains(file)) + { + // In case if we clicked on @Import'ed asset (JS or CSS) file, then we can't obtain tapestry context for it + // because we don't have any naming conventions for these files, and also these files may be + // referenced from multiple components/pages, so they may belong to multiple contexts. + // Anyway if we can't find any elements for the context -- we simply show the previous one. + + provider = contentProvider; + } + + contentProvider = provider; + + getViewSite().getWorkbenchWindow().getShell().getDisplay().syncExec(new Runnable() + { + @Override + public void run() + { + viewer.setContentProvider(contentProvider); + } + }); + } + + @Override + public void dispose() + { + ResourcesPlugin.getWorkspace().removeResourceChangeListener(postBuildListener); + + ResourcesPlugin.getWorkspace().removeResourceChangeListener(postChangeListener); + + getSite().getPage().removeSelectionListener(selectionListener); + + super.dispose(); + } + + /** + * Passing the focus request to the viewer's control. + */ + public void setFocus() + { + viewer.getControl().setFocus(); + } +} \ No newline at end of file diff --git a/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/views/TreeObject.java b/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/views/TreeObject.java new file mode 100644 index 0000000..077397a --- /dev/null +++ b/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/views/TreeObject.java @@ -0,0 +1,98 @@ +package com.anjlab.eclipse.tapestry5.views; + +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.ui.IContributorResourceAdapter; +/* + * The content provider class is responsible for providing objects to the + * view. It can wrap existing objects in adapters or simply return objects + * as-is. These objects may be sensitive to the current input of the view, + * or ignore it and always show the same content (like Task List, for + * example). + */ + +public class TreeObject implements IAdaptable +{ + private String name; + private Object data; + private TreeParent parent; + + public TreeObject(String name, Object data) + { + if (name == null || data == null) + { + throw new NullPointerException("Nulls not allowed"); + } + + this.name = name; + this.data = data; + } + + public String getName() + { + return name; + } + + public Object getData() + { + return data; + } + + public void setParent(TreeParent parent) + { + this.parent = parent; + } + + public TreeParent getParent() + { + return parent; + } + + public String toString() + { + return getName(); + } + + @SuppressWarnings("rawtypes") + public Object getAdapter(Class key) + { + if (key.equals(IContributorResourceAdapter.class)) + { + return new IContributorResourceAdapter() + { + + @Override + public IResource getAdaptedResource(IAdaptable adaptable) + { + // TODO Auto-generated method stub + return (IResource) data; + } + }; + } + return null; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + + if (!(obj instanceof TreeObject)) + { + return false; + } + + TreeObject other = (TreeObject) obj; + + return data.equals(other.data) && name.equals(other.name); + } + + @Override + public int hashCode() + { + return data.hashCode() + name.hashCode(); + } +} \ No newline at end of file diff --git a/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/views/TreeParent.java b/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/views/TreeParent.java new file mode 100644 index 0000000..c12354e --- /dev/null +++ b/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/views/TreeParent.java @@ -0,0 +1,36 @@ +package com.anjlab.eclipse.tapestry5.views; + +import java.util.ArrayList; + +public class TreeParent extends TreeObject +{ + private ArrayList children; + + public TreeParent(String name, Object data) + { + super(name, data); + children = new ArrayList(); + } + + public void addChild(TreeObject child) + { + children.add(child); + child.setParent(this); + } + + public void removeChild(TreeObject child) + { + children.remove(child); + child.setParent(null); + } + + public TreeObject[] getChildren() + { + return (TreeObject[]) children.toArray(new TreeObject[children.size()]); + } + + public boolean hasChildren() + { + return children.size() > 0; + } +} \ No newline at end of file diff --git a/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/views/ViewContentProvider.java b/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/views/ViewContentProvider.java new file mode 100644 index 0000000..8896ca3 --- /dev/null +++ b/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/views/ViewContentProvider.java @@ -0,0 +1,102 @@ +package com.anjlab.eclipse.tapestry5.views; + +import org.eclipse.core.resources.IFile; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.ui.IViewSite; + +import com.anjlab.eclipse.tapestry5.TapestryContext; +import com.anjlab.eclipse.tapestry5.TapestryUtils; + +public class ViewContentProvider implements ITreeContentProvider +{ + private TreeParent invisibleRoot; + private IViewSite viewSite; + private IFile file; + private TapestryContext context; + + public ViewContentProvider(IViewSite viewSite, IFile file, TapestryContext context) + { + this.viewSite = viewSite; + this.file = file; + } + + public ViewContentProvider(IViewSite viewSite, IFile file) + { + this(viewSite, file, null); + } + + private void initialize() + { + invisibleRoot = new TreeParent("", new Object()); + + if (file != null) + { + context = TapestryUtils.createTapestryContext(file); + + for (IFile relatedFile : context.getFiles()) + { + invisibleRoot.addChild(new TreeObject(relatedFile.getName(), relatedFile)); + } + } + } + + public TapestryContext getContext() + { + return context; + } + + public Object[] getElements(Object parent) + { + if (parent.equals(viewSite)) + { + if (invisibleRoot == null) + { + initialize(); + } + return getChildren(invisibleRoot); + } + return getChildren(parent); + } + + public Object getParent(Object child) + { + if (child instanceof TreeObject) + { + return ((TreeObject) child).getParent(); + } + return null; + } + + public Object[] getChildren(Object parent) + { + if (parent instanceof TreeParent) + { + return ((TreeParent) parent).getChildren(); + } + return new Object[0]; + } + + public boolean hasChildren(Object parent) + { + if (parent instanceof TreeParent) + { + return ((TreeParent) parent).hasChildren(); + } + return false; + } + + public boolean hasElements() + { + return getElements(viewSite).length > 0; + } + + public void inputChanged(Viewer v, Object oldInput, Object newInput) + { + } + + public void dispose() + { + } + +} \ No newline at end of file diff --git a/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/views/ViewLabelProvider.java b/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/views/ViewLabelProvider.java new file mode 100644 index 0000000..95253be --- /dev/null +++ b/com.anjlab.eclipse.tapestry5/src/com/anjlab/eclipse/tapestry5/views/ViewLabelProvider.java @@ -0,0 +1,70 @@ +package com.anjlab.eclipse.tapestry5.views; + +import org.eclipse.core.resources.IFile; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.viewers.DecorationOverlayIcon; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.swt.graphics.Image; +import org.eclipse.ui.ISharedImages; +import org.eclipse.ui.PlatformUI; + +import com.anjlab.eclipse.tapestry5.AssetException; +import com.anjlab.eclipse.tapestry5.AssetPath; + +public class ViewLabelProvider extends LabelProvider +{ + @Override + public String getText(Object obj) + { + return obj.toString(); + } + + @SuppressWarnings("restriction") + @Override + public Image getImage(Object obj) + { + if (obj instanceof TreeParent) + { + return PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_FOLDER); + } + + if (obj instanceof TreeObject) + { + Object data = ((TreeObject) obj).getData(); + + if (data instanceof IFile) + { + Image image = PlatformUI.getWorkbench().getEditorRegistry() + .getImageDescriptor(((IFile) data).getName()) + .createImage(); + + if (data instanceof AssetPath) + { + ImageDescriptor[] overlays; + + try + { + ((AssetPath) data).resolveFile(false); + + overlays = new ImageDescriptor[0]; + } + catch (AssetException e) + { + overlays = new ImageDescriptor[] + { + org.eclipse.jdt.internal.ui.JavaPluginImages.DESC_OVR_WARNING + }; + } + + DecorationOverlayIcon overlayIcon = new DecorationOverlayIcon(image, overlays); + + return overlayIcon.createImage(); + } + + return image; + } + } + + return PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_FILE); + } +} \ No newline at end of file diff --git a/update-site/bintray-upload.sh b/update-site/bintray-upload.sh new file mode 100755 index 0000000..7e381dc --- /dev/null +++ b/update-site/bintray-upload.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +PACKAGE_NAME=eclipse-tapestry5-plugin +VERSION_NAME=1.1.0 + +curl -vT content.jar \ + -udmitrygusev:$bintray_api_key \ + https://api.bintray.com/content/anjlab/eclipse/$PACKAGE_NAME/$VERSION_NAME/ + +curl -vT artifacts.jar \ + -udmitrygusev:$bintray_api_key \ + https://api.bintray.com/content/anjlab/eclipse/$PACKAGE_NAME/$VERSION_NAME/ + +curl -vT features/com.anjlab.eclipse.tapestry5.feature_$VERSION_NAME.jar \ + -udmitrygusev:$bintray_api_key \ + https://api.bintray.com/content/anjlab/eclipse/$PACKAGE_NAME/$VERSION_NAME/features/ + +curl -vT plugins/com.anjlab.eclipse.tapestry5_$VERSION_NAME.jar \ + -udmitrygusev:$bintray_api_key \ + https://api.bintray.com/content/anjlab/eclipse/$PACKAGE_NAME/$VERSION_NAME/plugins/ + diff --git a/update-site/site.xml b/update-site/site.xml index b4846bd..e04182a 100644 --- a/update-site/site.xml +++ b/update-site/site.xml @@ -1,6 +1,6 @@ - +