/*
* Copyright 2013-2014 Urs Wolfer
*
* 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.urswolfer.intellij.plugin.gerrit.push;
import com.google.inject.Inject;
import com.intellij.openapi.components.ApplicationComponent;
import com.intellij.openapi.diagnostic.Logger;
import com.urswolfer.intellij.plugin.gerrit.GerritModule;
import com.urswolfer.intellij.plugin.gerrit.GerritSettings;
import git4idea.push.GitPushOperation;
import javassist.*;
import org.jetbrains.annotations.NotNull;
/**
* Since there are no entry points for modifying the push dialog without copying a lot of source, some modifications
* to the Git push setting panel (where you can set an alternative remote branch) are done with byte-code modification
* with javassist:
*
* * Some methods of GitPushSupport are overwritten in order to inject Gerrit push support.
* * GerritPushExtensionPanel, GerritPushOptionsPanel and GerritPushTargetPanel get copied to the Git plugin class loader.
*
* @author Urs Wolfer
*/
@SuppressWarnings("ComponentNotRegistered") // proxy class below is registered
public class GerritPushExtension implements ApplicationComponent {
@Inject
private GerritSettings gerritSettings;
@Inject
private Logger log;
public void initComponent() {
try {
ClassPool classPool = ClassPool.getDefault();
ClassLoader gitIdeaPluginClassLoader = GitPushOperation.class.getClassLoader(); // it must be a class which is not modified (loaded) by javassist later on
ClassLoader gerritPluginClassLoader = GerritPushExtensionPanel.class.getClassLoader();
classPool.appendClassPath(new LoaderClassPath(gitIdeaPluginClassLoader));
classPool.appendClassPath(new LoaderClassPath(gerritPluginClassLoader));
copyGerritPluginClassesToGitPlugin(classPool, gitIdeaPluginClassLoader);
modifyGitBranchPanel(classPool, gitIdeaPluginClassLoader);
} catch (Exception e) {
log.error("Failed to inject Gerrit push UI.", e);
} catch (Error e) {
log.error("Failed to inject Gerrit push UI.", e);
}
}
private void modifyGitBranchPanel(ClassPool classPool, ClassLoader classLoader) {
try {
boolean pushToGerrit = gerritSettings.getPushToGerrit();
CtClass gitPushSupportClass = classPool.get("git4idea.push.GitPushSupport");
CtClass gerritPushOptionsPanelClass = classPool.get("com.urswolfer.intellij.plugin.gerrit.push.GerritPushOptionsPanel");
gitPushSupportClass.addField(new CtField(gerritPushOptionsPanelClass, "gerritPushOptionsPanel", gitPushSupportClass),
"new com.urswolfer.intellij.plugin.gerrit.push.GerritPushOptionsPanel(" + pushToGerrit + ");");
CtMethod createOptionsPanelMethod = gitPushSupportClass.getDeclaredMethod("createOptionsPanel");
createOptionsPanelMethod.setBody(
"{" +
"gerritPushOptionsPanel.initPanel(mySettings.getPushTagMode(), git4idea.config.GitVersionSpecialty.SUPPORTS_FOLLOW_TAGS.existsIn(myVcs.getVersion()));" +
"return gerritPushOptionsPanel;" +
"}"
);
CtMethod createTargetPanelMethod = gitPushSupportClass.getDeclaredMethod("createTargetPanel");
createTargetPanelMethod.setBody(
"{" +
"return new com.urswolfer.intellij.plugin.gerrit.push.GerritPushTargetPanel($1, $2, gerritPushOptionsPanel);" +
"}"
);
gitPushSupportClass.toClass(classLoader, GitPushOperation.class.getProtectionDomain());
gitPushSupportClass.detach();
} catch (CannotCompileException e) {
log.error("Failed to inject Gerrit push UI.", e);
} catch (NotFoundException e) {
log.error("Failed to inject Gerrit push UI.", e);
}
}
private void copyGerritPluginClassesToGitPlugin(ClassPool classPool, ClassLoader targetClassLoader) {
loadClass(classPool, targetClassLoader, "com.urswolfer.intellij.plugin.gerrit.push.GerritPushOptionsPanel");
loadClass(classPool, targetClassLoader, "com.urswolfer.intellij.plugin.gerrit.push.GerritPushTargetPanel");
loadClass(classPool, targetClassLoader, "com.urswolfer.intellij.plugin.gerrit.push.GerritPushTargetPanel$1");
loadClass(classPool, targetClassLoader, "com.urswolfer.intellij.plugin.gerrit.push.GerritPushExtensionPanel");
loadClass(classPool, targetClassLoader, "com.urswolfer.intellij.plugin.gerrit.push.GerritPushExtensionPanel$1");
loadClass(classPool, targetClassLoader, "com.urswolfer.intellij.plugin.gerrit.push.GerritPushExtensionPanel$ChangeActionListener");
loadClass(classPool, targetClassLoader, "com.urswolfer.intellij.plugin.gerrit.push.GerritPushExtensionPanel$ChangeTextActionListener");
loadClass(classPool, targetClassLoader, "com.urswolfer.intellij.plugin.gerrit.push.GerritPushExtensionPanel$SettingsStateActionListener");
}
private void loadClass(ClassPool classPool, ClassLoader targetClassLoader, String className) {
try {
CtClass loadedClass = classPool.get(className);
loadedClass.toClass(targetClassLoader, GitPushOperation.class.getProtectionDomain());
loadedClass.detach();
} catch (CannotCompileException e) {
log.error("Failed to load class required for Gerrit push UI injections.", e);
} catch (NotFoundException e) {
log.error("Failed to load class required for Gerrit push UI injections.", e);
}
}
public void disposeComponent() {}
@NotNull
public String getComponentName() {
return "GerritPushExtension";
}
public static final class Proxy implements ApplicationComponent {
private final GerritPushExtension delegate;
public Proxy() {
delegate = GerritModule.getInstance(GerritPushExtension.class);
}
@Override
public void initComponent() {
delegate.initComponent();
}
@Override
public void disposeComponent() {
delegate.disposeComponent();
}
@NotNull
@Override
public String getComponentName() {
return delegate.getComponentName();
}
}
}