package xapi.mojo.gwt;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DefaultArtifact;
import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.artifact.handler.ArtifactHandler;
import org.apache.maven.artifact.handler.DefaultArtifactHandler;
import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.model.Resource;
import org.apache.maven.plugin.ContextEnabled;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugin.logging.SystemStreamLog;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
import org.eclipse.aether.resolution.ArtifactResult;
import xapi.dev.gwt.gui.CodeServerGui;
import xapi.inject.impl.SingletonProvider;
import xapi.log.X_Log;
import xapi.mojo.api.AbstractXapiMojo;
import xapi.mojo.api.SourceDependency;
import xapi.mvn.X_Maven;
import xapi.util.X_Debug;
import xapi.util.X_Namespace;
import xapi.util.X_Properties;
import xapi.util.X_String;
import xapi.util.api.Pair;
import xapi.util.api.ReceivesValue;
import xapi.util.impl.PairBuilder;
import com.google.gwt.core.shared.GWT;
import javax.swing.*;
import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Map;
/**
* Goal which launches a gui which runs the gwt 2.5 codeserver from maven
* dependencies
* @author <a href="mailto:internetparty@wetheinter.net">Ajax</a>
* @version $Id$
*/
@org.apache.maven.plugins.annotations.Mojo(
name="codeserver"
,requiresDependencyResolution=ResolutionScope.COMPILE_PLUS_RUNTIME
,defaultPhase=LifecyclePhase.COMPILE
,threadSafe=true
)
@SuppressWarnings("serial")
public class CodeServerMojo extends AbstractXapiMojo implements ContextEnabled {
private static final FileFilter gwt_xml_filter = new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.isDirectory() || pathname.getName().endsWith(".gwt.xml");
}
};
/**
* The gwt module to use; if not set, we'll scan the classpath for .gwt.xml
*/
@Parameter(property="gwt.module", defaultValue="")
private String module;
/**
* The gwt version to use; if not set, we'll scan the classpath for gwt dependencies
*/
@Parameter(property="gwt.version")
private String gwtVersion;
/**
* The port on which to start opening codeservers.
*/
@Parameter(property="port",defaultValue="1337")
private Integer port;
/**
* The port on which to listen for a debugger
*/
@Parameter(property="debug.port",defaultValue="0")
private Integer debugPort;
/**
* The amount of time to wait for the debugger; negative values disable debugger.
* default = -1
*/
@Parameter(property="debug.delay", defaultValue="-1")
private Integer debugDelay;
/**
* Whether or not to scan test source paths.
*/
@Parameter(property="xapi.include.test", defaultValue="false")
private Boolean includeTestSource;
@Parameter(property = "gwt.js.interop", defaultValue = "JS")
private String jsInteropMode;
@Parameter(property = "gwt.log.level", defaultValue = "INFO")
private String logLevel;
public String getLogLevel() {
return logLevel;
}
private Log log;
@SuppressWarnings("rawtypes")
private Map pluginContext;
@Override
public void setLog(Log log) {
this.log = log;
}
@Override
public Log getLog() {
if (log == null) {
log = new SystemStreamLog();
}
return log;
}
@Override
@SuppressWarnings("rawtypes")
public Map getPluginContext() {
return pluginContext;
}
@Override
@SuppressWarnings("rawtypes")
public void setPluginContext(Map pluginContext) {
this.pluginContext = pluginContext;
}
private final SingletonProvider<String> _gwtVersion = new SingletonProvider<String>() {
@Override
protected String initialValue() {
if (X_String.isNotEmpty(gwtVersion)) {
return gwtVersion;
}
String version = X_Properties.getProperty("gwt.version");
if (version != null) {
return version;
}
// version = GWT.getVersion();
// if (version != null)
// return version;
return superGuess("com.google.gwt", X_Namespace.GWT_VERSION);
};
};
/**
* Whether or not to compile immediately.
*/
@Parameter(property="xapi.auto.launch", defaultValue="true")
private Boolean autoCompile;
private class CodeServerView extends CodeServerGui {
@Override
protected int getPort() {
return port == null ? super.getPort() : port;
}
@Override
protected int debugTimeout() {
return debugDelay == null ? -1 : debugDelay;
}
@Override
protected int debugPort() {
int delay = debugDelay;
if (delay < 1) {
return 0;
}
return debugPort == 0 ? super.debugPort() : debugPort;
}
@Override
protected LinkedList<String> getSourcePaths(boolean includeTestSources) {
final LinkedList<String> sources = super.getSourcePaths(includeTestSources);
ReceivesValue<String> addSource = new ReceivesValue<String>() {
@Override
public void set(String location) {
if (new File(location).exists()) {
sources.add(new File(location).getAbsolutePath());
} else {
X_Log.trace(getClass(), "Skipping non-existent file @ ", location);
}
}
};
if (hasSourceDependencies()) {
for (SourceDependency sourceDependency : getSourceDependencies()) {
try {
final MavenProject resultProject = findInWorkspace(sourceDependency.getGroupId(),
sourceDependency.getArtifactId());
if (resultProject != null) {
for (String location : resultProject.getCompileSourceRoots()) {
addSource.set(location);
}
addSource.set(resultProject.getBuild().getOutputDirectory());
for (Resource resource : resultProject.getResources()) {
addSource.set(resource.getDirectory()); // We are ignoring excludes. If someone needs them, patches are welcome.
}
if (sourceDependency.isIncludeTests()) {
for (String location : resultProject.getTestCompileSourceRoots()) {
addSource.set(location);
}
addSource.set(resultProject.getBuild().getTestOutputDirectory());
for (Resource resource : resultProject.getTestResources()) {
addSource.set(resource.getDirectory());
}
}
} else {
X_Log.warn(getClass(), "No artifact found in workspace for ", sourceDependency);
}
} catch (Exception e) {
X_Log.warn(getClass(), "Error resolving source paths for ", sourceDependency, e);
}
}
}
return sources;
}
public void keepAlive() throws MojoExecutionException {
final MavenProject project = getProject();
X_Log.info(getClass(),"Preparing gwt recompiler for "+project,"Include test sources? "+isUseTestSources());
if (null != project) {
addSource(project.getBasedir());
LinkedList<String> modules = new LinkedList<String>();
if (isUseTestSources()) {
for (Object o : project.getTestCompileSourceRoots()) {
File f = new File(String.valueOf(o));
if (f.exists()) {
// Add the source location
addTestSource(f);
// also scan for .gwt.xml modules
try {
modules.addAll(findModules(f));
} catch (Exception e) {
getLog()
.warn(
"An error was encountered while searching for .gwt.xml modules",
e);
}
} else {
X_Log.warn(getClass(), "Test source does not exist",f);
}
}
for (Resource o : project.getTestResources()) {
File f = new File(o.getDirectory());
if (f.exists()) {
addTestSource(f);
// scan for .gwt.xml modules; the resources will be on classpath
// already
try {
modules.addAll(findModules(f));
} catch (Exception e) {
getLog()
.warn(
"An error was encountered while searching for .gwt.xml modules",
e);
}
} else {
X_Log.warn(getClass(), "Test resource does not exist",f);
}
}
}
for (Object o : project.getCompileSourceRoots()) {
File f = new File(String.valueOf(o));
if (f.exists()) {
// Add the source location
addSource(f);
// also scan for .gwt.xml modules
try {
modules.addAll(findModules(f));
} catch (Exception e) {
getLog().warn(
"An error was encountered while searching for .gwt.xml modules in "
+ f, e);
}
}
}
for (Resource o : project.getResources()) {
File f = new File(o.getDirectory());
if (f.exists()) {
addSource(f);
// only scan for .gwt.xml modules; the resources will be on classpath
// already
try {
modules.addAll(findModules(f));
} catch (Exception e) {
getLog().warn(
"An error was encountered while searching for .gwt.xml modules in "
+ f, e);
}
}
}
if (modules.size() > 0) {
setModule(modules.get(0));
}
try {
if (isUseTestSources()) {
for (Object o : project.getTestClasspathElements()) {
File f = new File(String.valueOf(o));
if (f.exists()) {
if (f.isDirectory()) {
// directories are to be handled differently
addToTestClasspath(f);
} else {
addTestSource(f);
}
} else {
X_Log.warn(getClass(), "Test classpath element does not exist",f,"from "+o);
}
}
} else {
for (Object o : project.getCompileClasspathElements()) {
File f = new File(String.valueOf(o));
if (f.exists()) {
if (f.isDirectory()) {
// directories are to be handled differently
addToClasspath(f);
} else {
addSource(f);
}
}
}
}
} catch (DependencyResolutionRequiredException e1) {
getLog()
.error(
"Unable to load compile-scoped classpath elements."
+ "\nIf you are extending this plugin, "
+ "you may need to include <requiresDependencyResolution>compile</requiresDependencyResolution> "
+ "in your @Mojo annotation / plugin.xml file", e1);
}
}
setVisible(true);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
String cp = getClasspath(includeTestSource, ":");
gwtLocations.findArtifact(cp, "gwt-dev", ":");
gwtLocations.findArtifact(cp, "gwt-codeserver", ":");
int before = cp.length();
gwtLocations.findArtifact(cp, "gwt-user", ":");
if (before != cp.length()) {
// Missing gwt-user means we're probably missing org.json and validation apis...
}
if (autoCompile) {
launchServer(includeTestSource, jsInteropMode, logLevel);
}
}
});
try {
while (isVisible()) {
Thread.sleep(1000);
}
} catch (Exception e) {
throw new MojoExecutionException("Error while waiting on codeserver", e);
}
}
@Override
protected boolean isUseTestSources() {
return Boolean.TRUE.equals(includeTestSource);
}
@Override
protected Pair<String, Boolean> findGwt(String cp, String cpSep) {
VersionRange versions;
try {
versions = VersionRange.createFromVersionSpec("[2.5.0,)");
} catch (InvalidVersionSpecificationException e) {
throw X_Debug.rethrow(e);
}
ArtifactHandler artifactHandler = new DefaultArtifactHandler("default");
Artifact gwtUser = new DefaultArtifact("com.google.gwt", "gwt-user", versions, "compile", "default", "jar",
artifactHandler);
// Check maven first
Artifact local = getSession().getLocalRepository().find(gwtUser);
if (local != null) {
return PairBuilder.pairOf(local.getFile().getParentFile().getParent(), true);
}
return super.findGwt(cp, cpSep);
}
@Override
protected String getModuleDefault() {
if (!"".equals(module)) {
return module;
}
for (String source : getProject().getCompileSourceRoots()) {
File f = new File(source);
if (f.exists()) {
try {
String module = findModule(f);
if (module != null) {
return module;
}
}catch(Throwable e) {
X_Log.error("Failed lookup of gwt module for ", f, e);
}
}
}
return super.getModuleDefault();
}
protected Collection<String> findModules(File f) throws FileNotFoundException,
XmlPullParserException, IOException {
ArrayList<String> list = new ArrayList<String>();
findModules(f, f, list);
return list;
}
private void findModules(File rootFile, File f, Collection<String> into) throws FileNotFoundException,
XmlPullParserException, IOException {
if (f.isDirectory()) {
for (File child : f.listFiles(gwt_xml_filter)) {
findModules(rootFile, child, into);
}
} else if (f.getName().endsWith(".gwt.xml")) {
getLog().debug("Checking for entry points in " + f);
// try to get entry points
Xpp3Dom dom = Xpp3DomBuilder.build(new FileReader(f));
getLog().debug(dom.toString());
for (Xpp3Dom entry : dom.getChildren("entry-point")) {
String attr = entry.getAttribute("class");
if (null != attr && attr.length() > 0) {
// into.add(attr.substring(0,
// attr.lastIndexOf('.', attr.lastIndexOf('.') - 1))
// + "." + f.getName().replace(".gwt.xml", ""));
String mod = f.getAbsolutePath().substring(rootFile.getAbsolutePath().length()+1);
into.add(mod.replace('/', '.').replace(".gwt.xml", ""));
}
}
}
}
private String findModule(File f) throws FileNotFoundException,
XmlPullParserException, IOException {
if (f.isDirectory()) {
String module;
for (File child : f.listFiles(gwt_xml_filter)) {
module = findModule(child);
if (module != null) {
return module;
}
}
} else if (f.getName().endsWith(".gwt.xml")) {
getLog().debug("Checking for entry points in " + f);
// try to get entry points
Xpp3Dom dom = Xpp3DomBuilder.build(new FileReader(f));
getLog().debug(dom.toString());
for (Xpp3Dom entry : dom.getChildren("entry-point")) {
String attr = entry.getAttribute("class");
if (null != attr && attr.length() > 0) {
return attr.substring(0,
attr.lastIndexOf('.', attr.lastIndexOf('.') - 1))
+ "." + f.getName().replace(".gwt.xml", "");
}
}
}
return null;
}
@Override
protected GwtFinder initFinder() {
return new GwtFinder() {
@Override
public String findArtifact(String cp, String artifact, String cpSep) {
if (cp.contains(artifact)) {
return cp;
}
String location = locateArtifact(cp, artifact, cpSep);
File f = new File(location);
if (f.exists()){
addToClasspath(f);
return f.getAbsolutePath() + cpSep + cp;
}
else {
X_Log.warn("Could not find artifact",artifact,"looked in",f);
return super.findArtifact(cp, artifact, cpSep);
}
}
};
}
protected String locateArtifact(String cp, String artifact, String cpSep) {
ArtifactResult location = X_Maven.loadArtifact("com.google.gwt", artifact, guessVersion("com.google.gwt", "2.6.1"));
if (location.isResolved()) {
return location.getArtifact().getFile().getAbsolutePath();
}
return null;
}
}
/**
* TODO: instead of parsing the objects directly from the maven project,
* assemble a command line argument to pass to {@link Runtime#exec(String)},
* so we can fork a single codeserver gui, save pid w/
* {@link System#setProperty(String, String)}, and send new executions by
* writing to the processes {@link Process#getOutputStream()}.
*
* This will also allow us to launch the gui as a maven goal, or as a java
* executable; both of which can be created as an IDE launch config.
*
*/
@Override
public void doExecute() throws MojoExecutionException, MojoFailureException {
new CodeServerView().keepAlive();
}
protected String superGuess(String groupId, String backup) {
return super.guessVersion(groupId, backup);
}
@Override
public String guessVersion(String groupId, String backup) {
String version = X_Properties.getProperty("gwt.version");
if (version != null) {
return version;
}
version = GWT.getVersion();
if (version != null) {
return version;
}
return superGuess(groupId, backup);
}
}