IDEA-69867 Support services as an artifact type in Griffon projects

This commit is contained in:
peter
2012-06-08 12:35:55 +02:00
parent ee50d99da0
commit 78a07ff82a
6 changed files with 423 additions and 46 deletions
@@ -18,6 +18,8 @@ package org.jetbrains.plugins.groovy.griffon;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.JavaParameters;
import com.intellij.lang.properties.IProperty;
import com.intellij.lang.properties.psi.PropertiesFile;
import com.intellij.openapi.application.AccessToken;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.module.Module;
@@ -36,6 +38,8 @@ import com.intellij.openapi.vcs.changes.IgnoredBeanFactory;
import com.intellij.openapi.vfs.JarFileSystem;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import gnu.trove.TIntArrayList;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
@@ -49,7 +53,9 @@ import org.jetbrains.plugins.groovy.mvc.MvcProjectStructure;
import javax.swing.*;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -246,7 +252,7 @@ public class GriffonFramework extends MvcFramework {
for (final VirtualFile child : dist.getChildren()) {
final String name = child.getName();
if (name.endsWith(".jar")) {
if (name.startsWith("griffon-cli-") || name.startsWith("griffon-rt-")) {
if (name.startsWith("griffon-cli-") || name.startsWith("griffon-rt-") || name.startsWith("griffon-resources-")) {
params.getClassPath().add(child);
}
}
@@ -296,6 +302,9 @@ public class GriffonFramework extends MvcFramework {
final String confpath = griffonHomePath + GROOVY_STARTER_CONF;
params.getVMParametersList().add("-Dgroovy.starter.conf=" + confpath);
params.getVMParametersList().add(
"-Dgroovy.sanitized.stacktraces=\"groovy., org.codehaus.groovy., java., javax., sun., gjdk.groovy., gant., org.codehaus.gant.\"");
params.getProgramParametersList().add("--main");
params.getProgramParametersList().add("org.codehaus.griffon.cli.GriffonScriptRunner");
params.getProgramParametersList().add("--conf");
@@ -314,7 +323,7 @@ public class GriffonFramework extends MvcFramework {
params.getProgramParametersList().add(argsString);
params.setDefaultCharset(module.getProject());
return params;
}
@@ -384,6 +393,24 @@ public class GriffonFramework extends MvcFramework {
return EP_NAME.findExtension(GriffonFramework.class);
}
public VirtualFile getApplicationPropertiesFile(Module module) {
final VirtualFile appRoot = findAppRoot(module);
return appRoot != null ? appRoot.findChild("application.properties") : null;
}
@Override
public String getApplicationName(Module module) {
final VirtualFile appProperties = getApplicationPropertiesFile(module);
if (appProperties != null) {
final PsiFile file = PsiManager.getInstance(module.getProject()).findFile(appProperties);
if (file instanceof PropertiesFile) {
final IProperty property = ((PropertiesFile)file).findPropertyByKey("application.name");
return property != null ? property.getValue() : super.getApplicationName(module);
}
}
return super.getApplicationName(module);
}
private static class GriffonProjectStructure extends MvcProjectStructure {
public GriffonProjectStructure(Module module, final boolean auxModule) {
super(module, auxModule, getUserHomeGriffon(), GriffonFramework.getInstance().getSdkWorkDir(module));
@@ -395,11 +422,55 @@ public class GriffonFramework extends MvcFramework {
}
public String[] getSourceFolders() {
return new String[]{"griffon-app/controllers", "griffon-app/models", "griffon-app/views", "src/main", "griffon-app/services"};
List<String> sourceFolders = new ArrayList<String>();
for (VirtualFile file : ModuleRootManager.getInstance(myModule).getContentRoots()) {
handleSrc(file.findChild("src"), sourceFolders);
handleGriffonApp(file.findChild("griffon-app"), sourceFolders);
List<GriffonSourceInspector.GriffonSource> sources =
GriffonSourceInspector.processModuleMetadata(myModule);
for (GriffonSourceInspector.GriffonSource source : sources) {
sourceFolders.add(source.getPath());
}
}
return sourceFolders.toArray(new String[sourceFolders.size()]);
}
private void handleGriffonApp(VirtualFile griffonApp, List<String> sourceFolders) {
if (griffonApp == null) return;
// Add standard artifacts, i.e, models, views, controllers, services, conf, lifecycle
for (String child : new String[]{"models", "views", "controllers", "services", "conf", "lifecycle"}) {
if (griffonApp.findChild(child) != null) {
sourceFolders.add("griffon-app/" + child);
}
}
}
private void handleSrc(VirtualFile src, List<String> sourceFolders) {
if (src == null) return;
for (String child : new String[]{"main", "cli"}) {
if (src.findChild(child) != null) {
sourceFolders.add("src/" + child);
}
}
}
private void handleTest(VirtualFile test, List<String> sourceFolders) {
if (test == null) return;
for (String child : new String[]{"unit", "integration", "shared"}) {
if (test.findChild(child) != null) {
sourceFolders.add("test/" + child);
}
}
}
public String[] getTestFolders() {
return new String[]{"test/unit", "test/integration"};
List<String> sourceFolders = new ArrayList<String>();
for (VirtualFile file : ModuleRootManager.getInstance(myModule).getContentRoots()) {
handleTest(file.findChild("test"), sourceFolders);
}
return sourceFolders.toArray(new String[sourceFolders.size()]);
}
public String[] getInvalidSourceFolders() {
@@ -0,0 +1,250 @@
/*
* Copyright 2000-2011 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.plugins.groovy.griffon;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.containers.ContainerUtil;
import javax.swing.*;
import java.io.File;
import java.util.*;
/**
* @author aalmiray
*/
public class GriffonSourceInspector {
public static List<GriffonSource> processModuleMetadata(final Module module) {
return CachedValuesManager.getManager(module.getProject()).getCachedValue(module, new CachedValueProvider<List<GriffonSource>>() {
@Override
public Result<List<GriffonSource>> compute() {
List<GriffonSource> sources = new ArrayList<GriffonSource>();
List<VirtualFile> dependencies = new ArrayList<VirtualFile>();
ContainerUtil.addIfNotNull(dependencies, GriffonFramework.getInstance().getApplicationPropertiesFile(module));
String applicationName = GriffonFramework.getInstance().getApplicationName(module);
File sdkWorkDir = GriffonFramework.getInstance().getSdkWorkDir(module);
// construct $griffonWorkDir/projects/$appName/plugins
File pluginsDir = new File(sdkWorkDir, "/projects/" + applicationName + "/plugins/");
if (pluginsDir.exists() && pluginsDir.canRead() && pluginsDir.isDirectory()) {
//noinspection ConstantConditions
for (File pluginDir : pluginsDir.listFiles()) {
if (!pluginDir.isDirectory() || !pluginDir.canRead()) continue;
File srcIdeSupportDir = new File(pluginDir, "src/ide-support");
if (!srcIdeSupportDir.exists() || !srcIdeSupportDir.canRead()) continue;
File ideaSupport = new File(srcIdeSupportDir, "idea.xml");
VirtualFile ideaMetadata = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(ideaSupport);
if (ideaMetadata != null) {
dependencies.add(ideaMetadata);
PsiFile psiFile = PsiManager.getInstance(module.getProject()).findFile(ideaMetadata);
if (psiFile instanceof XmlFile) {
XmlTag rootTag = ((XmlFile)psiFile).getRootTag();
if (rootTag == null) continue;
/*
Metadata file has the following format
<idea-griffon>
<source-root path="src/commons">
<navigation
description="Common Sources"
icon="groovy-icon"
weight="75" />
</source-root>
</idea-griffon>
*/
for (XmlTag sourceRootTag : rootTag.findSubTags("source-root")) {
String path = sourceRootTag.getAttributeValue("path");
XmlTag navigationTag = sourceRootTag.findFirstSubTag("navigation");
String description = navigationTag == null ? "" : navigationTag.getAttributeValue("description");
XmlAttribute iconAttr = navigationTag == null ? null : navigationTag.getAttribute("icon");
XmlAttribute weightAttr = navigationTag == null ? null : navigationTag.getAttribute("weight");
String icon = iconAttr != null ? iconAttr.getValue() : "groovy-icon";
int weight = weightAttr != null ? Integer.parseInt(weightAttr.getValue()) : 75;
sources.add(new GriffonSource(path, new GriffonSource.Navigation(description, icon, weight)));
}
}
}
}
}
return Result.create(sources, dependencies);
}
});
}
public static class GriffonSource {
private final String path;
private final Navigation navigation;
public GriffonSource(String path, Navigation navigation) {
this.path = path;
this.navigation = navigation;
}
public String getPath() {
return path;
}
public Navigation getNavigation() {
return navigation;
}
@Override
public String toString() {
return "GriffonSource{" + "path='" + path + '\'' + ", navigation=" + navigation + '}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
GriffonSource that = (GriffonSource)o;
if (!navigation.equals(that.navigation)) return false;
if (!path.equals(that.path)) return false;
return true;
}
@Override
public int hashCode() {
int result = path.hashCode();
result = 31 * result + navigation.hashCode();
return result;
}
public static class Navigation {
private final String description;
private final String icon;
private final int weight;
public Navigation(String description, String icon, int weight) {
this.description = getNaturalName(description);
this.icon = icon;
this.weight = weight;
}
public String getDescription() {
return description;
}
public Icon getIcon() {
String iconStr = icon.endsWith(".png") ? icon : icon + ".png";
if (iconStr.startsWith("/")) {
return IconLoader.getIcon(iconStr);
}
else {
return IconLoader.getIcon("/icons/griffon/" + iconStr);
}
}
public int getWeight() {
return weight;
}
@Override
public String toString() {
return "Navigation{" + "description='" + description + '\'' + ", icon='" + icon + '\'' + ", weight=" + weight + '}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Navigation)) return false;
Navigation that = (Navigation)o;
if (weight != that.weight) return false;
if (description != null ? !description.equals(that.description) : that.description != null) return false;
if (icon != null ? !icon.equals(that.icon) : that.icon != null) return false;
return true;
}
@Override
public int hashCode() {
int result = description != null ? description.hashCode() : 0;
result = 31 * result + (icon != null ? icon.hashCode() : 0);
result = 31 * result + weight;
return result;
}
}
}
private static String getNaturalName(String name) {
name = getShortName(name);
List<String> words = new ArrayList<String>();
int i = 0;
char[] chars = name.toCharArray();
for (int j = 0; j < chars.length; j++) {
char c = chars[j];
String w;
if (i >= words.size()) {
w = "";
words.add(i, w);
}
else {
w = words.get(i);
}
if (Character.isLowerCase(c) || Character.isDigit(c)) {
if (Character.isLowerCase(c) && w.length() == 0) {
c = Character.toUpperCase(c);
}
else if (w.length() > 1 && Character.isUpperCase(w.charAt(w.length() - 1))) {
w = "";
words.add(++i, w);
}
words.set(i, w + c);
}
else if (Character.isUpperCase(c)) {
if ((i == 0 && w.length() == 0) || Character.isUpperCase(w.charAt(w.length() - 1))) {
words.set(i, w + c);
}
else {
words.add(++i, String.valueOf(c));
}
}
}
StringBuilder buf = new StringBuilder();
for (Iterator<String> j = words.iterator(); j.hasNext(); ) {
String word = j.next();
buf.append(word);
if (j.hasNext()) {
buf.append(' ');
}
}
return buf.toString();
}
private static String getShortName(String className) {
return StringUtil.getShortName(className);
}
}
@@ -20,16 +20,20 @@ import com.intellij.ide.projectView.ViewSettings;
import com.intellij.ide.util.treeView.AbstractTreeNode;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.util.PlatformIcons;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.util.Icons;
import com.intellij.util.containers.hash.LinkedHashMap;
import org.jetbrains.plugins.groovy.GroovyIcons;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
import org.jetbrains.plugins.groovy.mvc.MvcIcons;
import org.jetbrains.plugins.groovy.mvc.projectView.*;
import javax.swing.*;
import java.util.List;
import java.util.Map;
/**
* @author peter
@@ -42,51 +46,67 @@ public class GriffonToolWindowFactory extends MvcToolWindowDescriptor {
@Override
public void fillModuleChildren(List<AbstractTreeNode> result, Module module, ViewSettings viewSettings, VirtualFile root) {
final Project project = module.getProject();
final PsiDirectory domains = findDirectory(project, root, "griffon-app/models");
if (domains != null) {
result.add(new TopLevelDirectoryNode(module, domains, viewSettings, "Model classes", MvcIcons.DOMAIN_CLASSES_FOLDER, AbstractMvcPsiNodeDescriptor.DOMAIN_CLASSES_FOLDER) {
@Override
protected AbstractTreeNode createClassNode(final GrTypeDefinition typeDefinition) {
return new DomainClassNode(getModule(), typeDefinition, getSettings());
// process well-known artifact paths
for (VirtualFile file : ModuleRootManager.getInstance(module).getSourceRoots()) {
PsiDirectory sourceRoot = PsiManager.getInstance(project).findDirectory(file);
if (sourceRoot != null) {
if ("griffon-app".equals(file.getParent().getName())) {
GriffonDirectoryMetadata metadata = DIRECTORY_METADATA.get(file.getName());
if (metadata == null) continue;
result.add(new TopLevelDirectoryNode(module, sourceRoot, viewSettings, metadata.description, metadata.icon, metadata.weight));
}
});
}
final PsiDirectory conf = findDirectory(project, root, "griffon-app/conf");
if (conf != null) {
result.add(new TopLevelDirectoryNode(module, conf, viewSettings, "Configuration", MvcIcons.CONFIG_FOLDER, AbstractMvcPsiNodeDescriptor.CONFIG_FOLDER));
}
final PsiDirectory controllers = findDirectory(project, root, "griffon-app/controllers");
if (controllers != null) {
result.add(new TopLevelDirectoryNode(module, controllers, viewSettings, "Controllers", MvcIcons.CONTROLLERS_FOLDER, AbstractMvcPsiNodeDescriptor.CONTROLLERS_FOLDER));
}
final PsiDirectory services = findDirectory(project, root, "griffon-app/services");
if (services != null) {
result.add(new TopLevelDirectoryNode(module, services, viewSettings, "Services", MvcIcons.SERVICE, AbstractMvcPsiNodeDescriptor.SERVICES_FOLDER));
}
final PsiDirectory views = findDirectory(project, root, "griffon-app/views");
if (views != null) {
result.add(new TopLevelDirectoryNode(module, views, viewSettings, "Views", GroovyIcons.GROOVY_ICON_16x16, AbstractMvcPsiNodeDescriptor.VIEWS_FOLDER));
}
}
// add standard source folder
final PsiDirectory srcMain = findDirectory(project, root, "src/main");
if (srcMain != null) {
result.add(new TopLevelDirectoryNode(module, srcMain, viewSettings, "Project Sources", GroovyIcons.GROOVY_ICON_16x16, AbstractMvcPsiNodeDescriptor.SRC_FOLDERS));
result.add(new TopLevelDirectoryNode(module, srcMain, viewSettings, "Project Sources", GroovyIcons.GROOVY_ICON_16x16,
AbstractMvcPsiNodeDescriptor.SRC_FOLDERS));
}
final PsiDirectory srcCli = findDirectory(project, root, "src/cli");
if (srcCli != null) {
result.add(new TopLevelDirectoryNode(module, srcCli, viewSettings, "Build Sources", GroovyIcons.GROOVY_ICON_16x16,
AbstractMvcPsiNodeDescriptor.SRC_FOLDERS));
}
// add standard test sources
final PsiDirectory testsUnit = findDirectory(project, root, "test/unit");
if (testsUnit != null) {
result.add(new TestsTopLevelDirectoryNode(module, testsUnit, viewSettings, "Unit Tests", PlatformIcons.TEST_SOURCE_FOLDER,
PlatformIcons.TEST_SOURCE_FOLDER));
result.add(
new TestsTopLevelDirectoryNode(module, testsUnit, viewSettings, "Unit Tests", Icons.TEST_SOURCE_FOLDER, Icons.TEST_SOURCE_FOLDER));
}
final PsiDirectory testsIntegration = findDirectory(project, root, "test/integration");
if (testsIntegration != null) {
result.add(new TestsTopLevelDirectoryNode(module, testsIntegration, viewSettings, "Integration Tests", PlatformIcons.TEST_SOURCE_FOLDER,
PlatformIcons.TEST_SOURCE_FOLDER));
result.add(new TestsTopLevelDirectoryNode(module, testsIntegration, viewSettings, "Integration Tests", Icons.TEST_SOURCE_FOLDER,
Icons.TEST_SOURCE_FOLDER));
}
final PsiDirectory testsShared = findDirectory(project, root, "test/shared");
if (testsShared != null) {
result.add(new TestsTopLevelDirectoryNode(module, testsShared, viewSettings, "Shared Test Sources", Icons.TEST_SOURCE_FOLDER,
Icons.TEST_SOURCE_FOLDER));
}
// add additional sources provided by plugins
for (VirtualFile file : ModuleRootManager.getInstance(module).getContentRoots()) {
List<GriffonSourceInspector.GriffonSource> sources = GriffonSourceInspector.processModuleMetadata(module);
for (GriffonSourceInspector.GriffonSource source : sources) {
final PsiDirectory dir = findDirectory(project, file, source.getPath());
if (dir != null) {
result.add(
new TopLevelDirectoryNode(module, dir, viewSettings, source.getNavigation().getDescription(), source.getNavigation().getIcon(),
source.getNavigation().getWeight()));
}
}
}
final VirtualFile applicationPropertiesFile = GriffonFramework.getInstance().getApplicationPropertiesFile(module);
if (applicationPropertiesFile != null) {
PsiFile appProperties = PsiManager.getInstance(module.getProject()).findFile(applicationPropertiesFile);
if (appProperties != null) {
result.add(new FileNode(module, appProperties, null, viewSettings));
}
}
}
@@ -95,5 +115,30 @@ public class GriffonToolWindowFactory extends MvcToolWindowDescriptor {
return GriffonFramework.GRIFFON_ICON;
}
private static final Map<String, GriffonDirectoryMetadata> DIRECTORY_METADATA = new LinkedHashMap<String, GriffonDirectoryMetadata>();
static {
DIRECTORY_METADATA.put("models", new GriffonDirectoryMetadata("Models", loadIcon("folder-models"), 20));
DIRECTORY_METADATA.put("views", new GriffonDirectoryMetadata("Views", loadIcon("folder-views"), 30));
DIRECTORY_METADATA.put("controllers", new GriffonDirectoryMetadata("Controllers", loadIcon("folder-controllers"), 40));
DIRECTORY_METADATA.put("services", new GriffonDirectoryMetadata("Services", loadIcon("folder-services"), 50));
DIRECTORY_METADATA.put("lifecycle", new GriffonDirectoryMetadata("Lifecycle", loadIcon("folder-lifecycle"), 60));
DIRECTORY_METADATA.put("conf", new GriffonDirectoryMetadata("Configuration", loadIcon("folder-conf"), 65));
}
private static Icon loadIcon(String name) {
return IconLoader.getIcon("/icons/griffon/" + name + ".png");
}
private static class GriffonDirectoryMetadata {
public final String description;
public final Icon icon;
public final int weight;
public GriffonDirectoryMetadata(String description, Icon icon, int weight) {
this.description = description;
this.icon = icon;
this.weight = weight;
}
}
}
@@ -504,10 +504,16 @@ public abstract class MvcFramework {
final File grailsWorkDir = getSdkWorkDir(module);
if (grailsWorkDir == null) return null;
final String applicationName = getApplicationName(module);
if (applicationName == null) return null;
return new File(grailsWorkDir, "projects/" + applicationName + "/plugins");
}
public String getApplicationName(Module module) {
final VirtualFile root = findAppRoot(module);
if (root == null) return null;
return new File(grailsWorkDir, "projects/" + root.getName() + "/plugins");
return root.getName();
}
protected abstract String getCommonPluginSuffix();
@@ -32,23 +32,22 @@ public abstract class MvcProjectStructure {
protected final Module myModule;
private final boolean myAuxModule;
private final String myUserHomeSdkRoot;
private final String mySdkWorkDir;
private final String mySdkWorkDirPath;
public MvcProjectStructure(Module module, boolean auxModule, String userHomeSdkRoot, final File sdkWorkDir) {
myAuxModule = auxModule;
myModule = module;
myUserHomeSdkRoot = userHomeSdkRoot;
final VirtualFile sdkWorkDirVFile = MvcModuleStructureUtil.refreshAndFind(sdkWorkDir);
mySdkWorkDir = sdkWorkDirVFile == null ? "" : sdkWorkDirVFile.getPath() + "/";
mySdkWorkDirPath = sdkWorkDirVFile == null ? "" : sdkWorkDirVFile.getPath() + "/";
}
public boolean isValidContentRoot(@NotNull VirtualFile file) {
if (file.getPath().startsWith(myUserHomeSdkRoot)) {
if (!myAuxModule) {
return false;
}
if (!file.getPath().startsWith(mySdkWorkDir)) {
if (!file.getPath().startsWith(mySdkWorkDirPath)) {
return false;
}
}
@@ -18,6 +18,7 @@ package org.jetbrains.plugins.groovy.mvc.projectView;
import com.intellij.ide.projectView.ViewSettings;
import com.intellij.ide.projectView.PresentationData;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.util.Comparing;
import com.intellij.psi.PsiDirectory;
import org.jetbrains.annotations.NotNull;
@@ -41,6 +42,11 @@ public class TopLevelDirectoryNode extends AbstractFolderNode {
myIcon = icon;
}
@Override
public boolean equals(Object object) {
return super.equals(object) && Comparing.equal(myTitle, ((TopLevelDirectoryNode) object).myTitle);
}
@Override
protected void updateImpl(PresentationData data) {
data.setPresentableText(myTitle);