Merge remote-tracking branch 'origin/master'

This commit is contained in:
Alexander Lobas
2012-06-04 19:50:39 +04:00
52 changed files with 3761 additions and 561 deletions
@@ -77,7 +77,7 @@ public abstract class FacetBasedPackagingSourceItemsProvider<F extends Facet, E
protected abstract PackagingElement<?> createElement(ArtifactEditorContext context, F facet);
private static class FacetBasedSourceItem<F extends Facet> extends PackagingSourceItem {
protected static class FacetBasedSourceItem<F extends Facet> extends PackagingSourceItem {
private final FacetBasedPackagingSourceItemsProvider<F, ?> myProvider;
private final F myFacet;
@@ -0,0 +1,72 @@
/*
* Copyright 2000-2012 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.ui.mac;
import com.apple.eawt.AppEvent;
import com.apple.eawt.FullScreenAdapter;
import com.intellij.Patches;
import com.intellij.openapi.wm.impl.IdeFrameImpl;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class MacFullScreenListener extends FullScreenAdapter {
private final MacMainFrameDecorator myDecorator;
private final IdeFrameImpl myFrame;
public MacFullScreenListener(final MacMainFrameDecorator decorator,
final IdeFrameImpl frame) {
myDecorator = decorator;
myFrame = frame;
}
@Override
public void windowEnteredFullScreen(AppEvent.FullScreenEvent event) {
myDecorator.setInFullScreen(true);
JRootPane rootPane = myFrame.getRootPane();
if (rootPane != null) rootPane.putClientProperty(MacMainFrameDecorator.FULL_SCREEN, Boolean.TRUE);
if (Patches.APPLE_BUG_ID_10207064) {
// fix problem with bottom empty bar
// it seems like the title is still visible in fullscreen but the window itself shifted up for titlebar height
// and the size of the frame is still calculated to be the height of the screen which is wrong
// so just add these titlebar height to the frame height once again
Timer timer = new Timer(300, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
myFrame.setSize(myFrame.getWidth(), myFrame.getHeight() + myFrame.getInsets().top);
}
});
}
});
timer.setRepeats(false);
timer.start();
}
}
@Override
public void windowExitedFullScreen(AppEvent.FullScreenEvent event) {
myDecorator.setInFullScreen(false);
myFrame.storeFullScreenStateIfNeeded(false);
JRootPane rootPane = myFrame.getRootPane();
if (rootPane != null) rootPane.putClientProperty(MacMainFrameDecorator.FULL_SCREEN, null);
}
}
@@ -15,9 +15,6 @@
*/
package com.intellij.ui.mac;
import com.apple.eawt.AppEvent;
import com.apple.eawt.FullScreenAdapter;
import com.apple.eawt.FullScreenUtilities;
import com.intellij.Patches;
import com.intellij.ide.ui.UISettings;
import com.intellij.ide.ui.UISettingsListener;
@@ -36,8 +33,6 @@ import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.concurrent.atomic.AtomicInteger;
@@ -150,43 +145,10 @@ public class MacMainFrameDecorator implements UISettingsListener, Disposable {
if (SystemInfo.isMacOSLion) {
if (!FULL_SCREEN_AVAILABLE) return;
FullScreenUtilities.addFullScreenListenerTo(frame, new FullScreenAdapter() {
@Override
public void windowEnteredFullScreen(AppEvent.FullScreenEvent event) {
myInFullScreen = true;
JRootPane rootPane = frame.getRootPane();
if (rootPane != null) rootPane.putClientProperty(FULL_SCREEN, Boolean.TRUE);
if (Patches.APPLE_BUG_ID_10207064) {
// fix problem with bottom empty bar
// it seems like the title is still visible in fullscreen but the window itself shifted up for titlebar height
// and the size of the frame is still calculated to be the height of the screen which is wrong
// so just add these titlebar height to the frame height once again
Timer timer = new Timer(300, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
frame.setSize(frame.getWidth(), frame.getHeight() + frame.getInsets().top);
}
});
}
});
timer.setRepeats(false);
timer.start();
}
}
@Override
public void windowExitedFullScreen(AppEvent.FullScreenEvent event) {
myInFullScreen = false;
frame.storeFullScreenStateIfNeeded(false);
JRootPane rootPane = frame.getRootPane();
if (rootPane != null) rootPane.putClientProperty(FULL_SCREEN, null);
}
});
try {
Class<?> clazz = Class.forName("com.apple.eawt.FullScreenUtilities");
clazz.getMethod("addFullScreenListenerTo").invoke(null, frame, new MacFullScreenListener(this, frame));
} catch (Exception ignored) {}
} else {
// toggle toolbar
String className = "IdeaToolbar" + v;
@@ -243,4 +205,8 @@ public class MacMainFrameDecorator implements UISettingsListener, Disposable {
invoke(window, "toggleFullScreen:", window);
}
}
void setInFullScreen(boolean inFullScreen) {
myInFullScreen = inFullScreen;
}
}
@@ -25,9 +25,7 @@ import com.intellij.execution.process.ProcessAdapter;
import com.intellij.execution.process.ProcessEvent;
import com.intellij.execution.process.ProcessHandler;
import com.intellij.execution.testframework.TestConsoleProperties;
import com.intellij.execution.testframework.sm.runner.GeneralToSMTRunnerEventsConvertor;
import com.intellij.execution.testframework.sm.runner.OutputToGeneralTestEventsConverter;
import com.intellij.execution.testframework.sm.runner.SMTRunnerConsoleProperties;
import com.intellij.execution.testframework.sm.runner.*;
import com.intellij.execution.testframework.sm.runner.ui.SMTRunnerConsoleView;
import com.intellij.execution.testframework.sm.runner.ui.SMTRunnerNotificationsHandler;
import com.intellij.execution.testframework.sm.runner.ui.SMTRunnerUIActionsHandler;
@@ -87,6 +85,16 @@ public class SMTestRunnerConnectionUtil {
final RunnerSettings runnerSettings,
final ConfigurationPerRunnerSettings configurationSettings,
@Nullable final TestLocationProvider locator) {
return createConsoleWithCustomLocator(testFrameworkName, consoleProperties, runnerSettings,
configurationSettings, locator, false);
}
public static BaseTestsOutputConsoleView createConsoleWithCustomLocator(@NotNull final String testFrameworkName,
@NotNull final TestConsoleProperties consoleProperties,
final RunnerSettings runnerSettings,
final ConfigurationPerRunnerSettings configurationSettings,
@Nullable final TestLocationProvider locator,
final boolean idBasedTreeConstruction) {
// Console
final String splitterPropertyName = testFrameworkName + ".Splitter.Proportion";
final SMTRunnerConsoleView console =
@@ -97,7 +105,7 @@ public class SMTestRunnerConnectionUtil {
super.attachToProcess(processHandler);
attachEventsProcessors(consoleProperties, getResultsViewer(),
getResultsViewer().getStatisticsPane(),
processHandler, testFrameworkName, locator);
processHandler, testFrameworkName, locator, idBasedTreeConstruction);
}
};
console.setHelpId("reference.runToolWindow.testResultsTab");
@@ -200,15 +208,24 @@ public class SMTestRunnerConnectionUtil {
final StatisticsPanel statisticsPane,
final ProcessHandler processHandler,
@NotNull final String testFrameworkName,
@Nullable final TestLocationProvider locator) {
@Nullable final TestLocationProvider locator,
boolean idBasedTreeConstruction) {
//build messages consumer
final OutputToGeneralTestEventsConverter outputConsumer = consoleProperties instanceof SMCustomMessagesParsing
? ((SMCustomMessagesParsing)consoleProperties).createTestEventsConverter(testFrameworkName, consoleProperties)
: new OutputToGeneralTestEventsConverter(testFrameworkName, consoleProperties);
final OutputToGeneralTestEventsConverter outputConsumer;
if (consoleProperties instanceof SMCustomMessagesParsing) {
outputConsumer = ((SMCustomMessagesParsing)consoleProperties).createTestEventsConverter(testFrameworkName, consoleProperties);
}
else {
outputConsumer = new OutputToGeneralTestEventsConverter(testFrameworkName, consoleProperties);
}
//events processor
final GeneralToSMTRunnerEventsConvertor eventsProcessor = new GeneralToSMTRunnerEventsConvertor(resultsViewer.getTestsRootNode(),
testFrameworkName);
final GeneralTestEventsProcessor eventsProcessor;
if (idBasedTreeConstruction) {
eventsProcessor = new GeneralIdBasedToSMTRunnerEventsConvertor(resultsViewer.getTestsRootNode(), testFrameworkName);
} else {
eventsProcessor = new GeneralToSMTRunnerEventsConvertor(resultsViewer.getTestsRootNode(), testFrameworkName);
}
if (locator != null) {
eventsProcessor.setLocator(locator);
}
@@ -0,0 +1,633 @@
/*
* Copyright 2000-2012 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.execution.testframework.sm.runner;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.intellij.execution.process.ProcessOutputTypes;
import com.intellij.execution.testframework.sm.SMRunnerUtil;
import com.intellij.execution.testframework.sm.SMTestRunnerConnectionUtil;
import com.intellij.execution.testframework.sm.runner.events.*;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Key;
import com.intellij.testIntegration.TestLocationProvider;
import gnu.trove.TIntObjectHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* @author Sergey Simonchik
*/
public class GeneralIdBasedToSMTRunnerEventsConvertor implements GeneralTestEventsProcessor {
private static final Logger LOG = Logger.getInstance(GeneralIdBasedToSMTRunnerEventsConvertor.class.getName());
private final TIntObjectHashMap<Node> myNodeByIdMap = new TIntObjectHashMap<Node>();
private final Set<Node> myRunningNodes = Sets.newHashSet();
private final List<SMTRunnerEventsListener> myEventsListeners = new ArrayList<SMTRunnerEventsListener>();
private final SMTestProxy.SMRootTestProxy myTestsRootProxy;
private final Node myTestsRootNode;
private final String myTestFrameworkName;
private boolean myIsTestingFinished = false;
private TestLocationProvider myLocator = null;
public GeneralIdBasedToSMTRunnerEventsConvertor(@NotNull SMTestProxy.SMRootTestProxy testsRootProxy,
@NotNull String testFrameworkName) {
myTestsRootProxy = testsRootProxy;
myTestsRootNode = new Node(0, null, testsRootProxy);
myTestFrameworkName = testFrameworkName;
myNodeByIdMap.put(myTestsRootNode.getId(), myTestsRootNode);
myRunningNodes.add(myTestsRootNode);
}
public void setLocator(@NotNull TestLocationProvider customLocator) {
myLocator = customLocator;
}
public void addEventsListener(@NotNull SMTRunnerEventsListener listener) {
myEventsListeners.add(listener);
}
public void onStartTesting() {
SMRunnerUtil.addToInvokeLater(new Runnable() {
public void run() {
myTestsRootProxy.setStarted();
fireOnTestingStarted();
}
});
}
@Override
public void onTestsReporterAttached() {
SMRunnerUtil.addToInvokeLater(new Runnable() {
public void run() {
myTestsRootProxy.setTestsReporterAttached();
}
});
}
public void onFinishTesting() {
SMRunnerUtil.addToInvokeLater(new Runnable() {
public void run() {
if (myIsTestingFinished) {
// has been already invoked!
return;
}
myIsTestingFinished = true;
// We don't know whether process was destroyed by user
// or it finished after all tests have been run
// Lets assume, if at finish all suites except root suite are passed
// then all is ok otherwise process was terminated by user
if (myRunningNodes.size() == 1 && myRunningNodes.contains(myTestsRootNode)) {
myTestsRootProxy.setFinished();
} else {
logProblem("Unexpected running nodes: " + myRunningNodes);
myTestsRootProxy.setTerminated();
}
myNodeByIdMap.clear();
myRunningNodes.clear();
fireOnTestingFinished();
}
});
}
public void onTestStarted(@NotNull final TestStartedEvent testStartedEvent) {
SMRunnerUtil.addToInvokeLater(new Runnable() {
public void run() {
doStartNode(testStartedEvent, false);
}
});
}
public void onSuiteStarted(@NotNull final TestSuiteStartedEvent suiteStartedEvent) {
SMRunnerUtil.addToInvokeLater(new Runnable() {
public void run() {
doStartNode(suiteStartedEvent, true);
}
});
}
private void doStartNode(@NotNull BaseStartedNodeEvent startedNodeEvent, boolean suite) {
Node parentNode = findValidParentNode(startedNodeEvent);
if (parentNode == null) {
return;
}
if (!validateNodeId(startedNodeEvent)) {
return;
}
int nodeId = startedNodeEvent.getId();
Node childNode = myNodeByIdMap.get(nodeId);
if (childNode != null) {
logProblem(startedNodeEvent + " has been already started: " + childNode + "!");
return;
}
SMTestProxy childProxy = new SMTestProxy(startedNodeEvent.getName(), suite, startedNodeEvent.getLocationUrl(), true);
childNode = new Node(startedNodeEvent.getId(), parentNode, childProxy);
myNodeByIdMap.put(nodeId, childNode);
myRunningNodes.add(childNode);
if (myLocator != null) {
childProxy.setLocator(myLocator);
}
parentNode.getProxy().addChild(childProxy);
// progress started
childProxy.setStarted();
if (suite) {
fireOnSuiteStarted(childProxy);
} else {
fireOnTestStarted(childProxy);
}
}
@Nullable
private Node findValidParentNode(@NotNull BaseStartedNodeEvent startedNodeEvent) {
int parentId = startedNodeEvent.getParentId();
if (parentId < 0) {
logProblem("Parent node id should be non-negative: " + startedNodeEvent + ".");
return null;
}
Node parentNode = myNodeByIdMap.get(startedNodeEvent.getParentId());
if (parentNode == null) {
logProblem("Parent node is undefined for " + startedNodeEvent + ".");
return null;
}
if (parentNode.getState() != State.RUNNING) {
logProblem("Parent node should be running: " + parentNode + ", " + startedNodeEvent);
return null;
}
return parentNode;
}
public void onTestFinished(@NotNull final TestFinishedEvent testFinishedEvent) {
SMRunnerUtil.addToInvokeLater(new Runnable() {
public void run() {
doFinishNode(testFinishedEvent, false);
}
});
}
public void onSuiteFinished(@NotNull final TestSuiteFinishedEvent suiteFinishedEvent) {
SMRunnerUtil.addToInvokeLater(new Runnable() {
public void run() {
doFinishNode(suiteFinishedEvent, true);
}
});
}
private void doFinishNode(@NotNull TreeNodeEvent treeNodeEvent, boolean suite) {
Node finishedNode = findNode(treeNodeEvent);
if (finishedNode == null) {
String nodeType = suite ? "Suite" : "Test";
logProblem("Trying to finish not started " + nodeType + ": " + treeNodeEvent);
return;
}
stopRunningNode(finishedNode, State.FINISHED, treeNodeEvent);
finishedNode.getProxy().setFinished();
if (suite) {
fireOnSuiteFinished(finishedNode.getProxy());
} else {
fireOnTestFinished(finishedNode.getProxy());
}
}
public void onUncapturedOutput(@NotNull final String text, final Key outputType) {
SMRunnerUtil.addToInvokeLater(new Runnable() {
public void run() {
Node activeNode = findActiveNode();
SMTestProxy activeProxy = activeNode.getProxy();
if (ProcessOutputTypes.STDERR.equals(outputType)) {
activeProxy.addStdErr(text);
} else if (ProcessOutputTypes.SYSTEM.equals(outputType)) {
activeProxy.addSystemOutput(text);
} else {
activeProxy.addStdOutput(text, outputType);
}
}
});
}
public void onError(@NotNull final String localizedMessage,
@Nullable final String stackTrace,
final boolean isCritical) {
SMRunnerUtil.addToInvokeLater(new Runnable() {
public void run() {
Node activeNode = findActiveNode();
SMTestProxy activeProxy = activeNode.getProxy();
activeProxy.addError(localizedMessage, stackTrace, isCritical);
}
});
}
public void onCustomProgressTestsCategory(@Nullable final String categoryName,
final int testCount) {
SMRunnerUtil.addToInvokeLater(new Runnable() {
public void run() {
fireOnCustomProgressTestsCategory(categoryName, testCount);
}
});
}
public void onCustomProgressTestStarted() {
SMRunnerUtil.addToInvokeLater(new Runnable() {
public void run() {
fireOnCustomProgressTestStarted();
}
});
}
public void onCustomProgressTestFailed() {
SMRunnerUtil.addToInvokeLater(new Runnable() {
public void run() {
fireOnCustomProgressTestFailed();
}
});
}
public void onTestFailure(@NotNull final TestFailedEvent testFailedEvent) {
SMRunnerUtil.addToInvokeLater(new Runnable() {
public void run() {
Node node = findNode(testFailedEvent);
if (node == null) {
logProblem("Test wasn't started! " + testFailedEvent + ".");
return;
}
stopRunningNode(node, State.FAILED, testFailedEvent);
SMTestProxy testProxy = node.getProxy();
String comparisonFailureActualText = testFailedEvent.getComparisonFailureActualText();
String comparisonFailureExpectedText = testFailedEvent.getComparisonFailureExpectedText();
String failureMessage = testFailedEvent.getLocalizedFailureMessage();
String stackTrace = testFailedEvent.getStacktrace();
if (comparisonFailureActualText != null && comparisonFailureExpectedText != null) {
testProxy.setTestComparisonFailed(failureMessage, stackTrace,
comparisonFailureActualText, comparisonFailureExpectedText);
} else if (comparisonFailureActualText == null && comparisonFailureExpectedText == null) {
testProxy.setTestFailed(failureMessage, stackTrace, testFailedEvent.isTestError());
} else {
logProblem("Comparison failure actual and expected texts should be both null or not null.\n"
+ "Expected:\n"
+ comparisonFailureExpectedText + "\n"
+ "Actual:\n"
+ comparisonFailureActualText);
}
// fire event
fireOnTestFailed(testProxy);
}
});
}
public void onTestIgnored(@NotNull final TestIgnoredEvent testIgnoredEvent) {
SMRunnerUtil.addToInvokeLater(new Runnable() {
public void run() {
Node node = findNode(testIgnoredEvent);
if (node == null) {
logProblem("Test wasn't started! " + testIgnoredEvent + ".");
return;
}
stopRunningNode(node, State.IGNORED, testIgnoredEvent);
SMTestProxy testProxy = node.getProxy();
testProxy.setTestIgnored(testIgnoredEvent.getIgnoreComment(), testIgnoredEvent.getStacktrace());
// fire event
fireOnTestIgnored(testProxy);
}
});
}
public void onTestOutput(@NotNull final TestOutputEvent testOutputEvent) {
SMRunnerUtil.addToInvokeLater(new Runnable() {
public void run() {
Node node = findNode(testOutputEvent);
if (node == null) {
logProblem("Test wasn't started! But " + testOutputEvent + "!");
return;
}
SMTestProxy testProxy = node.getProxy();
if (testOutputEvent.isStdOut()) {
testProxy.addStdOutput(testOutputEvent.getText(), ProcessOutputTypes.STDOUT);
} else {
testProxy.addStdErr(testOutputEvent.getText());
}
}
});
}
public void onTestsCountInSuite(final int count) {
SMRunnerUtil.addToInvokeLater(new Runnable() {
public void run() {
fireOnTestsCountInSuite(count);
}
});
}
private boolean validateNodeId(@NotNull TreeNodeEvent treeNodeEvent) {
int nodeId = treeNodeEvent.getId();
if (nodeId <= 0) {
logProblem("Node id should be positive: " + treeNodeEvent + ".");
return false;
}
return true;
}
@Nullable
private Node findNode(@NotNull TreeNodeEvent treeNodeEvent) {
if (!validateNodeId(treeNodeEvent)) {
return null;
}
return myNodeByIdMap.get(treeNodeEvent.getId());
}
private void fireOnTestingStarted() {
for (SMTRunnerEventsListener listener : myEventsListeners) {
listener.onTestingStarted(myTestsRootProxy);
}
}
private void fireOnTestingFinished() {
for (SMTRunnerEventsListener listener : myEventsListeners) {
listener.onTestingFinished(myTestsRootProxy);
}
}
private void fireOnTestsCountInSuite(final int count) {
for (SMTRunnerEventsListener listener : myEventsListeners) {
listener.onTestsCountInSuite(count);
}
}
private void fireOnTestStarted(final SMTestProxy test) {
for (SMTRunnerEventsListener listener : myEventsListeners) {
listener.onTestStarted(test);
}
}
private void fireOnTestFinished(final SMTestProxy test) {
for (SMTRunnerEventsListener listener : myEventsListeners) {
listener.onTestFinished(test);
}
}
private void fireOnTestFailed(final SMTestProxy test) {
for (SMTRunnerEventsListener listener : myEventsListeners) {
listener.onTestFailed(test);
}
}
private void fireOnTestIgnored(final SMTestProxy test) {
for (SMTRunnerEventsListener listener : myEventsListeners) {
listener.onTestIgnored(test);
}
}
private void fireOnSuiteStarted(final SMTestProxy suite) {
for (SMTRunnerEventsListener listener : myEventsListeners) {
listener.onSuiteStarted(suite);
}
}
private void fireOnSuiteFinished(final SMTestProxy suite) {
for (SMTRunnerEventsListener listener : myEventsListeners) {
listener.onSuiteFinished(suite);
}
}
private void fireOnCustomProgressTestsCategory(@Nullable final String categoryName, int testCount) {
for (SMTRunnerEventsListener listener : myEventsListeners) {
listener.onCustomProgressTestsCategory(categoryName, testCount);
}
}
private void fireOnCustomProgressTestStarted() {
for (SMTRunnerEventsListener listener : myEventsListeners) {
listener.onCustomProgressTestStarted();
}
}
private void fireOnCustomProgressTestFailed() {
for (SMTRunnerEventsListener listener : myEventsListeners) {
listener.onCustomProgressTestFailed();
}
}
/*
* Remove listeners, etc
*/
public void dispose() {
SMRunnerUtil.addToInvokeLater(new Runnable() {
public void run() {
myEventsListeners.clear();
if (!myRunningNodes.isEmpty()) {
Application application = ApplicationManager.getApplication();
if (!application.isHeadlessEnvironment() && !application.isUnitTestMode()) {
logProblem("Not all events were processed!");
}
}
myRunningNodes.clear();
myNodeByIdMap.clear();
}
});
}
private void stopRunningNode(@NotNull Node node, @NotNull State stoppedState, @NotNull TreeNodeEvent event) {
if (stoppedState == State.RUNNING) {
throw new RuntimeException("newState shouldn't be " + State.RUNNING);
}
// check if has been already processed
if (node.getState() != State.RUNNING) {
logProblem("Can't change state of already stopped node" + node + " to " + stoppedState + ", " + event + ".");
return;
}
myRunningNodes.remove(node);
node.setState(stoppedState);
}
@NotNull
private Node findActiveNode() {
List<Node> runningLeaves = Lists.newArrayListWithExpectedSize(1);
for (Node node : myRunningNodes) {
if (!node.hasRunningChildren()) {
runningLeaves.add(node);
}
}
if (runningLeaves.isEmpty()) {
throw new RuntimeException("No running leaves found, running nodes: " + myRunningNodes);
}
if (runningLeaves.size() == 1) {
return runningLeaves.iterator().next();
}
List<Node> commonPathToRoot = null;
for (Node leaf : runningLeaves) {
List<Node> pathToRoot = leaf.getAncestorsFromParentToRoot();
if (commonPathToRoot == null) {
commonPathToRoot = pathToRoot;
} else {
commonPathToRoot = intersectPathsToRoot(commonPathToRoot, pathToRoot);
}
}
if (commonPathToRoot == null || commonPathToRoot.isEmpty()) {
throw new RuntimeException("Unexpected common path to root: " + commonPathToRoot + ", running leaves: " + runningLeaves);
}
return commonPathToRoot.get(0);
}
@NotNull
private static List<Node> intersectPathsToRoot(@NotNull List<Node> pathToRoot1, @NotNull List<Node> pathToRoot2) {
final int minSize = Math.min(pathToRoot1.size(), pathToRoot2.size());
final int shift1 = pathToRoot1.size() - minSize;
final int shift2 = pathToRoot2.size() - minSize;
int commonSize = 0;
for (int i = 0; i < minSize; i++) {
Node node1 = pathToRoot1.get(i + shift1);
Node node2 = pathToRoot2.get(i + shift2);
if (node1 == node2) {
commonSize = minSize - i;
break;
}
}
return pathToRoot1.subList(pathToRoot1.size() - commonSize, pathToRoot1.size());
}
private static String getTestFrameworkPrefix(@NotNull String testFrameworkName) {
return "[" + testFrameworkName + "] ";
}
private void logProblem(@NotNull String msg) {
logProblem(LOG, msg, myTestFrameworkName);
}
private static void logProblem(@NotNull Logger log, @NotNull String msg, @NotNull String testFrameworkName) {
logProblem(log, msg, SMTestRunnerConnectionUtil.isInDebugMode(), testFrameworkName);
}
private static void logProblem(@NotNull Logger log, @NotNull String msg, boolean throwError, @NotNull String testFrameworkName) {
final String text = getTestFrameworkPrefix(testFrameworkName) + msg;
if (throwError) {
log.error(text);
}
else {
log.warn(text);
}
}
private enum State {
RUNNING, FINISHED, FAILED, IGNORED
}
private static class Node {
private final int myId;
private final Node myParentNode;
private final SMTestProxy myProxy;
private State myState = State.RUNNING;
private int myRunningChildCount = 0;
Node(int id, @Nullable Node parentNode, @NotNull SMTestProxy proxy) {
myId = id;
myParentNode = parentNode;
myProxy = proxy;
if (myParentNode != null) {
myParentNode.myRunningChildCount++;
}
}
public int getId() {
return myId;
}
@Nullable
public Node getParentNode() {
return myParentNode;
}
@NotNull
public SMTestProxy getProxy() {
return myProxy;
}
@NotNull
public State getState() {
return myState;
}
public void setState(@NotNull State state) {
if (myState == State.RUNNING && state != State.RUNNING) {
if (myParentNode != null) {
myParentNode.myRunningChildCount--;
}
} else {
throw new RuntimeException("Attempt to change state from " + myState + " to " + state + ":" + toString());
}
myState = state;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Node node = (Node)o;
return myId == node.myId;
}
@Override
public int hashCode() {
return myId;
}
@Override
public String toString() {
return "{" +
"id=" + myId +
", parentId=" + (myParentNode != null ? String.valueOf(myParentNode.getId()) : "<undefined>") +
", name='" + myProxy.getName() +
"', isSuite=" + myProxy.isSuite() +
", state=" + myState +
'}';
}
public boolean hasRunningChildren() {
return myRunningChildCount > 0;
}
@NotNull
public List<Node> getAncestorsFromParentToRoot() {
List<Node> ancestors = Lists.newArrayList();
Node parent = getParentNode();
while (parent != null) {
ancestors.add(parent);
parent = parent.getParentNode();
}
return ancestors;
}
}
}
@@ -15,8 +15,10 @@
*/
package com.intellij.execution.testframework.sm.runner;
import com.intellij.execution.testframework.sm.runner.events.*;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.util.Key;
import com.intellij.testIntegration.TestLocationProvider;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -29,33 +31,23 @@ import org.jetbrains.annotations.Nullable;
* and name of test method
*/
public interface GeneralTestEventsProcessor extends Disposable {
void onStartTesting();
void onTestsCountInSuite(final int count);
void onTestStarted(@NotNull final String testName,
@Nullable final String locationUrl);
void onTestStarted(@NotNull TestStartedEvent testStartedEvent);
void onTestFinished(@NotNull final String testName,
final int duration);
void onTestFinished(@NotNull TestFinishedEvent testFinishedEvent);
void onTestFailure(@NotNull final String testName,
@NotNull final String localizedMessage,
@Nullable final String stackTrace,
final boolean testError,
@Nullable final String comparisionFailureActualText,
@Nullable final String comparisionFailureExpectedText);
void onTestFailure(@NotNull TestFailedEvent testFailedEvent);
void onTestIgnored(@NotNull final String testName,
@NotNull final String ignoreComment,
@Nullable final String stackTrace);
void onTestIgnored(@NotNull TestIgnoredEvent testIgnoredEvent);
void onTestOutput(@NotNull final String testName,
@NotNull final String text,
final boolean stdOut);
void onTestOutput(@NotNull TestOutputEvent testOutputEvent);
void onSuiteStarted(@NotNull final String suiteName,
@Nullable final String locationUrl);
void onSuiteStarted(@NotNull TestSuiteStartedEvent suiteStartedEvent);
void onSuiteFinished(@NotNull final String suiteName);
void onSuiteFinished(@NotNull TestSuiteFinishedEvent suiteFinishedEvent);
void onUncapturedOutput(@NotNull final String text,
final Key outputType);
@@ -76,4 +68,10 @@ public interface GeneralTestEventsProcessor extends Disposable {
void onCustomProgressTestStarted();
void onCustomProgressTestFailed();
void onTestsReporterAttached();
}
void setLocator(@NotNull TestLocationProvider locator);
void addEventsListener(@NotNull SMTRunnerEventsListener viewer);
void onFinishTesting();
}
@@ -19,11 +19,13 @@ import com.intellij.execution.process.ProcessOutputTypes;
import com.intellij.execution.testframework.AbstractTestProxy;
import com.intellij.execution.testframework.sm.SMRunnerUtil;
import com.intellij.execution.testframework.sm.SMTestRunnerConnectionUtil;
import com.intellij.execution.testframework.sm.runner.events.*;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Key;
import com.intellij.testIntegration.TestLocationProvider;
import com.intellij.util.ObjectUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
@@ -55,11 +57,11 @@ public class GeneralToSMTRunnerEventsConvertor implements GeneralTestEventsProce
myTestFrameworkName = testFrameworkName;
}
public void setLocator(TestLocationProvider customLocator) {
public void setLocator(@NotNull TestLocationProvider customLocator) {
myLocator = customLocator;
}
public void addEventsListener(final SMTRunnerEventsListener listener) {
public void addEventsListener(@NotNull final SMTRunnerEventsListener listener) {
myEventsListeners.add(listener);
}
@@ -111,10 +113,11 @@ public class GeneralToSMTRunnerEventsConvertor implements GeneralTestEventsProce
});
}
public void onTestStarted(@NotNull final String testName,
@Nullable final String locationUrl) {
public void onTestStarted(@NotNull final TestStartedEvent testStartedEvent) {
SMRunnerUtil.addToInvokeLater(new Runnable() {
public void run() {
final String testName = testStartedEvent.getName();
final String locationUrl = testStartedEvent.getLocationUrl();
final String fullName = getFullTestName(testName);
if (myRunningTestsFullNameToProxy.containsKey(fullName)) {
@@ -146,9 +149,11 @@ public class GeneralToSMTRunnerEventsConvertor implements GeneralTestEventsProce
});
}
public void onSuiteStarted(@NotNull final String suiteName, @Nullable final String locationUrl) {
public void onSuiteStarted(@NotNull final TestSuiteStartedEvent suiteStartedEvent) {
SMRunnerUtil.addToInvokeLater(new Runnable() {
public void run() {
final String suiteName = suiteStartedEvent.getName();
final String locationUrl = suiteStartedEvent.getLocationUrl();
final SMTestProxy parentSuite = getCurrentSuite();
//new suite
final SMTestProxy newSuite = new SMTestProxy(suiteName, true, locationUrl);
@@ -168,10 +173,11 @@ public class GeneralToSMTRunnerEventsConvertor implements GeneralTestEventsProce
});
}
public void onTestFinished(@NotNull final String testName,
final int duration) {
public void onTestFinished(@NotNull final TestFinishedEvent testFinishedEvent) {
SMRunnerUtil.addToInvokeLater(new Runnable() {
public void run() {
final String testName = testFinishedEvent.getName();
final int duration = testFinishedEvent.getDuration();
final String fullTestName = getFullTestName(testName);
final SMTestProxy testProxy = getProxyByFullTestName(fullTestName);
@@ -191,9 +197,10 @@ public class GeneralToSMTRunnerEventsConvertor implements GeneralTestEventsProce
});
}
public void onSuiteFinished(@NotNull final String suiteName) {
public void onSuiteFinished(@NotNull final TestSuiteFinishedEvent suiteFinishedEvent) {
SMRunnerUtil.addToInvokeLater(new Runnable() {
public void run() {
final String suiteName = suiteFinishedEvent.getName();
final SMTestProxy mySuite = mySuitesStack.popSuite(suiteName);
if (mySuite != null) {
mySuite.setFinished();
@@ -257,14 +264,15 @@ public class GeneralToSMTRunnerEventsConvertor implements GeneralTestEventsProce
});
}
public void onTestFailure(@NotNull final String testName,
@NotNull final String localizedMessage,
@Nullable final String stackTrace,
final boolean isTestError,
@Nullable final String comparisionFailureActualText,
@Nullable final String comparisionFailureExpectedText) {
public void onTestFailure(@NotNull final TestFailedEvent testFailedEvent) {
SMRunnerUtil.addToInvokeLater(new Runnable() {
public void run() {
final String testName = ObjectUtils.assertNotNull(testFailedEvent.getName());
final String localizedMessage = testFailedEvent.getLocalizedFailureMessage();
final String stackTrace = testFailedEvent.getStacktrace();
final boolean isTestError = testFailedEvent.isTestError();
final String comparisionFailureActualText = testFailedEvent.getComparisonFailureActualText();
final String comparisionFailureExpectedText = testFailedEvent.getComparisonFailureExpectedText();
final boolean inDebugMode = SMTestRunnerConnectionUtil.isInDebugMode();
final String fullTestName = getFullTestName(testName);
@@ -281,7 +289,7 @@ public class GeneralToSMTRunnerEventsConvertor implements GeneralTestEventsProce
if (!myFailedTestsSet.contains(testProxy)) {
// if hasn't been already reported
// 1. report
onTestStarted(testName, null);
onTestStarted(new TestStartedEvent(testName, null));
// 2. add failure
testProxy = getProxyByFullTestName(fullTestName);
}
@@ -323,11 +331,12 @@ public class GeneralToSMTRunnerEventsConvertor implements GeneralTestEventsProce
});
}
public void onTestIgnored(@NotNull final String testName,
@NotNull final String ignoreComment,
@Nullable final String stackTrace) {
public void onTestIgnored(@NotNull final TestIgnoredEvent testIgnoredEvent) {
SMRunnerUtil.addToInvokeLater(new Runnable() {
public void run() {
final String testName = ObjectUtils.assertNotNull(testIgnoredEvent.getName());
final String ignoreComment = testIgnoredEvent.getIgnoreComment();
final String stackTrace = testIgnoredEvent.getStacktrace();
final String fullTestName = getFullTestName(testName);
SMTestProxy testProxy = getProxyByFullTestName(fullTestName);
if (testProxy == null) {
@@ -341,7 +350,7 @@ public class GeneralToSMTRunnerEventsConvertor implements GeneralTestEventsProce
} else {
// try to fix
// 1. report test opened
onTestStarted(testName, null);
onTestStarted(new TestStartedEvent(testName, null));
// 2. report failure
testProxy = getProxyByFullTestName(fullTestName);
@@ -359,10 +368,12 @@ public class GeneralToSMTRunnerEventsConvertor implements GeneralTestEventsProce
});
}
public void onTestOutput(@NotNull final String testName,
@NotNull final String text, final boolean stdOut) {
public void onTestOutput(@NotNull final TestOutputEvent testOutputEvent) {
SMRunnerUtil.addToInvokeLater(new Runnable() {
public void run() {
final String testName = testOutputEvent.getName();
final String text = testOutputEvent.getText();
final boolean stdOut = testOutputEvent.isStdOut();
final String fullTestName = getFullTestName(testName);
final SMTestProxy testProxy = getProxyByFullTestName(fullTestName);
if (testProxy == null) {
@@ -17,6 +17,7 @@ package com.intellij.execution.testframework.sm.runner;
import com.intellij.execution.process.ProcessOutputTypes;
import com.intellij.execution.testframework.TestConsoleProperties;
import com.intellij.execution.testframework.sm.runner.events.*;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.text.StringUtil;
@@ -41,13 +42,11 @@ import static com.intellij.execution.testframework.sm.runner.GeneralToSMTRunnerE
public class OutputToGeneralTestEventsConverter implements ProcessOutputConsumer {
private static final Logger LOG = Logger.getInstance(OutputToGeneralTestEventsConverter.class.getName());
private static final String TEAMCITY_SERVICE_MESSAGE_PREFIX = "##teamcity[";
private GeneralTestEventsProcessor myProcessor;
private final MyServiceMessageVisitor myServiceMessageVisitor;
private final String myTestFrameworkName;
private OutputLineSplitter mySplitter;
private final OutputLineSplitter mySplitter;
private boolean myPendingLineBreakFlag;
public OutputToGeneralTestEventsConverter(@NotNull final String testFrameworkName,
@@ -63,7 +62,7 @@ public class OutputToGeneralTestEventsConverter implements ProcessOutputConsumer
};
}
public void setProcessor(final GeneralTestEventsProcessor processor) {
public void setProcessor(@Nullable final GeneralTestEventsProcessor processor) {
myProcessor = processor;
}
@@ -142,52 +141,38 @@ public class OutputToGeneralTestEventsConverter implements ProcessOutputConsumer
}
private void fireOnTestStarted(final String testName, @Nullable final String locationUrl) {
assertNotNull(testName);
private void fireOnTestStarted(@NotNull TestStartedEvent testStartedEvent) {
// local variable is used to prevent concurrent modification
final GeneralTestEventsProcessor processor = myProcessor;
if (processor != null) {
processor.onTestStarted(testName, locationUrl);
processor.onTestStarted(testStartedEvent);
}
}
private void fireOnTestFailure(final String testName,
final String localizedMessage, final String stackTrace,
final boolean isTestError,
@Nullable final String comparisionFailureActualText,
@Nullable final String comparisionFailureExpectedText) {
assertNotNull(testName);
assertNotNull(localizedMessage);
private void fireOnTestFailure(@NotNull TestFailedEvent testFailedEvent) {
assertNotNull(testFailedEvent.getLocalizedFailureMessage());
// local variable is used to prevent concurrent modification
final GeneralTestEventsProcessor processor = myProcessor;
if (processor != null) {
processor.onTestFailure(testName, localizedMessage, stackTrace, isTestError,
comparisionFailureActualText,
comparisionFailureExpectedText);
processor.onTestFailure(testFailedEvent);
}
}
private void fireOnTestIgnored(final String testName, final String ignoreComment,
@Nullable final String details) {
assertNotNull(testName);
assertNotNull(ignoreComment);
private void fireOnTestIgnored(@NotNull TestIgnoredEvent testIgnoredEvent) {
// local variable is used to prevent concurrent modification
final GeneralTestEventsProcessor processor = myProcessor;
if (processor != null) {
processor.onTestIgnored(testName, ignoreComment, details);
processor.onTestIgnored(testIgnoredEvent);
}
}
private void fireOnTestFinished(final String testName, final int duration) {
assertNotNull(testName);
private void fireOnTestFinished(@NotNull TestFinishedEvent testFinishedEvent) {
// local variable is used to prevent concurrent modification
final GeneralTestEventsProcessor processor = myProcessor;
if (processor != null) {
processor.onTestFinished(testName, duration);
processor.onTestFinished(testFinishedEvent);
}
}
@@ -224,14 +209,11 @@ public class OutputToGeneralTestEventsConverter implements ProcessOutputConsumer
}
}
private void fireOnTestOutput(final String testName, final String text, final boolean stdOut) {
assertNotNull(testName);
assertNotNull(text);
private void fireOnTestOutput(@NotNull TestOutputEvent testOutputEvent) {
// local variable is used to prevent concurrent modification
final GeneralTestEventsProcessor processor = myProcessor;
if (processor != null) {
processor.onTestOutput(testName, text, stdOut);
processor.onTestOutput(testOutputEvent);
}
}
@@ -257,23 +239,19 @@ public class OutputToGeneralTestEventsConverter implements ProcessOutputConsumer
}
}
private void fireOnSuiteStarted(final String suiteName, @Nullable final String locationUrl) {
assertNotNull(suiteName);
private void fireOnSuiteStarted(@NotNull TestSuiteStartedEvent suiteStartedEvent) {
// local variable is used to prevent concurrent modification
final GeneralTestEventsProcessor processor = myProcessor;
if (processor != null) {
processor.onSuiteStarted(suiteName, locationUrl);
processor.onSuiteStarted(suiteStartedEvent);
}
}
private void fireOnSuiteFinished(final String suiteName) {
assertNotNull(suiteName);
private void fireOnSuiteFinished(@NotNull TestSuiteFinishedEvent nodeFinishedEvent) {
// local variable is used to prevent concurrent modification
final GeneralTestEventsProcessor processor = myProcessor;
if (processor != null) {
processor.onSuiteFinished(suiteName);
processor.onSuiteFinished(nodeFinishedEvent);
}
}
@@ -321,7 +299,8 @@ public class OutputToGeneralTestEventsConverter implements ProcessOutputConsumer
public void visitTestSuiteStarted(@NotNull final TestSuiteStarted suiteStarted) {
final String locationUrl = fetchTestLocation(suiteStarted);
fireOnSuiteStarted(suiteStarted.getSuiteName(), locationUrl);
TestSuiteStartedEvent suiteStartedEvent = new TestSuiteStartedEvent(suiteStarted, locationUrl);
fireOnSuiteStarted(suiteStartedEvent);
}
@Nullable
@@ -343,7 +322,8 @@ public class OutputToGeneralTestEventsConverter implements ProcessOutputConsumer
}
public void visitTestSuiteFinished(@NotNull final TestSuiteFinished suiteFinished) {
fireOnSuiteFinished(suiteFinished.getSuiteName());
TestSuiteFinishedEvent finishedEvent = new TestSuiteFinishedEvent(suiteFinished);
fireOnSuiteFinished(finishedEvent);
}
public void visitTestStarted(@NotNull final TestStarted testStarted) {
@@ -351,7 +331,8 @@ public class OutputToGeneralTestEventsConverter implements ProcessOutputConsumer
// final String locationUrl = testStarted.getLocationHint();
final String locationUrl = testStarted.getAttributes().get(ATTR_KEY_LOCATION_URL);
fireOnTestStarted(testStarted.getTestName(), locationUrl);
TestStartedEvent testStartedEvent = new TestStartedEvent(testStarted, locationUrl);
fireOnTestStarted(testStartedEvent);
}
public void visitTestFinished(@NotNull final TestFinished testFinished) {
@@ -368,31 +349,27 @@ public class OutputToGeneralTestEventsConverter implements ProcessOutputConsumer
duration = convertToInt(durationStr, testFinished);
}
fireOnTestFinished(testFinished.getTestName(), duration);
TestFinishedEvent testFinishedEvent = new TestFinishedEvent(testFinished, duration);
fireOnTestFinished(testFinishedEvent);
}
public void visitTestIgnored(@NotNull final TestIgnored testIgnored) {
final String details = testIgnored.getAttributes().get(ATTR_KEY_STACKTRACE_DETAILS);
fireOnTestIgnored(testIgnored.getTestName(), testIgnored.getIgnoreComment(), details);
final String stacktrace = testIgnored.getAttributes().get(ATTR_KEY_STACKTRACE_DETAILS);
fireOnTestIgnored(new TestIgnoredEvent(testIgnored, stacktrace));
}
public void visitTestStdOut(@NotNull final TestStdOut testStdOut) {
fireOnTestOutput(testStdOut.getTestName(), testStdOut.getStdOut(), true);
fireOnTestOutput(new TestOutputEvent(testStdOut, testStdOut.getStdOut(), true));
}
public void visitTestStdErr(@NotNull final TestStdErr testStdErr) {
fireOnTestOutput(testStdErr.getTestName(), testStdErr.getStdErr(), false);
fireOnTestOutput(new TestOutputEvent(testStdErr.getTestName(), testStdErr.getStdErr(), false));
}
public void visitTestFailed(@NotNull final TestFailed testFailed) {
final boolean isTestError = testFailed.getAttributes().get(ATTR_KEY_TEST_ERROR) != null;
fireOnTestFailure(testFailed.getTestName(),
testFailed.getFailureMessage(),
testFailed.getStacktrace(),
isTestError,
testFailed.getActual(),
testFailed.getExpected());
final boolean testError = testFailed.getAttributes().get(ATTR_KEY_TEST_ERROR) != null;
TestFailedEvent testFailedEvent = new TestFailedEvent(testFailed, testError);
fireOnTestFailure(testFailedEvent);
}
public void visitPublishArtifacts(@NotNull final PublishArtifacts publishArtifacts) {
@@ -425,7 +402,6 @@ public class OutputToGeneralTestEventsConverter implements ProcessOutputConsumer
@Override
public void visitMessageWithStatus(@NotNull Message msg) {
final String name = msg.getMessageName();
final Map<String, String> msgAttrs = msg.getAttributes();
final String text = msgAttrs.get(ATTR_KEY_TEXT);
@@ -61,12 +61,20 @@ public class SMTestProxy extends AbstractTestProxy {
private boolean myIsEmptyIsCached = false; // is used for separating unknown and unset values
private boolean myIsEmpty = true;
TestLocationProvider myCustomLocator = null;
private final boolean myPreservePresentableName;
public SMTestProxy(final String testName, final boolean isSuite,
@Nullable final String locationUrl) {
this(testName, isSuite, locationUrl, false);
}
public SMTestProxy(final String testName, final boolean isSuite,
@Nullable final String locationUrl,
boolean preservePresentableName) {
myName = testName;
myIsSuite = isSuite;
myLocationUrl = locationUrl;
myPreservePresentableName = preservePresentableName;
}
public void setLocator(@NotNull TestLocationProvider locator) {
@@ -470,6 +478,9 @@ public class SMTestProxy extends AbstractTestProxy {
@NotNull
public String getPresentableName() {
if (myPreservePresentableName) {
return TestsPresentationUtil.getPresentableNameTrimmedOnly(this);
}
return TestsPresentationUtil.getPresentableName(this);
}
@@ -0,0 +1,71 @@
/*
* Copyright 2000-2012 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.execution.testframework.sm.runner.events;
import jetbrains.buildServer.messages.serviceMessages.MessageWithAttributes;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author Sergey Simonchik
*/
public abstract class BaseStartedNodeEvent extends TreeNodeEvent {
private final int myParentId;
private final String myLocationUrl;
protected BaseStartedNodeEvent(@NotNull String name,
int id,
int parentId,
@Nullable final String locationUrl) {
super(name, id);
myParentId = parentId;
myLocationUrl = locationUrl;
validate();
}
private void validate() {
if (myParentId < -1) {
fail("parentId should be greater than -2");
}
if (getId() == -1 ^ myParentId == -1) {
fail("id and parentId should be -1 or non-negative");
}
}
/**
* @return parent node id (non-negative integer), or -1 if undefined
*/
public int getParentId() {
return myParentId;
}
@Nullable
public String getLocationUrl() {
return myLocationUrl;
}
@Override
protected void appendToStringInfo(@NotNull StringBuilder buf) {
append(buf, "parentId", myParentId);
append(buf, "locationUrl", myLocationUrl);
}
public static int getParentNodeId(@NotNull MessageWithAttributes message) {
return TreeNodeEvent.getIntAttribute(message, "parentNodeId");
}
}
@@ -0,0 +1,90 @@
/*
* Copyright 2000-2012 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.execution.testframework.sm.runner.events;
import com.google.common.base.Preconditions;
import jetbrains.buildServer.messages.serviceMessages.TestFailed;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author Sergey Simonchik
*/
public class TestFailedEvent extends TreeNodeEvent {
private final String myLocalizedFailureMessage;
private final String myStacktrace;
private final boolean myTestError;
private final String myComparisonFailureActualText;
private final String myComparisonFailureExpectedText;
public TestFailedEvent(@NotNull TestFailed testFailed, boolean testError) {
super(testFailed.getTestName(), TreeNodeEvent.getNodeId(testFailed));
myLocalizedFailureMessage = Preconditions.checkNotNull(testFailed.getFailureMessage());
myStacktrace = testFailed.getStacktrace();
myTestError = testError;
myComparisonFailureActualText = testFailed.getActual();
myComparisonFailureExpectedText = testFailed.getExpected();
}
public TestFailedEvent(@NotNull String testName,
@NotNull String localizedFailureMessage,
@Nullable String stackTrace,
boolean testError,
@Nullable String comparisonFailureActualText,
@Nullable String comparisonFailureExpectedText) {
super(testName, -1);
myLocalizedFailureMessage = Preconditions.checkNotNull(localizedFailureMessage);
myStacktrace = stackTrace;
myTestError = testError;
myComparisonFailureActualText = comparisonFailureActualText;
myComparisonFailureExpectedText = comparisonFailureExpectedText;
}
@NotNull
public String getLocalizedFailureMessage() {
return myLocalizedFailureMessage;
}
@Nullable
public String getStacktrace() {
return myStacktrace;
}
public boolean isTestError() {
return myTestError;
}
@Nullable
public String getComparisonFailureActualText() {
return myComparisonFailureActualText;
}
@Nullable
public String getComparisonFailureExpectedText() {
return myComparisonFailureExpectedText;
}
@Override
protected void appendToStringInfo(@NotNull StringBuilder buf) {
append(buf, "localizedFailureMessage", myLocalizedFailureMessage);
append(buf, "stacktrace", myStacktrace);
append(buf, "isTestError", myTestError);
append(buf, "comparisonFailureActualText", myComparisonFailureActualText);
append(buf, "comparisonFailureExpectedText", myComparisonFailureExpectedText);
}
}
@@ -0,0 +1,50 @@
/*
* Copyright 2000-2012 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.execution.testframework.sm.runner.events;
import jetbrains.buildServer.messages.serviceMessages.TestFinished;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author Sergey Simonchik
*/
public class TestFinishedEvent extends TreeNodeEvent {
private final int myDuration;
public TestFinishedEvent(@NotNull TestFinished testFinished, int duration) {
this(testFinished.getTestName(), TreeNodeEvent.getNodeId(testFinished), duration);
}
public TestFinishedEvent(@Nullable String name, int id, int duration) {
super(name, id);
myDuration = duration;
}
public TestFinishedEvent(@NotNull String name, int duration) {
this(name, -1, duration);
}
public int getDuration() {
return myDuration;
}
@Override
protected void appendToStringInfo(@NotNull StringBuilder buf) {
append(buf, "duration", myDuration);
}
}
@@ -0,0 +1,56 @@
/*
* Copyright 2000-2012 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.execution.testframework.sm.runner.events;
import jetbrains.buildServer.messages.serviceMessages.TestIgnored;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author Sergey Simonchik
*/
public class TestIgnoredEvent extends TreeNodeEvent {
private final String myIgnoreComment;
private final String myStacktrace;
public TestIgnoredEvent(@NotNull String testName, @NotNull String ignoreComment, @Nullable String stacktrace) {
super(testName, -1);
myIgnoreComment = ignoreComment;
myStacktrace = stacktrace;
}
public TestIgnoredEvent(@NotNull TestIgnored testIgnored, @Nullable String stacktrace) {
super(testIgnored.getTestName(), TreeNodeEvent.getNodeId(testIgnored));
myIgnoreComment = testIgnored.getIgnoreComment();
myStacktrace = stacktrace;
}
@NotNull
public String getIgnoreComment() {
return myIgnoreComment;
}
@Nullable
public String getStacktrace() {
return myStacktrace;
}
@Override
protected void appendToStringInfo(@NotNull StringBuilder buf) {
append(buf, "ignoreComment", myIgnoreComment);
append(buf, "stacktrace", myStacktrace);
}
}
@@ -0,0 +1,55 @@
/*
* Copyright 2000-2012 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.execution.testframework.sm.runner.events;
import jetbrains.buildServer.messages.serviceMessages.BaseTestMessage;
import org.jetbrains.annotations.NotNull;
/**
* @author Sergey Simonchik
*/
public class TestOutputEvent extends TreeNodeEvent {
private final String myText;
private final boolean myStdOut;
public TestOutputEvent(@NotNull BaseTestMessage message, @NotNull String text, boolean stdOut) {
super(message.getTestName(), TreeNodeEvent.getNodeId(message));
myText = text;
myStdOut = stdOut;
}
public TestOutputEvent(@NotNull String testName, @NotNull String text, boolean stdOut) {
super(testName, -1);
myText = text;
myStdOut = stdOut;
}
@NotNull
public String getText() {
return myText;
}
public boolean isStdOut() {
return myStdOut;
}
@Override
protected void appendToStringInfo(@NotNull StringBuilder buf) {
append(buf, "text", myText);
append(buf, "stdOut", myStdOut);
}
}
@@ -0,0 +1,36 @@
/*
* Copyright 2000-2012 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.execution.testframework.sm.runner.events;
import jetbrains.buildServer.messages.serviceMessages.TestStarted;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author Sergey Simonchik
*/
public class TestStartedEvent extends BaseStartedNodeEvent {
public TestStartedEvent(@NotNull TestStarted testStarted,
@Nullable String locationUrl) {
super(testStarted.getTestName(), TreeNodeEvent.getNodeId(testStarted),
getParentNodeId(testStarted), locationUrl);
}
public TestStartedEvent(@NotNull String name, @Nullable String locationUrl) {
super(name, -1, -1, locationUrl);
}
}
@@ -0,0 +1,37 @@
/*
* Copyright 2000-2012 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.execution.testframework.sm.runner.events;
import jetbrains.buildServer.messages.serviceMessages.TestSuiteFinished;
import org.jetbrains.annotations.NotNull;
/**
* @author Sergey Simonchik
*/
public class TestSuiteFinishedEvent extends TreeNodeEvent {
public TestSuiteFinishedEvent(@NotNull TestSuiteFinished suiteFinished) {
super(suiteFinished.getSuiteName(), TreeNodeEvent.getNodeId(suiteFinished));
}
public TestSuiteFinishedEvent(@NotNull String name) {
super(name, -1);
}
@Override
protected void appendToStringInfo(@NotNull StringBuilder buf) {
}
}
@@ -0,0 +1,37 @@
/*
* Copyright 2000-2012 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.execution.testframework.sm.runner.events;
import jetbrains.buildServer.messages.serviceMessages.TestSuiteStarted;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author Sergey Simonchik
*/
public class TestSuiteStartedEvent extends BaseStartedNodeEvent {
public TestSuiteStartedEvent(@NotNull TestSuiteStarted suiteStarted,
@Nullable String locationUrl) {
super(suiteStarted.getSuiteName(), TreeNodeEvent.getNodeId(suiteStarted),
getParentNodeId(suiteStarted), locationUrl);
}
public TestSuiteStartedEvent(@NotNull String name, @Nullable String locationUrl) {
super(name, -1, -1, locationUrl);
}
}
@@ -0,0 +1,101 @@
/*
* Copyright 2000-2012 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.execution.testframework.sm.runner.events;
import jetbrains.buildServer.messages.serviceMessages.MessageWithAttributes;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author Sergey Simonchik
*/
public abstract class TreeNodeEvent {
private final String myName;
private final int myId;
public TreeNodeEvent(@Nullable String name, int id) {
myName = name;
myId = id;
validate();
}
private void validate() {
if (myId < -1) {
fail("id should be greater than -2");
}
if (myName != null && myName.isEmpty()) {
fail("Tree node name is empty");
}
}
protected void fail(@NotNull String message) {
throw new IllegalStateException(message + ", " + toString());
}
@Nullable
public String getName() {
return myName;
}
/**
* @return tree node id (non-negative integer), or -1 if undefined
*/
public int getId() {
return myId;
}
@Override
public final String toString() {
StringBuilder buf = new StringBuilder(getClass().getSimpleName() + "{");
append(buf, "name", myName);
append(buf, "id", myId);
appendToStringInfo(buf);
// drop last 2 chars: ', '
buf.setLength(buf.length() - 2);
buf.append("}");
return buf.toString();
}
protected abstract void appendToStringInfo(@NotNull StringBuilder buf);
protected static void append(@NotNull StringBuilder buffer,
@NotNull String key, @Nullable Object value) {
if (value != null) {
buffer.append(key).append("=");
if (value instanceof String) {
buffer.append("'").append(value).append("'");
}
else {
buffer.append(String.valueOf(value));
}
buffer.append(", ");
}
}
public static int getNodeId(@NotNull MessageWithAttributes message) {
return getIntAttribute(message, "nodeId");
}
public static int getIntAttribute(@NotNull MessageWithAttributes message, @NotNull String key) {
String value = message.getAttributes().get(key);
if (value == null) {
return -1;
}
return Integer.parseInt(value);
}
}
@@ -226,6 +226,18 @@ public class TestsPresentationUtil {
return presentationCandidate;
}
@NotNull
public static String getPresentableNameTrimmedOnly(@NotNull SMTestProxy testProxy) {
String name = testProxy.getName();
if (name != null) {
name = name.trim();
}
if (name == null || name.isEmpty()) {
name = NO_NAME_TEST;
}
return name;
}
@Nullable
private static Icon getIcon(final SMTestProxy testProxy,
final TestConsoleProperties consoleProperties) {
@@ -18,6 +18,7 @@ package com.intellij.execution.testframework.sm.runner;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.execution.testframework.TestConsoleProperties;
import com.intellij.execution.testframework.sm.Marker;
import com.intellij.execution.testframework.sm.runner.events.*;
import com.intellij.execution.testframework.sm.runner.ui.SMTRunnerConsoleView;
import com.intellij.execution.testframework.sm.runner.ui.SMTRunnerTestTreeView;
import com.intellij.execution.testframework.sm.runner.ui.SMTestRunnerResultsForm;
@@ -125,7 +126,7 @@ public class GeneralToSMTRunnerEventsConvertorTest extends BaseSMTRunnerTestCase
public void testOnTestFailure() {
onTestStarted("some_test");
myEventsProcessor.onTestFailure("some_test", "", "", false, null, null);
myEventsProcessor.onTestFailure(new TestFailedEvent("some_test", "", "", false, null, null));
final String fullName = myEventsProcessor.getFullTestName("some_test");
final SMTestProxy proxy = myEventsProcessor.getProxyByFullTestName(fullName);
@@ -136,7 +137,7 @@ public class GeneralToSMTRunnerEventsConvertorTest extends BaseSMTRunnerTestCase
public void testOnTestComparisionFailure() {
onTestStarted("some_test");
myEventsProcessor.onTestFailure("some_test", "", "", false, "actual", "expected");
myEventsProcessor.onTestFailure(new TestFailedEvent("some_test", "", "", false, "actual", "expected"));
final String fullName = myEventsProcessor.getFullTestName("some_test");
final SMTestProxy proxy = myEventsProcessor.getProxyByFullTestName(fullName);
@@ -147,8 +148,8 @@ public class GeneralToSMTRunnerEventsConvertorTest extends BaseSMTRunnerTestCase
public void testOnTestFailure_Twice() {
onTestStarted("some_test");
myEventsProcessor.onTestFailure("some_test", "", "", false, null, null);
myEventsProcessor.onTestFailure("some_test", "", "", false, null, null);
myEventsProcessor.onTestFailure(new TestFailedEvent("some_test", "", "", false, null, null));
myEventsProcessor.onTestFailure(new TestFailedEvent("some_test", "", "", false, null, null));
assertEquals(1, myEventsProcessor.getRunningTestsQuantity());
assertEquals(1, myEventsProcessor.getFailedTestsSet().size());
@@ -156,7 +157,7 @@ public class GeneralToSMTRunnerEventsConvertorTest extends BaseSMTRunnerTestCase
public void testOnTestError() {
onTestStarted("some_test");
myEventsProcessor.onTestFailure("some_test", "", "", true, null, null);
myEventsProcessor.onTestFailure(new TestFailedEvent("some_test", "", "", true, null, null));
final String fullName = myEventsProcessor.getFullTestName("some_test");
final SMTestProxy proxy = myEventsProcessor.getProxyByFullTestName(fullName);
@@ -167,7 +168,7 @@ public class GeneralToSMTRunnerEventsConvertorTest extends BaseSMTRunnerTestCase
public void testOnTestIgnored() {
onTestStarted("some_test");
myEventsProcessor.onTestIgnored("some_test", "", null);
myEventsProcessor.onTestIgnored(new TestIgnoredEvent("some_test", "", null));
final String fullName = myEventsProcessor.getFullTestName("some_test");
final SMTestProxy proxy = myEventsProcessor.getProxyByFullTestName(fullName);
@@ -180,7 +181,7 @@ public class GeneralToSMTRunnerEventsConvertorTest extends BaseSMTRunnerTestCase
onTestStarted("some_test");
final String fullName = myEventsProcessor.getFullTestName("some_test");
final SMTestProxy proxy = myEventsProcessor.getProxyByFullTestName(fullName);
myEventsProcessor.onTestFinished("some_test", 10);
myEventsProcessor.onTestFinished(new TestFinishedEvent("some_test", 10));
assertEquals(0, myEventsProcessor.getRunningTestsQuantity());
assertEquals(0, myEventsProcessor.getFailedTestsSet().size());
@@ -224,8 +225,8 @@ public class GeneralToSMTRunnerEventsConvertorTest extends BaseSMTRunnerTestCase
public void testOnFinishedTesting_WithFailure() {
onTestStarted("test");
myEventsProcessor.onTestFailure("test", "", "", false, null, null);
myEventsProcessor.onTestFinished("test", 10);
myEventsProcessor.onTestFailure(new TestFailedEvent("test", "", "", false, null, null));
myEventsProcessor.onTestFinished(new TestFinishedEvent("test", 10));
myEventsProcessor.onFinishTesting();
//Tree
@@ -240,8 +241,8 @@ public class GeneralToSMTRunnerEventsConvertorTest extends BaseSMTRunnerTestCase
public void testOnFinishedTesting_WithError() {
onTestStarted("test");
myEventsProcessor.onTestFailure("test", "", "", true, null, null);
myEventsProcessor.onTestFinished("test", 10);
myEventsProcessor.onTestFailure(new TestFailedEvent("test", "", "", true, null, null));
myEventsProcessor.onTestFinished(new TestFinishedEvent("test", 10));
myEventsProcessor.onFinishTesting();
//Tree
@@ -256,8 +257,8 @@ public class GeneralToSMTRunnerEventsConvertorTest extends BaseSMTRunnerTestCase
public void testOnFinishedTesting_WithIgnored() {
onTestStarted("test");
myEventsProcessor.onTestIgnored("test", "", null);
myEventsProcessor.onTestFinished("test", 10);
myEventsProcessor.onTestIgnored(new TestIgnoredEvent("test", "", null));
myEventsProcessor.onTestFinished(new TestFinishedEvent("test", 10));
myEventsProcessor.onFinishTesting();
//Tree
@@ -302,18 +303,18 @@ public class GeneralToSMTRunnerEventsConvertorTest extends BaseSMTRunnerTestCase
assertEquals("suite3", test2.getParent().getName());
assertEquals("suite2", test2.getParent().getParent().getName());
myEventsProcessor.onTestFinished("test2", 10);
myEventsProcessor.onTestFinished(new TestFinishedEvent("test2", 10));
//check that after finishing suite (suite3), current will be parent of finished suite (i.e. suite2)
myEventsProcessor.onSuiteFinished("suite3");
myEventsProcessor.onSuiteFinished(new TestSuiteFinishedEvent("suite3"));
onTestStarted("test3");
final SMTestProxy test3 =
myEventsProcessor.getProxyByFullTestName(myEventsProcessor.getFullTestName("test3"));
assertEquals("suite2", test3.getParent().getName());
//clean up
myEventsProcessor.onSuiteFinished("suite2");
myEventsProcessor.onSuiteFinished("suite1");
myEventsProcessor.onSuiteFinished(new TestSuiteFinishedEvent("suite2"));
myEventsProcessor.onSuiteFinished(new TestSuiteFinishedEvent("suite1"));
}
public void testOnSuiteStarted_WithLocation() {
@@ -336,17 +337,17 @@ public class GeneralToSMTRunnerEventsConvertorTest extends BaseSMTRunnerTestCase
}
public void testConcurrentSuite_intersected() {
myEventsProcessor.onSuiteStarted("suite1", null);
myEventsProcessor.onTestStarted("suite2.test1", null);
myEventsProcessor.onSuiteStarted(new TestSuiteStartedEvent("suite1", null));
myEventsProcessor.onTestStarted(new TestStartedEvent("suite2.test1", null));
final SMTestProxy test1 =
myEventsProcessor.getProxyByFullTestName(myEventsProcessor.getFullTestName("suite2.test1"));
myEventsProcessor.onSuiteFinished("suite1");
myEventsProcessor.onSuiteFinished(new TestSuiteFinishedEvent("suite1"));
myEventsProcessor.onSuiteStarted("suite2", null);
myEventsProcessor.onTestFinished("suite2.test1", 10);
myEventsProcessor.onSuiteFinished("suite2");
myEventsProcessor.onSuiteStarted(new TestSuiteStartedEvent("suite2", null));
myEventsProcessor.onTestFinished(new TestFinishedEvent("suite2.test1", 10));
myEventsProcessor.onSuiteFinished(new TestSuiteFinishedEvent("suite2"));
assertEquals("suite1", test1.getParent().getName());
@@ -371,7 +372,7 @@ public class GeneralToSMTRunnerEventsConvertorTest extends BaseSMTRunnerTestCase
}
private void onTestStarted(final String testName, @Nullable final String locationUrl) {
myEventsProcessor.onTestStarted(testName, locationUrl);
myEventsProcessor.onTestStarted(new TestStartedEvent(testName, locationUrl));
myResultsViewer.performUpdate();
}
@@ -380,7 +381,7 @@ public class GeneralToSMTRunnerEventsConvertorTest extends BaseSMTRunnerTestCase
}
private void onTestSuiteStarted(final String suiteName, @Nullable final String locationUrl) {
myEventsProcessor.onSuiteStarted(suiteName, locationUrl);
myEventsProcessor.onSuiteStarted(new TestSuiteStartedEvent(suiteName, locationUrl));
myResultsViewer.performUpdate();
}
}
@@ -15,7 +15,9 @@
*/
package com.intellij.execution.testframework.sm.runner;
import com.intellij.execution.testframework.sm.runner.events.*;
import com.intellij.openapi.util.Key;
import com.intellij.testIntegration.TestLocationProvider;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -24,46 +26,46 @@ import org.jetbrains.annotations.Nullable;
*/
public class MockGeneralTestEventsProcessorAdapter implements GeneralTestEventsProcessor {
private final StringBuilder myOutputBuffer = new StringBuilder();
@Override
public void onStartTesting() {
}
@Override
public void onTestsCountInSuite(int count) {
}
@Override
public void onTestStarted(@NotNull String testName, @Nullable String locationUrl) {
public void onTestStarted(@NotNull TestStartedEvent testStartedEvent) {
}
@Override
public void onTestFinished(@NotNull String testName, int duration) {
public void onTestFinished(@NotNull TestFinishedEvent testFinishedEvent) {
}
@Override
public void onTestFailure(@NotNull String testName,
@NotNull String localizedMessage,
@Nullable String stackTrace,
boolean testError,
@Nullable String comparisionFailureActualText,
@Nullable String comparisionFailureExpectedText) {
public void onTestFailure(@NotNull TestFailedEvent testFailedEvent) {
}
@Override
public void onTestIgnored(@NotNull String testName, @NotNull String ignoreComment, @Nullable String stackTrace) {
public void onTestIgnored(@NotNull TestIgnoredEvent testIgnoredEvent) {
}
@Override
public void onTestOutput(@NotNull String testName, @NotNull String text, boolean stdOut) {
public void onTestOutput(@NotNull TestOutputEvent testOutputEvent) {
}
@Override
public void onSuiteStarted(@NotNull String suiteName, @Nullable String locationUrl) {
public void onSuiteStarted(@NotNull TestSuiteStartedEvent suiteStartedEvent) {
}
@Override
public void onSuiteFinished(@NotNull String suiteName) {
public void onSuiteFinished(@NotNull TestSuiteFinishedEvent suiteFinishedEvent) {
}
@Override
public void onUncapturedOutput(@NotNull String text, Key outputType) {
myOutputBuffer.append("[").append(outputType.toString()).append("]"+ text);
myOutputBuffer.append("[").append(outputType.toString()).append("]").append(text);
}
@Override
@@ -86,6 +88,18 @@ public class MockGeneralTestEventsProcessorAdapter implements GeneralTestEventsP
public void onTestsReporterAttached() {
}
@Override
public void setLocator(@NotNull TestLocationProvider locator) {
}
@Override
public void addEventsListener(@NotNull SMTRunnerEventsListener viewer) {
}
@Override
public void onFinishTesting() {
}
@Override
public void dispose() {
myOutputBuffer.setLength(0);
@@ -22,6 +22,7 @@ import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.execution.testframework.Printable;
import com.intellij.execution.testframework.Printer;
import com.intellij.execution.testframework.TestConsoleProperties;
import com.intellij.execution.testframework.sm.runner.events.*;
import com.intellij.execution.testframework.sm.runner.ui.MockPrinter;
import com.intellij.execution.testframework.sm.runner.ui.SMTRunnerConsoleView;
import com.intellij.execution.testframework.sm.runner.ui.SMTestRunnerResultsForm;
@@ -174,8 +175,8 @@ public class SMTRunnerConsoleTest extends BaseSMTRunnerTestCase {
public void testProcessor_OnTestStdOutput() {
startTestWithPrinter("my_test");
myEventsProcessor.onTestOutput("my_test", "stdout1 ", true);
myEventsProcessor.onTestOutput("my_test", "stdout2", true);
myEventsProcessor.onTestOutput(new TestOutputEvent("my_test", "stdout1 ", true));
myEventsProcessor.onTestOutput(new TestOutputEvent("my_test", "stdout2", true));
assertStdOutput(myMockResetablePrinter, "stdout1 stdout2");
}
@@ -183,8 +184,8 @@ public class SMTRunnerConsoleTest extends BaseSMTRunnerTestCase {
public void testProcessor_OnTestStdErr() {
startTestWithPrinter("my_test");
myEventsProcessor.onTestOutput("my_test", "stderr1 ", false);
myEventsProcessor.onTestOutput("my_test", "stderr2", false);
myEventsProcessor.onTestOutput(new TestOutputEvent("my_test", "stderr1 ", false));
myEventsProcessor.onTestOutput(new TestOutputEvent("my_test", "stderr2", false));
assertStdErr(myMockResetablePrinter, "stderr1 stderr2");
}
@@ -192,10 +193,10 @@ public class SMTRunnerConsoleTest extends BaseSMTRunnerTestCase {
public void testProcessor_OnTestMixedStd() {
startTestWithPrinter("my_test");
myEventsProcessor.onTestOutput("my_test", "stdout1 ", true);
myEventsProcessor.onTestOutput("my_test", "stderr1 ", false);
myEventsProcessor.onTestOutput("my_test", "stdout2", true);
myEventsProcessor.onTestOutput("my_test", "stderr2", false);
myEventsProcessor.onTestOutput(new TestOutputEvent("my_test", "stdout1 ", true));
myEventsProcessor.onTestOutput(new TestOutputEvent("my_test", "stderr1 ", false));
myEventsProcessor.onTestOutput(new TestOutputEvent("my_test", "stdout2", true));
myEventsProcessor.onTestOutput(new TestOutputEvent("my_test", "stderr2", false));
assertAllOutputs(myMockResetablePrinter, "stdout1 stdout2", "stderr1 stderr2", "");
}
@@ -203,9 +204,9 @@ public class SMTRunnerConsoleTest extends BaseSMTRunnerTestCase {
public void testProcessor_OnFailure() {
final SMTestProxy myTest1 = startTestWithPrinter("my_test");
myEventsProcessor.onTestFailure("my_test", "error msg", "method1:1\nmethod2:2", false, null, null);
myEventsProcessor.onTestOutput("my_test", "stdout1 ", true);
myEventsProcessor.onTestOutput("my_test", "stderr1 ", false);
myEventsProcessor.onTestFailure(new TestFailedEvent("my_test", "error msg", "method1:1\nmethod2:2", false, null, null));
myEventsProcessor.onTestOutput(new TestOutputEvent("my_test", "stdout1 ", true));
myEventsProcessor.onTestOutput(new TestOutputEvent("my_test", "stderr1 ", false));
assertAllOutputs(myMockResetablePrinter, "stdout1 ", "\nerror msg\nmethod1:1\nmethod2:2\nstderr1 ", "");
@@ -215,9 +216,9 @@ public class SMTRunnerConsoleTest extends BaseSMTRunnerTestCase {
//other output order
final SMTestProxy myTest2 = startTestWithPrinter("my_test2");
myEventsProcessor.onTestOutput("my_test2", "stdout1 ", true);
myEventsProcessor.onTestOutput("my_test2", "stderr1 ", false);
myEventsProcessor.onTestFailure("my_test2", "error msg", "method1:1\nmethod2:2", false, null, null);
myEventsProcessor.onTestOutput(new TestOutputEvent("my_test2", "stdout1 ", true));
myEventsProcessor.onTestOutput(new TestOutputEvent("my_test2", "stderr1 ", false));
myEventsProcessor.onTestFailure(new TestFailedEvent("my_test2", "error msg", "method1:1\nmethod2:2", false, null, null));
assertAllOutputs(myMockResetablePrinter, "stdout1 ", "stderr1 \nerror msg\nmethod1:1\nmethod2:2\n", "");
final MockPrinter mockPrinter2 = new MockPrinter(true);
@@ -228,9 +229,9 @@ public class SMTRunnerConsoleTest extends BaseSMTRunnerTestCase {
public void testProcessor_OnFailure_EmptyStacktrace() {
final SMTestProxy myTest1 = startTestWithPrinter("my_test");
myEventsProcessor.onTestFailure("my_test", "error msg", "\n\n", false, null, null);
myEventsProcessor.onTestOutput("my_test", "stdout1 ", true);
myEventsProcessor.onTestOutput("my_test", "stderr1 ", false);
myEventsProcessor.onTestFailure(new TestFailedEvent("my_test", "error msg", "\n\n", false, null, null));
myEventsProcessor.onTestOutput(new TestOutputEvent("my_test", "stdout1 ", true));
myEventsProcessor.onTestOutput(new TestOutputEvent("my_test", "stderr1 ", false));
assertAllOutputs(myMockResetablePrinter, "stdout1 ", "\nerror msg\nstderr1 ", "");
@@ -242,9 +243,9 @@ public class SMTRunnerConsoleTest extends BaseSMTRunnerTestCase {
public void testProcessor_OnFailure_Comparision_Strings() {
final SMTestProxy myTest1 = startTestWithPrinter("my_test");
myEventsProcessor.onTestFailure("my_test", "error msg", "method1:1\nmethod2:2", false, "actual", "expected");
myEventsProcessor.onTestOutput("my_test", "stdout1 ", true);
myEventsProcessor.onTestOutput("my_test", "stderr1 ", false);
myEventsProcessor.onTestFailure(new TestFailedEvent("my_test", "error msg", "method1:1\nmethod2:2", false, "actual", "expected"));
myEventsProcessor.onTestOutput(new TestOutputEvent("my_test", "stdout1 ", true));
myEventsProcessor.onTestOutput(new TestOutputEvent("my_test", "stderr1 ", false));
assertAllOutputs(myMockResetablePrinter,
// std out
@@ -281,10 +282,10 @@ public class SMTRunnerConsoleTest extends BaseSMTRunnerTestCase {
public void testProcessor_OnFailure_Comparision_MultilineTexts() {
final SMTestProxy myTest1 = startTestWithPrinter("my_test");
myEventsProcessor.onTestFailure("my_test", "error msg", "method1:1\nmethod2:2", false,
"this is:\nactual", "this is:\nexpected");
myEventsProcessor.onTestOutput("my_test", "stdout1 ", true);
myEventsProcessor.onTestOutput("my_test", "stderr1 ", false);
myEventsProcessor.onTestFailure(new TestFailedEvent("my_test", "error msg", "method1:1\nmethod2:2", false,
"this is:\nactual", "this is:\nexpected"));
myEventsProcessor.onTestOutput(new TestOutputEvent("my_test", "stdout1 ", true));
myEventsProcessor.onTestOutput(new TestOutputEvent("my_test", "stderr1 ", false));
assertAllOutputs(myMockResetablePrinter, "stdout1 ", "\nerror msg \n" +
"\n" +
@@ -304,9 +305,9 @@ public class SMTRunnerConsoleTest extends BaseSMTRunnerTestCase {
public void testProcessor_OnError() {
final SMTestProxy myTest1 = startTestWithPrinter("my_test");
myEventsProcessor.onTestFailure("my_test", "error msg", "method1:1\nmethod2:2", true, null, null);
myEventsProcessor.onTestOutput("my_test", "stdout1 ", true);
myEventsProcessor.onTestOutput("my_test", "stderr1 ", false);
myEventsProcessor.onTestFailure(new TestFailedEvent("my_test", "error msg", "method1:1\nmethod2:2", true, null, null));
myEventsProcessor.onTestOutput(new TestOutputEvent("my_test", "stdout1 ", true));
myEventsProcessor.onTestOutput(new TestOutputEvent("my_test", "stderr1 ", false));
assertAllOutputs(myMockResetablePrinter, "stdout1 ", "\nerror msg\nmethod1:1\nmethod2:2\nstderr1 ", "");
@@ -316,9 +317,9 @@ public class SMTRunnerConsoleTest extends BaseSMTRunnerTestCase {
//other output order
final SMTestProxy myTest2 = startTestWithPrinter("my_test2");
myEventsProcessor.onTestOutput("my_test2", "stdout1 ", true);
myEventsProcessor.onTestOutput("my_test2", "stderr1 ", false);
myEventsProcessor.onTestFailure("my_test2", "error msg", "method1:1\nmethod2:2", true, null, null);
myEventsProcessor.onTestOutput(new TestOutputEvent("my_test2", "stdout1 ", true));
myEventsProcessor.onTestOutput(new TestOutputEvent("my_test2", "stderr1 ", false));
myEventsProcessor.onTestFailure(new TestFailedEvent("my_test2", "error msg", "method1:1\nmethod2:2", true, null, null));
assertAllOutputs(myMockResetablePrinter, "stdout1 ", "stderr1 \nerror msg\nmethod1:1\nmethod2:2\n", "");
final MockPrinter mockPrinter2 = new MockPrinter(true);
@@ -330,8 +331,8 @@ public class SMTRunnerConsoleTest extends BaseSMTRunnerTestCase {
final SMTestProxy myTest1 = startTestWithPrinter("my_test");
myEventsProcessor.onError("error msg", "method1:1\nmethod2:2", true);
myEventsProcessor.onTestOutput("my_test", "stdout1 ", true);
myEventsProcessor.onTestOutput("my_test", "stderr1 ", false);
myEventsProcessor.onTestOutput(new TestOutputEvent("my_test", "stdout1 ", true));
myEventsProcessor.onTestOutput(new TestOutputEvent("my_test", "stderr1 ", false));
assertAllOutputs(myMockResetablePrinter, "stdout1 ", "\nerror msg\nmethod1:1\nmethod2:2\nstderr1 ", "");
@@ -342,13 +343,13 @@ public class SMTRunnerConsoleTest extends BaseSMTRunnerTestCase {
"method1:1\n" +
"method2:2\n" +
"stderr1 ", "");
myEventsProcessor.onTestFinished("my_test", 1);
myEventsProcessor.onTestFinished(new TestFinishedEvent("my_test", 1));
myTest1.setFinished();
//other output order
final SMTestProxy myTest2 = startTestWithPrinter("my_test2");
myEventsProcessor.onTestOutput("my_test2", "stdout1 ", true);
myEventsProcessor.onTestOutput("my_test2", "stderr1 ", false);
myEventsProcessor.onTestOutput(new TestOutputEvent("my_test2", "stdout1 ", true));
myEventsProcessor.onTestOutput(new TestOutputEvent("my_test2", "stderr1 ", false));
myEventsProcessor.onError("error msg", "method1:1\nmethod2:2", true);
assertAllOutputs(myMockResetablePrinter, "stdout1 ", "stderr1 \nerror msg\nmethod1:1\nmethod2:2\n", "");
@@ -360,7 +361,7 @@ public class SMTRunnerConsoleTest extends BaseSMTRunnerTestCase {
public void testProcessor_Suite_OnErrorMsg() {
myEventsProcessor.onError("error msg:root", "method1:1\nmethod2:2", true);
myEventsProcessor.onSuiteStarted("suite", null);
myEventsProcessor.onSuiteStarted(new TestSuiteStartedEvent("suite", null));
final SMTestProxy suite = myEventsProcessor.getCurrentSuite();
suite.setPrinter(myMockResetablePrinter);
myEventsProcessor.onError("error msg:suite", "method1:1\nmethod2:2", true);
@@ -391,9 +392,9 @@ public class SMTRunnerConsoleTest extends BaseSMTRunnerTestCase {
public void testProcessor_OnIgnored() {
final SMTestProxy myTest1 = startTestWithPrinter("my_test");
myEventsProcessor.onTestIgnored("my_test", "ignored msg", null);
myEventsProcessor.onTestOutput("my_test", "stdout1 ", true);
myEventsProcessor.onTestOutput("my_test", "stderr1 ", false);
myEventsProcessor.onTestIgnored(new TestIgnoredEvent("my_test", "ignored msg", null));
myEventsProcessor.onTestOutput(new TestOutputEvent("my_test", "stdout1 ", true));
myEventsProcessor.onTestOutput(new TestOutputEvent("my_test", "stderr1 ", false));
assertAllOutputs(myMockResetablePrinter, "stdout1 ", "stderr1 ", "\nignored msg\n");
@@ -403,9 +404,9 @@ public class SMTRunnerConsoleTest extends BaseSMTRunnerTestCase {
//other output order
final SMTestProxy myTest2 = startTestWithPrinter("my_test2");
myEventsProcessor.onTestOutput("my_test2", "stdout1 ", true);
myEventsProcessor.onTestOutput("my_test2", "stderr1 ", false);
myEventsProcessor.onTestIgnored("my_test2", "ignored msg", null);
myEventsProcessor.onTestOutput(new TestOutputEvent("my_test2", "stdout1 ", true));
myEventsProcessor.onTestOutput(new TestOutputEvent("my_test2", "stderr1 ", false));
myEventsProcessor.onTestIgnored(new TestIgnoredEvent("my_test2", "ignored msg", null));
assertAllOutputs(myMockResetablePrinter, "stdout1 ", "stderr1 ", "\nignored msg\n");
final MockPrinter mockPrinter2 = new MockPrinter(true);
@@ -416,9 +417,9 @@ public class SMTRunnerConsoleTest extends BaseSMTRunnerTestCase {
public void testProcessor_OnIgnored_WithStacktrace() {
final SMTestProxy myTest1 = startTestWithPrinter("my_test");
myEventsProcessor.onTestIgnored("my_test", "ignored2 msg", "method1:1\nmethod2:2");
myEventsProcessor.onTestOutput("my_test", "stdout1 ", true);
myEventsProcessor.onTestOutput("my_test", "stderr1 ", false);
myEventsProcessor.onTestIgnored(new TestIgnoredEvent("my_test", "ignored2 msg", "method1:1\nmethod2:2"));
myEventsProcessor.onTestOutput(new TestOutputEvent("my_test", "stdout1 ", true));
myEventsProcessor.onTestOutput(new TestOutputEvent("my_test", "stderr1 ", false));
assertAllOutputs(myMockResetablePrinter, "stdout1 ",
"\nmethod1:1\nmethod2:2\nstderr1 ",
@@ -433,9 +434,9 @@ public class SMTRunnerConsoleTest extends BaseSMTRunnerTestCase {
//other output order
final SMTestProxy myTest2 = startTestWithPrinter("my_test2");
myEventsProcessor.onTestOutput("my_test2", "stdout1 ", true);
myEventsProcessor.onTestOutput("my_test2", "stderr1 ", false);
myEventsProcessor.onTestIgnored("my_test2", "ignored msg", "method1:1\nmethod2:2");
myEventsProcessor.onTestOutput(new TestOutputEvent("my_test2", "stdout1 ", true));
myEventsProcessor.onTestOutput(new TestOutputEvent("my_test2", "stderr1 ", false));
myEventsProcessor.onTestIgnored(new TestIgnoredEvent("my_test2", "ignored msg", "method1:1\nmethod2:2"));
assertAllOutputs(myMockResetablePrinter,
"stdout1 ",
@@ -465,7 +466,7 @@ public class SMTRunnerConsoleTest extends BaseSMTRunnerTestCase {
public void testOnUncapturedOutput_SomeSuite() {
myEventsProcessor.onStartTesting();
myEventsProcessor.onSuiteStarted("my suite", null);
myEventsProcessor.onSuiteStarted(new TestSuiteStartedEvent("my suite", null));
final SMTestProxy mySuite = myEventsProcessor.getCurrentSuite();
assertTrue(mySuite != myRootSuite);
mySuite.setPrinter(myMockResetablePrinter);
@@ -476,7 +477,7 @@ public class SMTRunnerConsoleTest extends BaseSMTRunnerTestCase {
public void testOnUncapturedOutput_SomeTest() {
myEventsProcessor.onStartTesting();
myEventsProcessor.onSuiteStarted("my suite", null);
myEventsProcessor.onSuiteStarted(new TestSuiteStartedEvent("my suite", null));
startTestWithPrinter("my test");
assertOnUncapturedOutput();
@@ -514,9 +515,9 @@ public class SMTRunnerConsoleTest extends BaseSMTRunnerTestCase {
myConsole.attachToProcess(null);
myEventsProcessor.onStartTesting();
myEventsProcessor.onSuiteStarted("suite", null);
myEventsProcessor.onSuiteStarted(new TestSuiteStartedEvent("suite", null));
final SMTestProxy suite = myEventsProcessor.getCurrentSuite();
myEventsProcessor.onSuiteFinished("suite");
myEventsProcessor.onSuiteFinished(new TestSuiteFinishedEvent("suite"));
myEventsProcessor.onUncapturedOutput("preved", ProcessOutputTypes.STDOUT);
myEventsProcessor.onFinishTesting();
@@ -536,7 +537,7 @@ public class SMTRunnerConsoleTest extends BaseSMTRunnerTestCase {
}
private SMTestProxy startTestWithPrinter(final String testName) {
myEventsProcessor.onTestStarted(testName, null);
myEventsProcessor.onTestStarted(new TestStartedEvent(testName, null));
final SMTestProxy proxy =
myEventsProcessor.getProxyByFullTestName(myEventsProcessor.getFullTestName(testName));
proxy.setPrinter(myMockResetablePrinter);
@@ -21,6 +21,7 @@ import com.intellij.execution.testframework.sm.Marker;
import com.intellij.execution.testframework.sm.runner.BaseSMTRunnerTestCase;
import com.intellij.execution.testframework.sm.runner.GeneralToSMTRunnerEventsConvertor;
import com.intellij.execution.testframework.sm.runner.SMTestProxy;
import com.intellij.execution.testframework.sm.runner.events.*;
import com.intellij.openapi.progress.util.ColorProgressBar;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Ref;
@@ -226,24 +227,24 @@ public class SMTestRunnerResultsFormTest extends BaseSMTRunnerTestCase {
TestConsoleProperties.HIDE_PASSED_TESTS.set(myConsoleProperties, true);
myEventsProcessor.onStartTesting();
myEventsProcessor.onSuiteStarted("suite", null);
myEventsProcessor.onSuiteStarted(new TestSuiteStartedEvent("suite", null));
myResultsViewer.performUpdate();
myEventsProcessor.onTestStarted("test_failed", null);
myEventsProcessor.onTestStarted(new TestStartedEvent("test_failed", null));
myResultsViewer.performUpdate();
myEventsProcessor.onTestFailure("test_failed", "", "", false, null, null);
myEventsProcessor.onTestFailure(new TestFailedEvent("test_failed", "", "", false, null, null));
myResultsViewer.performUpdate();
myEventsProcessor.onTestFinished("test_failed", 10);
myEventsProcessor.onTestFinished(new TestFinishedEvent("test_failed", 10));
myResultsViewer.performUpdate();
myEventsProcessor.onTestStarted("test", null);
myEventsProcessor.onTestStarted(new TestStartedEvent("test", null));
myResultsViewer.performUpdate();
assertEquals(2, myTreeModel.getChildCount(myTreeModel.getChild(myTreeModel.getRoot(), 0)));
myEventsProcessor.onTestFinished("test", 10);
myEventsProcessor.onTestFinished(new TestFinishedEvent("test", 10));
assertEquals(2, myTreeModel.getChildCount(myTreeModel.getChild(myTreeModel.getRoot(), 0)));
myEventsProcessor.onSuiteFinished("suite");
myEventsProcessor.onSuiteFinished(new TestSuiteFinishedEvent("suite"));
myEventsProcessor.onFinishTesting();
assertEquals(1, myTreeModel.getChildCount(myTreeModel.getChild(myTreeModel.getRoot(), 0)));
@@ -251,27 +252,27 @@ public class SMTestRunnerResultsFormTest extends BaseSMTRunnerTestCase {
public void testExpandIfOnlyOneRootChild() throws InterruptedException {
myEventsProcessor.onStartTesting();
myEventsProcessor.onSuiteStarted("suite1", null);
myEventsProcessor.onSuiteStarted(new TestSuiteStartedEvent("suite1", null));
myResultsViewer.performUpdate();
myEventsProcessor.onSuiteStarted("suite2", null);
myEventsProcessor.onSuiteStarted(new TestSuiteStartedEvent("suite2", null));
myResultsViewer.performUpdate();
myEventsProcessor.onTestStarted("test_failed", null);
myEventsProcessor.onTestStarted(new TestStartedEvent("test_failed", null));
myResultsViewer.performUpdate();
myEventsProcessor.onTestFailure("test_failed", "", "", false, null, null);
myEventsProcessor.onTestFailure(new TestFailedEvent("test_failed", "", "", false, null, null));
myResultsViewer.performUpdate();
myEventsProcessor.onTestFinished("test_failed", 10);
myEventsProcessor.onTestFinished(new TestFinishedEvent("test_failed", 10));
myResultsViewer.performUpdate();
myEventsProcessor.onTestStarted("test", null);
myEventsProcessor.onTestStarted(new TestStartedEvent("test", null));
myResultsViewer.performUpdate();
myEventsProcessor.onTestFinished("test", 10);
myEventsProcessor.onTestFinished(new TestFinishedEvent("test", 10));
myResultsViewer.performUpdate();
myEventsProcessor.onSuiteFinished("suite2");
myEventsProcessor.onSuiteFinished(new TestSuiteFinishedEvent("suite2"));
myResultsViewer.performUpdate();
myEventsProcessor.onSuiteFinished("suite1");
myEventsProcessor.onSuiteFinished(new TestSuiteFinishedEvent("suite1"));
myResultsViewer.performUpdate();
myEventsProcessor.onFinishTesting();
myResultsViewer.performUpdate();
@@ -15,6 +15,7 @@
*/
package org.jetbrains.android.util;
import com.android.jarutils.SignedJarBuilder;
import com.android.resources.ResourceFolderType;
import com.android.resources.ResourceType;
import com.android.sdklib.IAndroidTarget;
@@ -38,6 +39,9 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.*;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
@@ -90,6 +94,8 @@ public class AndroidCommonUtils {
@NonNls public static final String PACKAGE_MANIFEST_ATTRIBUTE = "package";
@NonNls public static final String ANDROID_FINAL_PACKAGE_FOR_ARTIFACT_SUFFIX = ".afp";
private AndroidCommonUtils() {
}
@@ -529,4 +535,32 @@ public class AndroidCommonUtils {
final String b = path.substring(dot);
return a + suffix + b;
}
public static void signApk(@NotNull File srcApk,
@NotNull File destFile,
@NotNull PrivateKey privateKey,
@NotNull X509Certificate certificate)
throws IOException, GeneralSecurityException {
FileOutputStream fos = new FileOutputStream(destFile);
SignedJarBuilder builder = new SafeSignedJarBuilder(fos, privateKey, certificate, destFile.getPath());
FileInputStream fis = new FileInputStream(srcApk);
try {
builder.writeZip(fis, null);
builder.close();
}
finally {
try {
fis.close();
}
catch (IOException ignored) {
}
finally {
try {
fos.close();
}
catch (IOException ignored) {
}
}
}
}
}
+5
View File
@@ -219,6 +219,11 @@
<usageTargetProvider implementation="org.jetbrains.android.AndroidUsagesTargetProvider"/>
<renameHandler implementation="org.jetbrains.android.AndroidRenameHandler" order="first"/>
<packaging.elementType implementation="org.jetbrains.android.compiler.artifact.AndroidFinalPackageElementType"/>
<packaging.sourceItemProvider implementation="org.jetbrains.android.compiler.artifact.AndroidSourceItemsProvider"/>
<packaging.artifactPropertiesProvider implementation="org.jetbrains.android.compiler.artifact.AndroidArtifactPropertiesProvider"/>
<packaging.artifactType implementation="org.jetbrains.android.compiler.artifact.AndroidApplicationArtifactType"/>
<compileServer.plugin jar-path="jps/android-jps-plugin.jar"/>
<compileServer.plugin jar-path="android-common.jar"/>
<compileServer.plugin jar-path="android-rt.jar"/>
@@ -44,6 +44,9 @@ import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.*;
import com.intellij.packaging.artifacts.Artifact;
import com.intellij.packaging.artifacts.ArtifactProperties;
import com.intellij.packaging.impl.compiler.ArtifactCompileScope;
import com.intellij.psi.JavaPsiFacade;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiFile;
@@ -52,6 +55,9 @@ import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.HashMap;
import com.intellij.util.containers.HashSet;
import org.jetbrains.android.compiler.artifact.AndroidApplicationArtifactProperties;
import org.jetbrains.android.compiler.artifact.AndroidArtifactPropertiesProvider;
import org.jetbrains.android.compiler.artifact.AndroidArtifactSigningMode;
import org.jetbrains.android.dom.manifest.Manifest;
import org.jetbrains.android.dom.resources.Attr;
import org.jetbrains.android.dom.resources.DeclareStyleable;
@@ -86,6 +92,7 @@ public class AndroidCompileUtil {
@NonNls public static final String PROGUARD_CFG_FILE_NAME = "proguard-project.txt";
@NonNls public static final String OLD_PROGUARD_CFG_FILE_NAME = "proguard.cfg";
public static final String UNSIGNED_SUFFIX = ".unsigned";
private AndroidCompileUtil() {
}
@@ -131,7 +138,7 @@ public class AndroidCompileUtil {
return null;
}
static void addMessages(final CompileContext context, final Map<CompilerMessageCategory, List<String>> messages, Module module) {
public static void addMessages(final CompileContext context, final Map<CompilerMessageCategory, List<String>> messages, @Nullable Module module) {
addMessages(context, messages, null, module);
}
@@ -150,7 +157,7 @@ public class AndroidCompileUtil {
static void addMessages(final CompileContext context,
final Map<CompilerMessageCategory, List<String>> messages,
@Nullable final Map<VirtualFile, VirtualFile> presentableFilesMap,
final Module module) {
@Nullable final Module module) {
ApplicationManager.getApplication().runReadAction(new Runnable() {
public void run() {
if (context.getProject().isDisposed()) return;
@@ -167,7 +174,7 @@ public class AndroidCompileUtil {
line = Integer.parseInt(matcher.group(2));
}
}
context.addMessage(category, '[' + module.getName() + "] " + message, url, line, -1);
context.addMessage(category, (module != null ? '[' + module.getName() + "] " : "") + message, url, line, -1);
}
}
}
@@ -619,7 +626,22 @@ public class AndroidCompileUtil {
public static boolean isReleaseBuild(@NotNull CompileContext context) {
final Boolean value = context.getCompileScope().getUserData(RELEASE_BUILD_KEY);
return value != null && value.booleanValue();
if (value != null && value.booleanValue()) {
return true;
}
final Project project = context.getProject();
final Set<Artifact> artifacts = ArtifactCompileScope.getArtifactsToBuild(project, context.getCompileScope(), false);
if (artifacts != null) {
for (Artifact artifact : artifacts) {
final ArtifactProperties<?> properties = artifact.getProperties(AndroidArtifactPropertiesProvider.getInstance());
if (properties instanceof AndroidApplicationArtifactProperties &&
((AndroidApplicationArtifactProperties)properties).getSigningMode() != AndroidArtifactSigningMode.DEBUG) {
return true;
}
}
}
return false;
}
public static void setReleaseBuild(@NotNull CompileScope compileScope) {
@@ -668,7 +690,10 @@ public class AndroidCompileUtil {
}
}
private static void initializeGenSourceRoot(@NotNull Module module, @Nullable String sourceRootPath, boolean createIfNotExist, boolean exclude) {
private static void initializeGenSourceRoot(@NotNull Module module,
@Nullable String sourceRootPath,
boolean createIfNotExist,
boolean exclude) {
if (sourceRootPath == null) {
return;
}
@@ -716,31 +741,31 @@ public class AndroidCompileUtil {
final Resources resources = pair.getFirst();
waitForSmartMode(project);
ApplicationManager.getApplication().runReadAction(new Runnable() {
@Override
public void run() {
if (!resources.isValid() || facet.getModule().isDisposed() || project.isDisposed()) {
return;
}
ApplicationManager.getApplication().runReadAction(new Runnable() {
@Override
public void run() {
if (!resources.isValid() || facet.getModule().isDisposed() || project.isDisposed()) {
return;
}
for (final Attr attr : resources.getAttrs()) {
final String name = attr.getName().getValue();
for (final Attr attr : resources.getAttrs()) {
final String name = attr.getName().getValue();
if (name != null) {
resourceSet.add(new ResourceEntry(ResourceType.ATTR.getName(), name));
}
}
for (final DeclareStyleable styleable : resources.getDeclareStyleables()) {
final String name = styleable.getName().getValue();
if (name != null) {
resourceSet.add(new ResourceEntry(ResourceType.DECLARE_STYLEABLE.getName(), name));
}
if (name != null) {
resourceSet.add(new ResourceEntry(ResourceType.ATTR.getName(), name));
}
}
});
}
for (final DeclareStyleable styleable : resources.getDeclareStyleables()) {
final String name = styleable.getName().getValue();
if (name != null) {
resourceSet.add(new ResourceEntry(ResourceType.DECLARE_STYLEABLE.getName(), name));
}
}
}
});
}
waitForSmartMode(project);
@@ -929,4 +954,22 @@ public class AndroidCompileUtil {
}
return false;
}
@Nullable
public static String getUnsignedApkPath(@NotNull AndroidFacet facet) {
final String apkPath = AndroidRootUtil.getApkPath(facet);
return apkPath != null ? AndroidCommonUtils.addSuffixToFileName(apkPath, UNSIGNED_SUFFIX) : null;
}
@Nullable
public static <T> T handleExceptionError(@NotNull CompileContext context,
@NotNull String messagePrefix,
@NotNull Exception e) {
reportException(context, messagePrefix, e);
return null;
}
public static void reportException(@NotNull CompileContext context, @NotNull String messagePrefix, @NotNull Exception e) {
context.addMessage(CompilerMessageCategory.ERROR, messagePrefix + e.getClass().getSimpleName() + ": " + e.getMessage(), null, -1, -1);
}
}
@@ -16,6 +16,7 @@
package org.jetbrains.android.compiler;
import com.intellij.compiler.CompilerIOUtil;
import com.intellij.compiler.impl.CompilerUtil;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.compiler.*;
import com.intellij.openapi.diagnostic.Logger;
@@ -27,6 +28,7 @@ import com.intellij.openapi.roots.ModuleOrderEntry;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.OrderEntry;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.JarFileSystem;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.containers.HashSet;
@@ -54,8 +56,6 @@ import java.util.*;
public class AndroidPackagingCompiler implements PackagingCompiler {
private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.compiler.AndroidPackagingCompiler");
public static final String UNSIGNED_SUFFIX = ".unsigned";
public void processOutdatedItem(CompileContext context, String url, @Nullable ValidityState state) {
}
@@ -235,7 +235,7 @@ public class AndroidPackagingCompiler implements PackagingCompiler {
: item.getResPackagePath();
final String finalPath = unsigned
? AndroidCommonUtils.addSuffixToFileName(item.getFinalPath(), UNSIGNED_SUFFIX)
? AndroidCommonUtils.addSuffixToFileName(item.getFinalPath(), AndroidCompileUtil.UNSIGNED_SUFFIX)
: item.getFinalPath();
final String[] sourceRoots = AndroidCompileUtil.toOsPaths(item.getSourceRoots());
@@ -247,6 +247,18 @@ public class AndroidPackagingCompiler implements PackagingCompiler {
item.getAdditionalNativeLibs(), finalPath, unsigned, item.mySdkPath, item.getCustomKeystorePath(),
new ExcludedSourcesFilter(project)));
if (messages.get(CompilerMessageCategory.ERROR).size() == 0) {
if (item.myReleaseBuild == unsigned) {
final File dst = new File(
AndroidCommonUtils.addSuffixToFileName(item.getFinalPath(), AndroidCommonUtils.ANDROID_FINAL_PACKAGE_FOR_ARTIFACT_SUFFIX));
FileUtil.copy(new File(finalPath), dst);
CompilerUtil.refreshIOFile(dst);
final VirtualFile jar = JarFileSystem.getInstance().refreshAndFindFileByPath(dst.getPath() + "!/");
if (jar != null) {
jar.refresh(false, true);
}
}
}
AndroidCompileUtil.addMessages(context, messages, item.myModule);
}
catch (final IOException e) {
@@ -17,6 +17,8 @@ package org.jetbrains.android.compiler;
import com.intellij.compiler.CompilerConfiguration;
import com.intellij.compiler.CompilerConfigurationImpl;
import com.intellij.compiler.options.CompileStepBeforeRun;
import com.intellij.facet.ProjectFacetManager;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.compiler.*;
@@ -35,7 +37,13 @@ import com.intellij.openapi.roots.OrderEntry;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.packaging.artifacts.Artifact;
import com.intellij.packaging.artifacts.ArtifactProperties;
import com.intellij.packaging.impl.compiler.ArtifactCompileScope;
import com.intellij.util.containers.hash.HashSet;
import org.jetbrains.android.compiler.artifact.AndroidApplicationArtifactProperties;
import org.jetbrains.android.compiler.artifact.AndroidArtifactPropertiesProvider;
import org.jetbrains.android.compiler.artifact.AndroidArtifactSigningMode;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.android.facet.AndroidRootUtil;
import org.jetbrains.android.maven.AndroidMavenUtil;
@@ -55,10 +63,13 @@ public class AndroidPrecompileTask implements CompileTask {
@Override
public boolean execute(CompileContext context) {
if (!checkArtifacts(context)) {
return false;
}
checkAndroidDependencies(context);
final Project project = context.getProject();
ExcludedEntriesConfiguration configuration =
((CompilerConfigurationImpl)CompilerConfiguration.getInstance(project)).getExcludedEntriesConfiguration();
@@ -107,6 +118,62 @@ public class AndroidPrecompileTask implements CompileTask {
return true;
}
private static boolean checkArtifacts(@NotNull CompileContext context) {
final Project project = context.getProject();
if (!ProjectFacetManager.getInstance(project).hasFacets(AndroidFacet.ID)) {
return true;
}
final Set<Artifact> artifacts = ArtifactCompileScope.getArtifactsToBuild(project, context.getCompileScope(), false);
if (artifacts == null) {
return true;
}
final Set<Artifact> debugArtifacts = new HashSet<Artifact>();
final Set<Artifact> releaseArtifacts = new HashSet<Artifact>();
for (final Artifact artifact : artifacts) {
final ArtifactProperties<?> properties = artifact.getProperties(AndroidArtifactPropertiesProvider.getInstance());
if (properties instanceof AndroidApplicationArtifactProperties) {
final AndroidArtifactSigningMode mode = ((AndroidApplicationArtifactProperties)properties).getSigningMode();
if (mode == AndroidArtifactSigningMode.DEBUG) {
debugArtifacts.add(artifact);
}
else {
releaseArtifacts.add(artifact);
}
}
}
boolean success = true;
if (debugArtifacts.size() > 0 && releaseArtifacts.size() > 0) {
final String message = "Cannot build debug and release Android artifacts in the same session\n" +
"Debug artifacts: " + toString(debugArtifacts) + "\n" +
"Release artifacts: " + toString(releaseArtifacts);
context.addMessage(CompilerMessageCategory.ERROR, message, null, -1, -1);
success = false;
}
if (releaseArtifacts.size() > 0 &&
CompileStepBeforeRun.getRunConfiguration(context) != null) {
final String message = "Cannot build release Android artifacts in the 'make before run' session\n" +
"Release artifacts: " + toString(releaseArtifacts);
context.addMessage(CompilerMessageCategory.ERROR, message, null, -1, -1);
success = false;
}
return success;
}
private static String toString(Collection<Artifact> artifacts) {
final StringBuilder result = new StringBuilder();
for (Artifact artifact : artifacts) {
if (result.length() > 0) {
result.append(", ");
}
result.append(artifact.getName());
}
return result.toString();
}
private static void checkAndroidDependencies(@NotNull CompileContext context) {
for (Module module : context.getCompileScope().getAffectedModules()) {
final AndroidFacet facet = AndroidFacet.getInstance(module);
@@ -142,7 +209,7 @@ public class AndroidPrecompileTask implements CompileTask {
}
}
}
private static void clearResCache(@NotNull AndroidFacet facet, @NotNull CompileContext context) {
final Module module = facet.getModule();
@@ -0,0 +1,250 @@
package org.jetbrains.android.compiler.artifact;
import com.intellij.openapi.compiler.CompileContext;
import com.intellij.openapi.compiler.CompilerMessageCategory;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.packaging.artifacts.Artifact;
import com.intellij.packaging.artifacts.ArtifactProperties;
import com.intellij.packaging.ui.ArtifactEditorContext;
import com.intellij.packaging.ui.ArtifactPropertiesEditor;
import com.intellij.util.xmlb.XmlSerializerUtil;
import com.intellij.util.xmlb.annotations.Transient;
import org.apache.commons.codec.binary.Base64;
import org.jetbrains.android.compiler.AndroidCompileUtil;
import org.jetbrains.android.util.AndroidBundle;
import org.jetbrains.android.util.AndroidCommonUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.*;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
/**
* @author Eugene.Kudelevsky
*/
@SuppressWarnings("UnusedDeclaration")
public class AndroidApplicationArtifactProperties extends ArtifactProperties<AndroidApplicationArtifactProperties> {
private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.compiler.artifact.AndroidApplicationArtifactProperties");
private AndroidArtifactSigningMode mySigningMode = AndroidArtifactSigningMode.RELEASE_UNSIGNED;
private String myKeyStoreUrl = "";
private String myKeyStorePassword = "";
private String myKeyAlias = "";
private String myKeyPassword = "";
@Override
public void onBuildFinished(@NotNull Artifact artifact, @NotNull CompileContext context) {
if (!(artifact.getArtifactType() instanceof AndroidApplicationArtifactType) ||
mySigningMode != AndroidArtifactSigningMode.RELEASE_SIGNED) {
return;
}
final String errorPrefix = "[Artifact '" + artifact.getName() + "'] ";
final Pair<PrivateKey, X509Certificate> pair = getPrivateKeyAndCertificate(context, errorPrefix);
if (pair == null) {
return;
}
final String artifactFilePath = artifact.getOutputFilePath();
final String prefix = "Cannot sign artifact " + artifact.getName() + ": ";
if (artifactFilePath == null) {
context.addMessage(CompilerMessageCategory.ERROR, prefix + "output path is not specified", null, -1, -1);
return;
}
final File artifactFile = new File(artifactFilePath);
if (!artifactFile.exists()) {
context.addMessage(CompilerMessageCategory.ERROR, prefix + "file " + artifactFilePath + " hasn't been generated", null, -1, -1);
return;
}
File tmpDir = null;
try {
tmpDir = FileUtil.createTempDirectory("android_artifact", "tmp");
final File tmpArtifact = new File(tmpDir, "tmpArtifact.apk");
FileUtil.copy(artifactFile, tmpArtifact);
if (!FileUtil.delete(artifactFile)) {
context.addMessage(CompilerMessageCategory.ERROR, "Cannot delete file " + artifactFile.getPath(), null, -1, -1);
return;
}
AndroidCommonUtils.signApk(tmpArtifact, artifactFile, pair.getFirst(), pair.getSecond());
}
catch (IOException e) {
context.addMessage(CompilerMessageCategory.ERROR, prefix + "I/O error: " + e.getMessage(), null, -1, -1);
}
catch (GeneralSecurityException e) {
AndroidCompileUtil.reportException(context, prefix, e);
}
finally {
if (tmpDir != null) {
FileUtil.delete(tmpDir);
}
}
}
@Override
public ArtifactPropertiesEditor createEditor(@NotNull ArtifactEditorContext context) {
return new AndroidArtifactPropertiesEditor(this, context.getProject());
}
@Override
public AndroidApplicationArtifactProperties getState() {
return this;
}
@Override
public void loadState(AndroidApplicationArtifactProperties state) {
XmlSerializerUtil.copyBean(state, this);
}
@Nullable
private Pair<PrivateKey, X509Certificate> getPrivateKeyAndCertificate(@NotNull CompileContext context, @NotNull String errorPrefix) {
final String keyStoreFilePath = myKeyStoreUrl != null ? VfsUtilCore.urlToPath(myKeyStoreUrl) : "";
if (keyStoreFilePath.length() == 0) {
context.addMessage(CompilerMessageCategory.ERROR, errorPrefix + "Key store file is not specified", null, -1, -1);
return null;
}
if (myKeyStorePassword == null || myKeyStorePassword.length() == 0) {
context.addMessage(CompilerMessageCategory.ERROR, errorPrefix + "Key store password is not specified", null, -1, -1);
return null;
}
if (myKeyAlias == null || myKeyAlias.length() == 0) {
context.addMessage(CompilerMessageCategory.ERROR, errorPrefix + "Key alias is not specified", null, -1, -1);
return null;
}
if (myKeyPassword == null || myKeyPassword.length() == 0) {
context.addMessage(CompilerMessageCategory.ERROR, errorPrefix + "Key password is not specified", null, -1, -1);
return null;
}
final File keyStoreFile = new File(keyStoreFilePath);
final String keystorePasswordStr = getPlainKeystorePassword();
final char[] keystorePassword = keystorePasswordStr.toCharArray();
final String keyPasswordStr = getPlainKeyPassword();
final char[] keyPassword = keyPasswordStr.toCharArray();
final KeyStore keyStore;
InputStream is = null;
try {
//noinspection IOResourceOpenedButNotSafelyClosed
is = new FileInputStream(keyStoreFile);
keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(is, keystorePassword);
final KeyStore.PrivateKeyEntry entry =
(KeyStore.PrivateKeyEntry)keyStore.getEntry(myKeyAlias, new KeyStore.PasswordProtection(keyPassword));
if (entry == null) {
context.addMessage(CompilerMessageCategory.ERROR,
errorPrefix + AndroidBundle.message("android.extract.package.cannot.find.key.error", myKeyAlias), null, -1, -1);
return null;
}
final PrivateKey privateKey = entry.getPrivateKey();
final Certificate certificate = entry.getCertificate();
if (privateKey == null || certificate == null) {
context.addMessage(CompilerMessageCategory.ERROR,
errorPrefix + AndroidBundle.message("android.extract.package.cannot.find.key.error", myKeyAlias), null, -1, -1);
return null;
}
return Pair.create(privateKey, (X509Certificate)certificate);
}
catch (FileNotFoundException e) {
return AndroidCompileUtil.handleExceptionError(context, errorPrefix, e);
}
catch (KeyStoreException e) {
return AndroidCompileUtil.handleExceptionError(context, errorPrefix, e);
}
catch (CertificateException e) {
return AndroidCompileUtil.handleExceptionError(context, errorPrefix, e);
}
catch (NoSuchAlgorithmException e) {
return AndroidCompileUtil.handleExceptionError(context, errorPrefix, e);
}
catch (IOException e) {
return AndroidCompileUtil.handleExceptionError(context, errorPrefix, e);
}
catch (UnrecoverableEntryException e) {
return AndroidCompileUtil.handleExceptionError(context, errorPrefix, e);
}
finally {
if (is != null) {
try {
is.close();
}
catch (IOException e) {
LOG.info(e);
}
}
}
}
public AndroidArtifactSigningMode getSigningMode() {
return mySigningMode;
}
public void setSigningMode(AndroidArtifactSigningMode signingMode) {
mySigningMode = signingMode;
}
public String getKeyStoreUrl() {
return myKeyStoreUrl;
}
public String getKeyStorePassword() {
return myKeyStorePassword;
}
public String getKeyAlias() {
return myKeyAlias;
}
public String getKeyPassword() {
return myKeyPassword;
}
public void setKeyStoreUrl(String keyStoreUrl) {
myKeyStoreUrl = keyStoreUrl;
}
public void setKeyStorePassword(String keyStorePassword) {
myKeyStorePassword = keyStorePassword;
}
public void setKeyAlias(String keyAlias) {
myKeyAlias = keyAlias;
}
public void setKeyPassword(String keyPassword) {
myKeyPassword = keyPassword;
}
@Transient
@NotNull
public String getPlainKeystorePassword() {
return new String(new Base64().decode(myKeyStorePassword.getBytes()));
}
@Transient
public void setPlainKeystorePassword(@NotNull String password) {
myKeyStorePassword = new String(new Base64().encode(password.getBytes()));
}
@Transient
@NotNull
public String getPlainKeyPassword() {
return new String(new Base64().decode(myKeyPassword.getBytes()));
}
@Transient
public void setPlainKeyPassword(@NotNull String password) {
myKeyPassword = new String(new Base64().encode(password.getBytes()));
}
}
@@ -0,0 +1,95 @@
package org.jetbrains.android.compiler.artifact;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.roots.ui.configuration.artifacts.sourceItems.LibrarySourceItem;
import com.intellij.openapi.roots.ui.configuration.artifacts.sourceItems.ModuleOutputSourceItem;
import com.intellij.packaging.artifacts.ArtifactTemplate;
import com.intellij.packaging.artifacts.ArtifactType;
import com.intellij.packaging.elements.CompositePackagingElement;
import com.intellij.packaging.elements.PackagingElementFactory;
import com.intellij.packaging.elements.PackagingElementOutputKind;
import com.intellij.packaging.elements.PackagingElementResolvingContext;
import com.intellij.packaging.impl.artifacts.ArtifactUtil;
import com.intellij.packaging.impl.artifacts.PlainArtifactType;
import com.intellij.packaging.ui.PackagingSourceItem;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @author Eugene.Kudelevsky
*/
public class AndroidApplicationArtifactType extends ArtifactType {
public AndroidApplicationArtifactType() {
super("apk", "Android Application");
}
@NotNull
@Override
public Icon getIcon() {
return PlainArtifactType.ARTIFACT_ICON;
}
@Override
public String getDefaultPathFor(@NotNull PackagingElementOutputKind kind) {
return "/";
}
@Override
public boolean isSuitableItem(@NotNull PackagingSourceItem item) {
return !(item instanceof ModuleOutputSourceItem || item instanceof LibrarySourceItem);
}
@NotNull
@Override
public CompositePackagingElement<?> createRootElement(@NotNull String artifactName) {
return PackagingElementFactory.getInstance().createArchive(ArtifactUtil.suggestArtifactFileName(artifactName) + ".apk");
}
@NotNull
@Override
public List<? extends ArtifactTemplate> getNewArtifactTemplates(@NotNull PackagingElementResolvingContext context) {
return Collections.singletonList(new MyTemplate(context));
}
private class MyTemplate extends ArtifactTemplate {
protected PackagingElementResolvingContext myContext;
public MyTemplate(@NotNull PackagingElementResolvingContext context) {
myContext = context;
}
@Override
public String getPresentableName() {
return "From module...";
}
@Override
public NewArtifactConfiguration createArtifact() {
final List<Module> modules = new ArrayList<Module>();
for (Module module : myContext.getModulesProvider().getModules()) {
final AndroidFacet facet = AndroidFacet.getInstance(module);
if (facet != null && !facet.getConfiguration().LIBRARY_PROJECT) {
modules.add(module);
}
}
final AndroidFacet facet = AndroidArtifactUtil.chooseAndroidApplicationModule(myContext.getProject(), modules);
if (facet == null) {
return null;
}
final CompositePackagingElement<?> rootElement =
AndroidApplicationArtifactType.this.createRootElement(facet.getModule().getName());
rootElement.addFirstChild(new AndroidFinalPackageElement(myContext.getProject(), facet));
return new NewArtifactConfiguration(rootElement, facet.getModule().getName(), AndroidApplicationArtifactType.this);
}
}
}
@@ -0,0 +1,157 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.android.compiler.artifact.AndroidArtifactPropertiesEditor">
<grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="5" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<xy x="20" y="20" width="500" height="400"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<vspacer id="39e8a">
<constraints>
<grid row="4" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
</constraints>
</vspacer>
<component id="8bfdb" class="javax.swing.JRadioButton" binding="myReleaseSignedRadio">
<constraints>
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<enabled value="true"/>
<text value="Release &amp;signed"/>
</properties>
</component>
<component id="791e9" class="javax.swing.JRadioButton" binding="myReleaseUnsignedRadio">
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Release &amp;unsigned"/>
</properties>
</component>
<component id="ce323" class="javax.swing.JRadioButton" binding="myDebugRadio">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="&amp;Debug"/>
</properties>
</component>
<grid id="b6cd" binding="myReleaseKeyPanel" layout-manager="GridLayoutManager" row-count="5" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="2" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<component id="587da" class="com.intellij.ui.components.JBLabel">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<labelFor value="779"/>
<text value="&amp;Key store path: "/>
</properties>
</component>
<component id="e6a18" class="com.intellij.ui.components.JBLabel">
<constraints>
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<labelFor value="8c694"/>
<text value="Key store &amp;password:"/>
</properties>
</component>
<component id="8c694" class="javax.swing.JPasswordField" binding="myKeyStorePasswordField">
<constraints>
<grid row="2" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="150" height="-1"/>
</grid>
</constraints>
<properties/>
</component>
<component id="5d3f2" class="com.intellij.ui.components.JBLabel">
<constraints>
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<labelFor value="9b706"/>
<text value="K&amp;ey alias: "/>
</properties>
</component>
<component id="76382" class="com.intellij.ui.components.JBLabel">
<constraints>
<grid row="4" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<labelFor value="3398"/>
<text value="Key pass&amp;word:"/>
</properties>
</component>
<component id="3398" class="javax.swing.JPasswordField" binding="myKeyPasswordField">
<constraints>
<grid row="4" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="150" height="-1"/>
</grid>
</constraints>
<properties/>
</component>
<component id="9b706" class="com.intellij.openapi.ui.TextFieldWithBrowseButton" binding="myKeyAliasField">
<constraints>
<grid row="3" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
</component>
<component id="779" class="javax.swing.JTextField" binding="myKeyStorePathField">
<constraints>
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="150" height="-1"/>
</grid>
</constraints>
<properties/>
</component>
<grid id="bdf4e" layout-manager="GridLayoutManager" row-count="1" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="1" column="1" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<component id="48c0d" class="javax.swing.JButton" binding="myCreateKeyStoreButton">
<constraints>
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="&amp;Create new..."/>
</properties>
</component>
<component id="a6531" class="javax.swing.JButton" binding="myLoadKeyStoreButton">
<constraints>
<grid row="0" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="&amp;Choose existing..."/>
</properties>
</component>
<hspacer id="ed5f1">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
</hspacer>
</children>
</grid>
</children>
</grid>
</children>
</grid>
<buttonGroups>
<group name="myGroup1">
<member id="ce323"/>
<member id="8bfdb"/>
<member id="791e9"/>
</group>
</buttonGroups>
</form>
@@ -0,0 +1,260 @@
package org.jetbrains.android.compiler.artifact;
import com.android.annotations.NonNull;
import com.intellij.CommonBundle;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileChooser.FileChooser;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.packaging.ui.ArtifactPropertiesEditor;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.android.util.AndroidUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.List;
/**
* @author Eugene.Kudelevsky
*/
public class AndroidArtifactPropertiesEditor extends ArtifactPropertiesEditor {
private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.compiler.artifact.AndroidApplicationArtifactProperties");
private final AndroidApplicationArtifactProperties myProperties;
private JPanel myPanel;
private JRadioButton myDebugRadio;
private JRadioButton myReleaseSignedRadio;
private JRadioButton myReleaseUnsignedRadio;
private JPanel myReleaseKeyPanel;
private JPasswordField myKeyStorePasswordField;
private JTextField myKeyStorePathField;
private JPasswordField myKeyPasswordField;
private TextFieldWithBrowseButton myKeyAliasField;
private JButton myLoadKeyStoreButton;
private JButton myCreateKeyStoreButton;
public AndroidArtifactPropertiesEditor(@NonNull AndroidApplicationArtifactProperties properties, @NotNull final Project project) {
myProperties = properties;
final ActionListener listener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
UIUtil.setEnabled(myReleaseKeyPanel, myReleaseSignedRadio.isSelected(), true);
}
};
myDebugRadio.addActionListener(listener);
myReleaseUnsignedRadio.addActionListener(listener);
myReleaseSignedRadio.addActionListener(listener);
myLoadKeyStoreButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
final String defaultPath = getKeyStorePath();
final VirtualFile defaultFile = LocalFileSystem.getInstance().findFileByPath(defaultPath);
final FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor();
final VirtualFile file = FileChooser.chooseFile(descriptor, myPanel, project, defaultFile);
if (file != null) {
myKeyStorePathField.setText(FileUtil.toSystemDependentName(file.getPath()));
}
}
});
myCreateKeyStoreButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
final NewKeyStoreDialog dialog = new NewKeyStoreDialog(project, myKeyStorePathField.getText());
dialog.show();
if (dialog.getExitCode() == DialogWrapper.OK_EXIT_CODE) {
myKeyStorePathField.setText(dialog.getKeyStorePath());
myKeyStorePasswordField.setText(String.valueOf(dialog.getKeyStorePassword()));
myKeyAliasField.setText(dialog.getKeyAlias());
myKeyPasswordField.setText(String.valueOf(dialog.getKeyPassword()));
}
}
});
myKeyAliasField.getButton().addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
final List<String> keys = loadExistingKeys();
if (keys == null) {
return;
}
final ChooseKeyDialog dialog =
new ChooseKeyDialog(project, getKeyStorePath(), myKeyStorePasswordField.getPassword(), keys, getKeyAlias());
dialog.show();
if (dialog.getExitCode() == DialogWrapper.OK_EXIT_CODE) {
final String chosenKey = dialog.getChosenKey();
if (chosenKey != null) {
myKeyAliasField.setText(chosenKey);
}
final char[] password = dialog.getChosenKeyPassword();
if (password != null) {
myKeyPasswordField.setText(String.valueOf(password));
}
}
}
});
}
private String getKeyStorePath() {
return myKeyStorePathField.getText().trim();
}
@Nullable
private List<String> loadExistingKeys() {
final String errorPrefix = "Cannot load key store: ";
InputStream is = null;
try {
//noinspection IOResourceOpenedButNotSafelyClosed
is = new FileInputStream(new File(getKeyStorePath()));
final KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(is, myKeyStorePasswordField.getPassword());
return AndroidUtils.toList(keyStore.aliases());
}
catch (KeyStoreException e) {
Messages.showErrorDialog(myPanel, errorPrefix + e.getMessage(), CommonBundle.getErrorTitle());
return null;
}
catch (FileNotFoundException e) {
Messages.showErrorDialog(myPanel, errorPrefix + e.getMessage(), CommonBundle.getErrorTitle());
return null;
}
catch (CertificateException e) {
Messages.showErrorDialog(myPanel, errorPrefix + e.getMessage(), CommonBundle.getErrorTitle());
return null;
}
catch (NoSuchAlgorithmException e) {
Messages.showErrorDialog(myPanel, errorPrefix + e.getMessage(), CommonBundle.getErrorTitle());
return null;
}
catch (IOException e) {
Messages.showErrorDialog(myPanel, errorPrefix + e.getMessage(), CommonBundle.getErrorTitle());
return null;
}
finally {
if (is != null) {
try {
is.close();
}
catch (IOException e) {
LOG.info(e);
}
}
}
}
@Override
public String getTabName() {
return "Android";
}
@Override
public JComponent createComponent() {
return myPanel;
}
@Override
public boolean isModified() {
return getSigningMode() != myProperties.getSigningMode() ||
!getKeyStoreFileUrl().equals(myProperties.getKeyStoreUrl()) ||
!getKeyStorePassword().equals(myProperties.getPlainKeystorePassword()) ||
!getKeyAlias().equals(myProperties.getKeyAlias()) ||
!getKeyPassword().equals(myProperties.getPlainKeyPassword());
}
@Override
public void apply() {
myProperties.setSigningMode(getSigningMode());
myProperties.setKeyStoreUrl(getKeyStoreFileUrl());
myProperties.setPlainKeystorePassword(getKeyStorePassword());
myProperties.setKeyAlias(getKeyAlias());
myProperties.setPlainKeyPassword(getKeyPassword());
}
@Override
public void reset() {
switch (myProperties.getSigningMode()) {
case RELEASE_UNSIGNED:
myReleaseUnsignedRadio.setSelected(true);
myDebugRadio.setSelected(false);
myReleaseSignedRadio.setSelected(false);
break;
case DEBUG:
myReleaseUnsignedRadio.setSelected(false);
myDebugRadio.setSelected(true);
myReleaseSignedRadio.setSelected(false);
break;
case RELEASE_SIGNED:
myReleaseUnsignedRadio.setSelected(false);
myDebugRadio.setSelected(false);
myReleaseSignedRadio.setSelected(true);
break;
}
final String keyStoreUrl = myProperties.getKeyStoreUrl();
myKeyStorePathField.setText(keyStoreUrl != null ? VfsUtilCore.urlToPath(keyStoreUrl) : "");
myKeyStorePasswordField.setText(myProperties.getPlainKeystorePassword());
final String keyAlias = myProperties.getKeyAlias();
myKeyAliasField.setText(keyAlias != null ? keyAlias : "");
myKeyPasswordField.setText(myProperties.getPlainKeyPassword());
UIUtil.setEnabled(myReleaseKeyPanel, myProperties.getSigningMode() == AndroidArtifactSigningMode.RELEASE_SIGNED, true);
}
@Override
public void disposeUIResources() {
}
@NotNull
private AndroidArtifactSigningMode getSigningMode() {
if (myDebugRadio.isSelected()) {
return AndroidArtifactSigningMode.DEBUG;
}
else if (myReleaseSignedRadio.isSelected()) {
return AndroidArtifactSigningMode.RELEASE_SIGNED;
}
return AndroidArtifactSigningMode.RELEASE_UNSIGNED;
}
@NotNull
private String getKeyStoreFileUrl() {
final String path = getKeyStorePath();
return VfsUtilCore.pathToUrl(path);
}
@NotNull
private String getKeyStorePassword() {
return String.valueOf(myKeyStorePasswordField.getPassword());
}
@NotNull
private String getKeyAlias() {
return myKeyAliasField.getText().trim();
}
@NotNull
private String getKeyPassword() {
return String.valueOf(myKeyPasswordField.getPassword());
}
}
@@ -0,0 +1,30 @@
package org.jetbrains.android.compiler.artifact;
import com.intellij.packaging.artifacts.ArtifactProperties;
import com.intellij.packaging.artifacts.ArtifactPropertiesProvider;
import com.intellij.packaging.artifacts.ArtifactType;
import org.jetbrains.annotations.NotNull;
/**
* @author Eugene.Kudelevsky
*/
public class AndroidArtifactPropertiesProvider extends ArtifactPropertiesProvider {
protected AndroidArtifactPropertiesProvider() {
super("android-properties");
}
@Override
public boolean isAvailableFor(@NotNull ArtifactType type) {
return type instanceof AndroidApplicationArtifactType;
}
@NotNull
@Override
public ArtifactProperties<?> createProperties(@NotNull ArtifactType artifactType) {
return new AndroidApplicationArtifactProperties();
}
public static AndroidArtifactPropertiesProvider getInstance() {
return EP_NAME.findExtension(AndroidArtifactPropertiesProvider.class);
}
}
@@ -0,0 +1,122 @@
package org.jetbrains.android.compiler.artifact;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.compiler.CompileContext;
import com.intellij.openapi.compiler.CompileScope;
import com.intellij.openapi.compiler.PackagingCompiler;
import com.intellij.openapi.compiler.ValidityState;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
/**
* @author Eugene.Kudelevsky
*/
public class AndroidArtifactSigningCompiler implements PackagingCompiler {
@Override
public void processOutdatedItem(CompileContext context, String url, @Nullable ValidityState state) {
}
@NotNull
@Override
public ProcessingItem[] getProcessingItems(CompileContext context) {
return ApplicationManager.getApplication().runReadAction(new Computable<ProcessingItem[]>() {
@Override
public ProcessingItem[] compute() {
return null;
}
});
}
@Override
public ProcessingItem[] process(CompileContext context, ProcessingItem[] items) {
return new ProcessingItem[0]; //To change body of implemented methods use File | Settings | File Templates.
}
@NotNull
@Override
public String getDescription() {
return "Android Artifact Signing Compiler";
}
@Override
public boolean validateConfiguration(CompileScope scope) {
return true;
}
@Override
public ValidityState createValidityState(DataInput in) throws IOException {
return new MyValidityState(in);
}
private static class MyProcessingItem implements ProcessingItem {
private final VirtualFile myApkFile;
private final AndroidArtifactSigningMode mySigningMode;
private final String myDebugKeyStorePath;
private final MyValidityState myValidityState;
private MyProcessingItem(@NotNull VirtualFile apkFile,
@NotNull AndroidArtifactSigningMode signingMode,
@Nullable String debugKeyStorePath) {
myApkFile = apkFile;
mySigningMode = signingMode;
myDebugKeyStorePath = debugKeyStorePath;
myValidityState = new MyValidityState(myApkFile.getModificationStamp(),
mySigningMode.name(),
myDebugKeyStorePath);
}
@NotNull
@Override
public VirtualFile getFile() {
return myApkFile;
}
@Override
public ValidityState getValidityState() {
return myValidityState;
}
}
private static class MyValidityState implements ValidityState {
private final long myApkFileTimestamp;
private final String mySigningMode;
private final String myDebugKeyStorePath;
private MyValidityState(long apkFileTimestamp, @NotNull String signingMode, @NotNull String debugKeyStorePath) {
myApkFileTimestamp = apkFileTimestamp;
mySigningMode = signingMode;
myDebugKeyStorePath = debugKeyStorePath;
}
private MyValidityState(@NotNull DataInput in) throws IOException {
myApkFileTimestamp = in.readLong();
mySigningMode = in.readUTF();
myDebugKeyStorePath = in.readUTF();
}
@Override
public boolean equalsTo(ValidityState otherState) {
if (!(otherState instanceof MyValidityState)) {
return false;
}
final MyValidityState state = (MyValidityState)otherState;
return state.myApkFileTimestamp == myApkFileTimestamp &&
state.mySigningMode.equals(mySigningMode) &&
state.myDebugKeyStorePath.equals(myDebugKeyStorePath);
}
@Override
public void save(DataOutput out) throws IOException {
out.writeLong(myApkFileTimestamp);
out.writeUTF(mySigningMode);
out.writeUTF(myDebugKeyStorePath);
}
}
}
@@ -0,0 +1,8 @@
package org.jetbrains.android.compiler.artifact;
/**
* @author Eugene.Kudelevsky
*/
public enum AndroidArtifactSigningMode {
DEBUG, RELEASE_UNSIGNED, RELEASE_SIGNED
}
@@ -0,0 +1,56 @@
package org.jetbrains.android.compiler.artifact;
import com.intellij.CommonBundle;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ui.configuration.ChooseModulesDialog;
import com.intellij.openapi.ui.Messages;
import com.intellij.packaging.artifacts.Artifact;
import com.intellij.packaging.impl.artifacts.ArtifactUtil;
import com.intellij.packaging.ui.ArtifactEditorContext;
import com.intellij.util.Processor;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* @author Eugene.Kudelevsky
*/
public class AndroidArtifactUtil {
private AndroidArtifactUtil() {
}
public static boolean containsAndroidPackage(ArtifactEditorContext editorContext, Artifact artifact) {
return !ArtifactUtil
.processPackagingElements(artifact, AndroidFinalPackageElementType.getInstance(), new Processor<AndroidFinalPackageElement>() {
public boolean process(AndroidFinalPackageElement e) {
return false;
}
}, editorContext, true);
}
@Nullable
public static AndroidFacet chooseAndroidApplicationModule(@NotNull Project project, @NotNull List<Module> modules) {
final ChooseModulesDialog dialog = new ChooseModulesDialog(project, modules, "Select Module",
"Selected Android application module will be included in the created artifact with all dependencies");
dialog.setSingleSelectionMode();
dialog.show();
final List<Module> selected = dialog.getChosenElements();
if (selected.isEmpty()) {
return null;
}
assert selected.size() == 1;
final Module module = selected.get(0);
final String moduleName = module.getName();
final AndroidFacet facet = AndroidFacet.getInstance(module);
if (facet == null) {
final String message = "Cannot find Android facet for module " + moduleName;
Messages.showErrorDialog(project, message, CommonBundle.getErrorTitle());
return null;
}
return facet;
}
}
@@ -0,0 +1,154 @@
package org.jetbrains.android.compiler.artifact;
import com.intellij.compiler.ant.Generator;
import com.intellij.facet.pointers.FacetPointer;
import com.intellij.facet.pointers.FacetPointersManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.JarFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.packaging.artifacts.ArtifactType;
import com.intellij.packaging.elements.*;
import com.intellij.packaging.impl.elements.FacetBasedPackagingElement;
import com.intellij.packaging.impl.elements.ModuleOutputPackagingElement;
import com.intellij.packaging.impl.ui.DelegatedPackagingElementPresentation;
import com.intellij.packaging.ui.ArtifactEditorContext;
import com.intellij.packaging.ui.PackagingElementPresentation;
import com.intellij.util.xmlb.annotations.Attribute;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.android.facet.AndroidRootUtil;
import org.jetbrains.android.util.AndroidCommonUtils;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* @author Eugene.Kudelevsky
*/
public class AndroidFinalPackageElement extends PackagingElement<AndroidFinalPackageElement.AndroidFinalPackageElementState>
implements FacetBasedPackagingElement, ModuleOutputPackagingElement {
@NonNls static final String FACET_ATTRIBUTE = "facet";
private FacetPointer<AndroidFacet> myFacetPointer;
private final Project myProject;
public AndroidFinalPackageElement(@NotNull Project project, @Nullable AndroidFacet facet) {
super(AndroidFinalPackageElementType.getInstance());
myProject = project;
myFacetPointer = facet != null ? FacetPointersManager.getInstance(myProject).create(facet) : null;
}
@Override
public PackagingElementPresentation createPresentation(@NotNull ArtifactEditorContext context) {
return new DelegatedPackagingElementPresentation(new AndroidFinalPackagePresentation(myFacetPointer));
}
@Nullable
private String getApkPath() {
if (myFacetPointer == null) {
return null;
}
final AndroidFacet facet = myFacetPointer.getFacet();
if (facet == null) {
return null;
}
final String apkPath = AndroidRootUtil.getApkPath(facet);
final String path = apkPath != null
? AndroidCommonUtils.addSuffixToFileName(apkPath, AndroidCommonUtils.ANDROID_FINAL_PACKAGE_FOR_ARTIFACT_SUFFIX)
: null;
return path != null
? FileUtil.toSystemIndependentName(path) + "!/"
: null;
}
@Override
public List<? extends Generator> computeAntInstructions(@NotNull PackagingElementResolvingContext resolvingContext,
@NotNull AntCopyInstructionCreator creator,
@NotNull ArtifactAntGenerationContext generationContext,
@NotNull ArtifactType artifactType) {
final String apkPath = getApkPath();
if (apkPath != null) {
return Collections.singletonList(creator.createExtractedDirectoryInstruction(apkPath));
}
return Collections.emptyList();
}
@Override
public void computeIncrementalCompilerInstructions(@NotNull IncrementalCompilerInstructionCreator creator,
@NotNull PackagingElementResolvingContext resolvingContext,
@NotNull ArtifactIncrementalCompilerContext compilerContext,
@NotNull ArtifactType artifactType) {
final String apkPath = getApkPath();
if (apkPath != null) {
final VirtualFile apk = JarFileSystem.getInstance().findFileByPath(apkPath);
if (apk != null && apk.isValid() && apk.isDirectory()) {
creator.addDirectoryCopyInstructions(apk);
}
}
}
@Nullable
public AndroidFacet getFacet() {
return myFacetPointer != null ? myFacetPointer.getFacet() : null;
}
@Override
public boolean isEqualTo(@NotNull PackagingElement<?> element) {
if (!(element instanceof AndroidFinalPackageElement)) {
return false;
}
final AndroidFinalPackageElement packageElement = (AndroidFinalPackageElement)element;
return myFacetPointer == null
? packageElement.myFacetPointer == null
: myFacetPointer.equals(packageElement.myFacetPointer);
}
public AndroidFinalPackageElementState getState() {
final AndroidFinalPackageElementState state = new AndroidFinalPackageElementState();
state.myFacetPointer = myFacetPointer != null ? myFacetPointer.getId() : null;
return state;
}
@Override
public AndroidFacet findFacet(@NotNull PackagingElementResolvingContext context) {
return myFacetPointer != null ? myFacetPointer.findFacet(context.getModulesProvider(), context.getFacetsProvider()) : null;
}
public void loadState(AndroidFinalPackageElementState state) {
myFacetPointer = state.myFacetPointer != null
? FacetPointersManager.getInstance(myProject).<AndroidFacet>create(state.myFacetPointer)
: null;
}
@Override
public String getModuleName() {
return myFacetPointer != null ? myFacetPointer.getModuleName() : null;
}
@Override
public Module findModule(PackagingElementResolvingContext context) {
final AndroidFacet facet = findFacet(context);
return facet != null ? facet.getModule() : null;
}
@NotNull
@Override
public Collection<VirtualFile> getSourceRoots(PackagingElementResolvingContext context) {
return Collections.emptyList();
}
public static class AndroidFinalPackageElementState {
@Attribute(FACET_ATTRIBUTE)
public String myFacetPointer;
}
}
@@ -0,0 +1,88 @@
package org.jetbrains.android.compiler.artifact;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.packaging.artifacts.Artifact;
import com.intellij.packaging.elements.CompositePackagingElement;
import com.intellij.packaging.elements.PackagingElement;
import com.intellij.packaging.elements.PackagingElementType;
import com.intellij.packaging.ui.ArtifactEditorContext;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.android.util.AndroidUtils;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* @author Eugene.Kudelevsky
*/
public class AndroidFinalPackageElementType extends PackagingElementType<AndroidFinalPackageElement> {
@NonNls public static final String TYPE_ID = "android-final-package";
protected AndroidFinalPackageElementType() {
super(TYPE_ID, "Android Final Package");
}
public static AndroidFinalPackageElementType getInstance() {
return getInstance(AndroidFinalPackageElementType.class);
}
@Override
public Icon getCreateElementIcon() {
return AndroidUtils.ANDROID_ICON;
}
@Override
public boolean canCreate(@NotNull ArtifactEditorContext context, @NotNull Artifact artifact) {
return getAndroidApplicationFacets(context, context.getModulesProvider().getModules()).size() > 0 &&
!AndroidArtifactUtil.containsAndroidPackage(context, artifact);
}
@NotNull
private static List<AndroidFacet> getAndroidApplicationFacets(@NotNull ArtifactEditorContext context, @NotNull Module[] modules) {
final List<AndroidFacet> result = new ArrayList<AndroidFacet>();
for (Module module : modules) {
for (AndroidFacet facet : context.getFacetsProvider().getFacetsByType(module, AndroidFacet.ID)) {
if (!facet.getConfiguration().LIBRARY_PROJECT) {
result.add(facet);
}
}
}
return result;
}
@NotNull
private static List<Module> facetsToModules(@NotNull Collection<AndroidFacet> facets) {
final List<Module> result = new ArrayList<Module>(facets.size());
for (AndroidFacet facet : facets) {
result.add(facet.getModule());
}
return result;
}
@NotNull
@Override
public List<? extends PackagingElement<?>> chooseAndCreate(@NotNull ArtifactEditorContext context,
@NotNull Artifact artifact,
@NotNull CompositePackagingElement<?> parent) {
final List<AndroidFacet> facets = getAndroidApplicationFacets(context, context.getModulesProvider().getModules());
final List<Module> modules = facetsToModules(facets);
final AndroidFacet facet = AndroidArtifactUtil.chooseAndroidApplicationModule(context.getProject(), modules);
if (facet == null) {
return Collections.emptyList();
}
return Collections.singletonList(new AndroidFinalPackageElement(context.getProject(), facet));
}
@NotNull
@Override
public AndroidFinalPackageElement createEmpty(@NotNull Project project) {
return new AndroidFinalPackageElement(project, null);
}
}
@@ -0,0 +1,40 @@
package org.jetbrains.android.compiler.artifact;
import com.intellij.facet.pointers.FacetPointer;
import com.intellij.ide.projectView.PresentationData;
import com.intellij.packaging.ui.SourceItemPresentation;
import com.intellij.packaging.ui.SourceItemWeights;
import com.intellij.ui.SimpleTextAttributes;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author Eugene.Kudelevsky
*/
class AndroidFinalPackagePresentation extends SourceItemPresentation {
private final FacetPointer<AndroidFacet> myFacetPointer;
public AndroidFinalPackagePresentation(@Nullable FacetPointer<AndroidFacet> facetPointer) {
myFacetPointer = facetPointer;
}
@Override
public String getPresentableName() {
final String moduleName = myFacetPointer != null ? myFacetPointer.getModuleName() : "<unknown>";
return "'" + moduleName + "' Android final package";
}
@Override
public void render(@NotNull PresentationData presentationData,
SimpleTextAttributes mainAttributes,
SimpleTextAttributes commentAttributes) {
presentationData.setIcons(AndroidFacet.getFacetType().getIcon());
presentationData.addText(getPresentableName(), mainAttributes);
}
@Override
public int getWeight() {
return SourceItemWeights.LIBRARY_WEIGHT - 5;
}
}
@@ -0,0 +1,56 @@
package org.jetbrains.android.compiler.artifact;
import com.intellij.facet.pointers.FacetPointersManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.roots.ui.configuration.artifacts.sourceItems.FacetBasedPackagingSourceItemsProvider;
import com.intellij.openapi.roots.ui.configuration.artifacts.sourceItems.ModuleSourceItemGroup;
import com.intellij.packaging.artifacts.Artifact;
import com.intellij.packaging.ui.ArtifactEditorContext;
import com.intellij.packaging.ui.PackagingSourceItem;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* @author Eugene.Kudelevsky
*/
public class AndroidSourceItemsProvider extends FacetBasedPackagingSourceItemsProvider<AndroidFacet, AndroidFinalPackageElement> {
public AndroidSourceItemsProvider() {
super(AndroidFacet.ID, AndroidFinalPackageElementType.getInstance());
}
@Override
protected AndroidFinalPackagePresentation createPresentation(AndroidFacet facet) {
return new AndroidFinalPackagePresentation(FacetPointersManager.getInstance(facet.getModule().getProject()).create(facet));
}
@Override
protected AndroidFinalPackageElement createElement(ArtifactEditorContext context, AndroidFacet facet) {
return new AndroidFinalPackageElement(context.getProject(), facet);
}
@NotNull
@Override
public Collection<? extends PackagingSourceItem> getSourceItems(@NotNull ArtifactEditorContext editorContext,
@NotNull Artifact artifact,
@Nullable PackagingSourceItem parent) {
if (parent instanceof ModuleSourceItemGroup && !AndroidArtifactUtil.containsAndroidPackage(editorContext, artifact)) {
final Module module = ((ModuleSourceItemGroup)parent).getModule();
final Set<AndroidFacet> facets =
new HashSet<AndroidFacet>(editorContext.getFacetsProvider().getFacetsByType(module, AndroidFacet.ID));
return Collections.singletonList(new FacetBasedSourceItem<AndroidFacet>(this, facets.iterator().next()));
}
return Collections.emptyList();
}
@Override
protected AndroidFacet getFacet(AndroidFinalPackageElement element) {
return element.getFacet();
}
}
@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.android.compiler.artifact.ChooseKeyDialog">
<grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="4" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<xy x="20" y="20" width="500" height="400"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<component id="6262e" class="com.intellij.ui.components.JBRadioButton" binding="myUseExistingKeyRadioButton">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="&amp;Use an existing key:"/>
</properties>
</component>
<vspacer id="ba9f7">
<constraints>
<grid row="3" column="0" row-span="1" col-span="2" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
</constraints>
</vspacer>
<component id="91b01" class="com.intellij.ui.components.JBRadioButton" binding="myCreateNewKeyRadioButton">
<constraints>
<grid row="1" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Create a &amp;new key"/>
</properties>
</component>
<grid id="97118" binding="myNewKeyPanel" layout-manager="BorderLayout" hgap="0" vgap="0">
<constraints>
<grid row="2" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="2" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="none"/>
<children/>
</grid>
<component id="b9c2f" class="javax.swing.JComboBox" binding="myKeyCombo">
<constraints>
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
</component>
</children>
</grid>
<buttonGroups>
<group name="myGroup1">
<member id="6262e"/>
<member id="91b01"/>
</group>
</buttonGroups>
<inspectionSuppressions>
<suppress inspection="NoLabelFor" id="b9c2f"/>
</inspectionSuppressions>
</form>
@@ -0,0 +1,129 @@
package org.jetbrains.android.compiler.artifact;
import com.intellij.CommonBundle;
import com.intellij.ide.wizard.CommitStepException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.ui.CollectionComboBoxModel;
import com.intellij.ui.components.JBRadioButton;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.android.exportSignedPackage.NewKeyForm;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
/**
* @author Eugene.Kudelevsky
*/
public class ChooseKeyDialog extends DialogWrapper {
private JPanel myNewKeyPanel;
private JBRadioButton myCreateNewKeyRadioButton;
private JBRadioButton myUseExistingKeyRadioButton;
private JComboBox myKeyCombo;
private JPanel myPanel;
private final NewKeyForm myNewKeyForm = new MyNewKeyForm();
private final Project myProject;
private final String myKeyStorePath;
private final char[] myKeyStorePassword;
private final List<String> myExistingKeys;
public ChooseKeyDialog(@NotNull Project project,
@NotNull String keyStorePath,
@NotNull char[] password,
@NotNull List<String> existingKeys,
@Nullable String keyToSelect) {
super(project);
myProject = project;
myKeyStorePath = keyStorePath;
myKeyStorePassword = password;
myExistingKeys = existingKeys;
myKeyCombo.setModel(new CollectionComboBoxModel(existingKeys, existingKeys.get(0)));
if (keyToSelect != null && existingKeys.contains(keyToSelect)) {
myKeyCombo.setSelectedItem(keyToSelect);
}
myNewKeyPanel.add(myNewKeyForm.getContentPanel(), BorderLayout.CENTER);
final ActionListener listener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
UIUtil.setEnabled(myNewKeyPanel, myCreateNewKeyRadioButton.isSelected(), true);
}
};
myCreateNewKeyRadioButton.addActionListener(listener);
myUseExistingKeyRadioButton.addActionListener(listener);
final boolean useExisting = existingKeys.size() > 0;
myUseExistingKeyRadioButton.setSelected(useExisting);
myCreateNewKeyRadioButton.setSelected(!useExisting);
UIUtil.setEnabled(myNewKeyPanel, !useExisting, true);
setTitle("Create New Key");
init();
}
@Override
protected JComponent createCenterPanel() {
return myPanel;
}
@Override
protected void doOKAction() {
if (myCreateNewKeyRadioButton.isSelected()) {
try {
myNewKeyForm.createKey();
}
catch (CommitStepException e) {
Messages.showErrorDialog(myPanel, e.getMessage(), CommonBundle.getErrorTitle());
return;
}
}
super.doOKAction();
}
@Nullable
public String getChosenKey() {
return myUseExistingKeyRadioButton.isSelected()
? (String)myKeyCombo.getSelectedItem()
: myNewKeyForm.getKeyAlias();
}
@Nullable
public char[] getChosenKeyPassword() {
return myCreateNewKeyRadioButton.isSelected()
? myNewKeyForm.getKeyPassword()
: null;
}
private class MyNewKeyForm extends NewKeyForm {
@Override
protected List<String> getExistingKeyAliasList() {
return myExistingKeys;
}
@NotNull
@Override
protected Project getProject() {
return myProject;
}
@NotNull
@Override
protected char[] getKeyStorePassword() {
return myKeyStorePassword;
}
@NotNull
@Override
protected String getKeyStoreLocation() {
return myKeyStorePath;
}
}
}
@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.android.compiler.artifact.NewKeyStoreDialog">
<grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="5" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<xy x="20" y="20" width="507" height="400"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<component id="61ec4" class="com.intellij.ui.components.JBLabel">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<labelFor value="decbb"/>
<text value="&amp;Key store path:"/>
</properties>
</component>
<vspacer id="a56a5">
<constraints>
<grid row="4" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
</constraints>
</vspacer>
<component id="decbb" class="com.intellij.openapi.ui.TextFieldWithBrowseButton" binding="myKeyStorePathField">
<constraints>
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
</component>
<grid id="1d308" binding="myNewKeyPanel" layout-manager="BorderLayout" hgap="0" vgap="0">
<constraints>
<grid row="3" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<clientProperties>
<BorderFactoryClass class="java.lang.String" value="com.intellij.ui.IdeBorderFactory$PlainSmallWithIndent"/>
</clientProperties>
<border type="etched" title="Key"/>
<children/>
</grid>
<component id="f7c78" class="com.intellij.ui.components.JBLabel">
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<labelFor value="75dcc"/>
<text value="&amp;Password:"/>
</properties>
</component>
<component id="75dcc" class="javax.swing.JPasswordField" binding="myPasswordField">
<constraints>
<grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="150" height="-1"/>
</grid>
</constraints>
<properties/>
</component>
<component id="aa6ed" class="javax.swing.JLabel">
<constraints>
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<labelFor value="bffd5"/>
<text value="Co&amp;nfirm password:"/>
</properties>
</component>
<component id="bffd5" class="javax.swing.JPasswordField" binding="myConfirmedPassword">
<constraints>
<grid row="2" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="150" height="-1"/>
</grid>
</constraints>
<properties/>
</component>
</children>
</grid>
</form>
@@ -0,0 +1,124 @@
package org.jetbrains.android.compiler.artifact;
import com.intellij.CommonBundle;
import com.intellij.ide.wizard.CommitStepException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import org.jetbrains.android.exportSignedPackage.NewKeyForm;
import org.jetbrains.android.util.AndroidBundle;
import org.jetbrains.android.util.AndroidUtils;
import org.jetbrains.android.util.SaveFileListener;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.*;
import java.util.Collections;
import java.util.List;
/**
* @author Eugene.Kudelevsky
*/
public class NewKeyStoreDialog extends DialogWrapper {
private JPanel myNewKeyPanel;
private JPanel myPanel;
private TextFieldWithBrowseButton myKeyStorePathField;
private JPasswordField myPasswordField;
private JPasswordField myConfirmedPassword;
private final NewKeyForm myNewKeyForm;
private final Project myProject;
public NewKeyStoreDialog(@NotNull Project project, @NotNull String defaultKeyStorePath) {
super(project);
myProject = project;
myKeyStorePathField.setText(defaultKeyStorePath);
setTitle("Create New Key Store");
myNewKeyForm = new MyNewKeyForm();
myNewKeyPanel.add(myNewKeyForm.getContentPanel(), BorderLayout.CENTER);
myKeyStorePathField.addActionListener(new SaveFileListener(myPanel, myKeyStorePathField, AndroidBundle.message(
"android.extract.package.choose.keystore.title")) {
@Override
protected String getDefaultLocation() {
return getKeyStorePath();
}
});
init();
}
@Override
protected JComponent createCenterPanel() {
return myPanel;
}
@Override
public JComponent getPreferredFocusedComponent() {
return myKeyStorePathField;
}
@Override
protected void doOKAction() {
if (getKeyStorePath().length() == 0) {
Messages.showErrorDialog(myPanel, "Specify key store path", CommonBundle.getErrorTitle());
return;
}
try {
AndroidUtils.checkNewPassword(myPasswordField, myConfirmedPassword);
myNewKeyForm.createKey();
}
catch (CommitStepException e) {
Messages.showErrorDialog(myPanel, e.getMessage(), CommonBundle.getErrorTitle());
return;
}
super.doOKAction();
}
@NotNull
public String getKeyStorePath() {
return myKeyStorePathField.getText().trim();
}
@NotNull
public char[] getKeyStorePassword() {
return myPasswordField.getPassword();
}
@NotNull
public String getKeyAlias() {
return myNewKeyForm.getKeyAlias();
}
@NotNull
public char[] getKeyPassword() {
return myNewKeyForm.getKeyPassword();
}
private class MyNewKeyForm extends NewKeyForm {
@Override
protected List<String> getExistingKeyAliasList() {
return Collections.emptyList();
}
@NotNull
@Override
protected Project getProject() {
return myProject;
}
@NotNull
@Override
protected char[] getKeyStorePassword() {
return NewKeyStoreDialog.this.getKeyStorePassword();
}
@NotNull
@Override
protected String getKeyStoreLocation() {
return getKeyStorePath();
}
}
}
@@ -16,7 +16,6 @@
package org.jetbrains.android.exportSignedPackage;
import com.android.jarutils.SignedJarBuilder;
import com.android.sdklib.SdkConstants;
import com.intellij.CommonBundle;
import com.intellij.execution.ExecutionException;
@@ -50,7 +49,6 @@ import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.components.JBLabel;
import org.jetbrains.android.compiler.AndroidCompileUtil;
import org.jetbrains.android.compiler.AndroidPackagingCompiler;
import org.jetbrains.android.compiler.AndroidProguardCompiler;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.android.facet.AndroidFacetConfiguration;
@@ -58,7 +56,6 @@ import org.jetbrains.android.facet.AndroidRootUtil;
import org.jetbrains.android.sdk.AndroidPlatform;
import org.jetbrains.android.util.AndroidBundle;
import org.jetbrains.android.util.AndroidCommonUtils;
import org.jetbrains.android.util.SafeSignedJarBuilder;
import org.jetbrains.android.util.SaveFileListener;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -68,12 +65,8 @@ import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
/**
* @author Eugene.Kudelevsky
@@ -110,7 +103,7 @@ class ApkStep extends ExportSignedPackageWizardStep {
myWizard = wizard;
myApkPathLabel.setLabelFor(myApkPathField);
myProguardConfigFilePathLabel.setLabelFor(myProguardConfigFilePathField);
myApkPathField.getButton().addActionListener(
new SaveFileListener(myContentPanel, myApkPathField, AndroidBundle.message("android.extract.package.choose.dest.apk")) {
@Override
@@ -140,7 +133,7 @@ class ApkStep extends ExportSignedPackageWizardStep {
}
}
});
myProguardCheckBox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
@@ -193,7 +186,7 @@ class ApkStep extends ExportSignedPackageWizardStep {
myIncludeSystemProguardFileCheckBox.setVisible(AndroidCommonUtils.isIncludingInProguardSupported(sdkToolsRevision));
final String proguardCfgPath = properties.getValue(PROGUARD_CFG_PATH_PROPERTY);
if (proguardCfgPath != null &&
if (proguardCfgPath != null &&
LocalFileSystem.getInstance().refreshAndFindFileByPath(proguardCfgPath) != null) {
myProguardConfigFilePathField.setText(FileUtil.toSystemDependentName(proguardCfgPath));
final String includeSystemProguardFile = properties.getValue(INCLUDE_SYSTEM_PROGUARD_FILE_PROPERTY);
@@ -274,35 +267,11 @@ class ApkStep extends ExportSignedPackageWizardStep {
@SuppressWarnings({"IOResourceOpenedButNotSafelyClosed"})
private void createApk(File destFile) throws IOException, GeneralSecurityException {
final String srcApkPath = AndroidRootUtil.getApkPath(myWizard.getFacet()) + AndroidPackagingCompiler.UNSIGNED_SUFFIX;
final String srcApkPath = AndroidCompileUtil.getUnsignedApkPath(myWizard.getFacet());
final File srcApk = new File(FileUtil.toSystemDependentName(srcApkPath));
if (myWizard.isSigned()) {
FileOutputStream fos = new FileOutputStream(destFile);
PrivateKey privateKey = myWizard.getPrivateKey();
assert privateKey != null;
X509Certificate certificate = myWizard.getCertificate();
assert certificate != null;
SignedJarBuilder builder = new SafeSignedJarBuilder(fos, privateKey, certificate, destFile.getPath());
FileInputStream fis = new FileInputStream(srcApk);
try {
builder.writeZip(fis, null);
builder.close();
}
finally {
try {
fis.close();
}
catch (IOException ignored) {
}
finally {
try {
fos.close();
}
catch (IOException ignored) {
}
}
}
AndroidCommonUtils.signApk(srcApk, destFile, myWizard.getPrivateKey(), myWizard.getCertificate());
}
else {
FileUtil.copy(srcApk, destFile);
@@ -18,11 +18,9 @@ package org.jetbrains.android.exportSignedPackage;
import com.intellij.ide.wizard.CommitStepException;
import com.intellij.ide.wizard.StepAdapter;
import org.jetbrains.android.util.AndroidBundle;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.Arrays;
/**
* @author Eugene.Kudelevsky
@@ -44,42 +42,8 @@ public abstract class ExportSignedPackageWizardStep extends StepAdapter {
public abstract String getHelpId();
protected static void checkNewPassword(JPasswordField passwordField, JPasswordField confirmedPasswordField) throws CommitStepException {
char[] password = passwordField.getPassword();
char[] confirmedPassword = confirmedPasswordField.getPassword();
try {
checkPassword(password);
if (password.length < 6) {
throw new CommitStepException(AndroidBundle.message("android.export.package.incorrect.password.length"));
}
if (!Arrays.equals(password, confirmedPassword)) {
throw new CommitStepException(AndroidBundle.message("android.export.package.passwords.not.match.error"));
}
}
finally {
Arrays.fill(password, '\0');
Arrays.fill(confirmedPassword, '\0');
}
}
protected abstract void commitForNext() throws CommitStepException;
protected static void checkPassword(char[] password) throws CommitStepException {
if (password.length == 0) {
throw new CommitStepException(AndroidBundle.message("android.export.package.specify.password.error"));
}
}
protected static void checkPassword(JPasswordField passwordField) throws CommitStepException {
char[] password = passwordField.getPassword();
try {
checkPassword(password);
}
finally {
Arrays.fill(password, '\0');
}
}
@Override
public Icon getIcon() {
return null;
@@ -22,6 +22,7 @@ import com.intellij.openapi.ui.Messages;
import com.intellij.ui.CollectionComboBoxModel;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.android.util.AndroidBundle;
import org.jetbrains.android.util.AndroidUtils;
import javax.swing.*;
import java.awt.event.ActionEvent;
@@ -132,7 +133,7 @@ class InitialKeyStep extends ExportSignedPackageWizardStep {
if (myAliasCombo.getSelectedItem() == null) {
throw new CommitStepException(AndroidBundle.message("android.extract.package.select.key.alias.error"));
}
checkPassword(password);
AndroidUtils.checkPassword(password);
String alias = (String)myAliasCombo.getSelectedItem();
loadKey(alias, password);
}
@@ -26,6 +26,7 @@ import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.android.util.AndroidBundle;
import org.jetbrains.android.util.AndroidUtils;
import org.jetbrains.android.util.SaveFileListener;
import org.jetbrains.annotations.Nullable;
@@ -102,7 +103,7 @@ class KeystoreStep extends ExportSignedPackageWizardStep {
private KeyStore checkExistingKeystoreOptionsAndCreateKeystore(File keystoreFile) throws CommitStepException {
char[] password = myKeystorePasswordField.getPassword();
FileInputStream fis = null;
checkPassword(password);
AndroidUtils.checkPassword(password);
if (!keystoreFile.isFile()) {
throw new CommitStepException(AndroidBundle.message("android.cannot.find.file.error", keystoreFile.getPath()));
}
@@ -129,7 +130,7 @@ class KeystoreStep extends ExportSignedPackageWizardStep {
}
private void checkNewKeystoreOptions(File keystoreFile) throws CommitStepException {
checkNewPassword(myKeystorePasswordField, myConfirmKeystorePasswordField);
AndroidUtils.checkNewPassword(myKeystorePasswordField, myConfirmKeystorePasswordField);
if (keystoreFile.exists()) {
throw new CommitStepException(AndroidBundle.message(
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.android.exportSignedPackage.NewKeyStep">
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.android.exportSignedPackage.NewKeyForm">
<grid id="27dc6" binding="myContentPanel" layout-manager="GridLayoutManager" row-count="5" column-count="4" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
@@ -71,7 +71,7 @@
<grid row="3" column="0" row-span="1" col-span="4" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="etched"/>
<border type="etched" title="Certificate"/>
<children>
<component id="7b983" class="javax.swing.JLabel">
<constraints>
@@ -0,0 +1,270 @@
package org.jetbrains.android.exportSignedPackage;
import com.android.jarutils.DebugKeyProvider;
import com.android.jarutils.KeystoreHelper;
import com.intellij.ide.util.PropertiesComponent;
import com.intellij.ide.wizard.CommitStepException;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import org.jetbrains.android.util.AndroidBundle;
import org.jetbrains.android.util.AndroidUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* @author Eugene.Kudelevsky
*/
public abstract class NewKeyForm {
private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.exportSignedPackage.NewKeyForm");
private JPanel myContentPanel;
private JTextField myAliasField;
private JPasswordField myKeyPasswordField;
private JPasswordField myConfirmKeyPasswordField;
private JSpinner myValiditySpinner;
private JTextField myFirstAndLastNameField;
private JTextField myOrganizationUnitField;
private JTextField myCityField;
private JTextField myStateOrProvinceField;
private JTextField myCountryCodeField;
private JPanel myCertificatePanel;
private JTextField myOrganizationField;
private KeyStore myKeyStore;
private PrivateKey myPrivateKey;
private X509Certificate myCertificate;
public NewKeyForm() {
myValiditySpinner.setModel(new SpinnerNumberModel(25, 1, 1000, 1));
}
private int getValidity() {
SpinnerNumberModel model = (SpinnerNumberModel)myValiditySpinner.getModel();
return model.getNumber().intValue();
}
public void init() {
myAliasField.setText(generateAlias());
}
public JPanel getContentPanel() {
return myContentPanel;
}
private boolean findNonEmptyCertificateField() {
for (Component component : myCertificatePanel.getComponents()) {
if (component instanceof JTextField) {
if (((JTextField)component).getText().trim().length() > 0) {
return true;
}
}
}
return false;
}
public void createKey() throws CommitStepException {
if (getKeyAlias().length() == 0) {
throw new CommitStepException(AndroidBundle.message("android.export.package.specify.key.alias.error"));
}
AndroidUtils.checkNewPassword(myKeyPasswordField, myConfirmKeyPasswordField);
if (!findNonEmptyCertificateField()) {
throw new CommitStepException(AndroidBundle.message("android.export.package.specify.certificate.field.error"));
}
doCreateKey();
}
@NotNull
private String generateAlias() {
List<String> aliasList = getExistingKeyAliasList();
String prefix = "key";
if (aliasList == null) {
return prefix + '0';
}
Set<String> aliasSet = new HashSet<String>();
for (String alias : aliasList) {
aliasSet.add(alias.toLowerCase());
}
for (int i = 0; ; i++) {
String alias = prefix + i;
if (!aliasSet.contains(alias)) {
return alias;
}
}
}
@Nullable
protected abstract List<String> getExistingKeyAliasList();
private static void buildDName(StringBuilder builder, String prefix, JTextField textField) {
if (textField != null) {
String value = textField.getText().trim();
if (value.length() > 0) {
if (builder.length() > 0) {
builder.append(",");
}
builder.append(prefix);
builder.append('=');
builder.append(value);
}
}
}
private String getDName() {
StringBuilder builder = new StringBuilder();
buildDName(builder, "CN", myFirstAndLastNameField);
buildDName(builder, "OU", myOrganizationUnitField);
buildDName(builder, "O", myOrganizationField);
buildDName(builder, "L", myCityField);
buildDName(builder, "ST", myStateOrProvinceField);
buildDName(builder, "C", myCountryCodeField);
return builder.toString();
}
private void doCreateKey() throws CommitStepException {
String keystoreLocation = getKeyStoreLocation();
char[] keystorePassword = getKeyStorePassword();
char[] keyPassword = getKeyPassword();
String keyAlias = getKeyAlias();
String dname = getDName();
assert dname != null;
boolean createdStore = false;
final StringBuilder errorBuilder = new StringBuilder();
final StringBuilder outBuilder = new StringBuilder();
try {
createdStore = KeystoreHelper
.createNewStore(keystoreLocation, null, new String(keystorePassword), keyAlias, new String(keyPassword), dname, getValidity(),
new DebugKeyProvider.IKeyGenOutput() {
public void err(String message) {
errorBuilder.append(message).append('\n');
LOG.info("Error: " + message);
}
public void out(String message) {
outBuilder.append(message).append('\n');
LOG.info(message);
}
});
}
catch (Exception e) {
LOG.info(e);
errorBuilder.append(e.getMessage()).append('\n');
}
normalizeBuilder(errorBuilder);
normalizeBuilder(outBuilder);
try {
if (createdStore) {
if (errorBuilder.length() > 0) {
String prefix = AndroidBundle.message("android.create.new.key.error.prefix");
Messages.showErrorDialog(myContentPanel, prefix + '\n' + errorBuilder.toString());
}
}
else {
if (errorBuilder.length() > 0) {
throw new CommitStepException(errorBuilder.toString());
}
if (outBuilder.length() > 0) {
throw new CommitStepException(outBuilder.toString());
}
throw new CommitStepException(AndroidBundle.message("android.cannot.create.new.key.error"));
}
PropertiesComponent.getInstance(getProject()).setValue(KeystoreStep.DEFAULT_KEYSTORE_LOCATION, keystoreLocation);
loadKeystoreAndKey(keystoreLocation, keystorePassword, keyAlias, keyPassword);
}
finally {
Arrays.fill(keystorePassword, '\0');
Arrays.fill(keyPassword, '\0');
}
}
@NotNull
public char[] getKeyPassword() {
return myKeyPasswordField.getPassword();
}
@NotNull
public String getKeyAlias() {
return myAliasField.getText().trim();
}
@NotNull
protected abstract Project getProject();
@NotNull
protected abstract char[] getKeyStorePassword();
@NotNull
protected abstract String getKeyStoreLocation();
private void loadKeystoreAndKey(String keystoreLocation, char[] keystorePassword, String keyAlias, char[] keyPassword)
throws CommitStepException {
FileInputStream fis = null;
try {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
fis = new FileInputStream(new File(keystoreLocation));
keyStore.load(fis, keystorePassword);
myKeyStore = keyStore;
KeyStore.PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(keyAlias, new KeyStore.PasswordProtection(keyPassword));
if (entry == null) {
throw new CommitStepException(AndroidBundle.message("android.extract.package.cannot.find.key.error", keyAlias));
}
PrivateKey privateKey = entry.getPrivateKey();
Certificate certificate = entry.getCertificate();
if (privateKey == null || certificate == null) {
throw new CommitStepException(AndroidBundle.message("android.extract.package.cannot.find.key.error", keyAlias));
}
PropertiesComponent.getInstance(getProject()).setValue(InitialKeyStep.DEFAULT_KEY_ALIAS, keyAlias);
myPrivateKey = privateKey;
myCertificate = (X509Certificate)certificate;
}
catch (Exception e) {
throw new CommitStepException("Error: " + e.getMessage());
}
finally {
if (fis != null) {
try {
fis.close();
}
catch (IOException ignored) {
}
}
}
}
private static void normalizeBuilder(StringBuilder builder) {
if (builder.length() > 0) {
builder.deleteCharAt(builder.length() - 1);
}
}
@Nullable
public KeyStore getKeyStore() {
return myKeyStore;
}
@Nullable
public PrivateKey getPrivateKey() {
return myPrivateKey;
}
@Nullable
public X509Certificate getCertificate() {
return myCertificate;
}
}
@@ -16,79 +16,37 @@
package org.jetbrains.android.exportSignedPackage;
import com.android.jarutils.DebugKeyProvider;
import com.android.jarutils.KeystoreHelper;
import com.intellij.ide.util.PropertiesComponent;
import com.intellij.ide.wizard.CommitStepException;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.ui.Messages;
import org.jetbrains.android.util.AndroidBundle;
import com.intellij.openapi.project.Project;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* @author Eugene.Kudelevsky
*/
public class NewKeyStep extends ExportSignedPackageWizardStep {
private static final Logger LOG = Logger.getInstance("#com.jetbrains.android.exportSignedPackage.NewKeyStep");
private JPanel myContentPanel;
private JTextField myAliasField;
private JPasswordField myKeyPasswordField;
private JPasswordField myConfirmKeyPasswordField;
private JSpinner myValiditySpinner;
private JTextField myFirstAndLastNameField;
private JTextField myOrganizationUnitField;
private JTextField myCityField;
private JTextField myStateOrProvinceField;
private JTextField myCountryCodeField;
private JPanel myCertificatePanel;
private JTextField myOrganizationField;
private final ExportSignedPackageWizard myWizard;
private final NewKeyForm myNewKeyForm;
public NewKeyStep(ExportSignedPackageWizard wizard) {
myWizard = wizard;
myValiditySpinner.setModel(new SpinnerNumberModel(25, 1, 1000, 1));
}
private int getValidity() {
SpinnerNumberModel model = (SpinnerNumberModel)myValiditySpinner.getModel();
return model.getNumber().intValue();
myNewKeyForm = new MyNewKeyForm();
}
@Override
public void _init() {
myAliasField.setText(generateAlias());
myNewKeyForm.init();
}
@Override
public JComponent getComponent() {
return myContentPanel;
}
private boolean findNonEmptyCertificateField() {
for (Component component : myCertificatePanel.getComponents()) {
if (component instanceof JTextField) {
if (((JTextField)component).getText().trim().length() > 0) {
return true;
}
}
}
return false;
return myNewKeyForm.getContentPanel();
}
@Override
@@ -98,157 +56,42 @@ public class NewKeyStep extends ExportSignedPackageWizardStep {
@Override
protected void commitForNext() throws CommitStepException {
if (myAliasField.getText().trim().length() == 0) {
throw new CommitStepException(AndroidBundle.message("android.export.package.specify.key.alias.error"));
}
checkNewPassword(myKeyPasswordField, myConfirmKeyPasswordField);
if (!findNonEmptyCertificateField()) {
throw new CommitStepException(AndroidBundle.message("android.export.package.specify.certificate.field.error"));
}
createKey();
myNewKeyForm.createKey();
final KeyStore keyStore = myNewKeyForm.getKeyStore();
final PrivateKey privateKey = myNewKeyForm.getPrivateKey();
final X509Certificate certificate = myNewKeyForm.getCertificate();
assert keyStore != null && privateKey != null && certificate != null;
myWizard.setKeystore(keyStore);
myWizard.setPrivateKey(privateKey);
myWizard.setCertificate(certificate);
}
@NotNull
private String generateAlias() {
List<String> aliasList = myWizard.getKeyAliasList();
String prefix = "key";
if (aliasList == null) {
return prefix + '0';
private class MyNewKeyForm extends NewKeyForm {
@Override
protected List<String> getExistingKeyAliasList() {
return myWizard.getKeyAliasList();
}
Set<String> aliasSet = new HashSet<String>();
for (String alias : aliasList) {
aliasSet.add(alias.toLowerCase());
}
for (int i = 0; ; i++) {
String alias = prefix + i;
if (!aliasSet.contains(alias)) {
return alias;
}
}
}
private static void buildDName(StringBuilder builder, String prefix, JTextField textField) {
if (textField != null) {
String value = textField.getText().trim();
if (value.length() > 0) {
if (builder.length() > 0) {
builder.append(",");
}
builder.append(prefix);
builder.append('=');
builder.append(value);
}
@NotNull
@Override
protected String getKeyStoreLocation() {
final String location = myWizard.getKeystoreLocation();
assert location != null;
return location;
}
}
private String getDName() {
StringBuilder builder = new StringBuilder();
buildDName(builder, "CN", myFirstAndLastNameField);
buildDName(builder, "OU", myOrganizationUnitField);
buildDName(builder, "O", myOrganizationField);
buildDName(builder, "L", myCityField);
buildDName(builder, "ST", myStateOrProvinceField);
buildDName(builder, "C", myCountryCodeField);
return builder.toString();
}
@NotNull
@Override
protected char[] getKeyStorePassword() {
final char[] password = myWizard.getKeystorePassword();
assert password != null;
return password;
}
private void createKey() throws CommitStepException {
String keystoreLocation = myWizard.getKeystoreLocation();
assert keystoreLocation != null;
char[] keystorePassword = myWizard.getKeystorePassword();
assert keystorePassword != null;
char[] keyPassword = myKeyPasswordField.getPassword();
String keyAlias = myAliasField.getText().trim();
String dname = getDName();
assert dname != null;
boolean createdStore = false;
final StringBuilder errorBuilder = new StringBuilder();
final StringBuilder outBuilder = new StringBuilder();
try {
createdStore = KeystoreHelper
.createNewStore(keystoreLocation, null, new String(keystorePassword), keyAlias, new String(keyPassword), dname, getValidity(),
new DebugKeyProvider.IKeyGenOutput() {
public void err(String message) {
errorBuilder.append(message).append('\n');
LOG.info("Error: " + message);
}
public void out(String message) {
outBuilder.append(message).append('\n');
LOG.info(message);
}
});
}
catch (Exception e) {
LOG.info(e);
errorBuilder.append(e.getMessage()).append('\n');
}
normalizeBuilder(errorBuilder);
normalizeBuilder(outBuilder);
try {
if (createdStore) {
if (errorBuilder.length() > 0) {
String prefix = AndroidBundle.message("android.create.new.key.error.prefix");
Messages.showErrorDialog(myContentPanel, prefix + '\n' + errorBuilder.toString());
}
}
else {
if (errorBuilder.length() > 0) {
throw new CommitStepException(errorBuilder.toString());
}
if (outBuilder.length() > 0) {
throw new CommitStepException(outBuilder.toString());
}
throw new CommitStepException(AndroidBundle.message("android.cannot.create.new.key.error"));
}
PropertiesComponent.getInstance(myWizard.getProject()).setValue(KeystoreStep.DEFAULT_KEYSTORE_LOCATION, keystoreLocation);
loadKeystoreAndKey(keystoreLocation, keystorePassword, keyAlias, keyPassword);
}
finally {
Arrays.fill(keystorePassword, '\0');
Arrays.fill(keyPassword, '\0');
}
}
private void loadKeystoreAndKey(String keystoreLocation, char[] keystorePassword, String keyAlias, char[] keyPassword)
throws CommitStepException {
FileInputStream fis = null;
try {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
fis = new FileInputStream(new File(keystoreLocation));
keyStore.load(fis, keystorePassword);
myWizard.setKeystore(keyStore);
KeyStore.PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(keyAlias, new KeyStore.PasswordProtection(keyPassword));
if (entry == null) {
throw new CommitStepException(AndroidBundle.message("android.extract.package.cannot.find.key.error", keyAlias));
}
PrivateKey privateKey = entry.getPrivateKey();
Certificate certificate = entry.getCertificate();
if (privateKey == null || certificate == null) {
throw new CommitStepException(AndroidBundle.message("android.extract.package.cannot.find.key.error", keyAlias));
}
PropertiesComponent.getInstance(myWizard.getProject()).setValue(InitialKeyStep.DEFAULT_KEY_ALIAS, keyAlias);
myWizard.setPrivateKey(privateKey);
myWizard.setCertificate((X509Certificate)certificate);
}
catch (Exception e) {
throw new CommitStepException("Error: " + e.getMessage());
}
finally {
if (fis != null) {
try {
fis.close();
}
catch (IOException ignored) {
}
}
}
}
private static void normalizeBuilder(StringBuilder builder) {
if (builder.length() > 0) {
builder.deleteCharAt(builder.length() - 1);
@NotNull
@Override
protected Project getProject() {
return myWizard.getProject();
}
}
}
@@ -37,6 +37,7 @@ import com.intellij.facet.FacetManager;
import com.intellij.facet.ModifiableFacetModel;
import com.intellij.facet.ProjectFacetManager;
import com.intellij.ide.util.DefaultPsiElementCellRenderer;
import com.intellij.ide.wizard.CommitStepException;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
@@ -85,10 +86,7 @@ import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.*;
/**
* @author yole, coyote
@@ -714,4 +712,47 @@ public class AndroidUtils {
}
return result;
}
public static void checkNewPassword(JPasswordField passwordField, JPasswordField confirmedPasswordField) throws CommitStepException {
char[] password = passwordField.getPassword();
char[] confirmedPassword = confirmedPasswordField.getPassword();
try {
checkPassword(password);
if (password.length < 6) {
throw new CommitStepException(AndroidBundle.message("android.export.package.incorrect.password.length"));
}
if (!Arrays.equals(password, confirmedPassword)) {
throw new CommitStepException(AndroidBundle.message("android.export.package.passwords.not.match.error"));
}
}
finally {
Arrays.fill(password, '\0');
Arrays.fill(confirmedPassword, '\0');
}
}
public static void checkPassword(char[] password) throws CommitStepException {
if (password.length == 0) {
throw new CommitStepException(AndroidBundle.message("android.export.package.specify.password.error"));
}
}
public static void checkPassword(JPasswordField passwordField) throws CommitStepException {
char[] password = passwordField.getPassword();
try {
checkPassword(password);
}
finally {
Arrays.fill(password, '\0');
}
}
@NotNull
public static <T> List<T> toList(@NotNull Enumeration<T> enumeration) {
final List<T> result = new ArrayList<T>();
while (enumeration.hasMoreElements()) {
result.add(enumeration.nextElement());
}
return result;
}
}