blob: 49766b110bfaf3cc706f001af2ac35314c75265d [file] [log] [blame]
/*
* Copyright 2000-2009 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 com.intellij.uiDesigner.actions;
import com.intellij.CommonBundle;
import com.intellij.compiler.PsiClassWriter;
import com.intellij.compiler.impl.FileSetCompileScope;
import com.intellij.compiler.instrumentation.InstrumentationClassFinder;
import com.intellij.execution.*;
import com.intellij.execution.configurations.*;
import com.intellij.execution.executors.DefaultRunExecutor;
import com.intellij.execution.process.ProcessAdapter;
import com.intellij.execution.process.ProcessEvent;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.execution.runners.ExecutionEnvironmentBuilder;
import com.intellij.execution.runners.ExecutionUtil;
import com.intellij.execution.runners.ProgramRunner;
import com.intellij.lang.properties.PropertiesFileType;
import com.intellij.lang.properties.PropertiesReferenceManager;
import com.intellij.lang.properties.psi.PropertiesFile;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.compiler.CompileContext;
import com.intellij.openapi.compiler.CompileStatusNotification;
import com.intellij.openapi.compiler.CompilerManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileTypes.StdFileTypes;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.OrderEnumerator;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.uiDesigner.FormEditingUtil;
import com.intellij.uiDesigner.UIDesignerBundle;
import com.intellij.uiDesigner.compiler.AsmCodeGenerator;
import com.intellij.uiDesigner.compiler.FormErrorInfo;
import com.intellij.uiDesigner.compiler.Utils;
import com.intellij.uiDesigner.designSurface.GuiEditor;
import com.intellij.uiDesigner.lw.*;
import com.intellij.uiDesigner.make.PreviewNestedFormLoader;
import com.intellij.util.PathsList;
import com.intellij.util.containers.HashSet;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.incremental.java.CopyResourcesUtil;
import javax.swing.*;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.StringTokenizer;
/**
* @author Anton Katilin
* @author Vladimir Kondratyev
*/
public final class PreviewFormAction extends AnAction{
private static final Logger LOG = Logger.getInstance("#com.intellij.uiDesigner.actions.PreviewFormAction");
/**
* The problem is that this class is in a default package so it's not
* import this class to refer
*/
private static final String CLASS_TO_BIND_NAME = "FormPreviewFrame";
@NonNls private static final String RUNTIME_BUNDLE_PREFIX = "RuntimeBundle";
@NonNls public static final String PREVIEW_BINDING_FIELD = "myComponent";
@NotNull
public static InstrumentationClassFinder createClassFinder(@NotNull final String classPath){
final ArrayList<URL> urls = new ArrayList<URL>();
for (StringTokenizer tokenizer = new StringTokenizer(classPath, File.pathSeparator); tokenizer.hasMoreTokens();) {
final String s = tokenizer.nextToken();
try {
urls.add(new File(s).toURI().toURL());
}
catch (Exception exc) {
throw new RuntimeException(exc);
}
}
return new InstrumentationClassFinder(urls.toArray(new URL[urls.size()]));
}
public void actionPerformed(final AnActionEvent e) {
final GuiEditor editor = FormEditingUtil.getActiveEditor(e.getDataContext());
if (editor != null) {
showPreviewFrame(editor.getModule(), editor.getFile(), editor.getStringDescriptorLocale());
}
}
public void update(final AnActionEvent e) {
final GuiEditor editor = FormEditingUtil.getActiveEditor(e.getDataContext());
if(editor == null){
e.getPresentation().setVisible(false);
return;
}
final VirtualFile file = editor.getFile();
e.getPresentation().setVisible(
FileDocumentManager.getInstance().getDocument(file) != null &&
file.getFileType() == StdFileTypes.GUI_DESIGNER_FORM
);
}
private static void showPreviewFrame(@NotNull final Module module, @NotNull final VirtualFile formFile,
@Nullable final Locale stringDescriptorLocale) {
final String tempPath;
try {
final File tempDirectory = FileUtil.createTempDirectory("FormPreview", "");
tempPath = tempDirectory.getAbsolutePath();
CopyResourcesUtil.copyFormsRuntime(tempPath, true);
}
catch (IOException e) {
Messages.showErrorDialog(
module.getProject(),
UIDesignerBundle.message("error.cannot.preview.form", formFile.getPath().replace('/', File.separatorChar), e.toString()),
CommonBundle.getErrorTitle()
);
return;
}
final PathsList sources = OrderEnumerator.orderEntries(module).withoutSdk().withoutLibraries().withoutDepModules().getSourcePathsList();
final String classPath = OrderEnumerator.orderEntries(module).recursively().getPathsList().getPathsString() + File.pathSeparator +
sources.getPathsString() + File.pathSeparator + /* resources bundles */
tempPath;
final InstrumentationClassFinder finder = createClassFinder(classPath);
try {
final Document doc = FileDocumentManager.getInstance().getDocument(formFile);
final LwRootContainer rootContainer;
try {
rootContainer = Utils.getRootContainer(doc.getText(), new CompiledClassPropertiesProvider(finder.getLoader()));
}
catch (Exception e) {
Messages.showErrorDialog(
module.getProject(),
UIDesignerBundle.message("error.cannot.read.form", formFile.getPath().replace('/', File.separatorChar), e.getMessage()),
CommonBundle.getErrorTitle()
);
return;
}
if (rootContainer.getComponentCount() == 0) {
Messages.showErrorDialog(
module.getProject(),
UIDesignerBundle.message("error.cannot.preview.empty.form", formFile.getPath().replace('/', File.separatorChar)),
CommonBundle.getErrorTitle()
);
return;
}
setPreviewBindings(rootContainer, CLASS_TO_BIND_NAME);
// 2. Copy previewer class and all its superclasses into TEMP directory and instrument it.
try {
PreviewNestedFormLoader nestedFormLoader = new PreviewNestedFormLoader(module, tempPath, finder);
final File tempFile = CopyResourcesUtil.copyClass(tempPath, CLASS_TO_BIND_NAME, true);
//CopyResourcesUtil.copyClass(tempPath, CLASS_TO_BIND_NAME + "$1", true);
CopyResourcesUtil.copyClass(tempPath, CLASS_TO_BIND_NAME + "$MyExitAction", true);
CopyResourcesUtil.copyClass(tempPath, CLASS_TO_BIND_NAME + "$MyPackAction", true);
CopyResourcesUtil.copyClass(tempPath, CLASS_TO_BIND_NAME + "$MySetLafAction", true);
Locale locale = Locale.getDefault();
if (locale.getCountry().length() > 0 && locale.getLanguage().length() > 0) {
CopyResourcesUtil.copyProperties(tempPath, RUNTIME_BUNDLE_PREFIX + "_" + locale.getLanguage() +
"_" + locale.getCountry() + PropertiesFileType.DOT_DEFAULT_EXTENSION);
}
if (locale.getLanguage().length() > 0) {
CopyResourcesUtil.copyProperties(tempPath, RUNTIME_BUNDLE_PREFIX + "_" + locale.getLanguage() + PropertiesFileType.DOT_DEFAULT_EXTENSION);
}
CopyResourcesUtil.copyProperties(tempPath, RUNTIME_BUNDLE_PREFIX + "_" + locale.getLanguage() + PropertiesFileType.DOT_DEFAULT_EXTENSION);
CopyResourcesUtil.copyProperties(tempPath, RUNTIME_BUNDLE_PREFIX + PropertiesFileType.DOT_DEFAULT_EXTENSION);
final AsmCodeGenerator codeGenerator = new AsmCodeGenerator(
rootContainer, finder, nestedFormLoader, true, new PsiClassWriter(module)
);
codeGenerator.patchFile(tempFile);
final FormErrorInfo[] errors = codeGenerator.getErrors();
if(errors.length != 0){
Messages.showErrorDialog(
module.getProject(),
UIDesignerBundle.message("error.cannot.preview.form",
formFile.getPath().replace('/', File.separatorChar),
errors[0].getErrorMessage()),
CommonBundle.getErrorTitle()
);
return;
}
}
catch (Exception e) {
LOG.debug(e);
Messages.showErrorDialog(
module.getProject(),
UIDesignerBundle.message("error.cannot.preview.form", formFile.getPath().replace('/', File.separatorChar),
e.getMessage() != null ? e.getMessage() : e.toString()),
CommonBundle.getErrorTitle()
);
return;
}
// 2.5. Copy up-to-date properties files to the output directory.
final HashSet<String> bundleSet = new HashSet<String>();
FormEditingUtil.iterateStringDescriptors(
rootContainer,
new FormEditingUtil.StringDescriptorVisitor<IComponent>() {
public boolean visit(final IComponent component, final StringDescriptor descriptor) {
if (descriptor.getBundleName() != null) {
bundleSet.add(descriptor.getDottedBundleName());
}
return true;
}
});
if (bundleSet.size() > 0) {
HashSet<VirtualFile> virtualFiles = new HashSet<VirtualFile>();
HashSet<Module> modules = new HashSet<Module>();
PropertiesReferenceManager manager = PropertiesReferenceManager.getInstance(module.getProject());
for(String bundleName: bundleSet) {
for(PropertiesFile propFile: manager.findPropertiesFiles(module, bundleName)) {
virtualFiles.add(propFile.getVirtualFile());
final Module moduleForFile = ModuleUtil.findModuleForFile(propFile.getVirtualFile(), module.getProject());
if (moduleForFile != null) {
modules.add(moduleForFile);
}
}
}
FileSetCompileScope scope = new FileSetCompileScope(virtualFiles, modules.toArray(new Module[modules.size()]));
CompilerManager.getInstance(module.getProject()).make(scope, new CompileStatusNotification() {
public void finished(boolean aborted, int errors, int warnings, final CompileContext compileContext) {
if (!aborted && errors == 0) {
runPreviewProcess(tempPath, sources, module, formFile, stringDescriptorLocale);
}
}
});
}
else {
runPreviewProcess(tempPath, sources, module, formFile, stringDescriptorLocale);
}
}
finally {
finder.releaseResources();
}
}
public static void setPreviewBindings(final LwRootContainer rootContainer, final String classToBindName) {
// 1. Prepare form to preview. We have to change container so that it has only one binding.
rootContainer.setClassToBind(classToBindName);
FormEditingUtil.iterate(
rootContainer,
new FormEditingUtil.ComponentVisitor<LwComponent>() {
public boolean visit(final LwComponent iComponent) {
iComponent.setBinding(null);
return true;
}
}
);
if (rootContainer.getComponentCount() == 1) {
//noinspection HardCodedStringLiteral
((LwComponent)rootContainer.getComponent(0)).setBinding(PREVIEW_BINDING_FIELD);
}
}
private static void runPreviewProcess(final String tempPath, final PathsList sources, final Module module, final VirtualFile formFile,
@Nullable final Locale stringDescriptorLocale) {
// 3. Now we are ready to launch Java process
final JavaParameters parameters = new JavaParameters();
parameters.getClassPath().add(tempPath);
parameters.getClassPath().add(PathManager.findFileInLibDirectory("jgoodies-forms.jar").getAbsolutePath());
final List<String> paths = sources.getPathList();
for (final String path : paths) {
parameters.getClassPath().add(path);
}
try {
parameters.configureByModule(module, JavaParameters.JDK_AND_CLASSES);
}
catch (CantRunException e) {
Messages.showErrorDialog(
module.getProject(),
UIDesignerBundle.message("error.cannot.preview.form", formFile.getPath().replace('/', File.separatorChar), e.getMessage()),
CommonBundle.getErrorTitle()
);
return;
}
parameters.setMainClass("FormPreviewFrame");
parameters.setWorkingDirectory(tempPath);
if (stringDescriptorLocale != null && stringDescriptorLocale.getDisplayName().length() > 0) {
parameters.getVMParametersList().add("-Duser.language=" + stringDescriptorLocale.getLanguage());
}
try {
RunProfile profile = new MyRunProfile(module, parameters, tempPath,
UIDesignerBundle.message("progress.preview.started", formFile.getPresentableUrl()));
ExecutionEnvironmentBuilder.create(module.getProject(), DefaultRunExecutor.getRunExecutorInstance(), profile).buildAndExecute();
}
catch (ExecutionException e) {
Messages.showErrorDialog(
module.getProject(),
UIDesignerBundle.message("error.cannot.preview.form", formFile.getPath().replace('/', File.separatorChar), e.getMessage()),
CommonBundle.getErrorTitle()
);
}
}
private static final class MyRunProfile implements ModuleRunProfile {
private final Module myModule;
private final JavaParameters myParams;
private final String myTempPath;
private final String myStatusbarMessage;
public MyRunProfile(final Module module, final JavaParameters params, final String tempPath, final String statusbarMessage) {
myModule = module;
myParams = params;
myTempPath = tempPath;
myStatusbarMessage = statusbarMessage;
}
public Icon getIcon() {
return null;
}
public RunProfileState getState(@NotNull final Executor executor, @NotNull final ExecutionEnvironment env) throws ExecutionException {
return new JavaCommandLineState(env) {
protected JavaParameters createJavaParameters() {
return myParams;
}
public ExecutionResult execute(@NotNull final Executor executor, @NotNull final ProgramRunner runner) throws ExecutionException {
try {
ExecutionResult executionResult = super.execute(executor, runner);
executionResult.getProcessHandler().addProcessListener(new ProcessAdapter() {
@Override
public void processTerminated(ProcessEvent event) {
FileUtil.asyncDelete(new File(myTempPath));
}
});
return executionResult;
}
finally {
final Project project = myModule.getProject();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
WindowManager.getInstance().getStatusBar(project).setInfo(myStatusbarMessage);
}
});
}
}
};
}
public String getName() {
return UIDesignerBundle.message("title.form.preview");
}
@NotNull
public Module[] getModules() {
return new Module[] {myModule};
}
}
}