repo-reorg-2020: moved sts4-relevant spring-ide bundles from https://github.com/spring-projects/spring-ide/releases/tag/REPO-REORG-2020 into sts4 repo
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
|
||||
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
|
||||
<classpathentry kind="src" path="src"/>
|
||||
<classpathentry kind="output" path="bin"/>
|
||||
</classpath>
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.springframework.ide.eclipse.boot.wizard</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.pde.ManifestBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.pde.SchemaBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.pde.PluginNature</nature>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
@@ -0,0 +1,7 @@
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
|
||||
org.eclipse.jdt.core.compiler.compliance=1.8
|
||||
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.source=1.8
|
||||
@@ -0,0 +1,59 @@
|
||||
eclipse.preferences.version=1
|
||||
editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
|
||||
sp_cleanup.add_default_serial_version_id=true
|
||||
sp_cleanup.add_generated_serial_version_id=false
|
||||
sp_cleanup.add_missing_annotations=true
|
||||
sp_cleanup.add_missing_deprecated_annotations=true
|
||||
sp_cleanup.add_missing_methods=false
|
||||
sp_cleanup.add_missing_nls_tags=false
|
||||
sp_cleanup.add_missing_override_annotations=true
|
||||
sp_cleanup.add_missing_override_annotations_interface_methods=true
|
||||
sp_cleanup.add_serial_version_id=false
|
||||
sp_cleanup.always_use_blocks=true
|
||||
sp_cleanup.always_use_parentheses_in_expressions=false
|
||||
sp_cleanup.always_use_this_for_non_static_field_access=false
|
||||
sp_cleanup.always_use_this_for_non_static_method_access=false
|
||||
sp_cleanup.convert_functional_interfaces=false
|
||||
sp_cleanup.convert_to_enhanced_for_loop=false
|
||||
sp_cleanup.correct_indentation=false
|
||||
sp_cleanup.format_source_code=false
|
||||
sp_cleanup.format_source_code_changes_only=false
|
||||
sp_cleanup.insert_inferred_type_arguments=false
|
||||
sp_cleanup.make_local_variable_final=true
|
||||
sp_cleanup.make_parameters_final=false
|
||||
sp_cleanup.make_private_fields_final=true
|
||||
sp_cleanup.make_type_abstract_if_missing_method=false
|
||||
sp_cleanup.make_variable_declarations_final=false
|
||||
sp_cleanup.never_use_blocks=false
|
||||
sp_cleanup.never_use_parentheses_in_expressions=true
|
||||
sp_cleanup.on_save_use_additional_actions=true
|
||||
sp_cleanup.organize_imports=false
|
||||
sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
|
||||
sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
|
||||
sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
|
||||
sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
|
||||
sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
|
||||
sp_cleanup.remove_private_constructors=true
|
||||
sp_cleanup.remove_redundant_type_arguments=false
|
||||
sp_cleanup.remove_trailing_whitespaces=true
|
||||
sp_cleanup.remove_trailing_whitespaces_all=true
|
||||
sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
|
||||
sp_cleanup.remove_unnecessary_casts=false
|
||||
sp_cleanup.remove_unnecessary_nls_tags=false
|
||||
sp_cleanup.remove_unused_imports=false
|
||||
sp_cleanup.remove_unused_local_variables=false
|
||||
sp_cleanup.remove_unused_private_fields=true
|
||||
sp_cleanup.remove_unused_private_members=false
|
||||
sp_cleanup.remove_unused_private_methods=true
|
||||
sp_cleanup.remove_unused_private_types=true
|
||||
sp_cleanup.sort_members=false
|
||||
sp_cleanup.sort_members_all=false
|
||||
sp_cleanup.use_anonymous_class_creation=false
|
||||
sp_cleanup.use_blocks=false
|
||||
sp_cleanup.use_blocks_only_for_return_and_throw=false
|
||||
sp_cleanup.use_lambda=true
|
||||
sp_cleanup.use_parentheses_in_expressions=false
|
||||
sp_cleanup.use_this_for_non_static_field_access=false
|
||||
sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
|
||||
sp_cleanup.use_this_for_non_static_method_access=false
|
||||
sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
|
||||
@@ -0,0 +1,53 @@
|
||||
Manifest-Version: 1.0
|
||||
Bundle-ManifestVersion: 2
|
||||
Bundle-Name: Boot UI Support
|
||||
Bundle-SymbolicName: org.springframework.ide.eclipse.boot.wizard;singleton:=true
|
||||
Bundle-Version: 4.8.1.qualifier
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
|
||||
Require-Bundle: org.eclipse.ui,
|
||||
org.springsource.ide.eclipse.commons.livexp;bundle-version="3.8.4",
|
||||
org.json;bundle-version="1.0.0",
|
||||
org.eclipse.core.resources,
|
||||
com.google.guava,
|
||||
org.eclipse.jdt.ui,
|
||||
org.eclipse.jdt.core,
|
||||
org.springsource.ide.eclipse.commons.frameworks.core,
|
||||
org.springsource.ide.eclipse.commons.core,
|
||||
org.springframework.ide.eclipse.boot,
|
||||
org.apache.commons.codec,
|
||||
org.apache.commons.compress,
|
||||
org.apache.commons.io,
|
||||
org.apache.commons.logging,
|
||||
org.apache.commons.lang3,
|
||||
org.springsource.ide.eclipse.commons.ui,
|
||||
org.eclipse.m2e.core,
|
||||
org.eclipse.m2e.maven.runtime,
|
||||
org.springsource.ide.eclipse.commons.frameworks.ui,
|
||||
org.eclipse.ui.forms,
|
||||
org.eclipse.wst.sse.ui,
|
||||
org.eclipse.jface.text,
|
||||
org.springframework.ide.eclipse.editor.support,
|
||||
com.fasterxml.jackson.core.jackson-annotations;bundle-version="2.6.2",
|
||||
com.fasterxml.jackson.core.jackson-core;bundle-version="2.6.2",
|
||||
com.fasterxml.jackson.core.jackson-databind;bundle-version="2.6.2",
|
||||
org.eclipse.ui.ide,
|
||||
org.eclipse.compare,
|
||||
org.eclipse.core.contenttype,
|
||||
org.springframework.ide.eclipse.maven.pom,
|
||||
org.eclipse.core.expressions
|
||||
Bundle-Activator: org.springframework.ide.eclipse.boot.wizard.BootWizardActivator
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Export-Package: org.springframework.ide.eclipse.boot.wizard,
|
||||
org.springframework.ide.eclipse.boot.wizard.content,
|
||||
org.springframework.ide.eclipse.boot.wizard.github,
|
||||
org.springframework.ide.eclipse.boot.wizard.github.auth,
|
||||
org.springframework.ide.eclipse.boot.wizard.guides,
|
||||
org.springframework.ide.eclipse.boot.wizard.importing,
|
||||
org.springframework.ide.eclipse.boot.wizard.starters,
|
||||
org.springframework.ide.eclipse.boot.wizard.util
|
||||
Bundle-Vendor: Pivotal Inc.
|
||||
Import-Package: org.eclipse.core.runtime,
|
||||
org.eclipse.core.runtime.jobs,
|
||||
org.eclipse.osgi.util,
|
||||
org.osgi.framework
|
||||
Automatic-Module-Name: org.springframework.ide.eclipse.boot.wizard
|
||||
@@ -0,0 +1,5 @@
|
||||
# Specify gradle bundles as optional non greedy
|
||||
#requires.0.namespace=osgi.bundle
|
||||
#requires.0.name=org.glassfish.jersey.inject.jersey-hk2
|
||||
#requires.0.greedy=true
|
||||
#requires.0.optional=false
|
||||
@@ -0,0 +1,248 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
|
||||
<title>About Spring IDE</title>
|
||||
<meta name="author" content="Spring IDE Developers">
|
||||
</head>
|
||||
<body>
|
||||
<h2>About Spring IDE</h2>
|
||||
<p>March 26, 2007</p>
|
||||
<h3>Abstract</h3>
|
||||
<p>Spring IDE is a set of plugins which provide a user interface for
|
||||
<a href="https://www.springframework.org">The Spring Framework</a>'s bean
|
||||
factory XML configuration files including support for Spring 2.0
|
||||
namespaces, AOP configuration and Spring Web Flow.</p>
|
||||
<h3>License</h3>
|
||||
|
||||
<p>The Spring IDE Project makes available all content in this
|
||||
plug-in ("Content"). Unless otherwise indicated below, the
|
||||
Content is provided to you under the terms and conditions of the Eclipse
|
||||
Public License Version 1.0 ("EPL"). A copy of the EPL is
|
||||
available at <a href="https://www.eclipse.org/legal/epl-v10.html">https://www.eclipse.org/legal/epl-v10.html</a>.
|
||||
For purposes of the EPL, "Program" will mean the Content.</p>
|
||||
|
||||
<p>If you did not receive this Content directly from the Spring IDE
|
||||
Project, the Content is being redistributed by another party
|
||||
("Redistributor") and different terms and conditions may apply
|
||||
to your use of any object code in the Content. Check the Redistributor's
|
||||
license that was provided with the Content. If no such license exists,
|
||||
contact the Redistributor. Unless otherwise indicated below, the terms
|
||||
and conditions of the EPL still apply to any source code in the Content
|
||||
and such source code may be obtained at <a href="https://www.eclipse.org">https://www.eclipse.org</a>.</p>
|
||||
|
||||
<h3>Third Party Content</h3>
|
||||
<p>The Content includes items that have been sourced from third
|
||||
parties as set out below. If you did not receive this Content directly
|
||||
from the Spring IDE Project, the following is provided for informational
|
||||
purposes only, and you should look to the Redistributor's license for
|
||||
terms and conditions of use.</p>
|
||||
<p><em> <strong>The Spring Framework v3.0.5</strong>
|
||||
<p>This product includes software developed by the Spring Framework
|
||||
Project (<a href="https://www.springframework.org">https://www.springframework.org</a>).</p>
|
||||
|
||||
<p><em> <strong>Spring Web Flow v2.0.7.A</strong>
|
||||
<p>This product includes software developed by the Spring Framework
|
||||
Project (<a href="https://www.springframework.org">https://www.springframework.org</a>).</p>
|
||||
|
||||
<p><em> <strong>Spring Web Services v1.5.8</strong>
|
||||
<p>This product includes software developed by the Spring Framework
|
||||
Project (<a href="https://www.springframework.org">https://www.springframework.org</a>).</p>
|
||||
|
||||
<p><em> <strong>Spring Security v3.0.2</strong>
|
||||
<p>This product includes software developed by the Spring Framework
|
||||
Project (<a href="https://www.springframework.org">https://www.springframework.org</a>).</p>
|
||||
|
||||
<p><em> <strong>Spring Dynamic Modules for OSGi(TM) Runtimes v2.0.0.M2</strong>
|
||||
<p>This product includes software developed by the Spring Framework
|
||||
Project (<a href="https://www.springframework.org">https://www.springframework.org</a>).</p>
|
||||
|
||||
<p><em> <strong>Spring Batch v2.1.0</strong>
|
||||
<p>This product includes software developed by the Spring Framework
|
||||
Project (<a href="https://www.springframework.org">https://www.springframework.org</a>).</p>
|
||||
|
||||
<p><em> <strong>Commons Codec v1.3.0</strong>
|
||||
<p>This product includes software developed by The Apache Software
|
||||
Foundation (<a href="https://www.apache.org/">https://www.apache.org/</a>).</p>
|
||||
|
||||
<p><em> <strong>Commons Collections v3.2.0</strong>
|
||||
<p>This product includes software developed by The Apache Software
|
||||
Foundation (<a href="https://www.apache.org/">https://www.apache.org/</a>).</p>
|
||||
|
||||
<p><em> <strong>Commons Logging v1.1.1</strong>
|
||||
<p>This product includes software developed by The Apache Software
|
||||
Foundation (<a href="https://www.apache.org/">https://www.apache.org/</a>).</p>
|
||||
|
||||
<p><em> <strong>Apache MyFaces v1.2.2</strong>
|
||||
<p>This product includes software developed by The Apache Software
|
||||
Foundation (<a href="https://www.apache.org/">https://www.apache.org/</a>).</p>
|
||||
|
||||
<p><em> <strong>AspectJ Weaver v1.6.10</strong>
|
||||
<p>This product includes software developed by The Eclipse Software
|
||||
Foundation (<a href="https://www.eclipse.org/">https://www.eclipse.org/</a>).</p>
|
||||
|
||||
<p><em> <strong>Antlr v3.0.1</strong>
|
||||
<p>Spring IDE includes a binary version of Antlr v3.0.1 (<a
|
||||
href="https://www.antlr.org">https://www.antlr.org/</a>).
|
||||
The source code for Antlr is available from the Antlr download site at
|
||||
<a href="https://www.antlr.org/download.html">https://www.antlr.org/download.html</a>.
|
||||
|
||||
<p>The Antlr license is available at <a
|
||||
href="https://www.antlr.org/license.html">https://www.antlr.org/license.html</a>.
|
||||
The license is also reproduced here:</p>
|
||||
|
||||
<pre>Copyright (c) 2003-2007, Terence Parr
|
||||
All rights reserved.
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright notice, this list of conditions
|
||||
and the following disclaimer.
|
||||
Redistributions in binary form must reproduce the above copyright notice, this list of conditions
|
||||
and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
Neither the name of the author nor the names of its contributors may be used to endorse or promote
|
||||
products derived from this software without specific prior written permission.
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
|
||||
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
||||
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
</pre></p>
|
||||
|
||||
|
||||
<p><em> <strong>ASM v2.2.3</strong>
|
||||
<p>Spring IDE includes a binary version of ASM v2.2.3 (<a
|
||||
href="https://asm.objectweb.org/index.html">https://asm.objectweb.org/</a>).
|
||||
The source code for ASM is available from the ObjectWeb download site at
|
||||
<a href="https://asm.objectweb.org/download/">https://asm.objectweb.org/download/</a>.
|
||||
|
||||
<p>The ASM license is available at <a
|
||||
href="https://asm.objectweb.org/license.html">https://asm.objectweb.org/license.html</a>.
|
||||
The license is also reproduced here:</p>
|
||||
|
||||
<pre>Copyright (c) 2000-2005 INRIA, France Telecom
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holders nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||
THE POSSIBILITY OF SUCH DAMAGE.
|
||||
</pre></p>
|
||||
|
||||
|
||||
<p><em> <strong>backport-util-concurrent 3.1.0</strong>
|
||||
<p>Spring IDE includes a binary version of backport-util-concurrent v3.1.0 (<a
|
||||
href="https://dcl.mathcs.emory.edu/util/backport-util-concurrent/">https://dcl.mathcs.emory.edu/util/backport-util-concurrent/</a>).
|
||||
The source code for backport-util-concurrent is available from the download site at
|
||||
<a href="https://dcl.mathcs.emory.edu/util/backport-util-concurrent/dist/">https://dcl.mathcs.emory.edu/util/backport-util-concurrent/dist/</a>.
|
||||
|
||||
|
||||
<p>The backport-util-concurrent license is available at <a
|
||||
href="https://creativecommons.org/licenses/publicdomain">https://creativecommons.org/licenses/publicdomain</a>.
|
||||
The license is also reproduced here:</p>
|
||||
|
||||
<pre><em>Copyright-Only Dedication (based on United States law) or Public Domain Certification</em>
|
||||
|
||||
The person or persons who have associated work with this document (the
|
||||
"Dedicator" or "Certifier") hereby either (a) certifies that, to the
|
||||
best of his knowledge, the work of authorship identified is in the public
|
||||
domain of the country from which the work is published, or (b) hereby
|
||||
dedicates whatever copyright the dedicators holds in the work of authorship
|
||||
identified below (the "Work") to the public domain. A certifier, moreover,
|
||||
dedicates any copyright interest he may have in the associated work, and
|
||||
for these purposes, is described as a "dedicator" below.
|
||||
|
||||
A certifier has taken reasonable steps to verify the copyright status of
|
||||
this work. Certifier recognizes that his good faith efforts may not shield
|
||||
him from liability if in fact the work certified is not in the public domain.
|
||||
|
||||
Dedicator makes this dedication for the benefit of the public at large and
|
||||
to the detriment of the Dedicator's heirs and successors. Dedicator intends
|
||||
this dedication to be an overt act of relinquishment in perpetuity of all
|
||||
present and future rights under copyright law, whether vested or contingent,
|
||||
in the Work. Dedicator understands that such relinquishment of all rights
|
||||
includes the relinquishment of all rights to enforce (by lawsuit or otherwise)
|
||||
those copyrights in the Work.
|
||||
|
||||
Dedicator recognizes that, once placed in the public domain, the Work may be
|
||||
freely reproduced, distributed, transmitted, used, modified, built upon, or
|
||||
otherwise exploited by anyone for any purpose, commercial or non-commercial,
|
||||
and in any way, including by methods that have not yet been invented or
|
||||
conceived.
|
||||
</pre></p>
|
||||
|
||||
<p><em> <strong>AOP Alliance</strong>
|
||||
<p>LICENCE: all the source code provided by AOP Alliance is Public Domain.
|
||||
</p>
|
||||
|
||||
<p><em> <strong>javax.el v1.0.0</strong>
|
||||
<p>This product includes software distributed by Sun (meanwhile Oracle) under CDDL license
|
||||
(<a href="https://www.sun.com/cddl/cddl.html">https://www.sun.com/cddl/cddl.html</a>).</p>
|
||||
|
||||
<p><em> <strong>javax.jms v1.1.0</strong>
|
||||
<p>This product includes software distributed by Sun (meanwhile Oracle) under CDDL license
|
||||
(<a href="https://www.sun.com/cddl/cddl.html">https://www.sun.com/cddl/cddl.html</a>).</p>
|
||||
|
||||
<p><em> <strong>javax.servlet v2.5.0</strong>
|
||||
<p>This product includes software distributed by Sun (meanwhile Oracle) under CDDL license
|
||||
(<a href="https://www.sun.com/cddl/cddl.html">https://www.sun.com/cddl/cddl.html</a>).</p>
|
||||
|
||||
<p><em> <strong>javax.servlet.jsp v2.1.0</strong>
|
||||
<p>This product includes software distributed by Sun (meanwhile Oracle) under CDDL license
|
||||
(<a href="https://www.sun.com/cddl/cddl.html">https://www.sun.com/cddl/cddl.html</a>).</p>
|
||||
|
||||
<p><em> <strong>javax.servlet.jsp.jstl v1.2.0</strong>
|
||||
<p>This product includes software distributed by Sun (meanwhile Oracle) under CDDL license
|
||||
(<a href="https://www.sun.com/cddl/cddl.html">https://www.sun.com/cddl/cddl.html</a>).</p>
|
||||
|
||||
<p><em> <strong>javax.activation v1.1.0</strong>
|
||||
<p>This product includes software distributed by Sun (meanwhile Oracle) under CDDL license
|
||||
(<a href="https://www.sun.com/cddl/cddl.html">https://www.sun.com/cddl/cddl.html</a>).</p>
|
||||
|
||||
<p><em> <strong>javax.annotation v1.0.0</strong>
|
||||
<p>This product includes software distributed by Sun (meanwhile Oracle) under CDDL license
|
||||
(<a href="https://www.sun.com/cddl/cddl.html">https://www.sun.com/cddl/cddl.html</a>).</p>
|
||||
|
||||
<p><em> <strong>javax.mail v1.4.0</strong>
|
||||
<p>This product includes software distributed by Sun (meanwhile Oracle) under CDDL license
|
||||
(<a href="https://www.sun.com/cddl/cddl.html">https://www.sun.com/cddl/cddl.html</a>).</p>
|
||||
|
||||
<p><em> <strong>javax.xml.stream v1.0.1</strong>
|
||||
<p>This product includes software distributed by Sun (meanwhile Oracle) under CDDL license
|
||||
(<a href="https://www.sun.com/cddl/cddl.html">https://www.sun.com/cddl/cddl.html</a>).</p>
|
||||
|
||||
<p><em> <strong>javax.xml.ws v2.1.0</strong>
|
||||
<p>This product includes software distributed by Sun (meanwhile Oracle) under CDDL license
|
||||
(<a href="https://www.sun.com/cddl/cddl.html">https://www.sun.com/cddl/cddl.html</a>).</p>
|
||||
|
||||
</em></p>
|
||||
|
||||
|
||||
<address>Copyright (c) 2005, 2011 Spring IDE Developers</address>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,8 @@
|
||||
source.. = src/
|
||||
output.. = bin/
|
||||
bin.includes = META-INF/,\
|
||||
.,\
|
||||
about.html,\
|
||||
plugin.xml,\
|
||||
schema/,\
|
||||
icons/
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 617 B |
Binary file not shown.
|
After Width: | Height: | Size: 3.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1,252 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<?eclipse version="3.3"?>
|
||||
<!--
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016 Spring IDE Developers
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Spring IDE Developers - initial API and implementation
|
||||
*******************************************************************************/
|
||||
-->
|
||||
<plugin>
|
||||
<extension-point id="org.springframework.ide.eclipse.wizard.import.strategy" name="org.springframework.ide.eclipse.wizard.import.strategy" schema="schema/org.springframework.ide.eclipse.wizard.import.strategy.exsd"/>
|
||||
|
||||
<extension
|
||||
point="org.eclipse.ui.newWizards">
|
||||
<wizard
|
||||
category="org.springsource.ide.eclipse.boot.wizards.category"
|
||||
icon="icons/full/etool16/new_template_project_obj.png"
|
||||
class="org.springframework.ide.eclipse.boot.wizard.NewSpringBootWizard"
|
||||
hasPages="true"
|
||||
id="org.springsource.ide.eclipse.commons.gettingstarted.wizard.boot.NewSpringBootWizard"
|
||||
name="Spring Starter Project"
|
||||
project="true">
|
||||
<description>Create new Spring Starter Project</description>
|
||||
</wizard>
|
||||
|
||||
<wizard
|
||||
category="org.springsource.ide.eclipse.boot.wizards.category"
|
||||
class="org.springframework.ide.eclipse.boot.wizard.guides.GSImportWizard"
|
||||
icon="icons/full/etool16/new_template_project_obj.png"
|
||||
id="org.springsource.ide.eclipse.gettingstarted.wizards.import.generic.newalias"
|
||||
name="Import Spring Getting Started Content"
|
||||
project="true">
|
||||
<description>Import Spring Getting Started Content</description>
|
||||
<selection class="org.eclipse.core.resources.IResource"/>
|
||||
</wizard>
|
||||
|
||||
<category
|
||||
id="org.springsource.ide.eclipse.boot.wizards.category"
|
||||
name="Spring Boot">
|
||||
</category>
|
||||
|
||||
</extension>
|
||||
|
||||
<extension
|
||||
point="org.eclipse.ui.perspectiveExtensions">
|
||||
<perspectiveExtension
|
||||
targetID="org.eclipse.jdt.ui.JavaPerspective">
|
||||
<newWizardShortcut
|
||||
id="org.springsource.ide.eclipse.commons.gettingstarted.wizard.boot.NewSpringBootWizard">
|
||||
</newWizardShortcut>
|
||||
<newWizardShortcut
|
||||
id="org.springsource.ide.eclipse.gettingstarted.wizards.import.generic.newalias">
|
||||
</newWizardShortcut>
|
||||
</perspectiveExtension>
|
||||
<perspectiveExtension
|
||||
targetID="org.eclipse.ui.resourcePerspective">
|
||||
<newWizardShortcut
|
||||
id="org.springsource.ide.eclipse.commons.gettingstarted.wizard.boot.NewSpringBootWizard">
|
||||
</newWizardShortcut>
|
||||
<newWizardShortcut
|
||||
id="org.springsource.ide.eclipse.gettingstarted.wizards.import.generic.newalias">
|
||||
</newWizardShortcut>
|
||||
</perspectiveExtension>
|
||||
<perspectiveExtension
|
||||
targetID="org.eclipse.jst.j2ee.J2EEPerspective">
|
||||
<newWizardShortcut
|
||||
id="org.springsource.ide.eclipse.commons.gettingstarted.wizard.boot.NewSpringBootWizard">
|
||||
</newWizardShortcut>
|
||||
<newWizardShortcut
|
||||
id="org.springsource.ide.eclipse.gettingstarted.wizards.import.generic.newalias">
|
||||
</newWizardShortcut>
|
||||
</perspectiveExtension>
|
||||
<perspectiveExtension
|
||||
targetID="org.eclipse.jdt.ui.JavaBrowsingPerspective">
|
||||
<newWizardShortcut
|
||||
id="org.springsource.ide.eclipse.commons.gettingstarted.wizard.boot.NewSpringBootWizard">
|
||||
</newWizardShortcut>
|
||||
<newWizardShortcut
|
||||
id="org.springsource.ide.eclipse.gettingstarted.wizards.import.generic.newalias">
|
||||
</newWizardShortcut>
|
||||
</perspectiveExtension>
|
||||
<perspectiveExtension
|
||||
targetID="org.eclipse.jdt.ui.JavaHierarchyPerspective">
|
||||
<newWizardShortcut
|
||||
id="org.springsource.ide.eclipse.commons.gettingstarted.wizard.boot.NewSpringBootWizard">
|
||||
</newWizardShortcut>
|
||||
<newWizardShortcut
|
||||
id="org.springsource.ide.eclipse.gettingstarted.wizards.import.generic.newalias">
|
||||
</newWizardShortcut>
|
||||
</perspectiveExtension>
|
||||
</extension>
|
||||
|
||||
<extension
|
||||
point="org.eclipse.ui.handlers">
|
||||
<handler
|
||||
class="org.springframework.ide.eclipse.boot.wizard.AddBootStartersHandler"
|
||||
commandId="org.springframework.ide.eclipse.boot.commands.editStartersCommand">
|
||||
<enabledWhen>
|
||||
<and>
|
||||
<count
|
||||
value="1">
|
||||
</count>
|
||||
<iterate>
|
||||
<or>
|
||||
<adapt
|
||||
type="org.eclipse.core.resources.IResource">
|
||||
<and>
|
||||
<test
|
||||
forcePluginActivation="true"
|
||||
property="org.springsource.ide.eclipse.boot.isBootResource">
|
||||
</test>
|
||||
<or>
|
||||
<adapt
|
||||
type="org.eclipse.core.resources.IProject">
|
||||
<or>
|
||||
<test
|
||||
property="org.eclipse.core.resources.projectNature"
|
||||
value="org.eclipse.m2e.core.maven2Nature">
|
||||
</test>
|
||||
<and>
|
||||
<test
|
||||
property="org.eclipse.core.resources.projectNature"
|
||||
value="org.eclipse.buildship.core.gradleprojectnature">
|
||||
</test>
|
||||
</and>
|
||||
</or>
|
||||
</adapt>
|
||||
<test
|
||||
property="org.eclipse.core.resources.name"
|
||||
value="pom.xml">
|
||||
</test>
|
||||
</or>
|
||||
</and>
|
||||
</adapt>
|
||||
</or>
|
||||
</iterate>
|
||||
</and>
|
||||
</enabledWhen>
|
||||
</handler>
|
||||
</extension>
|
||||
|
||||
|
||||
<extension
|
||||
point="org.eclipse.ui.menus">
|
||||
<menuContribution
|
||||
allPopups="false"
|
||||
locationURI="popup:org.springframework.ide.eclipse.ui.tools?after=boot">
|
||||
<command
|
||||
commandId="org.springframework.ide.eclipse.boot.commands.editStartersCommand"
|
||||
icon="platform:/plugin/org.springframework.ide.eclipse.boot/resources/icons/boot-18x16.png"
|
||||
id="org.springframework.ide.eclipse.boot.menus.editStarters"
|
||||
label="Add Starters"
|
||||
mnemonic="S"
|
||||
style="push">
|
||||
<visibleWhen
|
||||
checkEnabled="false">
|
||||
<and>
|
||||
<count
|
||||
value="1">
|
||||
</count>
|
||||
<iterate>
|
||||
<or>
|
||||
<adapt
|
||||
type="org.eclipse.core.resources.IResource">
|
||||
<and>
|
||||
<test
|
||||
forcePluginActivation="true"
|
||||
property="org.springsource.ide.eclipse.boot.isBootResource">
|
||||
</test>
|
||||
<or>
|
||||
<adapt
|
||||
type="org.eclipse.core.resources.IProject">
|
||||
<or>
|
||||
<test
|
||||
property="org.eclipse.core.resources.projectNature"
|
||||
value="org.eclipse.m2e.core.maven2Nature">
|
||||
</test>
|
||||
<test
|
||||
property="org.eclipse.core.resources.projectNature"
|
||||
value="org.eclipse.buildship.core.gradleprojectnature">
|
||||
</test>
|
||||
</or>
|
||||
</adapt>
|
||||
<test
|
||||
property="org.eclipse.core.resources.name"
|
||||
value="pom.xml">
|
||||
</test>
|
||||
</or>
|
||||
</and>
|
||||
</adapt>
|
||||
<and>
|
||||
<instanceof
|
||||
value="org.eclipse.jdt.core.IJavaProject">
|
||||
</instanceof>
|
||||
<or>
|
||||
<test
|
||||
property="org.springsource.ide.eclipse.commons.java.projectNature"
|
||||
value="org.eclipse.m2e.core.maven2Nature">
|
||||
</test>
|
||||
<test
|
||||
property="org.springsource.ide.eclipse.commons.java.projectNature"
|
||||
value="org.eclipse.buildship.core.gradleprojectnature">
|
||||
</test>
|
||||
</or>
|
||||
<test
|
||||
forcePluginActivation="true"
|
||||
property="org.springsource.ide.eclipse.boot.javaelement.isInBootProject">
|
||||
</test>
|
||||
</and>
|
||||
</or>
|
||||
</iterate>
|
||||
</and>
|
||||
</visibleWhen>
|
||||
</command>
|
||||
</menuContribution>
|
||||
</extension>
|
||||
<extension
|
||||
point="org.eclipse.wst.sse.ui.completionProposal">
|
||||
<proposalCategory
|
||||
icon="icons/full/etool16/boot-icon.png"
|
||||
id="org.springframework.ide.eclipse.boot.proposalCategory"
|
||||
name="Boot">
|
||||
</proposalCategory>
|
||||
<proposalComputer
|
||||
activate="true"
|
||||
categoryId="org.springframework.ide.eclipse.boot.proposalCategory"
|
||||
class="org.springframework.ide.eclipse.boot.wizard.BootPomCompletionProposalComputer"
|
||||
icon="icons/full/etool16/boot-icon.png"
|
||||
id="org.springframework.ide.eclipse.boot.wizard.PomCompletionProposalComputer"
|
||||
name="Boot Pom Completions">
|
||||
<contentType
|
||||
id="org.eclipse.m2e.core.pomFile">
|
||||
</contentType>
|
||||
</proposalComputer>
|
||||
</extension>
|
||||
<extension
|
||||
point="org.eclipse.ui.preferencePages">
|
||||
<page
|
||||
category="org.springframework.ide.eclipse.boot.ui.preferences.InitializrPreferencePage"
|
||||
class="org.springframework.ide.eclipse.boot.wizard.preferences.AddStartersWizardPreferencePage"
|
||||
id="org.springframework.ide.eclipse.boot.wizard.initializr"
|
||||
name="Add Starters">
|
||||
</page>
|
||||
</extension>
|
||||
|
||||
|
||||
</plugin>
|
||||
@@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot.ide</groupId>
|
||||
<artifactId>org.springframework.boot.ide</artifactId>
|
||||
<version>4.8.1-SNAPSHOT</version>
|
||||
<relativePath>../../eclipse-distribution/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<groupId>org.springframework.ide.eclipse</groupId>
|
||||
<artifactId>org.springframework.ide.eclipse.boot.wizard</artifactId>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.eclipse.tycho</groupId>
|
||||
<artifactId>target-platform-configuration</artifactId>
|
||||
<version>${tycho-version}</version>
|
||||
<configuration>
|
||||
<resolver>p2</resolver>
|
||||
<pomDependencies>ignore</pomDependencies>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.eclipse.tycho</groupId>
|
||||
<artifactId>tycho-compiler-plugin</artifactId>
|
||||
<version>${tycho-version}</version>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.eclipse.tycho</groupId>
|
||||
<artifactId>tycho-source-plugin</artifactId>
|
||||
<version>${tycho-version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>plugin-source</id>
|
||||
<phase>generate-sources</phase>
|
||||
<goals>
|
||||
<goal>plugin-source</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.eclipse.tycho</groupId>
|
||||
<artifactId>tycho-p2-plugin</artifactId>
|
||||
<version>${tycho-version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>second-generate-p2-metadata</id>
|
||||
<goals>
|
||||
<goal>p2-metadata</goal>
|
||||
</goals>
|
||||
<phase>verify</phase>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,130 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Schema file written by PDE -->
|
||||
<schema targetNamespace="org.springframework.ide.eclipse.wizard" xmlns="http://www.w3.org/2001/XMLSchema">
|
||||
<annotation>
|
||||
<appInfo>
|
||||
<meta.schema plugin="org.springframework.ide.eclipse.wizard" id="org.springframework.ide.eclipse.wizard.import.strategy" name="org.springframework.ide.eclipse.wizard.import.strategy"/>
|
||||
</appInfo>
|
||||
<documentation>
|
||||
[Enter description of this extension point.]
|
||||
</documentation>
|
||||
</annotation>
|
||||
|
||||
<element name="extension">
|
||||
<annotation>
|
||||
<appInfo>
|
||||
<meta.element />
|
||||
</appInfo>
|
||||
</annotation>
|
||||
<complexType>
|
||||
<sequence minOccurs="1" maxOccurs="unbounded">
|
||||
<element ref="import-strategy"/>
|
||||
</sequence>
|
||||
<attribute name="point" type="string" use="required">
|
||||
<annotation>
|
||||
<documentation>
|
||||
|
||||
</documentation>
|
||||
</annotation>
|
||||
</attribute>
|
||||
<attribute name="id" type="string">
|
||||
<annotation>
|
||||
<documentation>
|
||||
|
||||
</documentation>
|
||||
</annotation>
|
||||
</attribute>
|
||||
<attribute name="name" type="string">
|
||||
<annotation>
|
||||
<documentation>
|
||||
|
||||
</documentation>
|
||||
<appInfo>
|
||||
<meta.attribute translatable="true"/>
|
||||
</appInfo>
|
||||
</annotation>
|
||||
</attribute>
|
||||
</complexType>
|
||||
</element>
|
||||
|
||||
<element name="import-strategy">
|
||||
<annotation>
|
||||
<documentation>
|
||||
Contributes a strategy for importing content that has a given BuildType,
|
||||
</documentation>
|
||||
</annotation>
|
||||
<complexType>
|
||||
<attribute name="buildType" type="string" use="required">
|
||||
<annotation>
|
||||
<documentation>
|
||||
Must be the name one of the BuildType enum constants.
|
||||
</documentation>
|
||||
</annotation>
|
||||
</attribute>
|
||||
<attribute name="class" type="string">
|
||||
<annotation>
|
||||
<documentation>
|
||||
|
||||
</documentation>
|
||||
<appInfo>
|
||||
<meta.attribute kind="java" basedOn=":org.springframework.ide.eclipse.wizard.gettingstarted.content.ImportStrategyFactory"/>
|
||||
</appInfo>
|
||||
</annotation>
|
||||
</attribute>
|
||||
<attribute name="notInstalledMessage" type="string">
|
||||
<annotation>
|
||||
<documentation>
|
||||
Message to show to the user when the strategy can not be instantiated, presumably because some required plugins are not installed.
|
||||
|
||||
Typically this message should provide a hint to the user on what they need to install to be able to use the strategy.
|
||||
</documentation>
|
||||
</annotation>
|
||||
</attribute>
|
||||
<attribute name="name" type="string" use="required">
|
||||
<annotation>
|
||||
<documentation>
|
||||
A name for the strategy. This is meant to distinguish strategies to the user when more than one strategy exists for the same build type.
|
||||
</documentation>
|
||||
</annotation>
|
||||
</attribute>
|
||||
</complexType>
|
||||
</element>
|
||||
|
||||
<annotation>
|
||||
<appInfo>
|
||||
<meta.section type="since"/>
|
||||
</appInfo>
|
||||
<documentation>
|
||||
[Enter the first release in which this extension point appears.]
|
||||
</documentation>
|
||||
</annotation>
|
||||
|
||||
<annotation>
|
||||
<appInfo>
|
||||
<meta.section type="examples"/>
|
||||
</appInfo>
|
||||
<documentation>
|
||||
[Enter extension point usage example here.]
|
||||
</documentation>
|
||||
</annotation>
|
||||
|
||||
<annotation>
|
||||
<appInfo>
|
||||
<meta.section type="apiinfo"/>
|
||||
</appInfo>
|
||||
<documentation>
|
||||
[Enter API information here.]
|
||||
</documentation>
|
||||
</annotation>
|
||||
|
||||
<annotation>
|
||||
<appInfo>
|
||||
<meta.section type="implementation"/>
|
||||
</appInfo>
|
||||
<documentation>
|
||||
[Enter information about supplied implementation of this extension point.]
|
||||
</documentation>
|
||||
</annotation>
|
||||
|
||||
|
||||
</schema>
|
||||
@@ -0,0 +1,107 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.livexp.ui;
|
||||
|
||||
import org.eclipse.core.runtime.Assert;
|
||||
import org.eclipse.jface.layout.GridDataFactory;
|
||||
import org.eclipse.jface.layout.GridLayoutFactory;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.graphics.Point;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Control;
|
||||
import org.eclipse.swt.widgets.Layout;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.DelegatingLiveExp;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.LiveExpression;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.UIValueListener;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.ValidationResult;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.IPageSection;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.IPageWithSections;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.ReflowableSection;
|
||||
|
||||
public class DynamicSection extends ReflowableSection {
|
||||
|
||||
public static final Point DEFAULT_MIN_SIZE = new Point(500, 500);
|
||||
|
||||
private LiveExpression<IPageSection> content;
|
||||
private Composite composite;
|
||||
|
||||
private final DelegatingLiveExp<ValidationResult> validator = new DelegatingLiveExp<>();
|
||||
|
||||
private Point minSize = DEFAULT_MIN_SIZE;
|
||||
private Integer widthHint = DEFAULT_MIN_SIZE.y;
|
||||
private Integer heightHint = DEFAULT_MIN_SIZE.x;
|
||||
|
||||
public DynamicSection(IPageWithSections owner, LiveExpression<IPageSection> content) {
|
||||
super(owner);
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createContents(Composite page) {
|
||||
composite = new Composite(page, SWT.NONE);
|
||||
Layout l = GridLayoutFactory.fillDefaults().margins(0, 0).create();
|
||||
composite.setLayout(l);
|
||||
// To set a minimum size in general and avoid the parent composite from being too small and having scrollbars before the dynamic content is created,
|
||||
// set a minimum size. This means setting both minSize and hint, along with grab, for it to work properly.
|
||||
GridData gd = GridDataFactory.fillDefaults().grab(true, true).minSize(minSize).create();
|
||||
if (widthHint!=null) {
|
||||
gd.widthHint = widthHint;
|
||||
}
|
||||
if (heightHint!=null) {
|
||||
gd.heightHint = heightHint;
|
||||
}
|
||||
composite.setLayoutData(gd);
|
||||
|
||||
content.addListener(UIValueListener.from((e, newContents) -> updateContent(newContents)));
|
||||
}
|
||||
|
||||
public DynamicSection setMinimumSize(Point minSize) {
|
||||
Assert.isLegal(minSize != null);
|
||||
this.minSize = minSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DynamicSection setWidthHint(Integer sz) {
|
||||
this.widthHint = sz;
|
||||
return this;
|
||||
}
|
||||
public DynamicSection setHeightHint(Integer sz) {
|
||||
this.heightHint = sz;
|
||||
return this;
|
||||
}
|
||||
|
||||
private void updateContent(IPageSection newContents) {
|
||||
if (composite!=null && !composite.isDisposed()) {
|
||||
validator.setDelegate(newContents.getValidator());
|
||||
for (Control oldWidget : composite.getChildren()) {
|
||||
oldWidget.dispose();
|
||||
}
|
||||
newContents.createContents(composite);
|
||||
reflow(owner, composite);
|
||||
} else {
|
||||
validator.setDelegate(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public LiveExpression<ValidationResult> getValidator() {
|
||||
return validator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
//Detach validator wiring from nested validator (if any is still attached).
|
||||
validator.setDelegate(null);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2020 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard;
|
||||
|
||||
import org.eclipse.core.commands.AbstractHandler;
|
||||
import org.eclipse.core.commands.ExecutionEvent;
|
||||
import org.eclipse.core.commands.ExecutionException;
|
||||
import org.eclipse.jface.dialogs.MessageDialog;
|
||||
import org.eclipse.jface.viewers.IStructuredSelection;
|
||||
import org.eclipse.swt.widgets.Shell;
|
||||
import org.eclipse.ui.handlers.HandlerUtil;
|
||||
import org.springframework.ide.eclipse.boot.wizard.starters.AddStartersWizard;
|
||||
import org.springsource.ide.eclipse.commons.livexp.util.ExceptionUtil;
|
||||
import org.springsource.ide.eclipse.commons.livexp.util.Log;
|
||||
|
||||
public class AddBootStartersHandler extends AbstractHandler {
|
||||
|
||||
/**
|
||||
* the command has been executed, so extract extract the needed information
|
||||
* from the application context.
|
||||
*/
|
||||
@Override
|
||||
public Object execute(ExecutionEvent event) throws ExecutionException {
|
||||
Shell activeShell = HandlerUtil.getActiveShell(event);
|
||||
try {
|
||||
IStructuredSelection selection = HandlerUtil.getCurrentStructuredSelection(event);
|
||||
if (selection!=null) {
|
||||
AddStartersWizard.openFor(activeShell, selection);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
Log.log(e);
|
||||
MessageDialog.openError(activeShell, "Error", ExceptionUtil.getMessage(e));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2020 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard;
|
||||
|
||||
import static org.springframework.ide.eclipse.boot.wizard.BootWizardImages.BOOT_SMALL_ICON;
|
||||
import static org.springframework.ide.eclipse.boot.wizard.BootWizardImages.getImage;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.core.runtime.IProgressMonitor;
|
||||
import org.eclipse.jface.text.IDocument;
|
||||
import org.eclipse.jface.text.contentassist.ICompletionProposal;
|
||||
import org.eclipse.jface.text.contentassist.IContextInformation;
|
||||
import org.eclipse.swt.graphics.Image;
|
||||
import org.eclipse.swt.graphics.Point;
|
||||
import org.eclipse.ui.PlatformUI;
|
||||
import org.eclipse.wst.sse.ui.contentassist.CompletionProposalInvocationContext;
|
||||
import org.eclipse.wst.sse.ui.contentassist.ICompletionProposalComputer;
|
||||
import org.springframework.ide.eclipse.boot.core.BootPropertyTester;
|
||||
import org.springframework.ide.eclipse.boot.util.Log;
|
||||
import org.springframework.ide.eclipse.boot.wizard.starters.AddStartersWizard;
|
||||
import org.springframework.ide.eclipse.editor.support.util.DocumentUtil;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
public class BootPomCompletionProposalComputer implements ICompletionProposalComputer {
|
||||
|
||||
Image BOOT_ICN = getImage(BOOT_SMALL_ICON);
|
||||
|
||||
public BootPomCompletionProposalComputer() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sessionStarted() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List computeCompletionProposals(CompletionProposalInvocationContext context, IProgressMonitor monitor) {
|
||||
IProject project = DocumentUtil.getProject(context.getDocument());
|
||||
if (BootPropertyTester.isBootProject(project)) {
|
||||
return ImmutableList.of(editStartersCompletion(project));
|
||||
}
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
private ICompletionProposal editStartersCompletion(IProject project) {
|
||||
return new ICompletionProposal() {
|
||||
|
||||
@Override
|
||||
public void apply(IDocument document) {
|
||||
try {
|
||||
AddStartersWizard.openFor(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), project);
|
||||
} catch (CoreException e) {
|
||||
Log.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point getSelection(IDocument document) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAdditionalProposalInfo() {
|
||||
return "Open 'Add Spring Starters' Dialog";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayString() {
|
||||
return "Add Starters...";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Image getImage() {
|
||||
return BOOT_ICN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IContextInformation getContextInformation() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public List computeContextInformation(CompletionProposalInvocationContext context, IProgressMonitor monitor) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getErrorMessage() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sessionEnded() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard;
|
||||
|
||||
import org.eclipse.core.runtime.IStatus;
|
||||
import org.eclipse.core.runtime.Status;
|
||||
import org.eclipse.ui.plugin.AbstractUIPlugin;
|
||||
import org.osgi.framework.BundleContext;
|
||||
|
||||
public class BootWizardActivator extends AbstractUIPlugin {
|
||||
|
||||
public static final String PLUGIN_ID = "org.springframework.ide.eclipse.boot.wizard"; //$NON-NLS-1$
|
||||
private static BootWizardActivator plugin;
|
||||
|
||||
public BootWizardActivator() {
|
||||
}
|
||||
|
||||
public void start(BundleContext context) throws Exception {
|
||||
super.start(context);
|
||||
plugin = this;
|
||||
}
|
||||
|
||||
public void stop(BundleContext context) throws Exception {
|
||||
plugin = null;
|
||||
super.stop(context);
|
||||
}
|
||||
|
||||
public static BootWizardActivator getDefault() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
public static IStatus createErrorStatus(Throwable exception) {
|
||||
String message = exception.getMessage();
|
||||
if (message == null) {
|
||||
message = "";
|
||||
}
|
||||
return new Status(IStatus.ERROR, PLUGIN_ID, 0, message, exception);
|
||||
}
|
||||
|
||||
public static IStatus createInfoStatus(String msg) {
|
||||
return new Status(IStatus.INFO, PLUGIN_ID, msg);
|
||||
}
|
||||
|
||||
public static void log(Throwable e) {
|
||||
getDefault().getLog().log(createErrorStatus(e));
|
||||
}
|
||||
|
||||
public static void info(String msg) {
|
||||
getDefault().getLog().log(createInfoStatus(msg));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2012 VMware, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* VMware, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
import org.eclipse.jface.resource.ImageDescriptor;
|
||||
import org.eclipse.jface.resource.ImageRegistry;
|
||||
import org.eclipse.swt.graphics.Image;
|
||||
|
||||
/**
|
||||
* @author Terry Denney
|
||||
*/
|
||||
public class BootWizardImages {
|
||||
|
||||
private static final String WIZBAN = "wizban";
|
||||
private static ImageRegistry imageRegistry;
|
||||
private static URL baseURL = null;
|
||||
|
||||
public static final ImageDescriptor BOOT_SMALL_ICON= create("etool16", "boot-icon.png");
|
||||
public static final ImageDescriptor BOOT_WIZARD_ICON = create(WIZBAN, "boot_wizard.png");
|
||||
public static final ImageDescriptor GUIDES_WIZARD_ICON = create(WIZBAN, "guides_wizard.png");
|
||||
|
||||
private static ImageDescriptor create(String prefix, String name) {
|
||||
try {
|
||||
return ImageDescriptor.createFromURL(makeIconFileURL(prefix, name));
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
return ImageDescriptor.getMissingImageDescriptor();
|
||||
}
|
||||
}
|
||||
|
||||
public static Image getImage(ImageDescriptor imageDescriptor) {
|
||||
ImageRegistry imageRegistry = getImageRegistry();
|
||||
Image image = imageRegistry.get("" + imageDescriptor.hashCode());
|
||||
if (image == null) {
|
||||
image = imageDescriptor.createImage(true);
|
||||
imageRegistry.put("" + imageDescriptor.hashCode(), image);
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
private static ImageRegistry getImageRegistry() {
|
||||
if (imageRegistry == null) {
|
||||
imageRegistry = new ImageRegistry();
|
||||
}
|
||||
return imageRegistry;
|
||||
}
|
||||
|
||||
private static void initBaseURL() {
|
||||
if (baseURL == null) {
|
||||
baseURL = BootWizardActivator.getDefault().getBundle().getEntry("/icons/full/");
|
||||
}
|
||||
}
|
||||
|
||||
private static URL makeIconFileURL(String prefix, String name) throws MalformedURLException {
|
||||
initBaseURL();
|
||||
if (baseURL == null) {
|
||||
throw new MalformedURLException();
|
||||
}
|
||||
|
||||
StringBuffer buffer = new StringBuffer(prefix);
|
||||
buffer.append("/");
|
||||
buffer.append(name);
|
||||
return new URL(baseURL, buffer.toString());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,347 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013, 2017 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.eclipse.core.runtime.Assert;
|
||||
import org.eclipse.jface.layout.GridDataFactory;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.events.SelectionEvent;
|
||||
import org.eclipse.swt.events.SelectionListener;
|
||||
import org.eclipse.swt.graphics.Point;
|
||||
import org.eclipse.swt.layout.FillLayout;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
import org.eclipse.swt.widgets.Button;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Group;
|
||||
import org.springsource.ide.eclipse.commons.core.util.StringUtil;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.LiveExpression;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.LiveVariable;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.ValidationResult;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.Validator;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.ValueListener;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.CommentSection;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.IPageWithSections;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.WizardPageSection;
|
||||
import org.springsource.ide.eclipse.commons.livexp.util.Filter;
|
||||
import org.springsource.ide.eclipse.commons.ui.HtmlTooltip;
|
||||
|
||||
/**
|
||||
* A section that shows a series of checkboxes in rows and columns.
|
||||
* <p>
|
||||
* The section has an optional label. If the label is provided then
|
||||
* the section will be created as a labeled group with a bounding border
|
||||
* around it. Otherwise a plain composite will be used to host the
|
||||
* rows/columns of checkboxes.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class CheckBoxesSection<T> extends WizardPageSection {
|
||||
|
||||
public static class CheckBoxModel<T> {
|
||||
private final String label;
|
||||
private final LiveVariable<Boolean> selection;
|
||||
private final LiveExpression<Boolean> enablement;
|
||||
private final T value;
|
||||
private Supplier<String> tooltipHtml; //optional HTML tooltip supplier
|
||||
private String requirementTooltip; //optional, a user-readable string explaining the requirements (this can be shown as a explanation to the user for a disabled checkbox).
|
||||
public CheckBoxModel(String label, T value, LiveVariable<Boolean> selection, LiveExpression<Boolean> enablement) {
|
||||
this.label = label;
|
||||
this.value = value;
|
||||
this.selection = selection;
|
||||
this.enablement = enablement;
|
||||
}
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
public LiveVariable<Boolean> getSelection() {
|
||||
return selection;
|
||||
}
|
||||
public LiveExpression<Boolean> getEnablement() {
|
||||
return enablement;
|
||||
}
|
||||
public Supplier<String> getTooltipHtml() {
|
||||
return tooltipHtml;
|
||||
}
|
||||
public void setTooltipHtml(Supplier<String> tooltipHtml) {
|
||||
this.tooltipHtml = tooltipHtml;
|
||||
}
|
||||
public T getValue() {
|
||||
return value;
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CheckBox("+label+", "+getSelection().getValue()+")" ;
|
||||
}
|
||||
public String getRequirementTooltip() {
|
||||
return requirementTooltip;
|
||||
}
|
||||
public void setRequirementTooltip(String requirementTooltip) {
|
||||
this.requirementTooltip = requirementTooltip;
|
||||
}
|
||||
}
|
||||
|
||||
private List<CheckBoxModel<T>> model;
|
||||
private Composite composite;
|
||||
private WizardPageSection[] subsections;
|
||||
private int numCols;
|
||||
private String label;
|
||||
|
||||
public CheckBoxesSection(IPageWithSections owner, List<CheckBoxModel<T>> model, String label) {
|
||||
super(owner);
|
||||
this.label = label;
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
public CheckBoxesSection(IPageWithSections owner, List<CheckBoxModel<T>> model) {
|
||||
this(owner, model, /*label*/null);
|
||||
}
|
||||
|
||||
public CheckBoxesSection<T> columns(int howMany) {
|
||||
Assert.isLegal(howMany>0);
|
||||
this.numCols = howMany;
|
||||
return this;
|
||||
}
|
||||
|
||||
private static class CheckBox<T> extends WizardPageSection {
|
||||
|
||||
private Button cb;
|
||||
private CheckBoxModel<T> model;
|
||||
private LiveVariable<Boolean> isVisible = new LiveVariable<>(true);
|
||||
private ValueListener<Boolean> selectionListener;
|
||||
private ValueListener<Boolean> enablementListener;
|
||||
|
||||
public CheckBox(IPageWithSections owner, CheckBoxModel<T> model) {
|
||||
super(owner);
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LiveExpression<ValidationResult> getValidator() {
|
||||
return Validator.OK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createContents(Composite page) {
|
||||
if (page!=null && !page.isDisposed()) {
|
||||
Composite composite = new Composite(page, SWT.NONE);
|
||||
composite.setLayout(new FillLayout());
|
||||
this.cb = new Button(composite, SWT.CHECK);
|
||||
cb.setText(model.getLabel());
|
||||
|
||||
Supplier<String> html = model.getTooltipHtml();
|
||||
if (html != null) {
|
||||
// Setup HTML tooltip and its content
|
||||
{
|
||||
HtmlTooltip tooltip = new HtmlTooltip(cb);
|
||||
tooltip.setMaxSize(400, 400);
|
||||
tooltip.setHtml(html);
|
||||
}
|
||||
String requirements = model.getRequirementTooltip();
|
||||
if (StringUtil.hasText(requirements)) {
|
||||
//The tooltip on the composite will only be shown when the checkbox widget itself is disabled:
|
||||
//See https://stackoverflow.com/questions/39096291/tooltip-is-not-visible-for-a-disabled-control-in-java-swt
|
||||
composite.setToolTipText(requirements);
|
||||
}
|
||||
}
|
||||
|
||||
model.getSelection().addListener(selectionListener = new ValueListener<Boolean>() {
|
||||
@Override
|
||||
public void gotValue(LiveExpression<Boolean> exp, Boolean value) {
|
||||
// note: checkbox button may be null in cases where the
|
||||
// model keeps changing in the container section and the selection
|
||||
// listener is notified after the button was disposed
|
||||
if (value!=null && cb != null && !cb.isDisposed()) {
|
||||
cb.setSelection(value);
|
||||
}
|
||||
}
|
||||
});
|
||||
GridDataFactory.fillDefaults().grab(true, false).applyTo(composite);
|
||||
cb.addSelectionListener(new SelectionListener() {
|
||||
@Override
|
||||
public void widgetSelected(SelectionEvent e) {
|
||||
handleSelection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void widgetDefaultSelected(SelectionEvent e) {
|
||||
handleSelection();
|
||||
}
|
||||
|
||||
private void handleSelection() {
|
||||
model.getSelection().setValue(cb.getSelection());
|
||||
}
|
||||
});
|
||||
LiveExpression<Boolean> enablement = model.getEnablement();
|
||||
if (enablement!=null) {
|
||||
enablement.addListener(enablementListener = new ValueListener<Boolean>() {
|
||||
@Override
|
||||
public void gotValue(LiveExpression<Boolean> exp, Boolean value) {
|
||||
if (value!=null) {
|
||||
cb.setEnabled(value);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
isVisible.addListener(new ValueListener<Boolean>() {
|
||||
@Override
|
||||
public void gotValue(LiveExpression<Boolean> exp, Boolean reveal) {
|
||||
if (reveal!=null && composite !=null && !composite.isDisposed()) {
|
||||
composite.setVisible(reveal);
|
||||
GridData data = (GridData) composite.getLayoutData();
|
||||
data.exclude = !reveal;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
if (cb!=null && !cb.isDisposed()) {
|
||||
cb.dispose();
|
||||
cb = null;
|
||||
}
|
||||
if (selectionListener != null) {
|
||||
model.getSelection().removeListener(selectionListener);
|
||||
}
|
||||
if (enablementListener != null) {
|
||||
model.getEnablement().removeListener(enablementListener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filter and return whether this widget's visibility has changed as a result.
|
||||
* @return Whether visibility of this widget changed.
|
||||
*/
|
||||
public boolean applyFilter(Filter<T> filter) {
|
||||
boolean wasVisible = isVisible.getValue();
|
||||
isVisible.setValue(filter.accept(model.getValue()));
|
||||
boolean changed = wasVisible != isVisible.getValue();
|
||||
return changed;
|
||||
}
|
||||
|
||||
public boolean isVisible() {
|
||||
return isVisible.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CheckBox("+model.getLabel()+")";
|
||||
}
|
||||
}
|
||||
|
||||
protected GridLayout createLayout() {
|
||||
return new GridLayout(numCols, true);
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public LiveExpression<ValidationResult> getValidator() {
|
||||
// return model.getValidator();
|
||||
// }
|
||||
|
||||
@Override
|
||||
public void createContents(Composite page) {
|
||||
composite = createComposite(page);
|
||||
GridLayout layout = createLayout();
|
||||
layout.marginHeight = 0;
|
||||
composite.setLayout(layout);
|
||||
GridDataFactory.fillDefaults().grab(true, false).applyTo(composite);
|
||||
createSubsections();
|
||||
}
|
||||
|
||||
private void createSubsections() {
|
||||
subsections = new WizardPageSection[Math.max(1, model.size())];
|
||||
|
||||
// GridData gd = (GridData) group.getLayoutData();
|
||||
// boolean visible = checkboxes.length>0;
|
||||
// gd.exclude = !visible;
|
||||
|
||||
if (model.isEmpty()) {
|
||||
// don't leave section empty it looks ugly
|
||||
subsections[0] = new CommentSection(owner, "No choices available");
|
||||
subsections[0].createContents(composite);
|
||||
}
|
||||
|
||||
for (int i = 0; i < model.size(); i++) {
|
||||
subsections[i] = new CheckBox<>(owner, model.get(i));
|
||||
subsections[i].createContents(composite);
|
||||
}
|
||||
}
|
||||
|
||||
protected Composite createComposite(Composite page) {
|
||||
if (this.label!=null) {
|
||||
Group comp = new Group(page, SWT.NONE);
|
||||
comp.setText(label);
|
||||
return comp;
|
||||
} else {
|
||||
return new Composite(page, SWT.NONE);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean applyFilter(Filter<T> filter) {
|
||||
if (subsections!=null) {
|
||||
boolean visibilityChanged = false;
|
||||
for (WizardPageSection subsection : subsections) {
|
||||
if (subsection instanceof CheckBox) {
|
||||
@SuppressWarnings("unchecked")
|
||||
CheckBox<T> cb = (CheckBox<T>) subsection;
|
||||
visibilityChanged |= cb.applyFilter(filter);
|
||||
}
|
||||
}
|
||||
if (visibilityChanged) {
|
||||
composite.layout(true);
|
||||
}
|
||||
return visibilityChanged;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean hasVisible() {
|
||||
if (subsections!=null) {
|
||||
for (WizardPageSection s : subsections) {
|
||||
if (s instanceof CheckBox) {
|
||||
@SuppressWarnings("unchecked")
|
||||
CheckBox<T> cb = ((CheckBox<T>) s);
|
||||
if (cb.isVisible()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isCreated() {
|
||||
return composite != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
|
||||
if (subsections != null) {
|
||||
for (WizardPageSection subsection : subsections) {
|
||||
subsection.dispose();
|
||||
}
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public void setModel(List<CheckBoxModel<T>> model) {
|
||||
dispose();
|
||||
this.model = model;
|
||||
createSubsections();
|
||||
composite.layout();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Pivotal Software, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal Software, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jface.layout.GridDataFactory;
|
||||
import org.eclipse.jface.layout.GridLayoutFactory;
|
||||
import org.eclipse.jface.viewers.ArrayContentProvider;
|
||||
import org.eclipse.jface.viewers.ILabelProvider;
|
||||
import org.eclipse.jface.viewers.ISelection;
|
||||
import org.eclipse.jface.viewers.ISelectionChangedListener;
|
||||
import org.eclipse.jface.viewers.IStructuredSelection;
|
||||
import org.eclipse.jface.viewers.SelectionChangedEvent;
|
||||
import org.eclipse.jface.viewers.StructuredSelection;
|
||||
import org.eclipse.jface.viewers.TableViewer;
|
||||
import org.eclipse.jface.viewers.Viewer;
|
||||
import org.eclipse.jface.viewers.ViewerFilter;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.events.PaintEvent;
|
||||
import org.eclipse.swt.events.PaintListener;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Control;
|
||||
import org.springframework.ide.eclipse.boot.core.initializr.InitializrServiceSpec.Dependency;
|
||||
import org.springframework.ide.eclipse.boot.wizard.CheckBoxesSection.CheckBoxModel;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.LiveExpression;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.SelectionModel;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.UIValueListener;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.ValidationResult;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.IPageWithSections;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.SimpleLabelProvider;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.WizardPageSection;
|
||||
import org.springsource.ide.eclipse.commons.livexp.util.Filter;
|
||||
|
||||
/**
|
||||
* This is one of the columns in a three-column version of the Spring starter
|
||||
* wizard: column 1 is the category selection section.
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
@Deprecated
|
||||
public class ColumnSectionForCategories extends WizardPageSection {
|
||||
|
||||
private SelectionModel<String> selection;
|
||||
private String[] categories; //The elements to choose from
|
||||
private final NewSpringBootWizardModel model;
|
||||
|
||||
private TableViewer viewer;
|
||||
private ILabelProvider labelProvider = new SimpleLabelProvider();
|
||||
List<String> hiddenCategories = new ArrayList<>();
|
||||
|
||||
public ColumnSectionForCategories(IPageWithSections owner, NewSpringBootWizardModel model, SelectionModel<String> selection) {
|
||||
super(owner);
|
||||
this.selection = selection;
|
||||
this.model = model;
|
||||
this.categories = model.dependencies.getCategories().toArray(new String[]{});
|
||||
|
||||
model.getDependencyFilter().addListener(new UIValueListener<Filter<Dependency>>() {
|
||||
@Override
|
||||
protected void uiGotValue(
|
||||
LiveExpression<Filter<Dependency>> exp,
|
||||
Filter<Dependency> value
|
||||
) {
|
||||
applyFilter(value);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public LiveExpression<ValidationResult> getValidator() {
|
||||
return selection.validator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createContents(Composite page) {
|
||||
Composite area = area(page);
|
||||
|
||||
viewer = new TableViewer(area, SWT.SINGLE|SWT.BORDER|SWT.V_SCROLL);
|
||||
viewer.addFilter(new ChoicesFilter());
|
||||
viewer.setLabelProvider(labelProvider);
|
||||
viewer.setContentProvider(ArrayContentProvider.getInstance());
|
||||
viewer.setInput(categories);
|
||||
|
||||
GridDataFactory grab = GridDataFactory.fillDefaults().grab(true, true);
|
||||
grab.applyTo(viewer.getTable());
|
||||
|
||||
|
||||
whenVisible(viewer.getControl(), () -> setSelectionInViewer());
|
||||
|
||||
viewer.addSelectionChangedListener(new ISelectionChangedListener() {
|
||||
public void selectionChanged(SelectionChangedEvent event) {
|
||||
ISelection sel = viewer.getSelection();
|
||||
if (sel instanceof IStructuredSelection){
|
||||
IStructuredSelection ss = (IStructuredSelection) sel;
|
||||
String val = (String)ss.getFirstElement();
|
||||
if (val != null) {
|
||||
selection.selection.setValue(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected Composite area(Composite parent) {
|
||||
|
||||
Composite area = new Composite(parent, SWT.NONE);
|
||||
GridLayoutFactory.fillDefaults().numColumns(1).applyTo(area);
|
||||
GridDataFactory.fillDefaults().grab(true, true).applyTo(area);
|
||||
|
||||
return area;
|
||||
}
|
||||
|
||||
protected void setSelectionInViewer() {
|
||||
if (viewer != null && !viewer.getControl().isDisposed()) {
|
||||
String preSelect = selection.selection.getValue();
|
||||
if (preSelect != null) {
|
||||
viewer.setSelection(new StructuredSelection(preSelect), true);
|
||||
} else {
|
||||
viewer.setSelection(StructuredSelection.EMPTY, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void whenVisible(final Control control, final Runnable runnable) {
|
||||
PaintListener l = new PaintListener() {
|
||||
public void paintControl(PaintEvent e) {
|
||||
runnable.run();
|
||||
control.removePaintListener(this);
|
||||
}
|
||||
};
|
||||
control.addPaintListener(l);
|
||||
}
|
||||
|
||||
private void applyFilter(Filter<Dependency> filter) {
|
||||
hiddenCategories.clear();
|
||||
if (viewer != null && !viewer.getControl().isDisposed()) {
|
||||
|
||||
|
||||
for (String cat : model.dependencies.getCategories()) {
|
||||
|
||||
MultiSelectionFieldModel<Dependency> dependencyGroup = model.dependencies.getContents(cat);
|
||||
List<CheckBoxModel<Dependency>> checkBoxModels = dependencyGroup.getCheckBoxModels();
|
||||
boolean showCategory = false;
|
||||
|
||||
if (checkBoxModels != null) {
|
||||
for (CheckBoxModel<Dependency> checkBoxModel : checkBoxModels) {
|
||||
// If filter applies to at least one item, show the category
|
||||
if (applyFilterOnItem(filter, checkBoxModel)) {
|
||||
showCategory = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!showCategory) {
|
||||
hiddenCategories.add(cat);
|
||||
}
|
||||
|
||||
}
|
||||
setSelectionInViewer();
|
||||
viewer.refresh(true);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean applyFilterOnItem(Filter<Dependency> filter, CheckBoxModel<Dependency> model) {
|
||||
return filter.accept(model.getValue());
|
||||
}
|
||||
|
||||
private class ChoicesFilter extends ViewerFilter {
|
||||
|
||||
public ChoicesFilter() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean select(Viewer viewer, Object parentElement, Object element) {
|
||||
String label = labelProvider.getText(element);
|
||||
if (match(label)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean match(String categoryLabel) {
|
||||
return !hiddenCategories.contains(categoryLabel);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016 Pivotal Software, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.core.runtime.Assert;
|
||||
import org.eclipse.jface.preference.IPreferenceStore;
|
||||
import org.springframework.ide.eclipse.boot.core.initializr.InitializrServiceSpec.Dependency;
|
||||
import org.springframework.ide.eclipse.boot.wizard.CheckBoxesSection.CheckBoxModel;
|
||||
|
||||
/**
|
||||
* Component responsible for storing/loading dependencies selected by default.
|
||||
* It is also capable to select the default dependencies if appropriate model is
|
||||
* passed.
|
||||
*
|
||||
* @author Alex Boyko
|
||||
*
|
||||
*/
|
||||
public class DefaultDependencies {
|
||||
|
||||
private static final String PREF_DEFAULT_DEPENDENCIES = "dependencies";
|
||||
|
||||
private static final String DEPENDECIES_DELIMITER = " ";
|
||||
|
||||
private String PREFIX = PreferredSelections.class.getName()+".";
|
||||
|
||||
private IPreferenceStore store;
|
||||
|
||||
public DefaultDependencies(IPreferenceStore store) {
|
||||
Assert.isNotNull(store);
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
protected String key(String id) {
|
||||
String key = PREFIX+id;
|
||||
return key;
|
||||
}
|
||||
|
||||
private void put(String id, String value) {
|
||||
String key = key(id);
|
||||
store.setValue(key, value);
|
||||
}
|
||||
|
||||
private String get(String name, String dflt) {
|
||||
String key = key(name);
|
||||
String v = store.getString(key);
|
||||
if (StringUtils.isNotBlank(v)) {
|
||||
return v;
|
||||
}
|
||||
return dflt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores currently selection dependencies as default dependencies
|
||||
*
|
||||
* @param model dependency selection model
|
||||
* @return <code>true</code> if dependencies were stored, i.e. currently stored are different from the current ones.
|
||||
*/
|
||||
public boolean save(HierarchicalMultiSelectionFieldModel<Dependency> model) {
|
||||
Set<String> ids = getDependciesIdSet();
|
||||
String[] currentSelection = model.getCurrentSelection().stream().map(Dependency::getId).toArray(String[]::new);
|
||||
if (ids.size() == currentSelection.length && ids.containsAll(Arrays.asList(currentSelection))) {
|
||||
// Nothing to store no changes detected
|
||||
return false;
|
||||
} else {
|
||||
// Store dependencies ids as current selection has differences from currently stored dependencies
|
||||
put(PREF_DEFAULT_DEPENDENCIES, String.join(DEPENDECIES_DELIMITER, currentSelection));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects stored default dependencies on the passed selection model
|
||||
*
|
||||
* @param model dependency selection model
|
||||
*/
|
||||
public void restore(HierarchicalMultiSelectionFieldModel<Dependency> model) {
|
||||
Arrays.asList(get(PREF_DEFAULT_DEPENDENCIES, "").split(DEPENDECIES_DELIMITER)).forEach(id -> {
|
||||
model.getCategories().stream().filter(category -> {
|
||||
Optional<Dependency> matchedDependency = Arrays.asList(model.getContents(category).getChoices()).stream().filter(dependency -> {
|
||||
return id.equals(dependency.getId());
|
||||
}).findFirst();
|
||||
matchedDependency.ifPresent(d -> {
|
||||
model.setSelection(category, d, true);
|
||||
});
|
||||
return matchedDependency.isPresent();
|
||||
}).findFirst();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds check-box models from the selection model corresponding to stored default dependencies (ids)
|
||||
*
|
||||
* @param model dependency selection model
|
||||
* @return check-box models of stored default dependencies
|
||||
*/
|
||||
public List<CheckBoxModel<Dependency>> getDependencies(HierarchicalMultiSelectionFieldModel<Dependency> model) {
|
||||
Set<String> ids = getDependciesIdSet();
|
||||
List<CheckBoxModel<Dependency>> dependencies = Collections.synchronizedList(new ArrayList<>(ids.size()));
|
||||
model.getCategories().parallelStream().forEach(category -> {
|
||||
model.getContents(category).getCheckBoxModels().stream().filter(checkboxModel -> {
|
||||
return ids.contains(checkboxModel.getValue().getId());
|
||||
}).forEach(dependencies::add);
|
||||
});
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set of stored default dependencies
|
||||
*
|
||||
* @return ids of default dependencies
|
||||
*/
|
||||
public Set<String> getDependciesIdSet() {
|
||||
return new HashSet<>(Arrays.asList(get(PREF_DEFAULT_DEPENDENCIES, "").split(DEPENDECIES_DELIMITER)));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015, 2016 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.ide.eclipse.boot.core.initializr.InitializrServiceSpec.Dependency;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.FilterBoxModel;
|
||||
import org.springsource.ide.eclipse.commons.livexp.util.Filter;
|
||||
import org.springsource.ide.eclipse.commons.livexp.util.Filters;
|
||||
|
||||
public class DependencyFilterBox extends FilterBoxModel<Dependency> {
|
||||
|
||||
@Override
|
||||
protected Filter<Dependency> createFilterForInput(String _text) {
|
||||
if (StringUtils.isNotBlank(_text)) {
|
||||
final String text = _text.toLowerCase();
|
||||
return (dependency) ->
|
||||
matches(text, dependency.getName())
|
||||
|| matches(text, dependency.getDescription());
|
||||
}
|
||||
return Filters.acceptAll();
|
||||
}
|
||||
|
||||
protected boolean matches(String pattern, Dependency dep) {
|
||||
return matches(pattern, dep.getName())
|
||||
|| matches(pattern, dep.getDescription());
|
||||
}
|
||||
|
||||
protected boolean matches(String pattern, String text) {
|
||||
if (text!=null) {
|
||||
return text.toLowerCase().contains(pattern);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.jface.resource.JFaceResources;
|
||||
import org.eclipse.jface.util.Util;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.graphics.FontData;
|
||||
import org.springframework.ide.eclipse.boot.core.initializr.InitializrServiceSpec;
|
||||
import org.springframework.ide.eclipse.boot.core.initializr.InitializrServiceSpec.Dependency;
|
||||
import org.springframework.ide.eclipse.boot.core.initializr.InitializrServiceSpec.Link;
|
||||
import org.springframework.ide.eclipse.boot.core.initializr.InitializrServiceSpec.Links;
|
||||
import org.springsource.ide.eclipse.commons.core.util.StringUtil;
|
||||
import org.springsource.ide.eclipse.commons.livexp.util.Log;
|
||||
import org.springsource.ide.eclipse.commons.ui.HTMLPrinter;
|
||||
import org.springframework.ide.eclipse.boot.util.version.Version;
|
||||
import org.springframework.ide.eclipse.boot.util.version.VersionParser;
|
||||
import org.springframework.ide.eclipse.boot.util.version.VersionRange;
|
||||
|
||||
/**
|
||||
* Utility class containing static method for creating HTML help/tooltip content for dependency
|
||||
*
|
||||
* @author Alex Boyko
|
||||
*
|
||||
*/
|
||||
public class DependencyTooltipContent {
|
||||
|
||||
private static final String UNIT; // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=155993
|
||||
static {
|
||||
UNIT= Util.isMac() ? "px" : "pt"; //$NON-NLS-1$//$NON-NLS-2$
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates HTML documentation for a dependency
|
||||
*
|
||||
* @param dep Dependency model
|
||||
* @param variables Map containing values for variables
|
||||
* @return HTML as a <code>string</code>
|
||||
*/
|
||||
public static String generateHtmlDocumentation(Dependency dep, Map<String, String> variables) {
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
HTMLPrinter.insertPageProlog(buffer, 0, String.join("\n", styles()));
|
||||
|
||||
Links links = dep.getLinks();
|
||||
// HTML Tooltip initial size calculations can't calculate <br/> height normally, can't tell new line jump from empty line
|
||||
// Therefore use <p> if links are present, otherwise just plain text
|
||||
if (links == null) {
|
||||
buffer.append(HTMLPrinter.convertToHTMLContent(dep.getDescription()));
|
||||
} else {
|
||||
buffer.append("<p>");
|
||||
buffer.append(HTMLPrinter.convertToHTMLContent(dep.getDescription()));
|
||||
buffer.append("</p>");
|
||||
if (links.getGuides() != null) {
|
||||
String bullets = linkBullets(links.getGuides(), variables);
|
||||
if (!bullets.isEmpty()) {
|
||||
HTMLPrinter.addSmallHeader(buffer, "Guides");
|
||||
HTMLPrinter.startBulletList(buffer);
|
||||
buffer.append(bullets);
|
||||
HTMLPrinter.endBulletList(buffer);
|
||||
}
|
||||
}
|
||||
if (links.getReferences() != null) {
|
||||
String bullets = linkBullets(links.getReferences(), variables);
|
||||
if (!bullets.isEmpty()) {
|
||||
HTMLPrinter.addSmallHeader(buffer, "References");
|
||||
HTMLPrinter.startBulletList(buffer);
|
||||
buffer.append(bullets);
|
||||
HTMLPrinter.endBulletList(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HTMLPrinter.addPageEpilog(buffer);
|
||||
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
private static String linkBullets(Link[] links, Map<String, String> variableValues) {
|
||||
StringBuilder bullets = new StringBuilder();
|
||||
for (Link link : links) {
|
||||
String href = link.getHref();
|
||||
if (link.isTemplated()) {
|
||||
if (href != null) {
|
||||
try {
|
||||
href = InitializrServiceSpec.substituteTemplateVariables(href, variableValues);
|
||||
} catch (CoreException e) {
|
||||
BootWizardActivator.log(e);
|
||||
href = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (href != null) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("<a href=\"");
|
||||
sb.append(href);
|
||||
sb.append("\">");
|
||||
if (link.getTitle() != null) {
|
||||
sb.append(HTMLPrinter.convertToHTMLContent(link.getTitle()));
|
||||
} else {
|
||||
if (href.contains("docs.spring.io")) {
|
||||
sb.append(HTMLPrinter.convertToHTMLContent("Spring Boot Reference Doc"));
|
||||
} else {
|
||||
sb.append(HTMLPrinter.convertToHTMLContent(href));
|
||||
}
|
||||
}
|
||||
sb.append("</a>");
|
||||
HTMLPrinter.addBullet(bullets, sb.toString());
|
||||
}
|
||||
}
|
||||
return bullets.toString();
|
||||
}
|
||||
|
||||
private static String[] styles() {
|
||||
StringBuilder mainStyle = new StringBuilder();
|
||||
FontData fontData = JFaceResources.getDialogFontDescriptor().getFontData()[0];
|
||||
boolean bold= (fontData.getStyle() & SWT.BOLD) != 0;
|
||||
boolean italic= (fontData.getStyle() & SWT.ITALIC) != 0;
|
||||
String size= Integer.toString(Math.max(5, fontData.getHeight())) + UNIT;
|
||||
String family= "'" + fontData.getName() + "',sans-serif"; //$NON-NLS-1$ //$NON-NLS-2$
|
||||
mainStyle.append("font-size:");
|
||||
mainStyle.append(size);
|
||||
mainStyle.append(';');
|
||||
mainStyle.append("font-family:");
|
||||
mainStyle.append(family);
|
||||
mainStyle.append(';');
|
||||
mainStyle.append("font-weight:");
|
||||
mainStyle.append(bold ? "bold" : "normal");
|
||||
mainStyle.append(';');
|
||||
mainStyle.append("font-style:");
|
||||
mainStyle.append(italic ? "italic" : "normal");
|
||||
mainStyle.append(';');
|
||||
|
||||
return new String[] {
|
||||
"html {" + mainStyle + "}",
|
||||
"body, h4, h5, h6, p, table, td, caption, th, ul, ol, dl, li, dd, dt { font-size: 1em; }",
|
||||
"h5 { margin-top: 0px; margin-bottom: 0px; }",
|
||||
"p { margin-top: 1em; margin-bottom: 1em; }",
|
||||
"ul { margin-top: 0px; margin-bottom: 0em; margin-left: 1em; padding-left: 1em; }",
|
||||
"li { margin-top: 0px; margin-bottom: 0px; }"
|
||||
};
|
||||
}
|
||||
|
||||
public static String generateRequirements(Dependency dep) {
|
||||
if (dep!=null) {
|
||||
String rangeString = dep.getVersionRange();
|
||||
if (StringUtil.hasText(rangeString)) { //check to avoid logging errors on empty string or null
|
||||
try {
|
||||
VersionRange versionRange = VersionParser.DEFAULT.parseRange(rangeString);
|
||||
Version l = versionRange.getLowerVersion();
|
||||
Version r = versionRange.getHigherVersion();
|
||||
if (l!=null && r!=null) {
|
||||
return "Requires Spring Boot "+
|
||||
rangeText(l, getLeftChar(versionRange)) + " and " + rangeText(r, getRightChar(versionRange));
|
||||
} else if (l!=null) {
|
||||
return "Requires Spring Boot "+
|
||||
rangeText(l, getLeftChar(versionRange));
|
||||
} else if (r!=null) {
|
||||
return "Requires Spring Boot "+
|
||||
rangeText(r, getRightChar(versionRange));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static char getRightChar(VersionRange versionRange) {
|
||||
return versionRange.isHigherInclusive() ? ']' : ')';
|
||||
}
|
||||
|
||||
private static char getLeftChar(VersionRange versionRange) {
|
||||
return versionRange.isLowerInclusive() ? '[' : '(';
|
||||
}
|
||||
|
||||
private static String rangeText(Version range, char type) {
|
||||
return rangeTypeText(type) + range.toString();
|
||||
}
|
||||
|
||||
private static String rangeTypeText(char type) {
|
||||
switch (type) {
|
||||
case '[':
|
||||
return ">=";
|
||||
case '(':
|
||||
return ">";
|
||||
case ']':
|
||||
return "<=";
|
||||
case ')':
|
||||
return "<";
|
||||
default:
|
||||
//Shouldn't happen... but anyhow.
|
||||
return "??";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2014 GoPivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard;
|
||||
|
||||
import org.w3c.dom.NamedNodeMap;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
public abstract class DomUtils {
|
||||
|
||||
public static String getAttributeValue(Node node, String attribName ) {
|
||||
NamedNodeMap attribs = node.getAttributes();
|
||||
if (attribs!=null) {
|
||||
Node value = attribs.getNamedItem(attribName);
|
||||
if (value!=null) {
|
||||
short nodeType = value.getNodeType();
|
||||
if (nodeType==Node.ATTRIBUTE_NODE) {
|
||||
return value.getNodeValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean getAttributeValue(Node node, String name, boolean defaultValue) {
|
||||
String str = getAttributeValue(node, name);
|
||||
if (str==null) {
|
||||
return defaultValue;
|
||||
} else {
|
||||
return "true".equals(str);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013 GoPivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.FieldModel;
|
||||
|
||||
/**
|
||||
* An array of FieldModels of the same type.
|
||||
*/
|
||||
public class FieldArrayModel<T> implements Iterable<FieldModel<T>> {
|
||||
|
||||
private final ArrayList<FieldModel<T>> fields;
|
||||
|
||||
public FieldArrayModel(FieldModel<T>... initialFields) {
|
||||
fields = new ArrayList<FieldModel<T>>(Arrays.asList(initialFields));
|
||||
}
|
||||
|
||||
public FieldModel<T> getField(String name) {
|
||||
for (FieldModel<T> f : fields) {
|
||||
if (name.equals(f.getName())) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//@Override
|
||||
public Iterator<FieldModel<T>> iterator() {
|
||||
return fields.iterator();
|
||||
}
|
||||
|
||||
public void add(FieldModel<T> f) {
|
||||
fields.add(f);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Pivotal Software, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal Software, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jface.layout.GridDataFactory;
|
||||
import org.eclipse.jface.layout.GridLayoutFactory;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.graphics.Point;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Display;
|
||||
import org.springframework.ide.eclipse.boot.core.initializr.InitializrServiceSpec.Dependency;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.LiveExpression;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.ExpandableSection;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.IPageWithSections;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.Scroller;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.WizardPageSection;
|
||||
import org.springsource.ide.eclipse.commons.livexp.util.Filter;
|
||||
|
||||
public class FilteredDependenciesSection extends WizardPageSection {
|
||||
|
||||
private Composite dependencyArea;
|
||||
private Scroller scroller;
|
||||
|
||||
private LiveExpression<Filter<Dependency>> filter;
|
||||
|
||||
private Point sizeHint = new Point(SWT.DEFAULT, SWT.DEFAULT);
|
||||
|
||||
private int columns = 1;
|
||||
private HierarchicalMultiSelectionFieldModel<Dependency> dependencies;
|
||||
|
||||
public FilteredDependenciesSection(IPageWithSections owner, NewSpringBootWizardModel model,
|
||||
LiveExpression<Filter<Dependency>> filter) {
|
||||
this(owner, model.dependencies, filter);
|
||||
}
|
||||
|
||||
public FilteredDependenciesSection(IPageWithSections owner, HierarchicalMultiSelectionFieldModel<Dependency> dependencies,
|
||||
LiveExpression<Filter<Dependency>> filter) {
|
||||
super(owner);
|
||||
this.dependencies = dependencies;
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
public FilteredDependenciesSection sizeHint(Point sizeHint) {
|
||||
if (sizeHint != null) {
|
||||
this.sizeHint = sizeHint;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public FilteredDependenciesSection columns(int columns) {
|
||||
this.columns = columns;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createContents(Composite page) {
|
||||
scroller = new Scroller(page);
|
||||
GridDataFactory.fillDefaults().grab(true, true).minSize(sizeHint).hint(sizeHint).applyTo(scroller);
|
||||
dependencyArea = scroller.getBody();
|
||||
GridLayoutFactory.fillDefaults().applyTo(dependencyArea);
|
||||
GridDataFactory.fillDefaults().grab(true, true).applyTo(dependencyArea);
|
||||
Map<String, CheckboxExpandableSection<Dependency>> sectionsToRefresh = new HashMap<>();
|
||||
for (String cat : dependencies.getCategories()) {
|
||||
MultiSelectionFieldModel<Dependency> dependencyGroup = dependencies.getContents(cat);
|
||||
CheckBoxesSection<Dependency> checkboxesSection = new CheckBoxesSection<>(owner,
|
||||
dependencyGroup.getCheckBoxModels()).columns(columns);
|
||||
CheckboxExpandableSection<Dependency> expandable = new CheckboxExpandableSection<>(owner, dependencyGroup.getLabel(), checkboxesSection);
|
||||
expandable.createContents(dependencyArea);
|
||||
|
||||
// Always expanded as it only shows selections. If there are no
|
||||
// selections, the expandable
|
||||
// section itself is hidden
|
||||
expandable.getExpansionState().setValue(false);
|
||||
|
||||
sectionsToRefresh.put(cat, expandable);
|
||||
}
|
||||
this.filter.addListener((exp, value) -> {
|
||||
// PT 143003753: there is a bit of lag when deleting characters in text filter that produce a lot of
|
||||
// results. Consequently running this asynchronously.
|
||||
Display.getCurrent().asyncExec(() -> {
|
||||
for (String cat : dependencies.getCategories()) {
|
||||
CheckboxExpandableSection<Dependency> expandable = sectionsToRefresh.get(cat);
|
||||
if (expandable != null) {
|
||||
onFilter(expandable, expandable.getCheckBoxSection(), cat);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void onFilter(ExpandableSection expandable, CheckBoxesSection<Dependency> checkboxesSection, String cat) {
|
||||
Filter<Dependency> filter = this.filter.getValue();
|
||||
checkboxesSection.applyFilter(filter);
|
||||
boolean isTrivialFilter = filter==null || filter.isTrivial();
|
||||
if (checkboxesSection.isCreated()) {
|
||||
boolean hasVisible = checkboxesSection.hasVisible();
|
||||
expandable.setVisible(hasVisible);
|
||||
if (hasVisible) {
|
||||
expandable.getExpansionState().setValue(!isTrivialFilter);
|
||||
}
|
||||
}
|
||||
layout();
|
||||
}
|
||||
|
||||
private void layout() {
|
||||
if (dependencyArea != null && !dependencyArea.isDisposed()) {
|
||||
dependencyArea.layout(true);
|
||||
dependencyArea.getParent().layout(true);
|
||||
}
|
||||
}
|
||||
|
||||
static class CheckboxExpandableSection<T> extends ExpandableSection {
|
||||
|
||||
private CheckBoxesSection<T> checkBoxSection;
|
||||
public CheckboxExpandableSection(IPageWithSections owner, String title, CheckBoxesSection<T> checkBoxSection) {
|
||||
super(owner, title, checkBoxSection);
|
||||
this.checkBoxSection = checkBoxSection;
|
||||
}
|
||||
|
||||
public CheckBoxesSection<T> getCheckBoxSection() {
|
||||
return checkBoxSection;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015, 2017 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.ide.eclipse.boot.wizard.CheckBoxesSection.CheckBoxModel;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.LiveExpression;
|
||||
|
||||
/**
|
||||
* Model for a UI widget that offers multiple choices. Similar to MultiSelectionFieldModel, however
|
||||
* the choices are organized into categories instead of a flat list of choices.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
|
||||
public class HierarchicalMultiSelectionFieldModel<T> {
|
||||
|
||||
private Map<String, MultiSelectionFieldModel<T>> categories = new TreeMap<String, MultiSelectionFieldModel<T>>();
|
||||
|
||||
private String name;
|
||||
private String label;
|
||||
private Class<T> type;
|
||||
|
||||
public HierarchicalMultiSelectionFieldModel(Class<T> type, String name) {
|
||||
this.name = name;
|
||||
this.label = name;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Collection<String> getCategories() {
|
||||
return Collections.unmodifiableCollection(categories.keySet());
|
||||
}
|
||||
|
||||
public MultiSelectionFieldModel<T> getContents(String category) {
|
||||
return categories.get(category);
|
||||
}
|
||||
|
||||
public HierarchicalMultiSelectionFieldModel<T> label(String label) {
|
||||
this.label = label;
|
||||
return this;
|
||||
}
|
||||
|
||||
private MultiSelectionFieldModel<T> ensureCategory(String categoryName) {
|
||||
MultiSelectionFieldModel<T> existing = categories.get(categoryName);
|
||||
if (existing==null) {
|
||||
categories.put(categoryName, existing = new MultiSelectionFieldModel<T>(type, name)
|
||||
.label(categoryName));
|
||||
}
|
||||
return existing;
|
||||
}
|
||||
|
||||
public void sort() {
|
||||
for (String cat : getCategories()) {
|
||||
getContents(cat).sort();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a choice to a category, create the category if it doesn't exist yet.
|
||||
*/
|
||||
public void choice(String catName, String name, T dep, Supplier<String> tooltipHtml, String requirementTooltip, LiveExpression<Boolean> enablement) {
|
||||
MultiSelectionFieldModel<T> cat = ensureCategory(catName);
|
||||
cat.choice(name, dep, tooltipHtml, requirementTooltip, enablement);
|
||||
}
|
||||
|
||||
public void setSelection(String catName, T dep, boolean selected) {
|
||||
MultiSelectionFieldModel<T> cat = categories.get(catName);
|
||||
if (cat!=null) {
|
||||
cat.getSelection(dep).setValue(selected);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public List<T> getCurrentSelection() {
|
||||
ArrayList<T> selecteds = new ArrayList<>();
|
||||
for (String cat : getCategories()) {
|
||||
selecteds.addAll(getContents(cat).getCurrentSelection());
|
||||
}
|
||||
return Collections.unmodifiableList(selecteds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the checkbox models for all the checkboxes in all the categories in a single
|
||||
* list.
|
||||
*/
|
||||
public List<CheckBoxModel<T>> getAllBoxes() {
|
||||
ArrayList<CheckBoxModel<T>> allUsedBoxes = new ArrayList<CheckBoxModel<T>>();
|
||||
for (String category : getCategories()) {
|
||||
allUsedBoxes.addAll(getContents(category).getCheckBoxModels());
|
||||
}
|
||||
return allUsedBoxes;
|
||||
}
|
||||
|
||||
public void clearSelection() {
|
||||
categories.values().forEach(MultiSelectionFieldModel::clearSelection);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard;
|
||||
|
||||
import org.eclipse.jface.preference.IPreferenceStore;
|
||||
import org.springframework.ide.eclipse.boot.core.BootActivator;
|
||||
import org.springframework.ide.eclipse.boot.core.BootPreferences;
|
||||
import org.springframework.ide.eclipse.boot.core.initializr.HttpRedirectionException;
|
||||
import org.springframework.ide.eclipse.boot.wizard.InitializrFactoryModel.ModelFactory;
|
||||
import org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager.URLConnectionFactory;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.AsyncLiveExpression;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.LiveExpression;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.LiveVariable;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.StringFieldModel;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.ValidationResult;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.OkButtonHandler;
|
||||
import org.springsource.ide.eclipse.commons.livexp.util.ExceptionUtil;
|
||||
|
||||
/**
|
||||
* Abstract base class for the shared functionality of a ui that depends on
|
||||
* an initializer service url.
|
||||
* <p>
|
||||
* Once the url is chosen the model for the rest of the ui is created dynamically.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public final class InitializrFactoryModel<M> implements OkButtonHandler {
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ModelFactory<M> {
|
||||
M createModel(String url) throws Exception;
|
||||
}
|
||||
|
||||
protected final String[] urls;
|
||||
|
||||
private final StringFieldModel serviceUrlField = new StringFieldModel("Service URL", null);
|
||||
private final LiveVariable<ValidationResult> modelValidator = new LiveVariable<>(ValidationResult.OK);
|
||||
private final LiveExpression<M> model = new AsyncLiveExpression<M>(null, "Building UI model") {
|
||||
{
|
||||
dependsOn(serviceUrlField.getVariable());
|
||||
}
|
||||
@Override
|
||||
protected M compute() {
|
||||
modelValidator.setValue(ValidationResult.info("Contacting web service and building ui model..."));
|
||||
try {
|
||||
M m = factory.createModel(getServiceUrlField().getValue());
|
||||
modelValidator.setValue(ValidationResult.OK);
|
||||
return m;
|
||||
} catch (Exception _e) {
|
||||
Throwable e = ExceptionUtil.getDeepestCause(_e);
|
||||
if (e instanceof HttpRedirectionException) {
|
||||
serviceUrlField.getVariable().setValue(((HttpRedirectionException)e).redirectedTo);
|
||||
} else {
|
||||
modelValidator.setValue(ValidationResult.error(ExceptionUtil.getMessage(e)));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
private final ModelFactory<M> factory;
|
||||
|
||||
public InitializrFactoryModel(ModelFactory<M> factory) {
|
||||
this.factory = factory;
|
||||
this.urls = BootPreferences.getInitializrUrls();
|
||||
serviceUrlField.validator(modelValidator);
|
||||
serviceUrlField.getVariable().setValue(BootPreferences.getInitializrUrl());
|
||||
}
|
||||
|
||||
public StringFieldModel getServiceUrlField() {
|
||||
return serviceUrlField;
|
||||
}
|
||||
|
||||
public String[] getUrls() {
|
||||
return urls;
|
||||
}
|
||||
|
||||
public LiveExpression<M> getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
public void save() {
|
||||
if (serviceUrlField.getValue() != null) {
|
||||
BootPreferences.addInitializrUrl(serviceUrlField.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performOk() throws Exception {
|
||||
M model = getModel().getValue();
|
||||
if (model!=null && model instanceof OkButtonHandler) {
|
||||
BootPreferences.addInitializrUrl(serviceUrlField.getValue());
|
||||
((OkButtonHandler)model).performOk();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard;
|
||||
|
||||
import org.eclipse.jface.layout.GridDataFactory;
|
||||
import org.eclipse.jface.layout.GridLayoutFactory;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.events.SelectionEvent;
|
||||
import org.eclipse.swt.events.SelectionListener;
|
||||
import org.eclipse.swt.widgets.Button;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.IPageWithSections;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.WizardPageSection;
|
||||
|
||||
public class MakeDefaultSection extends WizardPageSection {
|
||||
|
||||
private Runnable clear;
|
||||
private Runnable makeDefault;
|
||||
|
||||
public MakeDefaultSection(IPageWithSections owner, Runnable makeDefault, Runnable clear) {
|
||||
super(owner);
|
||||
this.makeDefault = makeDefault;
|
||||
this.clear = clear;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createContents(Composite page) {
|
||||
|
||||
final Composite composite = new Composite(page, SWT.NONE);
|
||||
GridLayoutFactory.fillDefaults().numColumns(2).equalWidth(false).spacing(5, SWT.DEFAULT).applyTo(composite);
|
||||
GridDataFactory.fillDefaults().grab(false, false).align(SWT.END, SWT.CENTER).applyTo(composite);
|
||||
|
||||
Button makeDefaultButton = new Button(composite, SWT.PUSH);
|
||||
makeDefaultButton.setText("Make Default");
|
||||
makeDefaultButton.setToolTipText("Make currently selected dependencies selected by default");
|
||||
GridDataFactory.fillDefaults().grab(false, false).align(SWT.END, SWT.CENTER).applyTo(makeDefaultButton);
|
||||
|
||||
Button clearButton = new Button(composite, SWT.PUSH);
|
||||
clearButton.setText("Clear Selection");
|
||||
clearButton.setToolTipText("Clear dependencies selection");
|
||||
GridDataFactory.fillDefaults().grab(false, false).align(SWT.END, SWT.CENTER).applyTo(clearButton);
|
||||
|
||||
makeDefaultButton.addSelectionListener(new SelectionListener() {
|
||||
@Override
|
||||
public void widgetSelected(SelectionEvent event) {
|
||||
if (makeDefault != null) {
|
||||
makeDefault.run();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void widgetDefaultSelected(SelectionEvent event) {
|
||||
widgetSelected(event);
|
||||
}
|
||||
});
|
||||
|
||||
clearButton.addSelectionListener(new SelectionListener() {
|
||||
@Override
|
||||
public void widgetSelected(SelectionEvent event) {
|
||||
if (clear != null) {
|
||||
clear.run();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void widgetDefaultSelected(SelectionEvent event) {
|
||||
widgetSelected(event);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013, 2017 GoPivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.eclipse.core.runtime.Assert;
|
||||
import org.springframework.ide.eclipse.boot.wizard.CheckBoxesSection.CheckBoxModel;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.LiveExpression;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.LiveVariable;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.ValidationResult;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.Validator;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.ValueListener;
|
||||
|
||||
/**
|
||||
* Model for a UI widget that offers multiple choices. Could be represented
|
||||
* by a set of Checkboxes or multi-selection enabled list/tree viewer.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class MultiSelectionFieldModel<T> {
|
||||
|
||||
private static final LiveExpression<Boolean> ALLWAYS_ENABLED = LiveExpression.constant(true);
|
||||
|
||||
private Class<T> type; //Type of data stored in the field.
|
||||
private String name; // used to submit value to some service that handles the form
|
||||
private String label; // Label to display in forms
|
||||
private LiveExpression<ValidationResult> validator;
|
||||
|
||||
private Map<T,String> labelMap = new LinkedHashMap<T, String>();
|
||||
private Map<T,Supplier<String>> tooltipsHtml = null; //don't allocate unless used.
|
||||
private Map<T,String> requirementToolstips = null; //don't allocate unless used.
|
||||
private boolean mustSort;
|
||||
|
||||
private Map<T, LiveExpression<Boolean>> enablements = null;
|
||||
|
||||
//selection state for each individual choice (created on an as-needed basis).
|
||||
private Map<T, LiveVariable<Boolean>> selections = new HashMap<T, LiveVariable<Boolean>>();
|
||||
|
||||
public MultiSelectionFieldModel(Class<T> type, String name) {
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
this.label = name;
|
||||
this.validator = Validator.OK;
|
||||
}
|
||||
|
||||
public Class<T> getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public String getLabel(T value) {
|
||||
return labelMap.get(value);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public LiveExpression<ValidationResult> getValidator() {
|
||||
return validator;
|
||||
}
|
||||
|
||||
public MultiSelectionFieldModel<T> validator(LiveExpression<ValidationResult> v) {
|
||||
this.validator = v;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void select(T v) {
|
||||
getSelection(v).setValue(true);
|
||||
}
|
||||
|
||||
public synchronized LiveVariable<Boolean> getSelection(T v) {
|
||||
LiveVariable<Boolean> existing = selections.get(v);
|
||||
if (existing==null) {
|
||||
selections.put(v, existing = new LiveVariable<Boolean>(false));
|
||||
}
|
||||
return existing;
|
||||
}
|
||||
|
||||
public void unselect(T v) {
|
||||
getSelection(v).setValue(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a valid choice to the multi selection model.
|
||||
* @param label String to show the choice to a user
|
||||
* @param key Value added to the set when user selects this choice.
|
||||
*/
|
||||
public MultiSelectionFieldModel<T> choice(String label, T value) {
|
||||
Assert.isLegal(labelMap.get(value)==null, "Duplicate choice "+value+" already added");
|
||||
labelMap.put(value, label);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void choice(String label, T value, Supplier<String> tooltipHtml, String requirementTooltip) {
|
||||
choice(label, value);
|
||||
setTooltipHtml(value, tooltipHtml);
|
||||
setRequirementTooltip(value, requirementTooltip);
|
||||
}
|
||||
|
||||
public void choice(String label, T value, Supplier<String> tooltipHtml, String requirementTooltip, LiveExpression<Boolean> enablement) {
|
||||
choice(label, value, tooltipHtml, requirementTooltip);
|
||||
if (enablements==null) {
|
||||
enablements = new HashMap<T, LiveExpression<Boolean>>();
|
||||
}
|
||||
enablements.put(value, enablement);
|
||||
}
|
||||
|
||||
public void setTooltipHtml(T value, Supplier<String> tooltipHtml) {
|
||||
if (tooltipsHtml==null) {
|
||||
tooltipsHtml = new HashMap<T, Supplier<String>>();
|
||||
}
|
||||
tooltipsHtml.put(value, tooltipHtml);
|
||||
}
|
||||
|
||||
public Supplier<String> getTooltipHtml(T value) {
|
||||
if (tooltipsHtml!=null) {
|
||||
return tooltipsHtml.get(value);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public MultiSelectionFieldModel<T> label(String label) {
|
||||
this.label = label;
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public synchronized T[] getChoices() {
|
||||
Collection<T> values = labelMap.keySet();
|
||||
T[] choices = values.toArray((T[]) Array.newInstance(getType(), values.size()));
|
||||
if (mustSort) {
|
||||
Arrays.sort(choices, new Comparator<T>() {
|
||||
@Override
|
||||
public int compare(T o1, T o2) {
|
||||
String s1 = getLabel(o1);
|
||||
String s2 = getLabel(o2);
|
||||
return s1.compareTo(s2);
|
||||
}
|
||||
});
|
||||
}
|
||||
return choices;
|
||||
}
|
||||
|
||||
public void sort() {
|
||||
mustSort = true;
|
||||
}
|
||||
|
||||
public LiveExpression<Boolean> getEnablement(T choice) {
|
||||
if (enablements!=null) {
|
||||
LiveExpression<Boolean> e = enablements.get(choice);
|
||||
if (e!=null) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
return ALLWAYS_ENABLED;
|
||||
}
|
||||
|
||||
public synchronized List<T> getCurrentSelection() {
|
||||
List<T> selecteds = new ArrayList<T>();
|
||||
for (Entry<T, LiveVariable<Boolean>> e : selections.entrySet()) {
|
||||
if (e.getValue().getValue()) {
|
||||
selecteds.add(e.getKey());
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableList(selecteds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts all the current choices into CheckBoxModel objects. One checkbox to
|
||||
* represent each choice.
|
||||
*/
|
||||
public List<CheckBoxModel<T>> getCheckBoxModels() {
|
||||
List<CheckBoxModel<T>> checkboxes = new ArrayList<CheckBoxModel<T>>(labelMap.size());
|
||||
for (T choice : labelMap.keySet()) {
|
||||
CheckBoxModel<T> cb;
|
||||
checkboxes.add(cb = new CheckBoxModel<T>(getLabel(choice), choice, getSelection(choice), getEnablement(choice)));
|
||||
cb.setTooltipHtml(getTooltipHtml(choice));
|
||||
cb.setRequirementTooltip(getRequirementTooltip(choice));
|
||||
}
|
||||
return Collections.unmodifiableList(checkboxes);
|
||||
}
|
||||
|
||||
private String getRequirementTooltip(T choice) {
|
||||
if (requirementToolstips!=null) {
|
||||
return requirementToolstips.get(choice);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setRequirementTooltip(T value, String tt) {
|
||||
if (requirementToolstips==null) {
|
||||
requirementToolstips = new HashMap<T, String>();
|
||||
}
|
||||
requirementToolstips.put(value, tt);
|
||||
}
|
||||
|
||||
public void clearSelection() {
|
||||
selections.values().forEach(liveVar -> {
|
||||
liveVar.setValue(false);
|
||||
});
|
||||
}
|
||||
|
||||
public void addSelectionListener(ValueListener<Boolean> listener) {
|
||||
selections.values().forEach(livevar -> {
|
||||
livevar.addListener(listener);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Pivotal Software, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal Software, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.graphics.Point;
|
||||
import org.eclipse.ui.PlatformUI;
|
||||
import org.springframework.ide.eclipse.boot.core.initializr.InitializrServiceSpec.Dependency;
|
||||
import org.springframework.ide.eclipse.boot.livexp.ui.DynamicSection;
|
||||
import org.springframework.ide.eclipse.boot.wizard.CheckBoxesSection.CheckBoxModel;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.ChooseOneSectionCombo;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.CommentSection;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.GroupSection;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.WizardPageSection;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.WizardPageWithSections;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
public class MultipleViewsDependencyPage extends WizardPageWithSections {
|
||||
private static final int NUM_COLUMNS_FREQUENTLY_USED = 3;
|
||||
private static final int MAX_MOST_POPULAR = 3 * NUM_COLUMNS_FREQUENTLY_USED;
|
||||
private static final Point DEPENDENCY_SECTION_SIZE = new Point(SWT.DEFAULT, 300);
|
||||
|
||||
private CheckBoxesSection<Dependency> frequentlyUsedCheckboxes;
|
||||
|
||||
protected final InitializrFactoryModel<NewSpringBootWizardModel> factoryModel;
|
||||
|
||||
protected MultipleViewsDependencyPage(InitializrFactoryModel<NewSpringBootWizardModel> factoryModel) {
|
||||
super("page2", "New Spring Starter Project Dependencies", null);
|
||||
this.factoryModel = factoryModel;
|
||||
}
|
||||
|
||||
private void refreshFrequentlyUsedDependencies(NewSpringBootWizardModel model) {
|
||||
List<CheckBoxModel<Dependency>> dependenciesCheckboxes = model.getFrequentlyUsedDependencies(MAX_MOST_POPULAR);
|
||||
if (frequentlyUsedCheckboxes.isCreated()) {
|
||||
frequentlyUsedCheckboxes.setModel(dependenciesCheckboxes);
|
||||
}
|
||||
reflow();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<WizardPageSection> createSections() {
|
||||
DynamicSection dynamicSection = new DynamicSection(this, factoryModel.getModel().apply((dynamicModel) -> {
|
||||
if (dynamicModel != null) {
|
||||
return createDynamicSections(dynamicModel);
|
||||
}
|
||||
return new CommentSection(this, NewSpringBootWizard.NO_CONTENT_AVAILABLE);
|
||||
} ));
|
||||
|
||||
return ImmutableList.of(dynamicSection);
|
||||
}
|
||||
|
||||
protected WizardPageSection createDynamicSections(NewSpringBootWizardModel model) {
|
||||
List<WizardPageSection> sections = new ArrayList<>();
|
||||
|
||||
RadioGroup bootVersion = model.getBootVersion();
|
||||
sections.add(
|
||||
new ChooseOneSectionCombo<>(this, bootVersion.getLabel(),
|
||||
bootVersion.getSelection(), bootVersion.getRadios()
|
||||
)
|
||||
.useFieldLabelWidthHint(false)
|
||||
);
|
||||
|
||||
sections.add(createFrequentlyUsedSection(model));
|
||||
sections.add(createTwoColumnSection(model));
|
||||
return new GroupSection(this, null, sections.toArray(new WizardPageSection[0])).grabVertical(true);
|
||||
}
|
||||
|
||||
public WizardPageSection createTwoColumnSection(final NewSpringBootWizardModel model) {
|
||||
return new GroupSection(this,null,
|
||||
new GroupSection(this, null,
|
||||
new CommentSection(this, "Available:"),
|
||||
getSearchSection(model),
|
||||
new GroupSection(this, "",
|
||||
new FilteredDependenciesSection(this, model, model.getDependencyFilter())
|
||||
.sizeHint(DEPENDENCY_SECTION_SIZE)
|
||||
)
|
||||
.grabVertical(true)
|
||||
.noMargins(true)
|
||||
)
|
||||
.grabVertical(true)
|
||||
.noMargins(true),
|
||||
new GroupSection(this, null,
|
||||
new CommentSection(this, "Selected:"),
|
||||
new GroupSection(this, "",
|
||||
new SelectedDependenciesSection(this, model)
|
||||
.sizeHint(DEPENDENCY_SECTION_SIZE)
|
||||
)
|
||||
.grabVertical(true)
|
||||
.noMargins(true),
|
||||
new MakeDefaultSection(this, () -> {
|
||||
if (model.saveDefaultDependencies()) {
|
||||
refreshFrequentlyUsedDependencies(model);
|
||||
}
|
||||
}, () -> {
|
||||
model.dependencies.clearSelection();
|
||||
})
|
||||
)
|
||||
)
|
||||
.columns(2, true)
|
||||
.grabVertical(true);
|
||||
}
|
||||
|
||||
protected WizardPageSection getSearchSection(final NewSpringBootWizardModel model) {
|
||||
final SearchBoxSection searchBoxSection = new SearchBoxSection(this, model.getDependencyFilterBoxText()) {
|
||||
@Override
|
||||
protected String getSearchHint() {
|
||||
return "Type to search dependencies";
|
||||
}
|
||||
};
|
||||
PlatformUI.getWorkbench().getDisplay().asyncExec(() -> getControl().addListener(SWT.Show, event -> searchBoxSection.focusControl()));
|
||||
return new GroupSection(this, null, searchBoxSection.grabFocus(true));
|
||||
}
|
||||
|
||||
protected WizardPageSection createFrequentlyUsedSection(NewSpringBootWizardModel model) {
|
||||
List<CheckBoxModel<Dependency>> frequentDependencies = model.getFrequentlyUsedDependencies(MAX_MOST_POPULAR);
|
||||
frequentlyUsedCheckboxes = new CheckBoxesSection<>(this, frequentDependencies).columns(NUM_COLUMNS_FREQUENTLY_USED);
|
||||
GroupSection frequentlyUsedSection = new GroupSection(this,
|
||||
null,
|
||||
new CommentSection(this, "Frequently Used:"),
|
||||
new GroupSection(this, "", frequentlyUsedCheckboxes));
|
||||
frequentlyUsedSection.isVisible.setValue(!frequentDependencies.isEmpty());
|
||||
return frequentlyUsedSection;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013, 2017 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.core.runtime.Assert;
|
||||
import org.eclipse.core.runtime.IProgressMonitor;
|
||||
import org.eclipse.core.runtime.IStatus;
|
||||
import org.eclipse.core.runtime.Status;
|
||||
import org.eclipse.core.runtime.jobs.Job;
|
||||
import org.eclipse.jface.dialogs.MessageDialog;
|
||||
import org.eclipse.jface.resource.ImageDescriptor;
|
||||
import org.eclipse.jface.viewers.IStructuredSelection;
|
||||
import org.eclipse.jface.wizard.Wizard;
|
||||
import org.eclipse.ui.IImportWizard;
|
||||
import org.eclipse.ui.INewWizard;
|
||||
import org.eclipse.ui.IWorkbench;
|
||||
import org.springframework.ide.eclipse.boot.core.BootActivator;
|
||||
import org.springframework.ide.eclipse.boot.livexp.ui.DynamicSection;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.FieldModel;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.ChooseOneSectionCombo;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.CommentSection;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.DescriptionSection;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.GroupSection;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.IPageWithSections;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.ProjectLocationSection;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.StringFieldSection;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.WizardPageSection;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.WizardPageWithSections;
|
||||
import org.springsource.ide.eclipse.commons.livexp.util.ExceptionUtil;
|
||||
import org.springsource.ide.eclipse.commons.livexp.util.Parser;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
public class NewSpringBootWizard extends Wizard implements INewWizard, IImportWizard {
|
||||
|
||||
private static final ImageDescriptor IMAGE = BootWizardImages.BOOT_WIZARD_ICON;
|
||||
public static final String NO_CONTENT_AVAILABLE = "No content available.";
|
||||
|
||||
private InitializrFactoryModel<NewSpringBootWizardModel> model;
|
||||
|
||||
private IStructuredSelection selection;
|
||||
|
||||
private WorkingSetSection workingSetSection;
|
||||
|
||||
private WizardPageWithSections projectPage;
|
||||
|
||||
public NewSpringBootWizard() throws Exception {
|
||||
setDefaultPageImageDescriptor(IMAGE);
|
||||
}
|
||||
|
||||
//@Override
|
||||
@Override
|
||||
public void init(IWorkbench workbench, IStructuredSelection selection) {
|
||||
try {
|
||||
model = NewSpringBootWizardFactoryModel.create(BootActivator.getUrlConnectionFactory(), BootActivator.getDefault().getPreferenceStore());
|
||||
this.selection = selection;
|
||||
} catch (Exception e) {
|
||||
MessageDialog.openError(workbench.getActiveWorkbenchWindow().getShell(), "Error opening the wizard",
|
||||
ExceptionUtil.getMessage(e)+"\n\n"+
|
||||
"Note that this wizard uses a webservice and needs internet access.\n"+
|
||||
"A more detailed error message may be found in the Eclipse error log."
|
||||
);
|
||||
//Ensure exception is logged. (Eclipse UI may not log it!).
|
||||
BootWizardActivator.log(e);
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPages() {
|
||||
super.addPages();
|
||||
Assert.isLegal(model!=null, "The Spring Starter Wizard model was not initialized. Unable to open the wizard.");
|
||||
addPage(projectPage = new ProjectDetailsPage(model));
|
||||
addPage(new MultipleViewsDependencyPage(model));
|
||||
addPage(new PageThree(model));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean canFinish() {
|
||||
return super.canFinish() && getContainer().getCurrentPage()!=projectPage;
|
||||
}
|
||||
|
||||
public class ProjectDetailsSection extends GroupSection {
|
||||
|
||||
private final NewSpringBootWizardModel model;
|
||||
|
||||
public ProjectDetailsSection(IPageWithSections owner, NewSpringBootWizardModel model) {
|
||||
super(owner, null);
|
||||
this.model = model;
|
||||
addSections(createSections().toArray(new WizardPageSection[0]));
|
||||
}
|
||||
|
||||
protected List<WizardPageSection> createSections() {
|
||||
List<WizardPageSection> sections = new ArrayList<>();
|
||||
|
||||
FieldModel<String> projectName = model.getProjectName();
|
||||
sections.add(new StringFieldSection(owner, projectName));
|
||||
sections.add(new ProjectLocationSection(owner, model.getLocation(), projectName.getVariable(), model.getLocationValidator()));
|
||||
|
||||
WizardPageSection radios = createRadioGroupsSection(owner);
|
||||
if (radios!=null) {
|
||||
sections.add(radios);
|
||||
}
|
||||
|
||||
for (FieldModel<String> f : model.stringInputs) {
|
||||
//caution! we already created the section for projectName because we want it at the top
|
||||
if (projectName!=f) {
|
||||
sections.add(new StringFieldSection(owner, f));
|
||||
}
|
||||
}
|
||||
|
||||
sections.add(workingSetSection = new WorkingSetSection(owner, selection));
|
||||
|
||||
return sections;
|
||||
}
|
||||
|
||||
private WizardPageSection createRadioGroupsSection(IPageWithSections owner) {
|
||||
boolean notEmpty = false;
|
||||
RadioGroup bootVersion = model.getBootVersion(); //This is placed specifically somewhere else so must skip it here
|
||||
ArrayList<WizardPageSection> radioSections = new ArrayList<>();
|
||||
for (RadioGroup radioGroup : model.getRadioGroups().getGroups()) {
|
||||
if (radioGroup!=bootVersion) {
|
||||
if (radioGroup.getRadios().length>1) {
|
||||
//Don't add a UI elements for something that offers no real choice
|
||||
radioSections.add(
|
||||
new ChooseOneSectionCombo<>(owner, radioGroup.getLabel(), radioGroup.getSelection(), radioGroup.getRadios()).grabHorizontal(true)
|
||||
//new ChooseOneSectionCombo<RadioInfo>(owner, radioGroup.getLabel(), radioGroup.getSelection(), radioGroup.getRadios())
|
||||
);
|
||||
notEmpty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (notEmpty) {
|
||||
return new GroupSection(owner, null, radioSections.toArray(new WizardPageSection[radioSections.size()])).columns(2, false);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class ProjectDetailsPage extends WizardPageWithSections {
|
||||
|
||||
protected InitializrFactoryModel<NewSpringBootWizardModel> model;
|
||||
|
||||
public ProjectDetailsPage(InitializrFactoryModel<NewSpringBootWizardModel> model) {
|
||||
super("page1", "New Spring Starter Project", null);
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<WizardPageSection> createSections() {
|
||||
|
||||
ChooseOneSectionCombo<String> comboSection = new ChooseOneSectionCombo<>(this, model.getServiceUrlField(), model.getUrls())
|
||||
.grabHorizontal(true);
|
||||
comboSection.allowTextEdits(Parser.IDENTITY);
|
||||
|
||||
// Note: have to set the project page in the dynamic section because the dynamic section composite
|
||||
// gets created at the start, and determines the initial size of the wizard page, regardless of its content.
|
||||
DynamicSection dynamicSection = new DynamicSection(this, model.getModel().apply((dynamicModel) -> {
|
||||
if (dynamicModel != null) {
|
||||
return new ProjectDetailsSection(this, dynamicModel);
|
||||
}
|
||||
return new CommentSection(this, NO_CONTENT_AVAILABLE);
|
||||
} ));
|
||||
|
||||
return ImmutableList.of(comboSection, dynamicSection);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class PageThree extends WizardPageWithSections {
|
||||
|
||||
private final InitializrFactoryModel<NewSpringBootWizardModel> model;
|
||||
|
||||
protected PageThree(InitializrFactoryModel<NewSpringBootWizardModel> model) {
|
||||
super("page3", "New Spring Starter Project", null);
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
protected WizardPageSection createDynamicContent(NewSpringBootWizardModel model) {
|
||||
List<WizardPageSection> sections = new ArrayList<>();
|
||||
|
||||
sections.add(new GroupSection(this, "Site Info",
|
||||
new StringFieldSection(this, "Base Url", model.baseUrl, model.baseUrlValidator),
|
||||
new DescriptionSection(this, model.downloadUrl).label("Full Url").readOnly(false)
|
||||
));
|
||||
|
||||
return new GroupSection(this, null, sections.toArray(new WizardPageSection[0])).grabVertical(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<WizardPageSection> createSections() {
|
||||
|
||||
DynamicSection dynamicSection = new DynamicSection(this, model.getModel().apply((dynamicModel) -> {
|
||||
if (dynamicModel != null) {
|
||||
return createDynamicContent(dynamicModel);
|
||||
}
|
||||
return new CommentSection(this, NO_CONTENT_AVAILABLE);
|
||||
} ));
|
||||
return ImmutableList.of(dynamicSection);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performFinish() {
|
||||
if (model.getModel().getValue() == null) {
|
||||
return false;
|
||||
}
|
||||
model.getModel().getValue().setWorkingSets(workingSetSection.getWorkingSets()); //must be in ui thread. Don't put in job!
|
||||
Job job = new Job("Import Getting Started Content") {
|
||||
@Override
|
||||
protected IStatus run(IProgressMonitor mon) {
|
||||
try {
|
||||
// TODO: maybe the model factory should have a perform finish that does a save, which then calls the underlying model perform finish
|
||||
model.save();
|
||||
model.getModel().getValue().performFinish(mon);
|
||||
return Status.OK_STATUS;
|
||||
} catch (Throwable e) {
|
||||
return ExceptionUtil.status(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
//WARNING: Do not set a scheduling rule here. It breaks gradle import by causing a deadlock or rule conflict.
|
||||
//job.setRule(ResourcesPlugin.getWorkspace().getRuleFactory().buildRule());
|
||||
//See: https://www.pivotaltracker.com/story/show/128781771
|
||||
job.setPriority(Job.BUILD);
|
||||
job.setUser(true); //shows progress in default eclipse config
|
||||
job.schedule();
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard;
|
||||
|
||||
import org.eclipse.jface.preference.IPreferenceStore;
|
||||
import org.springsource.ide.eclipse.commons.core.util.StringUtil;
|
||||
import org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager.URLConnectionFactory;
|
||||
|
||||
/**
|
||||
* A 'FactoryModelModel' for NewSpringBootWizardModel. I.e. this is a model for the NewSpringBootWizard
|
||||
* the dynamically creates a {@link NewSpringBootWizardModel} based on some 'static' inputs.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class NewSpringBootWizardFactoryModel {
|
||||
|
||||
public static InitializrFactoryModel<NewSpringBootWizardModel> create(URLConnectionFactory urlConnectionFactory, IPreferenceStore prefs) {
|
||||
return new InitializrFactoryModel<>((url) -> {
|
||||
if (StringUtil.hasText(url)) {
|
||||
return new NewSpringBootWizardModel(urlConnectionFactory, url, prefs);
|
||||
} else {
|
||||
throw new IllegalArgumentException("No URL entered");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,633 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013, 2017 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.resources.ResourcesPlugin;
|
||||
import org.eclipse.core.runtime.Assert;
|
||||
import org.eclipse.core.runtime.IProgressMonitor;
|
||||
import org.eclipse.core.runtime.Path;
|
||||
import org.eclipse.core.runtime.SubMonitor;
|
||||
import org.eclipse.jface.operation.IRunnableWithProgress;
|
||||
import org.eclipse.jface.preference.IPreferenceStore;
|
||||
import org.eclipse.ui.IWorkingSet;
|
||||
import org.eclipse.ui.IWorkingSetManager;
|
||||
import org.eclipse.ui.PlatformUI;
|
||||
import org.springframework.ide.eclipse.boot.core.BootActivator;
|
||||
import org.springframework.ide.eclipse.boot.core.BootPreferences;
|
||||
import org.springframework.ide.eclipse.boot.core.initializr.InitializrServiceSpec;
|
||||
import org.springframework.ide.eclipse.boot.core.initializr.InitializrServiceSpec.Dependency;
|
||||
import org.springframework.ide.eclipse.boot.core.initializr.InitializrServiceSpec.DependencyGroup;
|
||||
import org.springframework.ide.eclipse.boot.core.initializr.InitializrServiceSpec.Option;
|
||||
import org.springframework.ide.eclipse.boot.core.initializr.InitializrServiceSpec.Type;
|
||||
import org.springframework.ide.eclipse.boot.wizard.CheckBoxesSection.CheckBoxModel;
|
||||
import org.springframework.ide.eclipse.boot.wizard.content.BuildType;
|
||||
import org.springframework.ide.eclipse.boot.wizard.content.CodeSet;
|
||||
import org.springframework.ide.eclipse.boot.wizard.importing.ImportStrategy;
|
||||
import org.springframework.ide.eclipse.boot.wizard.importing.ImportUtils;
|
||||
import org.springsource.ide.eclipse.commons.core.util.NameGenerator;
|
||||
import org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager.DownloadManager;
|
||||
import org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager.DownloadableItem;
|
||||
import org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager.URLConnectionFactory;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.FieldModel;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.LiveExpression;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.LiveVariable;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.StringFieldModel;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.ValidationResult;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.Validator;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.ValueListener;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.validators.NewProjectLocationValidator;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.validators.NewProjectNameValidator;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.validators.UrlValidator;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.ProjectLocationSection;
|
||||
import org.springsource.ide.eclipse.commons.livexp.util.Filter;
|
||||
import org.springsource.ide.eclipse.commons.livexp.util.Log;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
|
||||
/**
|
||||
* This is the model for the 'New Spring Starter Project' wizard.
|
||||
*/
|
||||
public class NewSpringBootWizardModel {
|
||||
|
||||
private static final String NAME_PROPRTY_ID = "name";
|
||||
private static final String ARTIFACT_PROPERTY_ID = "artifactId";
|
||||
|
||||
private static final Map<String,BuildType> KNOWN_TYPES = new HashMap<>();
|
||||
static {
|
||||
KNOWN_TYPES.put("gradle-project", BuildType.GRADLE); // New version of initialzr app
|
||||
KNOWN_TYPES.put("maven-project", BuildType.MAVEN); // New versions of initialzr app
|
||||
|
||||
KNOWN_TYPES.put("gradle.zip", BuildType.GRADLE); //Legacy, can remove when new initializr app uses "gradle-project" definitively
|
||||
KNOWN_TYPES.put("starter.zip", BuildType.MAVEN); //Legacy, can remove when initializr app uses "maven-project" definitively
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists known query parameters that map onto a String input field. The default values for these
|
||||
* parameters will be pulled from the json spec document.
|
||||
*/
|
||||
private static final Map<String,String> KNOWN_STRING_INPUTS = new LinkedHashMap<>();
|
||||
static {
|
||||
KNOWN_STRING_INPUTS.put(NAME_PROPRTY_ID, "Name");
|
||||
KNOWN_STRING_INPUTS.put("groupId", "Group");
|
||||
KNOWN_STRING_INPUTS.put(ARTIFACT_PROPERTY_ID, "Artifact");
|
||||
KNOWN_STRING_INPUTS.put("version", "Version");
|
||||
KNOWN_STRING_INPUTS.put("description", "Description");
|
||||
KNOWN_STRING_INPUTS.put("packageName", "Package");
|
||||
};
|
||||
|
||||
private static final Map<String, String> KNOWN_SINGLE_SELECTS = new LinkedHashMap<>();
|
||||
static {
|
||||
KNOWN_SINGLE_SELECTS.put("packaging", "Packaging:");
|
||||
KNOWN_SINGLE_SELECTS.put("javaVersion", "Java Version:");
|
||||
KNOWN_SINGLE_SELECTS.put("language", "Language:");
|
||||
KNOWN_SINGLE_SELECTS.put("bootVersion", "Spring Boot Version:");
|
||||
}
|
||||
|
||||
private final URLConnectionFactory urlConnectionFactory;
|
||||
private final String JSON_URL;
|
||||
private PopularityTracker popularities;
|
||||
private PreferredSelections preferredSelections;
|
||||
private DefaultDependencies defaultDependencies;
|
||||
|
||||
public NewSpringBootWizardModel(IPreferenceStore prefs) throws Exception {
|
||||
this(
|
||||
BootActivator.getUrlConnectionFactory(),
|
||||
prefs
|
||||
);
|
||||
}
|
||||
|
||||
public NewSpringBootWizardModel() throws Exception {
|
||||
this(
|
||||
BootActivator.getUrlConnectionFactory(),
|
||||
BootWizardActivator.getDefault().getPreferenceStore()
|
||||
);
|
||||
}
|
||||
|
||||
public NewSpringBootWizardModel(URLConnectionFactory urlConnectionFactory, IPreferenceStore prefs) throws Exception {
|
||||
this(urlConnectionFactory, BootPreferences.getInitializrUrl(), prefs);
|
||||
}
|
||||
|
||||
public NewSpringBootWizardModel(URLConnectionFactory urlConnectionFactory, String jsonUrl, IPreferenceStore prefs) throws Exception {
|
||||
this.popularities = new PopularityTracker(prefs);
|
||||
this.preferredSelections = new PreferredSelections(prefs);
|
||||
this.defaultDependencies = new DefaultDependencies(prefs);
|
||||
this.urlConnectionFactory = urlConnectionFactory;
|
||||
this.JSON_URL = jsonUrl;
|
||||
|
||||
baseUrl = new LiveVariable<>("<computed>");
|
||||
baseUrlValidator = new UrlValidator("Base Url", baseUrl);
|
||||
|
||||
discoverOptions(stringInputs, dependencies);
|
||||
dependencies.sort();
|
||||
|
||||
projectName = stringInputs.getField(NAME_PROPRTY_ID);
|
||||
projectName.validator(new NewProjectNameValidator(projectName.getVariable()));
|
||||
location = new LiveVariable<>(ProjectLocationSection.getDefaultProjectLocation(projectName.getValue()));
|
||||
locationValidator = new NewProjectLocationValidator("Location", location, projectName.getVariable());
|
||||
Assert.isNotNull(projectName, "The service at "+JSON_URL+" doesn't specify a 'name' text input");
|
||||
|
||||
UrlMaker computedUrl = new UrlMaker(baseUrl);
|
||||
for (FieldModel<String> param : stringInputs) {
|
||||
computedUrl.addField(param);
|
||||
}
|
||||
computedUrl.addField(dependencies);
|
||||
for (RadioGroup group : radioGroups.getGroups()) {
|
||||
computedUrl.addField(group);
|
||||
}
|
||||
computedUrl.addListener(new ValueListener<String>() {
|
||||
@Override
|
||||
public void gotValue(LiveExpression<String> exp, String value) {
|
||||
downloadUrl.setValue(value);
|
||||
}
|
||||
});
|
||||
|
||||
addBuildTypeValidator();
|
||||
|
||||
getArtifactId().setValue(projectName.getValue());
|
||||
syncOneDirectionally(projectName, getArtifactId());
|
||||
|
||||
preferredSelections.restore(this);
|
||||
defaultDependencies.restore(dependencies);
|
||||
generateValidProjectName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Establish one-directional value copying from one field to another. When the two field have equal contents,
|
||||
* the value is copied from the 'fromField' to the 'toField' any time the 'fromField' is changed (keeping them
|
||||
* in sync).
|
||||
* <p>
|
||||
* However, when the toField is changed the value is not copied back to the fromField and the synchronization
|
||||
* at that point is 'broken' (until the fields again become equal, at which point syncing becomes enabled again).
|
||||
*
|
||||
* @param fromField
|
||||
* @param toField
|
||||
*/
|
||||
private void syncOneDirectionally(FieldModel<String> fromField, FieldModel<String> toField) {
|
||||
if (fromField!=null && toField!=null) {
|
||||
syncOneDirectionally(fromField.getVariable(), toField.getVariable());
|
||||
}
|
||||
}
|
||||
|
||||
private void syncOneDirectionally(LiveVariable<String> fromVar, LiveVariable<String> toVar) {
|
||||
ValueListener<String> copyValue = (e, value) -> {
|
||||
toVar.setValue(value);
|
||||
};
|
||||
ValueListener<String> enableOrDisableSyncing = (e, value) -> {
|
||||
if (Objects.equal(fromVar.getValue(), toVar.getValue())){
|
||||
fromVar.addListener(copyValue);
|
||||
} else {
|
||||
fromVar.removeListener(copyValue);
|
||||
}
|
||||
};
|
||||
fromVar.addListener(enableOrDisableSyncing);
|
||||
toVar.addListener(enableOrDisableSyncing);
|
||||
}
|
||||
|
||||
private void generateValidProjectName() {
|
||||
boolean projectNameValid = projectName.getValidator().getValue() == ValidationResult.OK;
|
||||
if (!projectNameValid) {
|
||||
NameGenerator generator = new NameGenerator(projectName.getValue());
|
||||
int limit = 5; //See: https://github.com/spring-projects/spring-ide/issues/230
|
||||
while (!projectNameValid && limit-- > 0) {
|
||||
projectName.setValue(generator.generateNext());
|
||||
projectNameValid = projectName.getValidator().getValue() == ValidationResult.OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If this wizard has a 'type' radioGroup to select the build type then add a validator to check if the
|
||||
* build type is supported.
|
||||
*/
|
||||
private void addBuildTypeValidator() {
|
||||
final RadioGroup buildTypeGroup = getRadioGroups().getGroup("type");
|
||||
if (buildTypeGroup!=null) {
|
||||
buildTypeGroup.validator(new Validator() {
|
||||
{
|
||||
dependsOn(buildTypeGroup.getVariable());
|
||||
}
|
||||
@Override
|
||||
protected ValidationResult compute() {
|
||||
ImportStrategy s = getImportStrategy();
|
||||
if (s==null) {
|
||||
return ValidationResult.error("No 'type' selected");
|
||||
} else if (!s.isSupported()) {
|
||||
//This means some required STS component like m2e or gradle tooling is not installed
|
||||
return ValidationResult.error(s.getNotInstalledMessage());
|
||||
}
|
||||
return ValidationResult.OK;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public final FieldArrayModel<String> stringInputs = new FieldArrayModel<>(
|
||||
//The fields need to be discovered by parsing json from rest endpoint.
|
||||
);
|
||||
|
||||
public final HierarchicalMultiSelectionFieldModel<Dependency> dependencies = new HierarchicalMultiSelectionFieldModel<>(Dependency.class, "dependencies")
|
||||
.label("Dependencies:");
|
||||
|
||||
private final FieldModel<String> projectName; //an alias for stringFields.getField("name");
|
||||
private final LiveVariable<String> location;
|
||||
private final NewProjectLocationValidator locationValidator;
|
||||
|
||||
private boolean allowUIThread = false;
|
||||
|
||||
public final LiveVariable<String> baseUrl;
|
||||
public final LiveExpression<ValidationResult> baseUrlValidator;
|
||||
|
||||
public final LiveVariable<String> downloadUrl = new LiveVariable<>();
|
||||
private IWorkingSet[] workingSets = new IWorkingSet[0];
|
||||
private RadioGroups radioGroups = new RadioGroups();
|
||||
private RadioGroup bootVersion;
|
||||
|
||||
private DependencyFilterBox filterBox = new DependencyFilterBox();
|
||||
|
||||
/**
|
||||
* Retrieves the most popular dependencies based on the number of times they have
|
||||
* been used to create a project.
|
||||
*
|
||||
* @param howMany is an upper limit on the number of most popular items to be returned.
|
||||
* @return An array of the most popular dependencies. May return fewer items than requested.
|
||||
*/
|
||||
public List<CheckBoxModel<Dependency>> getMostPopular(int howMany) {
|
||||
return popularities.getMostPopular(dependencies, howMany);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves currently set default dependencies
|
||||
* @return list of default dependencies check-box models
|
||||
*/
|
||||
public List<CheckBoxModel<Dependency>> getDefaultDependencies() {
|
||||
return defaultDependencies.getDependencies(dependencies);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves frequently used dependencies based on currently set default dependencies and the most popular dependencies
|
||||
*
|
||||
* @param numberOfMostPopular max number of most popular dependencies
|
||||
* @return list of frequently used dependencies
|
||||
*/
|
||||
public List<CheckBoxModel<Dependency>> getFrequentlyUsedDependencies(int numberOfMostPopular) {
|
||||
List<CheckBoxModel<Dependency>> defaultDependencies = getDefaultDependencies();
|
||||
Set<String> defaultDependecyIds = getDefaultDependenciesIds();
|
||||
getMostPopular(numberOfMostPopular).stream().filter(checkboxModel -> {
|
||||
return !defaultDependecyIds.contains(checkboxModel.getValue().getId());
|
||||
}).forEach(defaultDependencies::add);
|
||||
// Sort alphbetically
|
||||
defaultDependencies.sort(new Comparator<CheckBoxModel<Dependency>>() {
|
||||
@Override
|
||||
public int compare(CheckBoxModel<Dependency> d1, CheckBoxModel<Dependency> d2) {
|
||||
return d1.getLabel().compareTo(d2.getLabel());
|
||||
}
|
||||
});
|
||||
return defaultDependencies;
|
||||
}
|
||||
|
||||
public Set<String> getDefaultDependenciesIds() {
|
||||
return defaultDependencies.getDependciesIdSet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shouldn't be public really. This is just to make it easier to call from unit test.
|
||||
*/
|
||||
public void updateUsageCounts() {
|
||||
popularities.incrementUsageCount(dependencies.getCurrentSelection());
|
||||
}
|
||||
|
||||
public boolean saveDefaultDependencies() {
|
||||
return defaultDependencies.save(dependencies);
|
||||
}
|
||||
|
||||
public void performFinish(IProgressMonitor mon) throws InvocationTargetException, InterruptedException {
|
||||
mon.beginTask("Importing "+baseUrl.getValue(), 4);
|
||||
updateUsageCounts();
|
||||
preferredSelections.save(this);
|
||||
importProject(mon);
|
||||
}
|
||||
|
||||
protected void importProject(IProgressMonitor mon) throws InvocationTargetException, InterruptedException {
|
||||
DownloadManager downloader = null;
|
||||
try {
|
||||
downloader = new DownloadManager(urlConnectionFactory).allowUIThread(allowUIThread);
|
||||
|
||||
DownloadableItem zip = new DownloadableItem(newURL(downloadUrl .getValue()), downloader);
|
||||
String projectNameValue = projectName.getValue();
|
||||
CodeSet cs = CodeSet.fromZip(projectNameValue, zip, new Path("/"));
|
||||
|
||||
ImportStrategy strat = getImportStrategy();
|
||||
if (strat==null) {
|
||||
strat = BuildType.GENERAL.getDefaultStrategy();
|
||||
}
|
||||
IRunnableWithProgress oper = strat.createOperation(ImportUtils.importConfig(
|
||||
new Path(location.getValue()),
|
||||
projectNameValue,
|
||||
cs
|
||||
));
|
||||
oper.run(SubMonitor.convert(mon, 3));
|
||||
|
||||
IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectNameValue);
|
||||
addToWorkingSets(project, SubMonitor.convert(mon, 1));
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new InvocationTargetException(e);
|
||||
} finally {
|
||||
if (downloader!=null) {
|
||||
downloader.dispose();
|
||||
}
|
||||
mon.done();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get currently selected import strategy.
|
||||
*/
|
||||
public ImportStrategy getImportStrategy() {
|
||||
TypeRadioInfo selected = getSelectedTypeRadio();
|
||||
if (selected!=null) {
|
||||
return selected.getImportStrategy();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method so that test code can easily select an import strategy.
|
||||
* This will throw an exception if the given importstragey is not present
|
||||
* in this wizardmodel.
|
||||
*/
|
||||
public void setImportStrategy(ImportStrategy is) {
|
||||
RadioGroup typeRadios = getRadioGroups().getGroup("type");
|
||||
RadioInfo radio = typeRadios.getRadio(is.getId());
|
||||
Assert.isLegal(radio!=null);
|
||||
typeRadios.setValue(radio);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the currently selected BuildType.
|
||||
*/
|
||||
public BuildType getBuildType() {
|
||||
ImportStrategy is = getImportStrategy();
|
||||
if (is!=null) {
|
||||
return is.getBuildType();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private TypeRadioInfo getSelectedTypeRadio() {
|
||||
RadioGroup buildTypeRadios = getRadioGroups().getGroup("type");
|
||||
if (buildTypeRadios!=null) {
|
||||
return (TypeRadioInfo) buildTypeRadios.getSelection().selection.getValue();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void addToWorkingSets(IProject project, IProgressMonitor monitor) {
|
||||
monitor.beginTask("Add '"+project.getName()+"' to working sets", 1);
|
||||
try {
|
||||
if (workingSets==null || workingSets.length==0) {
|
||||
return;
|
||||
}
|
||||
IWorkingSetManager wsm = PlatformUI.getWorkbench().getWorkingSetManager();
|
||||
wsm.addToWorkingSets(project, workingSets);
|
||||
} finally {
|
||||
monitor.done();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically discover input fields and 'style' options by parsing initializr form.
|
||||
*/
|
||||
private void discoverOptions(FieldArrayModel<String> fields, HierarchicalMultiSelectionFieldModel<Dependency> dependencies) throws Exception {
|
||||
InitializrServiceSpec serviceSpec = parseJsonFrom(new URL(JSON_URL));
|
||||
|
||||
Map<String, String> textInputs = serviceSpec.getTextInputs();
|
||||
for (Entry<String, String> e : KNOWN_STRING_INPUTS.entrySet()) {
|
||||
String name = e.getKey();
|
||||
String defaultValue = textInputs.get(name);
|
||||
if (defaultValue!=null) {
|
||||
fields.add(new StringFieldModel(name, defaultValue).label(e.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
{ //field: type
|
||||
String groupName = "type";
|
||||
RadioGroup group = radioGroups.ensureGroup(groupName);
|
||||
group.label("Type:");
|
||||
for (Type type : serviceSpec.getTypeOptions(groupName)) {
|
||||
BuildType bt = KNOWN_TYPES.get(type.getId());
|
||||
if (bt!=null) {
|
||||
for (ImportStrategy is : bt.getImportStrategies()) {
|
||||
TypeRadioInfo radio = new TypeRadioInfo(groupName, type, is);
|
||||
radio.setLabel(is.displayName());
|
||||
group.add(radio);
|
||||
}
|
||||
}
|
||||
}
|
||||
//When a type is selected the 'baseUrl' should be update according to its action.
|
||||
group.getSelection().selection.addListener(new ValueListener<RadioInfo>() {
|
||||
@Override
|
||||
public void gotValue(LiveExpression<RadioInfo> exp, RadioInfo value) {
|
||||
try {
|
||||
if (value!=null) {
|
||||
URI base = new URI(JSON_URL);
|
||||
URI resolved = base.resolve(((TypeRadioInfo)value).getAction());
|
||||
baseUrl.setValue(resolved.toString());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
BootWizardActivator.log(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (Entry<String, String> e : KNOWN_SINGLE_SELECTS.entrySet()) {
|
||||
String groupName = e.getKey();
|
||||
RadioGroup group = radioGroups.ensureGroup(groupName);
|
||||
group.label(e.getValue());
|
||||
addOptions(group, serviceSpec.getSingleSelectOptions(groupName));
|
||||
if (groupName.equals("bootVersion")) {
|
||||
this.bootVersion = group;
|
||||
}
|
||||
}
|
||||
|
||||
//styles
|
||||
for (DependencyGroup dgroup : serviceSpec.getDependencies()) {
|
||||
String catName = dgroup.getName();
|
||||
for (Dependency dep : dgroup.getContent()) {
|
||||
dependencies.choice(catName, dep.getName(), dep,
|
||||
() -> {
|
||||
// Setup link template variable values
|
||||
Map<String, String> variables = new HashMap<>();
|
||||
variables.put(InitializrServiceSpec.BOOT_VERSION_LINK_TEMPLATE_VARIABLE,
|
||||
bootVersion.getSelection().selection.getValue().getValue());
|
||||
return DependencyTooltipContent.generateHtmlDocumentation(dep, variables);
|
||||
},
|
||||
DependencyTooltipContent.generateRequirements(dep),
|
||||
createEnablementExp(bootVersion, dep)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private LiveExpression<Boolean> createEnablementExp(final RadioGroup bootVersion, final Dependency dep) {
|
||||
try {
|
||||
String versionRange = dep.getVersionRange();
|
||||
if (StringUtils.isNotBlank(versionRange)) {
|
||||
return new LiveExpression<Boolean>() {
|
||||
{ dependsOn(bootVersion.getSelection().selection); }
|
||||
@Override
|
||||
protected Boolean compute() {
|
||||
RadioInfo radio = bootVersion.getValue();
|
||||
if (radio!=null) {
|
||||
String versionString = radio.getValue();
|
||||
return dep.isSupportedFor(versionString);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
} catch (Exception e) {
|
||||
BootWizardActivator.log(e);
|
||||
}
|
||||
return LiveExpression.TRUE;
|
||||
}
|
||||
|
||||
private void addOptions(RadioGroup group, Option[] options) {
|
||||
for (Option option : options) {
|
||||
RadioInfo radio = new RadioInfo(group.getName(), option.getId(), option.isDefault());
|
||||
radio.setLabel(option.getName());
|
||||
group.add(radio);
|
||||
}
|
||||
}
|
||||
|
||||
private InitializrServiceSpec parseJsonFrom(URL url) throws Exception {
|
||||
return InitializrServiceSpec.parseFrom(urlConnectionFactory, url);
|
||||
}
|
||||
|
||||
private URL newURL(String value) {
|
||||
try {
|
||||
return new URL(value);
|
||||
} catch (MalformedURLException e) {
|
||||
//This should be impossible because the URL syntax is validated beforehand.
|
||||
BootWizardActivator.log(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is mostly for testing purposes where it is just easier to run stuff in the UIThread (test do so
|
||||
* by default). But in production we shouldn't allow downloading stuff in the UIThread.
|
||||
*/
|
||||
public void allowUIThread(boolean allow) {
|
||||
this.allowUIThread = allow;
|
||||
}
|
||||
|
||||
public LiveExpression<ValidationResult> getLocationValidator() {
|
||||
return locationValidator;
|
||||
}
|
||||
|
||||
public LiveVariable<String> getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
public FieldModel<String> getProjectName() {
|
||||
return projectName;
|
||||
}
|
||||
|
||||
public void setWorkingSets(IWorkingSet[] workingSets) {
|
||||
this.workingSets = workingSets;
|
||||
}
|
||||
|
||||
public RadioGroups getRadioGroups() {
|
||||
return this.radioGroups;
|
||||
}
|
||||
|
||||
public RadioGroup getBootVersion() {
|
||||
return bootVersion;
|
||||
}
|
||||
|
||||
public FieldModel<String> getArtifactId() {
|
||||
String fieldName = ARTIFACT_PROPERTY_ID;
|
||||
return getStringInput(fieldName);
|
||||
}
|
||||
|
||||
public FieldModel<String> getStringInput(String fieldName) {
|
||||
for (FieldModel<String> fieldModel : stringInputs) {
|
||||
if (fieldName.equals(fieldModel.getName())) {
|
||||
return fieldModel;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public LiveVariable<String> getDependencyFilterBoxText() {
|
||||
return filterBox.getText();
|
||||
}
|
||||
|
||||
public LiveExpression<Filter<Dependency>> getDependencyFilter() {
|
||||
return filterBox.getFilter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for easier scripting of the wizard model (used in testing). Not used
|
||||
* by the UI itself. If the dependencyId isn't found in the wizard model then an IllegalArgumentException
|
||||
* will be raised.
|
||||
*/
|
||||
public void addDependency(String dependencyId){
|
||||
for (String catName : dependencies.getCategories()) {
|
||||
MultiSelectionFieldModel<Dependency> cat = dependencies.getContents(catName);
|
||||
for (Dependency dep : cat.getChoices()) {
|
||||
if (dependencyId.equals(dep.getId())) {
|
||||
cat.select(dep);
|
||||
return; //dep found and added to selection
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("No such dependency: "+dependencyId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the checkbox model for a dependency by its id.
|
||||
* <p>
|
||||
* This is meant for making scripting of test code against the wizard model easier. It is not
|
||||
* used in production code.
|
||||
*/
|
||||
public CheckBoxModel<Dependency> getDependencyBox(String id) {
|
||||
for (CheckBoxModel<Dependency> d : dependencies.getAllBoxes()) {
|
||||
if (d.getValue().getId().equals(id)) {
|
||||
return d;
|
||||
}
|
||||
}
|
||||
throw new NoSuchElementException("No dependency with id: "+id);
|
||||
}
|
||||
|
||||
public RadioGroup getJavaVersion() {
|
||||
return getRadioGroups().getGroup("javaVersion");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2012 Pivotal Software, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal Software, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jface.preference.IPreferenceStore;
|
||||
import org.springframework.ide.eclipse.boot.core.initializr.InitializrServiceSpec.Dependency;
|
||||
import org.springframework.ide.eclipse.boot.wizard.CheckBoxesSection.CheckBoxModel;
|
||||
|
||||
/**
|
||||
* Component that keeps track of 'popular' dependencies used in the NewSpringBootWizard.
|
||||
* It keeps a persistent count of how many times a given dependency has been used.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class PopularityTracker {
|
||||
|
||||
public static final String PREFIX = PopularityTracker.class.getName()+".";
|
||||
|
||||
private IPreferenceStore store;
|
||||
|
||||
public PopularityTracker(IPreferenceStore store) {
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
protected String key(String id) {
|
||||
String key = PREFIX+id;
|
||||
return key;
|
||||
}
|
||||
|
||||
public void incrementUsageCount(Dependency d) {
|
||||
incrementUsageCount(d.getId());
|
||||
}
|
||||
|
||||
public int getUsageCount(Dependency d) {
|
||||
return getUsageCount(d.getId());
|
||||
}
|
||||
|
||||
public int getUsageCount(String id) {
|
||||
return store.getInt(key(id));
|
||||
}
|
||||
|
||||
private void incrementUsageCount(String id) {
|
||||
String key = key(id);
|
||||
store.setValue(key, store.getInt(key)+1);
|
||||
}
|
||||
|
||||
public void incrementUsageCount(Collection<Dependency> selected) {
|
||||
for (Dependency d : selected) {
|
||||
incrementUsageCount(d);
|
||||
}
|
||||
}
|
||||
|
||||
public List<CheckBoxModel<Dependency>> getMostPopular(HierarchicalMultiSelectionFieldModel<Dependency> dependencies, int howMany) {
|
||||
final Map<CheckBoxModel<Dependency>, Integer> useCounts = new HashMap<CheckBoxModel<Dependency>, Integer>(); //usecounts by dependency id
|
||||
|
||||
ArrayList<CheckBoxModel<Dependency>> allUsedBoxes = new ArrayList<CheckBoxModel<Dependency>>();
|
||||
for (String category : dependencies.getCategories()) {
|
||||
allUsedBoxes.addAll(dependencies.getContents(category).getCheckBoxModels());
|
||||
}
|
||||
|
||||
for (Iterator<CheckBoxModel<Dependency>> iterator = allUsedBoxes.iterator(); iterator.hasNext();) {
|
||||
CheckBoxModel<Dependency> cb = iterator.next();
|
||||
int useCount = this.getUsageCount(cb.getValue());
|
||||
if (useCount==0) {
|
||||
iterator.remove(); //don't care about those options never used at all.
|
||||
} else {
|
||||
useCounts.put(cb, this.getUsageCount(cb.getValue()));
|
||||
}
|
||||
}
|
||||
Collections.sort(allUsedBoxes, new Comparator<CheckBoxModel<Dependency>>() {
|
||||
public int compare(CheckBoxModel<Dependency> o1, CheckBoxModel<Dependency> o2) {
|
||||
return useCounts.get(o2) - useCounts.get(o1);
|
||||
}
|
||||
});
|
||||
|
||||
howMany = Math.min(allUsedBoxes.size(), howMany);
|
||||
List<CheckBoxModel<Dependency>> result = new ArrayList<CheckBoxModel<Dependency>>();
|
||||
for (int i = 0; i < howMany; i++) {
|
||||
result.add(allUsedBoxes.get(i));
|
||||
}
|
||||
return Collections.unmodifiableList(result);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2012, 2016 Pivotal Software, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal Software, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.core.runtime.Assert;
|
||||
import org.eclipse.jface.preference.IPreferenceStore;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.FieldModel;
|
||||
|
||||
/**
|
||||
* Component that keeps track of some of the 'New Spring Starter Project' wizard
|
||||
* selections upon finishing the wizard,
|
||||
* so that we may pre-select those options again, the next time the wizard
|
||||
* is opened.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class PreferredSelections {
|
||||
|
||||
private String PREFIX = PreferredSelections.class.getName()+".";
|
||||
|
||||
private IPreferenceStore store;
|
||||
|
||||
public PreferredSelections(IPreferenceStore store) {
|
||||
Assert.isNotNull(store);
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
protected String key(String id) {
|
||||
String key = PREFIX+id;
|
||||
return key;
|
||||
}
|
||||
|
||||
public void save(NewSpringBootWizardModel wizard) {
|
||||
for (FieldModel<String> input : wizard.stringInputs) {
|
||||
if (isInteresting(input)) {
|
||||
put(input.getName(), input.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
for (RadioGroup input : wizard.getRadioGroups().getGroups()) {
|
||||
if (isInteresting(input)) {
|
||||
put(input.getName(), input.getValue().getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isInteresting(RadioGroup input) {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean isInteresting(FieldModel<String> input) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public void restore(NewSpringBootWizardModel wizard) {
|
||||
for (FieldModel<String> input : wizard.stringInputs) {
|
||||
if (isInteresting(input)) {
|
||||
String v = get(input.getName(), input.getValue());
|
||||
input.setValue(v);
|
||||
}
|
||||
}
|
||||
for (RadioGroup input : wizard.getRadioGroups().getGroups()) {
|
||||
if (isInteresting(input)) {
|
||||
String choiceId = get(input.getName(), null);
|
||||
RadioInfo info = input.getRadio(choiceId);
|
||||
if (info!=null) {
|
||||
input.getVariable().setValue(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void put(String id, String value) {
|
||||
String key = key(id);
|
||||
store.setValue(key, value);
|
||||
}
|
||||
|
||||
private String get(String name, String dflt) {
|
||||
String key = key(name);
|
||||
String v = store.getString(key);
|
||||
if (StringUtils.isNotBlank(v)) {
|
||||
return v;
|
||||
}
|
||||
return dflt;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2014 GoPivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.FieldModel;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.SelectionModel;
|
||||
|
||||
public class RadioGroup extends FieldModel<RadioInfo> {
|
||||
|
||||
private List<RadioInfo> radios = new ArrayList<RadioInfo>();
|
||||
|
||||
public RadioGroup(String name) {
|
||||
super(RadioInfo.class, name, null);
|
||||
}
|
||||
|
||||
public void add(RadioInfo radioInfo) {
|
||||
this.radios.add(radioInfo);
|
||||
if (radioInfo.isCheckedInitially()) {
|
||||
setDefaultValue(radioInfo);
|
||||
getVariable().setValue(radioInfo);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public RadioInfo[] getRadios() {
|
||||
return radios.toArray(new RadioInfo[radios.size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RadioGroup("+getName()+")";
|
||||
}
|
||||
|
||||
public SelectionModel<RadioInfo> getSelection() {
|
||||
return new SelectionModel<RadioInfo>(getVariable(), getValidator());
|
||||
}
|
||||
|
||||
public RadioInfo getRadio(String value) {
|
||||
for (RadioInfo r : radios) {
|
||||
if (r.getValue().equals(value)) {
|
||||
return r;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2014 GoPivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* An instance of this class keeps track of a number of radio button models, grouped by their 'name'
|
||||
* attribute.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class RadioGroups {
|
||||
|
||||
|
||||
//In the form the radios look like this:
|
||||
// <label class="radio">
|
||||
// <input type="radio" name="packaging" value="jar" checked="true"/>
|
||||
// Jar
|
||||
// </label>
|
||||
|
||||
public interface GroupLabelProvider {
|
||||
/**
|
||||
* Try to discover label somehow. If it can't be discovered, is allowed to return null.
|
||||
* This function is only called when a new RadioGroupInfo is created.
|
||||
*/
|
||||
String discoverLabel();
|
||||
}
|
||||
|
||||
//Maps 'name' attribute to a RadioGroupInfo
|
||||
private Map<String, RadioGroup> groups = new LinkedHashMap<String, RadioGroup>();
|
||||
|
||||
/**
|
||||
* Ensure a group with given name exists. Create it if needed.
|
||||
* @return The RadioGroup, never null.
|
||||
*/
|
||||
public RadioGroup ensureGroup(String groupName) {
|
||||
RadioGroup group = groups.get(groupName);
|
||||
if (group==null) {
|
||||
group = new RadioGroup(groupName);
|
||||
groups.put(groupName, group);
|
||||
}
|
||||
return group;
|
||||
}
|
||||
|
||||
public List<RadioGroup> getGroups() {
|
||||
return new ArrayList<RadioGroup>(groups.values());
|
||||
}
|
||||
|
||||
public RadioGroup getGroup(String name) {
|
||||
return groups.get(name);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2014 GoPivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard;
|
||||
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.Ilabelable;
|
||||
|
||||
|
||||
/**
|
||||
* Contains information about a radio button (typically parsed from an html form element).
|
||||
*/
|
||||
public class RadioInfo implements Ilabelable {
|
||||
|
||||
private final boolean isCheckedInitially;
|
||||
private final String value;
|
||||
private final String groupName;
|
||||
private String label;
|
||||
|
||||
public RadioInfo(String groupName, String value, boolean checked) {
|
||||
this.groupName = groupName;
|
||||
this.value = value;
|
||||
this.label = value; //Default label
|
||||
this.isCheckedInitially = checked;
|
||||
}
|
||||
|
||||
public boolean isCheckedInitially() {
|
||||
return isCheckedInitially;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Radio(" + groupName+", "+value+")";
|
||||
}
|
||||
|
||||
public void setLabel(String label) {
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public String getGroupName() {
|
||||
return groupName;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
/**
|
||||
* The value to be passed as a query parameter to start.spring.io app,
|
||||
* when this option is selected. Typically this will be the same as the
|
||||
* value returned by getValue, but subclasses may override to change it
|
||||
* to something else.
|
||||
*/
|
||||
public String getUrlParamValue() {
|
||||
return getValue();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015, 2020 Pivotal Software, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal Software, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard;
|
||||
|
||||
import org.eclipse.jface.layout.GridDataFactory;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.events.ModifyEvent;
|
||||
import org.eclipse.swt.events.ModifyListener;
|
||||
import org.eclipse.swt.events.PaintEvent;
|
||||
import org.eclipse.swt.events.PaintListener;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Control;
|
||||
import org.eclipse.swt.widgets.Text;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.LiveExpression;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.LiveVariable;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.UIValueListener;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.ValidationResult;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.ValueListener;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.Disposable;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.IPageWithSections;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.WizardPageSection;
|
||||
|
||||
/**
|
||||
* A text box with the look and feel of a search box. The contents
|
||||
* of the searchbox text is mirrored into a LiveVariable 'model'.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class SearchBoxSection extends WizardPageSection implements Disposable {
|
||||
|
||||
private Text searchBox;
|
||||
private LiveVariable<String> model;
|
||||
private ValueListener<String> modelListener;
|
||||
private boolean grabFocus;
|
||||
|
||||
public SearchBoxSection(IPageWithSections owner, LiveVariable<String> model) {
|
||||
super(owner);
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LiveExpression<ValidationResult> getValidator() {
|
||||
return OK_VALIDATOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createContents(Composite page) {
|
||||
searchBox = new Text(page, SWT.SINGLE | SWT.BORDER | SWT.SEARCH | SWT.ICON_CANCEL);
|
||||
searchBox.setMessage(getSearchHint());
|
||||
searchBox.setLayoutData(GridDataFactory.fillDefaults().grab(true, false).create());
|
||||
searchBox.addModifyListener(new ModifyListener() {
|
||||
@Override
|
||||
public void modifyText(ModifyEvent e) {
|
||||
model.setValue(searchBox.getText());
|
||||
}
|
||||
});
|
||||
this.model.addListener(modelListener = new UIValueListener<String>() {
|
||||
@Override
|
||||
public void uiGotValue(LiveExpression<String> exp, String ignore) {
|
||||
String newText = model.getValue();
|
||||
if (searchBox.isDisposed()) {
|
||||
SearchBoxSection.this.dispose();
|
||||
} else {
|
||||
String oldText = searchBox.getText();
|
||||
if (!oldText.equals(newText)) { //Avoid cursor bug on macs.
|
||||
searchBox.setText(newText);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if (grabFocus) {
|
||||
focusControl();
|
||||
}
|
||||
// IContentProposalProvider proposalProvider = new TagContentProposalProvider(viewModel);
|
||||
// ContentProposalAdapter caAdapter = new ContentProposalAdapter(searchBox, new TextContentAdapter(), proposalProvider, UIUtils.CTRL_SPACE, null);
|
||||
// caAdapter.setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_REPLACE);
|
||||
}
|
||||
|
||||
protected String getSearchHint() {
|
||||
return "Type a text to search";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
if (modelListener!=null) {
|
||||
this.model.removeListener(modelListener);
|
||||
modelListener = null;
|
||||
}
|
||||
searchBox.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* When the control for this section is created, it will attempt to immediately grab
|
||||
* keyboard focus.
|
||||
*/
|
||||
public SearchBoxSection grabFocus(boolean grab) {
|
||||
this.grabFocus = grab;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void focusControl() {
|
||||
if (searchBox != null) {
|
||||
if (!searchBox.setFocus()) {
|
||||
searchBox.addPaintListener(new PaintListener() {
|
||||
@Override
|
||||
public void paintControl(PaintEvent e) {
|
||||
if (searchBox.setFocus()) {
|
||||
searchBox.removePaintListener(this);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected Control getControl() {
|
||||
return searchBox;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Pivotal Software, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal Software, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.eclipse.jface.layout.GridDataFactory;
|
||||
import org.eclipse.jface.layout.GridLayoutFactory;
|
||||
import org.eclipse.jface.resource.JFaceResources;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.events.MouseEvent;
|
||||
import org.eclipse.swt.events.MouseListener;
|
||||
import org.eclipse.swt.events.MouseTrackListener;
|
||||
import org.eclipse.swt.graphics.Color;
|
||||
import org.eclipse.swt.graphics.Font;
|
||||
import org.eclipse.swt.graphics.Point;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Display;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
import org.springframework.ide.eclipse.boot.wizard.CheckBoxesSection.CheckBoxModel;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.LiveExpression;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.LiveVariable;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.ValidationResult;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.Validator;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.ValueListener;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.IPageWithSections;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.WizardPageSection;
|
||||
import org.springsource.ide.eclipse.commons.livexp.util.Filter;
|
||||
import org.springsource.ide.eclipse.commons.ui.HtmlTooltip;
|
||||
|
||||
public class SelectedButtonSection<T> extends WizardPageSection {
|
||||
|
||||
|
||||
private Composite buttonComp;
|
||||
private ValueListener<Boolean> selectionListener;
|
||||
|
||||
|
||||
protected final CheckBoxModel<T> model;
|
||||
protected final LiveVariable<Boolean> isVisible = new LiveVariable<>(true);
|
||||
|
||||
public SelectedButtonSection(IPageWithSections owner, CheckBoxModel<T> model) {
|
||||
super(owner);
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createContents(Composite page) {
|
||||
if (page != null && !page.isDisposed()) {
|
||||
buttonComp = new Composite(page, SWT.NONE);
|
||||
|
||||
GridDataFactory.fillDefaults().grab(false, false).applyTo(buttonComp);
|
||||
|
||||
createButtonArea();
|
||||
isVisible.addListener(new ValueListener<Boolean>() {
|
||||
public void gotValue(LiveExpression<Boolean> exp, Boolean reveal) {
|
||||
if (reveal != null && buttonComp != null && !buttonComp.isDisposed()) {
|
||||
buttonComp.setVisible(reveal);
|
||||
GridData data = (GridData) buttonComp.getLayoutData();
|
||||
data.exclude = !reveal;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void createButtonArea() {
|
||||
GridLayoutFactory.fillDefaults().numColumns(2).margins(0, 0).spacing(10, 0).applyTo(buttonComp);
|
||||
Label xButton = new Label(buttonComp, SWT.READ_ONLY);
|
||||
GridDataFactory.fillDefaults().grab(false, false).align(SWT.BEGINNING, SWT.CENTER).applyTo(xButton);
|
||||
xButton.setText("X");
|
||||
xButton.setFont(getBold());
|
||||
applyDefaultEffects(xButton);
|
||||
|
||||
xButton.addMouseListener(new MouseListener() {
|
||||
|
||||
@Override
|
||||
public void mouseUp(MouseEvent event) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseDown(MouseEvent event) {
|
||||
removeSelection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseDoubleClick(MouseEvent event) {
|
||||
removeSelection();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
xButton.addMouseTrackListener(new MouseTrackListener() {
|
||||
|
||||
@Override
|
||||
public void mouseHover(MouseEvent event) {
|
||||
// Do nothing. Enter/exit events are faster than hover, so those will
|
||||
// be fired first before hover
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExit(MouseEvent event) {
|
||||
applyDefaultEffects(xButton);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseEnter(MouseEvent event) {
|
||||
applyHoverEffects(xButton);
|
||||
}
|
||||
});
|
||||
|
||||
xButton.setToolTipText("Click to remove the dependency");
|
||||
|
||||
Label label = new Label(buttonComp, SWT.NONE);
|
||||
GridDataFactory.fillDefaults().grab(false, false).align(SWT.BEGINNING, SWT.CENTER)
|
||||
.applyTo(label);
|
||||
|
||||
label.setText(model.getLabel());
|
||||
Supplier<String> tooltip = model.getTooltipHtml();
|
||||
if (tooltip != null) {
|
||||
// Setup HTML tooltip and its content
|
||||
HtmlTooltip htmlTooltip = new HtmlTooltip(label);
|
||||
htmlTooltip.setMaxSize(400, 400);
|
||||
htmlTooltip.setHtml(tooltip);
|
||||
}
|
||||
}
|
||||
|
||||
private Font getBold() {
|
||||
// Note: fonts from the registry do not need to be managed outside for disposal.
|
||||
return JFaceResources.getFontRegistry().getBold(JFaceResources.DEFAULT_FONT);
|
||||
}
|
||||
|
||||
protected void applyHoverEffects(Label xButton) {
|
||||
if (xButton != null && !xButton.isDisposed()) {
|
||||
xButton.setForeground(getSystemColor(SWT.COLOR_RED));
|
||||
}
|
||||
}
|
||||
|
||||
protected void applyDefaultEffects(Label xButton) {
|
||||
if (xButton != null && !xButton.isDisposed()) {
|
||||
xButton.setForeground(getSystemColor(SWT.COLOR_DARK_GRAY));
|
||||
}
|
||||
}
|
||||
|
||||
protected Color getSystemColor(int colorCode) {
|
||||
return Display.getDefault().getSystemColor(colorCode);
|
||||
}
|
||||
|
||||
protected void removeSelection() {
|
||||
model.getSelection().setValue(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
if (buttonComp != null && !buttonComp.isDisposed()) {
|
||||
buttonComp.dispose();
|
||||
buttonComp = null;
|
||||
}
|
||||
if (selectionListener != null) {
|
||||
model.getSelection().removeListener(selectionListener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filter and return whether this widget's visibility has changed as a
|
||||
* result.
|
||||
*
|
||||
* @return Whether visibility of this widget changed.
|
||||
*/
|
||||
public boolean applyFilter(Filter<T> filter) {
|
||||
boolean wasVisible = isVisible.getValue();
|
||||
isVisible.setValue(filter.accept(model.getValue()));
|
||||
boolean changed = wasVisible != isVisible.getValue();
|
||||
return changed;
|
||||
}
|
||||
|
||||
public boolean isVisible() {
|
||||
return isVisible.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SelectedButtonSection(" + model.getLabel() + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public LiveExpression<ValidationResult> getValidator() {
|
||||
return Validator.OK;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Pivotal Software, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal Software, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard;
|
||||
|
||||
import org.eclipse.jface.layout.GridDataFactory;
|
||||
import org.eclipse.jface.layout.GridLayoutFactory;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.graphics.Point;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.springframework.ide.eclipse.boot.core.initializr.InitializrServiceSpec.Dependency;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.LiveExpression;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.ValueListener;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.IPageWithSections;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.Scroller;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.WizardPageSection;
|
||||
import org.springsource.ide.eclipse.commons.livexp.util.Filter;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
public class SelectedDependenciesSection extends WizardPageSection {
|
||||
|
||||
private Composite dependencyArea;
|
||||
private Scroller scroller;
|
||||
|
||||
private LiveExpression<Filter<Dependency>> filter;
|
||||
|
||||
private Point sizeHint = new Point(SWT.DEFAULT, SWT.DEFAULT);
|
||||
private HierarchicalMultiSelectionFieldModel<Dependency> dependencies;
|
||||
|
||||
public SelectedDependenciesSection(IPageWithSections owner, HierarchicalMultiSelectionFieldModel<Dependency> deps) {
|
||||
super(owner);
|
||||
this.dependencies = deps;
|
||||
this.filter = createFilter(deps);
|
||||
}
|
||||
|
||||
public SelectedDependenciesSection(IPageWithSections owner, NewSpringBootWizardModel model) {
|
||||
this(owner, model.dependencies);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createContents(Composite page) {
|
||||
scroller = new Scroller(page);
|
||||
GridDataFactory.fillDefaults().grab(true, true).hint(sizeHint).applyTo(scroller);
|
||||
dependencyArea = scroller.getBody();
|
||||
GridLayoutFactory.fillDefaults().spacing(0, 0).applyTo(dependencyArea);
|
||||
GridDataFactory.fillDefaults().grab(true, true).applyTo(dependencyArea);
|
||||
|
||||
for (String cat : dependencies.getCategories()) {
|
||||
MultiSelectionFieldModel<Dependency> dependencyGroup = dependencies.getContents(cat);
|
||||
SelectedSection<Dependency> checkboxesSection = new SelectedSection<>(owner,
|
||||
dependencyGroup.getCheckBoxModels(), /* no label */ null);
|
||||
checkboxesSection.createContents(dependencyArea);
|
||||
|
||||
this.filter.addListener((exp, value) -> onFilter(checkboxesSection, cat));
|
||||
}
|
||||
}
|
||||
|
||||
private void onFilter(SelectedSection<Dependency> checkboxesSection, String cat) {
|
||||
|
||||
checkboxesSection.applyFilter(filter.getValue());
|
||||
|
||||
if (checkboxesSection.isCreated()) {
|
||||
boolean hasVisible = checkboxesSection.hasVisible();
|
||||
checkboxesSection.setVisible(hasVisible);
|
||||
}
|
||||
|
||||
layout();
|
||||
}
|
||||
|
||||
private void layout() {
|
||||
if (dependencyArea != null && !dependencyArea.isDisposed()) {
|
||||
dependencyArea.layout(true);
|
||||
dependencyArea.getParent().layout(true);
|
||||
}
|
||||
}
|
||||
|
||||
private static LiveExpression<Filter<Dependency>> createFilter(HierarchicalMultiSelectionFieldModel<Dependency> dependencies) {
|
||||
LiveExpression<Filter<Dependency>> filter = new LiveExpression<Filter<Dependency>>() {
|
||||
|
||||
@Override
|
||||
protected Filter<Dependency> compute() {
|
||||
ImmutableSet<Dependency> currentSelection = ImmutableSet
|
||||
.copyOf(dependencies.getCurrentSelection());
|
||||
return (dependency) -> currentSelection.contains(dependency);
|
||||
}
|
||||
};
|
||||
|
||||
ValueListener<Boolean> selectionListener = (exp, val) -> {
|
||||
filter.refresh();
|
||||
};
|
||||
|
||||
for (String cat : dependencies.getCategories()) {
|
||||
MultiSelectionFieldModel<Dependency> dependencyGroup = dependencies.getContents(cat);
|
||||
dependencyGroup.addSelectionListener(selectionListener);
|
||||
}
|
||||
|
||||
return filter;
|
||||
}
|
||||
|
||||
public SelectedDependenciesSection sizeHint(Point sizeHint) {
|
||||
if (sizeHint != null) {
|
||||
this.sizeHint = sizeHint;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Pivotal Software, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal Software, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jface.layout.GridDataFactory;
|
||||
import org.eclipse.jface.layout.GridLayoutFactory;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Group;
|
||||
import org.springframework.ide.eclipse.boot.wizard.CheckBoxesSection.CheckBoxModel;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.LiveExpression;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.LiveVariable;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.ValueListener;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.IPageWithSections;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.ReflowableSection;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.WizardPageSection;
|
||||
import org.springsource.ide.eclipse.commons.livexp.util.Filter;
|
||||
|
||||
public class SelectedSection<T> extends ReflowableSection {
|
||||
|
||||
private List<CheckBoxModel<T>> model;
|
||||
private Composite composite;
|
||||
private WizardPageSection[] subsections;
|
||||
|
||||
private String label;
|
||||
private LiveVariable<Boolean> visibleState = new LiveVariable<>(true);
|
||||
|
||||
|
||||
public SelectedSection(IPageWithSections owner, List<CheckBoxModel<T>> model, String label) {
|
||||
super(owner);
|
||||
this.label = label;
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createContents(Composite page) {
|
||||
composite = createComposite(page);
|
||||
GridLayoutFactory.fillDefaults().numColumns(1).margins(0, 0).spacing(0, 0).applyTo(composite);
|
||||
GridDataFactory.fillDefaults().grab(true, false).applyTo(composite);
|
||||
createSubsections();
|
||||
composite.pack();
|
||||
visibleState.addListener(new ValueListener<Boolean>() {
|
||||
@Override
|
||||
public void gotValue(LiveExpression<Boolean> exp, Boolean value) {
|
||||
if (value!=null && composite!=null && !composite.isDisposed()) {
|
||||
boolean newState = value;
|
||||
composite.setVisible(newState);
|
||||
GridData data = (GridData) composite.getLayoutData();
|
||||
data.exclude = !newState;
|
||||
reflow(owner, composite);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void createSubsections() {
|
||||
subsections = new WizardPageSection[Math.max(1, model.size())];
|
||||
|
||||
for (int i = 0; i < model.size(); i++) {
|
||||
subsections[i] = new SelectedButtonSection<T>(owner, model.get(i));
|
||||
subsections[i].createContents(composite);
|
||||
}
|
||||
}
|
||||
|
||||
protected Composite createComposite(Composite page) {
|
||||
if (this.label!=null) {
|
||||
Group comp = new Group(page, SWT.NONE);
|
||||
comp.setText(label);
|
||||
return comp;
|
||||
} else {
|
||||
return new Composite(page, SWT.NONE);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean applyFilter(Filter<T> filter) {
|
||||
if (subsections!=null) {
|
||||
boolean visibilityChanged = false;
|
||||
for (WizardPageSection subsection : subsections) {
|
||||
if (subsection instanceof SelectedButtonSection) {
|
||||
@SuppressWarnings("unchecked")
|
||||
SelectedButtonSection<T> cb = (SelectedButtonSection<T>) subsection;
|
||||
visibilityChanged |= cb.applyFilter(filter);
|
||||
}
|
||||
}
|
||||
if (visibilityChanged) {
|
||||
reflow(owner, composite);
|
||||
}
|
||||
return visibilityChanged;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean hasVisible() {
|
||||
if (subsections!=null) {
|
||||
for (WizardPageSection s : subsections) {
|
||||
if (s instanceof SelectedButtonSection) {
|
||||
@SuppressWarnings("unchecked")
|
||||
SelectedButtonSection<T> cb = ((SelectedButtonSection<T>) s);
|
||||
if (cb.isVisible()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean isCreated() {
|
||||
return composite != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
|
||||
if (subsections != null) {
|
||||
for (WizardPageSection subsection : subsections) {
|
||||
subsection.dispose();
|
||||
}
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public void setModel(List<CheckBoxModel<T>> model) {
|
||||
dispose();
|
||||
this.model = model;
|
||||
createSubsections();
|
||||
composite.layout();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2020 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.core.filebuffers.FileBuffers;
|
||||
import org.eclipse.core.filebuffers.ITextFileBuffer;
|
||||
import org.eclipse.core.filebuffers.LocationKind;
|
||||
import org.eclipse.core.resources.IFile;
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.core.runtime.NullProgressMonitor;
|
||||
import org.eclipse.jface.dialogs.MessageDialog;
|
||||
import org.eclipse.jface.dialogs.MessageDialogWithToggle;
|
||||
import org.eclipse.jface.preference.IPreferenceStore;
|
||||
import org.eclipse.jface.viewers.IStructuredSelection;
|
||||
import org.eclipse.swt.widgets.Shell;
|
||||
import org.springframework.ide.eclipse.boot.core.BootActivator;
|
||||
import org.springsource.ide.eclipse.commons.frameworks.ui.internal.utils.ProjectFilter;
|
||||
import org.springsource.ide.eclipse.commons.frameworks.ui.internal.utils.SelectionUtils;
|
||||
|
||||
public class StartersWizardUtil {
|
||||
|
||||
private static final String AUTO_SAVE_DIRTY_POM = "EditStartersDialog.pom.autosave";
|
||||
private static final int SAVE_BUTTON = 256;
|
||||
|
||||
public static IProject getProject(IStructuredSelection selection) {
|
||||
List<IProject> projects = SelectionUtils.getProjects(selection, ProjectFilter.anyProject);
|
||||
if (projects != null && !projects.isEmpty()) {
|
||||
return projects.get(0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static int promptIfPomFileDirty(IProject selectedProject, Shell shell) throws CoreException {
|
||||
ITextFileBuffer dirtyPom = getDirtyPomFile(selectedProject);
|
||||
if (dirtyPom != null) {
|
||||
if (!savePom(selectedProject, shell, dirtyPom)) {
|
||||
return MessageDialog.CANCEL;
|
||||
}
|
||||
}
|
||||
return MessageDialog.OK;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask the user if its okay to save the pom. If they say yes, save it.
|
||||
* <p>
|
||||
*
|
||||
* @return true if the pom was saved.
|
||||
*/
|
||||
private static boolean savePom(IProject selectedProject, Shell shell, ITextFileBuffer dirtyPom)
|
||||
throws CoreException {
|
||||
IPreferenceStore prefs = BootActivator.getDefault().getPreferenceStore();
|
||||
boolean autoSave = prefs.getBoolean(AUTO_SAVE_DIRTY_POM);
|
||||
boolean save = autoSave || askSavePom(selectedProject, shell, dirtyPom);
|
||||
if (save) {
|
||||
dirtyPom.commit(new NullProgressMonitor(), true);
|
||||
}
|
||||
return save;
|
||||
}
|
||||
|
||||
private static boolean askSavePom(IProject selectedProject, Shell shell, ITextFileBuffer dirtyPom)
|
||||
throws CoreException {
|
||||
MessageDialogWithToggle dlg = new MessageDialogWithToggle(shell, "Pom file needs saving!", null,
|
||||
"The pom file for project '" + selectedProject.getName()
|
||||
+ "' has unsaved edits. The 'Add Starters' dialog makes changes to "
|
||||
+ "the pom file on disk. Do you want to save it now?",
|
||||
MessageDialog.WARNING, new String[] { "Cancel", "Save Pom" }, 1,
|
||||
"Don't ask again and save automatically in the future", false);
|
||||
int code = dlg.open();
|
||||
if (code == SAVE_BUTTON) { // The Dialog has a weird logic but that's the number it gets for our 'save'
|
||||
// button based on its position.
|
||||
if (dlg.getToggleState()) {
|
||||
BootActivator.getDefault().getPreferenceStore().setValue(AUTO_SAVE_DIRTY_POM, true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static ITextFileBuffer getDirtyPomFile(IProject project) {
|
||||
if (project.isAccessible()) {
|
||||
IFile pomFile = project.getFile("pom.xml");
|
||||
ITextFileBuffer buffer = FileBuffers.getTextFileBufferManager().getTextFileBuffer(pomFile.getFullPath(),
|
||||
LocationKind.IFILE);
|
||||
if (buffer != null && buffer.isDirty()) {
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2014, 2016 GoPivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard;
|
||||
|
||||
import org.springframework.ide.eclipse.boot.core.initializr.InitializrServiceSpec.Type;
|
||||
import org.springframework.ide.eclipse.boot.wizard.importing.ImportStrategy;
|
||||
|
||||
/**
|
||||
* Extension of {@link RadioInfo} adds extra attribute(s) only
|
||||
* applicable to the 'type' input field.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class TypeRadioInfo extends RadioInfo {
|
||||
|
||||
private ImportStrategy importStrategy;
|
||||
private Type type;
|
||||
|
||||
public String getAction() {
|
||||
return type.getAction();
|
||||
}
|
||||
|
||||
public TypeRadioInfo(String groupName, Type type, ImportStrategy importStrategy) {
|
||||
super(groupName, importStrategy.getId(), type.isDefault());
|
||||
this.type = type;
|
||||
this.importStrategy = importStrategy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Radio(" + getGroupName()+", "+getValue()+")";
|
||||
}
|
||||
|
||||
public ImportStrategy getImportStrategy() {
|
||||
return importStrategy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUrlParamValue() {
|
||||
//We override super because the start.spring.io wizard doesn't understand the difference between
|
||||
// "GRADLE-STS" and "GRADLE-Buildship". Since our own wizard supports multipe import-strategies per
|
||||
//project type, our type IDs are distinct from the ones used by start.spring.io
|
||||
//This method returns the corresponding id to be used when passing it to start.spring.io as url
|
||||
//parameter value.
|
||||
return type.getId();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013, 2016 GoPivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.ide.eclipse.boot.core.SimpleUriBuilder;
|
||||
import org.springframework.ide.eclipse.boot.core.initializr.IdAble;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.FieldModel;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.LiveExpression;
|
||||
|
||||
/**
|
||||
* LiveExpression that computes a URL String based on a number of input fields.
|
||||
*/
|
||||
public class UrlMaker extends LiveExpression<String> {
|
||||
|
||||
private final List<FieldModel<String>> inputs = new ArrayList<FieldModel<String>>();
|
||||
private final List<MultiSelectionFieldModel<IdAble>> multiInputs = new ArrayList<MultiSelectionFieldModel<IdAble>>();
|
||||
private final List<FieldModel<RadioInfo>> radioInputs = new ArrayList<FieldModel<RadioInfo>>();
|
||||
|
||||
private final LiveExpression<String> baseUrl;
|
||||
|
||||
public UrlMaker(String baseUrl) {
|
||||
this(LiveExpression.constant(baseUrl));
|
||||
}
|
||||
|
||||
public UrlMaker(LiveExpression<String> baseUrl) {
|
||||
this.baseUrl = baseUrl;
|
||||
dependsOn(baseUrl);
|
||||
}
|
||||
|
||||
public UrlMaker addField(FieldModel<String> param) {
|
||||
inputs.add(param);
|
||||
dependsOn(param.getVariable()); //Recompute my value when the input changes.
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends IdAble> UrlMaker addField(MultiSelectionFieldModel<T> param) {
|
||||
multiInputs.add((MultiSelectionFieldModel<IdAble>) param);
|
||||
for (T choice : param.getChoices()) {
|
||||
//Recomput value when any 'checkbox' selection status changes
|
||||
dependsOn(param.getSelection(choice));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public void addField(HierarchicalMultiSelectionFieldModel<? extends IdAble> param) {
|
||||
for (String cat : param.getCategories()) {
|
||||
addField(param.getContents(cat));
|
||||
}
|
||||
}
|
||||
|
||||
public UrlMaker addField(RadioGroup group) {
|
||||
this.radioInputs.add(group);
|
||||
dependsOn(group.getVariable());
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String compute() {
|
||||
String baseUrl = this.baseUrl.getValue();
|
||||
if (baseUrl==null) {
|
||||
baseUrl = "";
|
||||
} else {
|
||||
baseUrl = baseUrl.trim();
|
||||
}
|
||||
SimpleUriBuilder builder = new SimpleUriBuilder(baseUrl);
|
||||
for (FieldModel<String> f : inputs) {
|
||||
String paramValue = f.getValue();
|
||||
if (paramValue!=null) {
|
||||
builder.addParameter(f.getName(), paramValue);
|
||||
}
|
||||
}
|
||||
|
||||
for (FieldModel<RadioInfo> f : radioInputs) {
|
||||
RadioInfo radio = f.getValue();
|
||||
if (radio!=null) {
|
||||
String paramValue = radio.getUrlParamValue();
|
||||
if (paramValue!=null) {
|
||||
builder.addParameter(f.getName(), paramValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (MultiSelectionFieldModel<IdAble> mf : multiInputs) {
|
||||
String name = mf.getName();
|
||||
for (IdAble selectedValue : mf.getCurrentSelection()) {
|
||||
//Note that it is possible for values to be selected and disabled at the same time
|
||||
// (i.e. a checkbox can be checked and 'greyed' out at the same time)
|
||||
//We must therefore check enablement before adding a selection to the URI.
|
||||
if (mf.getEnablement(selectedValue).getValue()) {
|
||||
builder.addParameter(name, selectedValue.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013 GoPivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard;
|
||||
|
||||
import org.eclipse.jdt.internal.ui.workingsets.IWorkingSetIDs;
|
||||
import org.eclipse.jface.viewers.IStructuredSelection;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.ui.IWorkingSet;
|
||||
import org.eclipse.ui.dialogs.WorkingSetGroup;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.LiveExpression;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.ValidationResult;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.Validator;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.IPageWithSections;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.WizardPageSection;
|
||||
|
||||
/**
|
||||
* Wraps a org.eclipse.ui.dialogs.WorkingSetConfigurationBlock so that it can
|
||||
* inserted into a WizardPageWithSections
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
@SuppressWarnings("restriction")
|
||||
public class WorkingSetSection extends WizardPageSection {
|
||||
|
||||
private WorkingSetGroup group;
|
||||
private final IStructuredSelection selection;
|
||||
|
||||
public WorkingSetSection(IPageWithSections owner, IStructuredSelection selection) {
|
||||
super(owner);
|
||||
this.selection = selection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LiveExpression<ValidationResult> getValidator() {
|
||||
return Validator.OK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createContents(Composite page) {
|
||||
String[] workingSetTypes= new String[] { IWorkingSetIDs.JAVA, IWorkingSetIDs.RESOURCE };
|
||||
this.group = new WorkingSetGroup(page, selection, workingSetTypes);
|
||||
}
|
||||
|
||||
public IWorkingSet[] getWorkingSets() {
|
||||
if (group!=null) {
|
||||
return group.getSelectedWorkingSets();
|
||||
}
|
||||
return new IWorkingSet[0];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013 GoPivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.content;
|
||||
|
||||
import org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager.DownloadManager;
|
||||
import org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager.UIThreadDownloadDisallowed;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.ValidationResult;
|
||||
|
||||
public abstract class AGSContent implements GSContent {
|
||||
|
||||
protected DownloadManager downloader;
|
||||
|
||||
public AGSContent(DownloadManager dl) {
|
||||
this.downloader = dl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeSet getCodeSet(String name) throws UIThreadDownloadDisallowed {
|
||||
for (CodeSet cs : getCodeSets()) {
|
||||
if (cs.getName().equals(name)) {
|
||||
return cs;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDownloaded() {
|
||||
return getZip().isDownloaded();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValidationResult downloadStatus() {
|
||||
return ValidationResult.from(getZip().getDownloadStatus());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDownloader(DownloadManager downloader) {
|
||||
this.downloader = downloader;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013, 2016 GoPivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.content;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.core.runtime.Assert;
|
||||
import org.eclipse.core.runtime.IPath;
|
||||
import org.eclipse.core.runtime.Path;
|
||||
import org.springframework.ide.eclipse.boot.wizard.importing.GeneralProjectStrategy;
|
||||
import org.springframework.ide.eclipse.boot.wizard.importing.ImportStrategies;
|
||||
import org.springframework.ide.eclipse.boot.wizard.importing.ImportStrategy;
|
||||
import org.springframework.ide.eclipse.boot.wizard.importing.ImportStrategyHolder;
|
||||
import org.springframework.ide.eclipse.boot.wizard.importing.MavenStrategy;
|
||||
|
||||
public enum BuildType {
|
||||
MAVEN("pom.xml",
|
||||
MavenStrategy.class.getName(),
|
||||
"M2E (Eclipse Maven Tooling) is not installed"
|
||||
),
|
||||
GRADLE("build.gradle"),
|
||||
GENERAL(null,
|
||||
GeneralProjectStrategy.class.getName(),
|
||||
"NA"
|
||||
);
|
||||
|
||||
private static final ContributedImportStrategies contributions = new ContributedImportStrategies();
|
||||
|
||||
/**
|
||||
* Location of the 'build script' relative to codeset (project) root.
|
||||
* This also serves as a way to recognize if a BuildType is
|
||||
* supported by a CodeSet. I.e. if the buildScript file
|
||||
* exists in the code set then it is assumed the codeset can
|
||||
* be imported with the corresponding ImportStrategy.
|
||||
*/
|
||||
private Path buildScriptPath;
|
||||
private List<ImportStrategyHolder> strategies = new ArrayList<>();
|
||||
private String displayName;
|
||||
private boolean hasDefaultStrategy = false;
|
||||
|
||||
private BuildType(String buildScriptPath) {
|
||||
if (buildScriptPath!=null) {
|
||||
this.buildScriptPath = new Path(buildScriptPath);
|
||||
}
|
||||
}
|
||||
|
||||
private BuildType(String buildScriptPath, String importStrategyClass, String notInstalledMessage) {
|
||||
this(buildScriptPath);
|
||||
hasDefaultStrategy = true;
|
||||
addStrategy(new ImportStrategyHolder(this,
|
||||
ImportStrategies.forClass(importStrategyClass),
|
||||
notInstalledMessage,
|
||||
"Default"
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
public IPath getBuildScript() {
|
||||
return buildScriptPath;
|
||||
}
|
||||
|
||||
public List<ImportStrategy> getImportStrategies() {
|
||||
contributions.initialize();
|
||||
ArrayList<ImportStrategy> instances = new ArrayList<>(strategies.size());
|
||||
for (ImportStrategyHolder f : strategies) {
|
||||
instances.add(f.get());
|
||||
}
|
||||
return Collections.unmodifiableList(instances);
|
||||
}
|
||||
|
||||
public String displayName() {
|
||||
if (displayName==null) {
|
||||
String name = name();
|
||||
displayName = name.substring(0,1) + name.substring(1).toLowerCase();
|
||||
}
|
||||
return displayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* The option that is preferred by the UI as the initial selection.
|
||||
*/
|
||||
public static final BuildType DEFAULT = MAVEN;
|
||||
|
||||
public void addStrategy(ImportStrategyHolder strategyHolder) {
|
||||
strategies.add(strategyHolder);
|
||||
}
|
||||
|
||||
public ImportStrategy getDefaultStrategy() {
|
||||
Assert.isLegal(hasDefaultStrategy);
|
||||
return getImportStrategies().get(0);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013, 2018 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.content;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.eclipse.core.runtime.IPath;
|
||||
import org.eclipse.core.runtime.Path;
|
||||
import org.springsource.ide.eclipse.commons.core.util.OsUtils;
|
||||
import org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager.DownloadableItem;
|
||||
import org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager.UIThreadDownloadDisallowed;
|
||||
import org.springsource.ide.eclipse.commons.frameworks.core.util.IOUtil;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.ValidationResult;
|
||||
|
||||
/**
|
||||
* A CodeSet represents a bunch of content that can somehow be imported into
|
||||
* the workspace to create a project.
|
||||
* <p>
|
||||
* A CodeSet instance is more like a handle to content than actual content.
|
||||
* I.e. it contains enough information to fetch the content but doesn't guarantee that the
|
||||
* content actually exists.
|
||||
* <p>
|
||||
* This means it is possible to create CodeSet instances without having to pay the
|
||||
* cost of downloading the Zip file upfront. Some operations on CodeSets however
|
||||
* will force download to be attempted.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public abstract class CodeSet {
|
||||
|
||||
public interface AfterCreateHook {
|
||||
void afterCreateAt(final File location) throws Exception;
|
||||
}
|
||||
|
||||
public static AfterCreateHook afterCreateHook = null;
|
||||
|
||||
private static final boolean IS_WINDOWS = OsUtils.isWindows();
|
||||
|
||||
/**
|
||||
* Represents an Entry in a CodeSet. API similar to ZipEntry but paths may be remapped
|
||||
* as they are relative to the root of the code set not the Zip (or other source of data)
|
||||
*/
|
||||
public abstract class CodeSetEntry {
|
||||
|
||||
public abstract IPath getPath();
|
||||
|
||||
public abstract boolean isDirectory();
|
||||
|
||||
public abstract InputStream getData() throws IOException;
|
||||
|
||||
/**
|
||||
* If a given implementation of CodeSetEntry has the means to determine
|
||||
* permission flags, then it can override this method.
|
||||
*/
|
||||
public int getUnixMode() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static abstract class Processor<T> {
|
||||
|
||||
/**
|
||||
* Do some work an a ZipEntry in a CodeSet.
|
||||
*/
|
||||
public abstract T doit(CodeSetEntry e) throws Exception;
|
||||
}
|
||||
|
||||
protected String name;
|
||||
private List<BuildType> buildTypes;
|
||||
|
||||
public CodeSet(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public static CodeSet fromZip(String name, DownloadableItem zip, IPath root) {
|
||||
return new ZipFileCodeSet(name, zip, root);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch list of build types that are supported by the CodeSet. Note that
|
||||
* determining this information requires inspecting some of the directory
|
||||
* entries in the CodeSet and therefore it requires downloading the
|
||||
* content / zip / repo the CodeSet is contained in (unless it is
|
||||
* already cached locally).
|
||||
*/
|
||||
public List<BuildType> getBuildTypes() throws UIThreadDownloadDisallowed {
|
||||
//Only use 'default' build type if no other build type works.
|
||||
if (this.buildTypes==null) {
|
||||
//Careful if the code in here throws (e.g. because not yet downloaded and in UI thread...
|
||||
//then do NOT stick an empty list into this.buildTypes!
|
||||
ArrayList<BuildType> buildTypes = new ArrayList<BuildType>();
|
||||
for (BuildType type : BuildType.values()) {
|
||||
IPath scriptFile = type.getBuildScript();
|
||||
if (scriptFile!=null && hasFile(scriptFile)) {
|
||||
buildTypes.add(type);
|
||||
}
|
||||
}
|
||||
if (buildTypes.isEmpty()) {
|
||||
buildTypes.add(BuildType.GENERAL);
|
||||
}
|
||||
this.buildTypes = buildTypes;
|
||||
}
|
||||
return this.buildTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if content for this codeset can be downloaded and
|
||||
* contains some data.
|
||||
*
|
||||
* If the CodeSet was not previously downloaded this will force a
|
||||
* download.
|
||||
*/
|
||||
public abstract boolean exists() throws Exception;
|
||||
|
||||
/**
|
||||
* Returns true if this codeset has a file with a given path.
|
||||
*
|
||||
* If the CodeSet was not previously downloaded this will force a
|
||||
* download.
|
||||
* @throws UIThreadDownloadDisallowed
|
||||
*/
|
||||
public abstract boolean hasFile(IPath path) throws UIThreadDownloadDisallowed;
|
||||
|
||||
/**
|
||||
* Returns true if this codeset has a folder with a given path.
|
||||
*
|
||||
* If the CodeSet was not previously downloaded this will force a
|
||||
* download.
|
||||
*/
|
||||
public abstract boolean hasFolder(IPath path);
|
||||
|
||||
/**
|
||||
* Convenience method that allows passing paths as Strings instead of IPath instances
|
||||
*/
|
||||
public final boolean hasFile(String path) throws UIThreadDownloadDisallowed {
|
||||
return hasFile(new Path(path));
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform some work on all content element in this codeSet.
|
||||
* The processor may return a non-null value or throw an Exception to
|
||||
* stop processing in the middle of the iteration.
|
||||
*/
|
||||
public abstract <T> T each(Processor<T> processor) throws Exception;
|
||||
|
||||
/**
|
||||
* Copies the contents of codeset to a given filesystem location
|
||||
*/
|
||||
public void createAt(final File location) throws Exception {
|
||||
if (location.exists()) {
|
||||
if (!FileUtils.deleteQuietly(location)) {
|
||||
throw new IOException("Data already exists at location and it could not be deleted: "+location);
|
||||
}
|
||||
}
|
||||
java.nio.file.Path normalizedLocationPath = location.toPath().normalize();
|
||||
each(new CodeSet.Processor<Void>() {
|
||||
@Override
|
||||
public Void doit(CodeSetEntry e) throws Exception {
|
||||
IPath path = e.getPath();
|
||||
File target = new File(location, path.toString());
|
||||
assertCorrectOutputLocation(normalizedLocationPath, target.toPath().normalize());
|
||||
if (e.isDirectory()) {
|
||||
target.mkdirs();
|
||||
} else {
|
||||
IOUtil.pipe(e.getData(), target);
|
||||
if (!IS_WINDOWS) {
|
||||
int mode = e.getUnixMode();
|
||||
if (mode>0) {
|
||||
Files.setPosixFilePermissions(target.toPath(), OsUtils.posixFilePermissions(mode));
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
if (afterCreateHook!=null) {
|
||||
afterCreateHook.afterCreateAt(location);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks output location folder and target file location
|
||||
* @param normalizedLocation normalized location folder path
|
||||
* @param normalizedTarget normalized target file path
|
||||
* @throws Exception throws exception if there is a problem
|
||||
*/
|
||||
protected void assertCorrectOutputLocation(java.nio.file.Path normalizedLocation, java.nio.file.Path normalizedTarget) throws Exception {
|
||||
|
||||
}
|
||||
|
||||
public ValidationResult validateBuildType(BuildType buildType) throws UIThreadDownloadDisallowed {
|
||||
List<BuildType> validBuildTypes = getBuildTypes();
|
||||
if (validBuildTypes.contains(buildType)) {
|
||||
return ValidationResult.OK;
|
||||
}
|
||||
//Not valid formulate a nice explanation
|
||||
IPath expectedScript = buildType.getBuildScript();
|
||||
if (expectedScript!=null) {
|
||||
return ValidationResult.error("Can't use '"+buildType.displayName()+"': There is no '"+expectedScript+"'");
|
||||
} else {
|
||||
StringBuilder message = new StringBuilder("Should be imported as ");
|
||||
boolean first = true;
|
||||
for (BuildType bt : validBuildTypes) {
|
||||
if (!first) {
|
||||
message.append(" or ");
|
||||
}
|
||||
message.append("'"+bt.displayName()+"'");
|
||||
first = false;
|
||||
}
|
||||
return ValidationResult.error(message.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a single entry from a codeset. Note that this operation might be expensive because
|
||||
* it might open and close a zipfile for a ZipFileCodeSet).
|
||||
*/
|
||||
public abstract <T> T readFileEntry(String path, Processor<T> processor) throws Exception;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013, 2016 Pivotal Software, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal Software, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.content;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.core.runtime.Assert;
|
||||
import org.eclipse.core.runtime.IProgressMonitor;
|
||||
import org.eclipse.core.runtime.SubMonitor;
|
||||
import org.eclipse.jface.operation.IRunnableContext;
|
||||
import org.springframework.ide.eclipse.boot.util.Log;
|
||||
import org.springframework.ide.eclipse.boot.wizard.BootWizardActivator;
|
||||
import org.springframework.ide.eclipse.boot.wizard.github.auth.AuthenticatedDownloader;
|
||||
import org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager.DownloadManager;
|
||||
import org.springsource.ide.eclipse.commons.frameworks.core.util.JobUtil;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.LiveVariable;
|
||||
|
||||
/**
|
||||
* An instance of the class manages lists of content of different types. The
|
||||
* idea is to create a subclass that provides all the concrete details on how
|
||||
* different types of content are discovered, downloaded and cached on the local
|
||||
* file system.
|
||||
* <p>
|
||||
* But the infrastructure for managing/downloading the content is shared.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
* @author Nieraj Singh
|
||||
*/
|
||||
public class ContentManager {
|
||||
|
||||
private final Map<Class<?>, TypedContentManager<?>> byClass = new HashMap<>();
|
||||
private final List<ContentType<?>> types = new ArrayList<>();
|
||||
|
||||
protected LiveVariable<DownloadState> prefetchContentTracker = new LiveVariable<>(DownloadState.NOT_STARTED);
|
||||
protected Throwable prefetchContentError = null;
|
||||
|
||||
protected LiveVariable<DownloadState> prefetchContentProviderPropertiesTracker = new LiveVariable<>(
|
||||
DownloadState.NOT_STARTED);
|
||||
|
||||
public synchronized <T extends GSContent> void register(Class<T> klass, String description, ContentProvider<T> provider) {
|
||||
try {
|
||||
Assert.isLegal(!byClass.containsKey(klass), "A content provider for " + klass + " is already registered");
|
||||
|
||||
prefetchContentTracker.setValue(DownloadState.NOT_STARTED);
|
||||
|
||||
ContentType<T> ctype = new ContentType<>(klass, description);
|
||||
types.add(ctype);
|
||||
DownloadManager downloader = downloadManagerFor(klass);
|
||||
byClass.put(klass, new TypedContentManager<>(downloader, provider));
|
||||
} catch (Throwable e) {
|
||||
BootWizardActivator.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a DownloadManager for a given content type name
|
||||
*/
|
||||
public DownloadManager downloadManagerFor(Class<?> contentType) throws IllegalStateException, IOException {
|
||||
return new DownloadManager(new AuthenticatedDownloader(),
|
||||
new File(BootWizardActivator.getDefault().getStateLocation().toFile(), contentType.getSimpleName()))
|
||||
.clearCache();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Fetch the content of a given type. May return null but only if no content
|
||||
* provider has been registered for the type.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T[] get(Class<T> type) {
|
||||
|
||||
TypedContentManager<T> man = (TypedContentManager<T>) byClass.get(type);
|
||||
if (man != null) {
|
||||
return man.getAll();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public synchronized ContentType<?>[] getTypes() {
|
||||
return types.toArray(new ContentType<?>[types.size()]);
|
||||
}
|
||||
|
||||
public Object[] get(ContentType<?> ct) {
|
||||
if (ct != null) {
|
||||
return get(ct.getKlass());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will return content for the given content type, IF and only if, there is no prefetching currently under way. Otherwise it returns null.
|
||||
* @param ct
|
||||
* @return content if no downloading is currently taking place. Null otherwise
|
||||
*/
|
||||
public Object[] getWithPrefetchCheck(ContentType<?> ct) {
|
||||
if (prefetchContentTracker.getValue() != DownloadState.IS_DOWNLOADING) {
|
||||
return get(ct);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a content manager that contains just a single content item.
|
||||
*/
|
||||
public static <T extends GSContent> ContentManager singleton(final Class<T> type, final String description, final T item) {
|
||||
ContentManager cm = new ContentManager();
|
||||
cm.register(type, description, new ContentProvider<T>() {
|
||||
// @Override
|
||||
@Override
|
||||
public T[] fetch(DownloadManager downloader) {
|
||||
@SuppressWarnings("unchecked")
|
||||
T[] array = (T[]) Array.newInstance(type, 1);
|
||||
item.setDownloader(downloader);
|
||||
array[0] = item;
|
||||
return array;
|
||||
}
|
||||
});
|
||||
return cm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefetch all the content. May require network access therefore do not run in the UI thread.
|
||||
*/
|
||||
public void prefetchAllContent(IProgressMonitor monitor) {
|
||||
|
||||
prefetchContentTracker.setValue(DownloadState.IS_DOWNLOADING);
|
||||
prefetchContentError = null;
|
||||
try {
|
||||
ContentType<?>[] allTypes = getTypes();
|
||||
|
||||
if (allTypes != null) {
|
||||
for (ContentType<?> type : allTypes) {
|
||||
if (monitor.isCanceled()) {
|
||||
break;
|
||||
}
|
||||
get(type);
|
||||
}
|
||||
}
|
||||
// Inform any listeners that completion has just finished (e.g. to let listeners refresh the content in their views)
|
||||
prefetchContentTracker.setValue(DownloadState.DOWNLOADING_COMPLETED);
|
||||
} catch (Throwable e) {
|
||||
prefetchContentError = e;
|
||||
prefetchContentTracker.setValue(DownloadState.DOWNLOADING_FAILED);
|
||||
throw e;
|
||||
} finally {
|
||||
// Mark the session as downloaded
|
||||
prefetchContentTracker.setValue(DownloadState.DOWNLOADED);
|
||||
}
|
||||
}
|
||||
|
||||
public LiveVariable<DownloadState> getPrefetchContentTracker() {
|
||||
return prefetchContentTracker;
|
||||
}
|
||||
|
||||
public Throwable getPrefetchContentError() {
|
||||
return prefetchContentError;
|
||||
}
|
||||
|
||||
public LiveVariable<DownloadState> getPrefetchContentProviderPropertiesTracker() {
|
||||
return prefetchContentProviderPropertiesTracker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will run {@link #prefetch(IProgressMonitor)} in the background.
|
||||
* @param runnableContext. Must not be null.
|
||||
*/
|
||||
public void prefetchInBackground(IRunnableContext runnableContext) {
|
||||
try {
|
||||
JobUtil.runBackgroundJobWithUIProgress((monitor) -> {
|
||||
|
||||
prefetch(monitor);
|
||||
|
||||
}, runnableContext, "Prefetching content...");
|
||||
} catch (Exception e) {
|
||||
BootWizardActivator.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Will prefetch (download) all content. This may be a long-running process
|
||||
* and is not expected to be run in the UI thread.
|
||||
*
|
||||
* @param monitor
|
||||
* must not be null
|
||||
*/
|
||||
protected void prefetch(IProgressMonitor monitor) {
|
||||
String downloadLabel = "Downloading all content. Please wait...";
|
||||
prefetchAllContent(SubMonitor.convert(monitor, downloadLabel, 50));
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes all prefetching tracking listeners. However, tracking states are preserved.
|
||||
*/
|
||||
public void disposePrefetchTrackingListeners() {
|
||||
// The purpose of disposing trackers is to clear any listeners that are registered to avoid possible memory leaks
|
||||
// on accumulating listeners over time, but the tracker states should be preserved.
|
||||
DownloadState propertiesDownloadState = prefetchContentProviderPropertiesTracker.getValue();
|
||||
DownloadState contentDownloadState = prefetchContentTracker.getValue();
|
||||
|
||||
// Dispose all listeners to avoid memory leaks for listeners that keep getting accumulating over time.
|
||||
prefetchContentProviderPropertiesTracker.dispose();
|
||||
prefetchContentTracker.dispose();
|
||||
|
||||
// Make sure new trackers are set
|
||||
prefetchContentProviderPropertiesTracker = new LiveVariable<>(propertiesDownloadState);
|
||||
prefetchContentTracker = new LiveVariable<>(contentDownloadState);
|
||||
}
|
||||
|
||||
public static enum DownloadState {
|
||||
/**
|
||||
* Downloading currently under way
|
||||
*/
|
||||
IS_DOWNLOADING,
|
||||
|
||||
/**
|
||||
* Active downloading session has just completed succesfully. For example, this state may be used to trigger
|
||||
* refresh of content in views.
|
||||
*/
|
||||
DOWNLOADING_COMPLETED,
|
||||
|
||||
/**
|
||||
* Active downloading sessions has just completed with an exception. An explanation of the error may be retrieved
|
||||
* by calling the 'getException' method
|
||||
*/
|
||||
DOWNLOADING_FAILED,
|
||||
|
||||
/**
|
||||
* A "Post-downloading" state, different than
|
||||
* {@link #DOWNLOADING_COMPLETED}, that indicates that all content has
|
||||
* already been downloaded before, and potentially no further action is
|
||||
* required on any participants that are listening for this state.
|
||||
*/
|
||||
DOWNLOADED,
|
||||
|
||||
/**
|
||||
* No downloading has been initiated yet
|
||||
*/
|
||||
NOT_STARTED
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013 VMware, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* VMware, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.content;
|
||||
|
||||
import org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager.DownloadManager;
|
||||
|
||||
/**
|
||||
* An instance of this interface implements some method of discovering
|
||||
* content of a particular type.
|
||||
*/
|
||||
public interface ContentProvider<T> {
|
||||
|
||||
T[] fetch(DownloadManager downloader);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013 GoPivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.content;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.core.runtime.Assert;
|
||||
|
||||
public class ContentType<T extends GSContent> implements Describable, DisplayNameable {
|
||||
|
||||
private final Class<T> klass;
|
||||
private final String description;
|
||||
|
||||
public ContentType(Class<T> klass, String description) {
|
||||
Assert.isNotNull(klass);
|
||||
Assert.isNotNull(description);
|
||||
this.klass = klass;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public Class<T> getKlass() {
|
||||
return klass;
|
||||
}
|
||||
|
||||
// @Override
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ContentType("+klass.getSimpleName()+")";
|
||||
}
|
||||
|
||||
// @Override
|
||||
public String getDisplayName() {
|
||||
return beatifyClassName(klass.getSimpleName());
|
||||
}
|
||||
|
||||
private String beatifyClassName(String simpleName) {
|
||||
//Assume class name is camel case. Just split it up at capital letters and
|
||||
// insert spaces there.
|
||||
StringBuilder result = new StringBuilder();
|
||||
Matcher m = Pattern.compile("[A-Z]").matcher(simpleName);
|
||||
int pos1 = 0;
|
||||
boolean found = m.find();
|
||||
while (found) {
|
||||
int pos2 = m.start();
|
||||
if (pos2>pos1) {
|
||||
result.append(simpleName.substring(pos1, pos2)+" ");
|
||||
}
|
||||
pos1 = pos2;
|
||||
found = m.find();
|
||||
}
|
||||
//Don't forget the last bit
|
||||
if (pos1>=0) {
|
||||
result.append(simpleName.substring(pos1));
|
||||
}
|
||||
return result.toString();
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.content;
|
||||
|
||||
import org.eclipse.core.runtime.IConfigurationElement;
|
||||
import org.eclipse.core.runtime.Platform;
|
||||
import org.springframework.ide.eclipse.boot.wizard.BootWizardActivator;
|
||||
import org.springframework.ide.eclipse.boot.wizard.importing.ImportStrategy;
|
||||
import org.springframework.ide.eclipse.boot.wizard.importing.ImportStrategyFactory;
|
||||
import org.springframework.ide.eclipse.boot.wizard.importing.ImportStrategyHolder;
|
||||
|
||||
/**
|
||||
* An instance of this class is responsible for reading the contributions to
|
||||
* extension point "org.springframework.ide.eclipse.wizard.import.strategy" and
|
||||
* adding them to the corresponding {@link BuildType}.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class ContributedImportStrategies {
|
||||
|
||||
private static final String EXTENSION_POINT = "org.springframework.ide.eclipse.wizard.import.strategy";
|
||||
|
||||
private boolean isInitialized;
|
||||
|
||||
public synchronized void initialize() {
|
||||
if (!isInitialized) {
|
||||
isInitialized = true;
|
||||
initializeFromExtensions();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void initializeFromExtensions() {
|
||||
try {
|
||||
IConfigurationElement[] elements = Platform.getExtensionRegistry().getConfigurationElementsFor(EXTENSION_POINT);
|
||||
|
||||
for (IConfigurationElement element : elements) {
|
||||
element.createExecutableExtension("class");
|
||||
BuildType buildType = getBuildType(element);
|
||||
ImportStrategyHolder strategyHolder = new ImportStrategyHolder(
|
||||
buildType,
|
||||
getFactory(element),
|
||||
getNotInstalledMessage(element),
|
||||
getName(element)
|
||||
);
|
||||
buildType.addStrategy(strategyHolder);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
BootWizardActivator.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private String getName(IConfigurationElement element) {
|
||||
return element.getAttribute("name");
|
||||
}
|
||||
|
||||
private String getNotInstalledMessage(IConfigurationElement element) {
|
||||
return element.getAttribute("notInstalledMessage");
|
||||
}
|
||||
|
||||
private ImportStrategyFactory getFactory(final IConfigurationElement element) {
|
||||
//Why wrap a factory in another factory?
|
||||
// This is so that if either creation of the factory, or creation of the instance fails, we
|
||||
// can catch the exceptions in the caller of the (wrapped) factory.
|
||||
//In other words, this makes sure that getFactory is 'safe' and won't throw exceptions.
|
||||
return new ImportStrategyFactory() {
|
||||
@Override
|
||||
public ImportStrategy create(BuildType buildType, String name, String notInstalledMessage) throws Exception {
|
||||
ImportStrategyFactory factory = (ImportStrategyFactory) element.createExecutableExtension("class");
|
||||
return factory.create(buildType, name, notInstalledMessage);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private BuildType getBuildType(IConfigurationElement element) {
|
||||
return BuildType.valueOf(element.getAttribute("buildType"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013 GoPivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.content;
|
||||
|
||||
/**
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public interface Describable {
|
||||
|
||||
String getDescription();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013 GoPivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.content;
|
||||
|
||||
/**
|
||||
* Interface to be implemented by content elements that can provide a 'nice' name
|
||||
* to display to the user. Typically this method will be used by label providers
|
||||
* and other UI widgetry.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public interface DisplayNameable {
|
||||
|
||||
String getDisplayName();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013 GoPivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.content;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.ide.eclipse.boot.wizard.guides.GSImportWizard;
|
||||
import org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager.DownloadManager;
|
||||
import org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager.DownloadableItem;
|
||||
import org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager.UIThreadDownloadDisallowed;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.ValidationResult;
|
||||
|
||||
/**
|
||||
* Interface that needs to be implemented by any content type that can be imported via
|
||||
* the generic {@link GSImportWizard}
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public interface GSContent extends Describable, DisplayNameable {
|
||||
|
||||
public String getName();
|
||||
public String getDisplayName();
|
||||
|
||||
public List<CodeSet> getCodeSets() throws UIThreadDownloadDisallowed;
|
||||
public CodeSet getCodeSet(String name) throws UIThreadDownloadDisallowed;
|
||||
public URL getHomePage();
|
||||
|
||||
public DownloadableItem getZip(); //Some content may not be packaged in a zip.
|
||||
// This method should be removed from the interface.
|
||||
//Shouldn't be needed to be used directly by client code if using codeset abstraction
|
||||
|
||||
|
||||
public boolean isDownloaded();
|
||||
|
||||
/**
|
||||
* If isDownloaded returns false, this may contain an explanation (if the
|
||||
* reason for isDownloaded false was some type of error that occurred
|
||||
* during download. The message is retained as a download error status.
|
||||
*/
|
||||
public ValidationResult downloadStatus();
|
||||
|
||||
public void setDownloader(DownloadManager downloader);
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013 GoPivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.content;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.core.runtime.Path;
|
||||
import org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager.DownloadableItem;
|
||||
import org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager.UIThreadDownloadDisallowed;
|
||||
|
||||
/**
|
||||
* 'Standalon' content item that contains just a single codeset taken from a zip file downloaded from
|
||||
* a URL.
|
||||
* <p>
|
||||
* These content items might be created from links found inside of guides or tutorial
|
||||
* webpages.
|
||||
*
|
||||
*/
|
||||
public class GSZipFileCodeSet extends AGSContent {
|
||||
|
||||
private final URI zipUrl;
|
||||
|
||||
public GSZipFileCodeSet(URI zipUrl) {
|
||||
super(null); //downloader may not be known yet until added to content manager.
|
||||
this.zipUrl = zipUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "No description";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
URL url = getHomePage();
|
||||
return zipUrl.getPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CodeSet> getCodeSets() throws UIThreadDownloadDisallowed {
|
||||
return Arrays.asList((CodeSet)new ZipFileCodeSet(getName(), getZip(), new Path("/")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getHomePage() {
|
||||
//Isolated zip file. No home page!
|
||||
return null;
|
||||
}
|
||||
|
||||
private URL zipUrl() {
|
||||
try {
|
||||
return zipUrl.toURL();
|
||||
} catch (MalformedURLException e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DownloadableItem getZip() {
|
||||
return new DownloadableItem(zipUrl(), downloader);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013, 2016 Pivotal Software, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal Software, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.content;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
import org.eclipse.core.runtime.IProgressMonitor;
|
||||
import org.eclipse.core.runtime.Platform;
|
||||
import org.eclipse.core.runtime.SubMonitor;
|
||||
import org.springframework.ide.eclipse.boot.wizard.github.GithubClient;
|
||||
import org.springframework.ide.eclipse.boot.wizard.github.Repo;
|
||||
import org.springsource.ide.eclipse.commons.core.preferences.StsProperties;
|
||||
import org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager.DownloadManager;
|
||||
import org.springsource.ide.eclipse.commons.livexp.util.ExceptionUtil;
|
||||
import org.springsource.ide.eclipse.commons.livexp.util.Log;
|
||||
|
||||
/**
|
||||
* Singleton class. The instance of this class provides access to all the
|
||||
* getting started content.
|
||||
*
|
||||
* NOTE: templates are not (yet?) included in this. Code to discover and
|
||||
* manage them already existed before this framework was implemented.
|
||||
*/
|
||||
public class GettingStartedContent extends ContentManager {
|
||||
|
||||
// IMPORTANT NOTE: Because this is a singleton class,
|
||||
// CARE needs to be taken with any listeners registered, especially in live expressions.
|
||||
// The ContentManager super class has two tracker LiveVariables to track
|
||||
// registration of content providers and downloading of content.
|
||||
// These two are publicly exposed and may result in listeners being registered by owners.
|
||||
// To avoid memory leaks, be sure that any live expressions that accumulate listeners are properly disposed
|
||||
|
||||
private static GettingStartedContent INSTANCE = null;
|
||||
|
||||
private final static boolean ADD_REAL = true;
|
||||
private final static boolean ADD_MOCKS = false; // (""+Platform.getLocation()).contains("kdvolder")
|
||||
|
||||
private static final boolean DEBUG = (""+Platform.getLocation()).contains("kdvolder")
|
||||
|| (""+Platform.getLocation()).contains("bamboo");
|
||||
|
||||
public static GettingStartedContent getInstance() {
|
||||
if (INSTANCE == null) {
|
||||
INSTANCE = new GettingStartedContent();
|
||||
}
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void prefetch(IProgressMonitor monitor) {
|
||||
// Register the content providers as part of prefetching, as registering
|
||||
// the providers may also require network access much like downloading content.
|
||||
String registeringProvidersLabel = "Registering content providers";
|
||||
registerAllContentProviders(SubMonitor.convert(monitor, registeringProvidersLabel, 50));
|
||||
super.prefetch(monitor);
|
||||
}
|
||||
|
||||
private final GithubClient github = new GithubClient();
|
||||
|
||||
|
||||
/**
|
||||
* We need this in multiple places. So cache it to avoid asking for it multiple times in a row.
|
||||
*/
|
||||
private Repo[] cachedRepos = null;
|
||||
|
||||
private Repo[] getGuidesRepos() {
|
||||
if (cachedRepos==null) {
|
||||
Repo[] repos = github.getOrgRepos("spring-guides");
|
||||
Arrays.sort(repos, new Comparator<Repo>() {
|
||||
@Override
|
||||
public int compare(Repo o1, Repo o2) {
|
||||
return o1.getName().compareTo(o2.getName());
|
||||
}
|
||||
});
|
||||
if (DEBUG) {
|
||||
// System.out.println("==== spring-guides-repos ====");
|
||||
// int count = 1;
|
||||
// for (Repo r : repos) {
|
||||
// System.out.println(count++ + ":" + r.getName());
|
||||
// }
|
||||
// System.out.println("==== spring-guides-repos ====");
|
||||
}
|
||||
cachedRepos = repos;
|
||||
}
|
||||
return cachedRepos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registering content providers may require network access if content provider properties
|
||||
* need to be fetched external. Avoid running in UI thread.
|
||||
* @param monitor must not be null.
|
||||
*/
|
||||
protected void registerAllContentProviders(IProgressMonitor monitor) {
|
||||
// Avoid registering and downloading content properties if already registered
|
||||
if (prefetchContentProviderPropertiesTracker.getValue()!=DownloadState.DOWNLOADED) {
|
||||
try {
|
||||
prefetchContentProviderPropertiesTracker.setValue(DownloadState.IS_DOWNLOADING);
|
||||
registerAllWithStsProperties(StsProperties.getInstance(monitor));
|
||||
prefetchContentProviderPropertiesTracker.setValue(DownloadState.DOWNLOADING_COMPLETED);
|
||||
} finally {
|
||||
prefetchContentProviderPropertiesTracker.setValue(DownloadState.DOWNLOADED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Will register all content providers using STS properties.
|
||||
*
|
||||
*/
|
||||
public void registerAllWithStsProperties(final StsProperties stsProps) {
|
||||
//Guides: are discoverable because they are all repos in org on github
|
||||
register(GettingStartedGuide.class, GettingStartedGuide.GUIDE_DESCRIPTION_TEXT,
|
||||
new ContentProvider<GettingStartedGuide>() {
|
||||
// @Override
|
||||
@Override
|
||||
public GettingStartedGuide[] fetch(DownloadManager downloader) {
|
||||
LinkedHashMap<String, GettingStartedGuide> guides = new LinkedHashMap<String, GettingStartedGuide>();
|
||||
if (ADD_MOCKS) {
|
||||
addGuidesFrom(github.getMyRepos(), guides, downloader);
|
||||
}
|
||||
if (ADD_REAL) {
|
||||
addGuidesFrom(getGuidesRepos(), guides, downloader);
|
||||
}
|
||||
return guides.values().toArray(new GettingStartedGuide[guides.size()]);
|
||||
}
|
||||
|
||||
private LinkedHashMap<String, GettingStartedGuide> addGuidesFrom(Repo[] repos, LinkedHashMap<String, GettingStartedGuide> guides, DownloadManager downloader) {
|
||||
for (Repo repo : repos) {
|
||||
String name = repo.getName();
|
||||
// System.out.println("repo : "+name + " "+repo.getUrl());
|
||||
if (name.startsWith("gs-") && !guides.containsKey(name)) {
|
||||
guides.put(name, new GettingStartedGuide(stsProps, repo, downloader));
|
||||
}
|
||||
}
|
||||
return guides;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Commented out: there are no more tutorial guides.
|
||||
// register(TutorialGuide.class, TutorialGuide.GUIDE_DESCRIPTION_TEXT,
|
||||
// new ContentProvider<TutorialGuide>() {
|
||||
// public TutorialGuide[] fetch(DownloadManager downloader) {
|
||||
// LinkedHashMap<String, TutorialGuide> guides = new LinkedHashMap<String, TutorialGuide>();
|
||||
// addGuidesFrom(getGuidesRepos(), guides, downloader);
|
||||
// return guides.values().toArray(new TutorialGuide[guides.size()]);
|
||||
// }
|
||||
//
|
||||
// private LinkedHashMap<String, TutorialGuide> addGuidesFrom(Repo[] repos, LinkedHashMap<String, TutorialGuide> guides, DownloadManager downloader) {
|
||||
// for (Repo repo : repos) {
|
||||
// String name = repo.getName();
|
||||
// if (name.startsWith("tut-") && !guides.containsKey(name)) {
|
||||
// guides.put(name, new TutorialGuide(stsProps, repo, downloader));
|
||||
// }
|
||||
// }
|
||||
// return guides;
|
||||
// }
|
||||
// }
|
||||
// );
|
||||
|
||||
|
||||
//References apps: are discoverable because we maintain a list of json metadata
|
||||
//that can be downloaded from some external url.
|
||||
register(ReferenceApp.class, ReferenceApp.REFERENCE_APP_DESCRIPTION,
|
||||
new ContentProvider<ReferenceApp>() {
|
||||
|
||||
@Override
|
||||
public ReferenceApp[] fetch(DownloadManager downloader) {
|
||||
ReferenceAppMetaData[] infos = github.get(stsProps.get("spring.reference.app.discovery.url"), ReferenceAppMetaData[].class);
|
||||
ReferenceApp[] apps = new ReferenceApp[infos.length];
|
||||
for (int i = 0; i < apps.length; i++) {
|
||||
//TODO: it could be quite costly to create all these since each one
|
||||
// entails a request to obtain info about github repo.
|
||||
// Maybe this is a good reason to put a bit more info in the
|
||||
// json metadata and thereby avoid querying github to fetch it.
|
||||
apps[i] = create(downloader, infos[i]);
|
||||
}
|
||||
return apps;
|
||||
}
|
||||
|
||||
private ReferenceApp create(DownloadManager dl, ReferenceAppMetaData md) {
|
||||
return new ReferenceApp(md, dl, github);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get all getting started guides.
|
||||
*/
|
||||
public GettingStartedGuide[] getGSGuides() {
|
||||
return get(GettingStartedGuide.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all tutorial guides
|
||||
*/
|
||||
public TutorialGuide[] getTutorials() {
|
||||
return get(TutorialGuide.class);
|
||||
}
|
||||
|
||||
public ReferenceApp[] getReferenceApps() {
|
||||
return get(ReferenceApp.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all guide content (i.e. tutorials + gs)
|
||||
*/
|
||||
public GithubRepoContent[] getAllGuides() {
|
||||
ArrayList<GithubRepoContent> all = new ArrayList<GithubRepoContent>();
|
||||
all.addAll(asList(getTutorials()));
|
||||
all.addAll(asList(getGSGuides()));
|
||||
return all.toArray(new GithubRepoContent[all.size()]);
|
||||
}
|
||||
|
||||
private <A> Collection<A> asList(A[] tutorials) {
|
||||
if (tutorials==null) {
|
||||
return Collections.emptyList();
|
||||
} else {
|
||||
return Arrays.asList(tutorials);
|
||||
}
|
||||
}
|
||||
|
||||
// public GettingStartedGuide getGuide(String guideName) {
|
||||
// GettingStartedGuide[] guides = getGuides();
|
||||
// if (guides!=null) {
|
||||
// for (GettingStartedGuide g : guides) {
|
||||
// if (guideName.equals(g.getName())) {
|
||||
// return g;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return null;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013 GoPivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.content;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.core.runtime.Assert;
|
||||
import org.eclipse.core.runtime.IPath;
|
||||
import org.springframework.ide.eclipse.boot.wizard.BootWizardActivator;
|
||||
import org.springframework.ide.eclipse.boot.wizard.content.CodeSet.CodeSetEntry;
|
||||
import org.springframework.ide.eclipse.boot.wizard.github.Repo;
|
||||
import org.springsource.ide.eclipse.commons.core.preferences.StsProperties;
|
||||
import org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager.DownloadManager;
|
||||
import org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager.DownloadableItem;
|
||||
import org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager.UIThreadDownloadDisallowed;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* Content for a GettingStartedGuide provided via a Github Repo
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class GettingStartedGuide extends GithubRepoContent {
|
||||
|
||||
protected Repo repo;
|
||||
|
||||
private List<CodeSet> codesets;
|
||||
|
||||
private final String springGuidesUrl;
|
||||
|
||||
public static final String GUIDE_DESCRIPTION_TEXT =
|
||||
"A guide is a short focussed tutorial "
|
||||
+ "on how to use Spring to accomplish a specific task. "
|
||||
+ "It has an 'initial' code set, a 'complete' code"
|
||||
+ "set and a readme file explaining how you get from "
|
||||
+ "one to the other.";
|
||||
|
||||
/**
|
||||
* All getting started guides are supposed to have the same codesets names. This constant defines those
|
||||
* names.
|
||||
*/
|
||||
public static final CodeSetMetaData[] defaultCodesets = {
|
||||
new CodeSetMetaData("initial").desciption(
|
||||
"Import this if you want to work " +
|
||||
"through the bulk of the guide, but skip basic project setup"
|
||||
),
|
||||
new CodeSetMetaData("complete").desciption(
|
||||
"A project representing the code in its 'complete' state, at " +
|
||||
"the end of the guide."
|
||||
)
|
||||
};
|
||||
|
||||
/**
|
||||
* Relative path from the 'root' codeset to where the optional
|
||||
* metadata file is that describes codeset layout for projects
|
||||
* that don't follow the default layout.
|
||||
*/
|
||||
private static final String CODE_SET_METADATA = ".codesets.json";
|
||||
|
||||
private static String[] _defaultCodesetNames;
|
||||
public static String[] defaultCodesetNames() {
|
||||
if (_defaultCodesetNames==null) {
|
||||
String[] defaultCodesetNames = new String[defaultCodesets.length];
|
||||
for (int i = 0; i < defaultCodesets.length; i++) {
|
||||
defaultCodesetNames[i] = defaultCodesets[i].name;
|
||||
}
|
||||
_defaultCodesetNames = defaultCodesetNames;
|
||||
}
|
||||
return _defaultCodesetNames;
|
||||
}
|
||||
|
||||
|
||||
public GettingStartedGuide(StsProperties props, Repo repo, DownloadManager dl) {
|
||||
super(dl);
|
||||
this.springGuidesUrl = props.get("spring.guides.url");
|
||||
this.repo = repo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getClass().getSimpleName()+"("+getName()+")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CodeSet> getCodeSets() throws UIThreadDownloadDisallowed {
|
||||
if (codesets==null) {
|
||||
DownloadableItem zip = getZip();
|
||||
try {
|
||||
CodeSet root = CodeSet.fromZip("ROOT", zip, getRootPath());
|
||||
if (root.hasFile(CODE_SET_METADATA)) {
|
||||
//TODO: we have to parse the metadata file and extract the codeset names and locations from it.
|
||||
CodeSetMetaData[] metadata = root.readFileEntry(CODE_SET_METADATA, new CodeSet.Processor<CodeSetMetaData[]>() {
|
||||
@Override
|
||||
public CodeSetMetaData[] doit(CodeSetEntry e) throws Exception {
|
||||
InputStream in = e.getData();
|
||||
try {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
return mapper.readValue(in, CodeSetMetaData[].class);
|
||||
} finally {
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
if (metadata==null) {
|
||||
metadata = new CodeSetMetaData[0];
|
||||
}
|
||||
CodeSet[] array = new CodeSet[metadata.length];
|
||||
for (int i = 0; i < array.length; i++) {
|
||||
String name = metadata[i].name;
|
||||
String dir = metadata[i].dir;
|
||||
Assert.isLegal(name!=null, ".codesets.json objects must specify at least a 'name'.");
|
||||
if (dir==null) {
|
||||
dir = name; //Use the name as the default. The convention is that a codeset is in a sudirectory with the same name as
|
||||
// the codeset name.
|
||||
}
|
||||
//'dir' can't be null at this point because of the assert above and the default value computed from the name
|
||||
IPath zipPath = getRootPath().append(dir);
|
||||
array[i] = CodeSet.fromZip(name, getZip(), zipPath);
|
||||
}
|
||||
//Success parsing .codesets.json and initialising codesets field.
|
||||
codesets = Arrays.asList(array);
|
||||
return codesets;
|
||||
}
|
||||
} catch (UIThreadDownloadDisallowed e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
BootWizardActivator.log(e);
|
||||
}
|
||||
//We get here if either
|
||||
// - there's no .codeset.json
|
||||
// - .codeset.json is broken.
|
||||
CodeSet[] array = new CodeSet[defaultCodesetNames().length];
|
||||
for (int i = 0; i < array.length; i++) {
|
||||
array[i] = CodeSet.fromZip(defaultCodesetNames()[i], getZip(), getRootPath().append(defaultCodesetNames()[i]));
|
||||
}
|
||||
codesets = Arrays.asList(array);
|
||||
}
|
||||
return codesets;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Repo getRepo() {
|
||||
return this.repo;
|
||||
}
|
||||
|
||||
private String beatify(String name) {
|
||||
if (name.startsWith("gs-")) {
|
||||
name = name.substring(3);
|
||||
}
|
||||
String[] words = name.split("\\-");
|
||||
StringBuilder buf = new StringBuilder();
|
||||
for (String w : words) {
|
||||
if (w.length()>0) {
|
||||
buf.append(w.substring(0,1).toUpperCase());
|
||||
buf.append(w.substring(1));
|
||||
}
|
||||
buf.append(' ');
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getHomePage() {
|
||||
//Looks like this now:
|
||||
//https://${spring.guides.url}/gs/spring-boot/
|
||||
try {
|
||||
String gsGuideName = getName();
|
||||
if (gsGuideName.startsWith("gs-")) {
|
||||
String guideName = gsGuideName.substring(3);
|
||||
return new URL(springGuidesUrl+"/gs/"+guideName);
|
||||
}
|
||||
} catch (MalformedURLException e) {
|
||||
BootWizardActivator.log(e);
|
||||
}
|
||||
//Fallback on default implementation if custom logic failed
|
||||
return super.getHomePage();
|
||||
}
|
||||
|
||||
/**
|
||||
* A more 'beautiful' name derived from the guide's repository name.
|
||||
*/
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return beatify(getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Metadata elements parsed from .codesets.json file are represented as instances of
|
||||
* this class.
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
static public class CodeSetMetaData {
|
||||
@JsonProperty
|
||||
public String name;
|
||||
@JsonProperty
|
||||
public String dir;
|
||||
|
||||
@JsonProperty
|
||||
public String description;
|
||||
|
||||
/**
|
||||
* No args constructor (needed for Jackson mapper).
|
||||
*/
|
||||
public CodeSetMetaData() {
|
||||
}
|
||||
|
||||
public CodeSetMetaData(String name) {
|
||||
this.name = name;
|
||||
this.dir = name;
|
||||
}
|
||||
|
||||
public CodeSetMetaData desciption(String d) {
|
||||
description = d;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CodeSetMD(name = "+name+", dir = "+dir+")";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013 GoPivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.content;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
import org.eclipse.core.runtime.IPath;
|
||||
import org.eclipse.core.runtime.Path;
|
||||
import org.springframework.ide.eclipse.boot.wizard.BootWizardActivator;
|
||||
import org.springframework.ide.eclipse.boot.wizard.content.CodeSet.CodeSetEntry;
|
||||
import org.springframework.ide.eclipse.boot.wizard.content.CodeSet.Processor;
|
||||
import org.springframework.ide.eclipse.boot.wizard.github.Repo;
|
||||
import org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager.DownloadManager;
|
||||
import org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager.DownloadableItem;
|
||||
import org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager.UIThreadDownloadDisallowed;
|
||||
import org.springsource.ide.eclipse.commons.livexp.util.ExceptionUtil;
|
||||
|
||||
public abstract class GithubRepoContent extends AGSContent {
|
||||
|
||||
private DownloadableItem zip;
|
||||
|
||||
protected GithubRepoContent(DownloadManager dl) {
|
||||
super(dl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getHomePage() {
|
||||
try {
|
||||
return new URL(getRepo().getHtmlUrl());
|
||||
} catch (MalformedURLException e) {
|
||||
BootWizardActivator.log(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract Repo getRepo();
|
||||
|
||||
/**
|
||||
* Get a URL pointing to zip file where the entire contents of this
|
||||
* repo (master branch) can be downloaded.
|
||||
*/
|
||||
@Override
|
||||
public DownloadableItem getZip() {
|
||||
if (zip==null) {
|
||||
String repoUrl = getRepo().getHtmlUrl();
|
||||
//repoUrl is something like "https://github.com/springframework-meta/gs-consuming-rest-android"
|
||||
//zipUrl is something like "https://github.com/springframework-meta/gs-consuming-rest-android/archive/master.zip"
|
||||
try {
|
||||
DownloadableItem item = new DownloadableItem(getZipDownloadUrl(repoUrl), downloader);
|
||||
item.setFileName(getRepo().getName());
|
||||
zip = item;
|
||||
} catch (MalformedURLException e) {
|
||||
BootWizardActivator.log(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return zip;
|
||||
}
|
||||
|
||||
private URL getZipDownloadUrl(String repoUrl) throws MalformedURLException {
|
||||
return new URL(repoUrl+"/archive/"+getBranch()+".zip");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return getRepo().getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the location of the 'root' relative to the zip file. The interesting contents
|
||||
* of the zip file may not be directly at the root of the archive.
|
||||
* <p>
|
||||
* Note this method is made public for testing purposes only. Clients shouldn't really
|
||||
* need to get at this information. Rather they should rely on the 'CodeSets' to
|
||||
* access this data.
|
||||
*/
|
||||
public final IPath getRootPath() throws UIThreadDownloadDisallowed {
|
||||
//tmp codeset to aid in discovering the real root path.
|
||||
CodeSet tmp = CodeSet.fromZip("TEMP", zip, Path.ROOT);
|
||||
Path guessedRoot = new Path(getRepo().getName()+"-"+getBranch());
|
||||
if (tmp.hasFolder(guessedRoot)) {
|
||||
return guessedRoot;
|
||||
}
|
||||
try {
|
||||
return tmp.each(new Processor<IPath>() {
|
||||
@Override
|
||||
public IPath doit(CodeSetEntry e) throws Exception {
|
||||
return e.isDirectory() ? e.getPath() : null;
|
||||
}
|
||||
});
|
||||
} catch (UIThreadDownloadDisallowed e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw ExceptionUtil.unchecked(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected String getBranch() {
|
||||
return "HEAD";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return getRepo().getDescription();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013 VMware, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* VMware, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.content;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.core.runtime.Assert;
|
||||
import org.springframework.ide.eclipse.boot.wizard.github.GithubClient;
|
||||
import org.springframework.ide.eclipse.boot.wizard.github.Repo;
|
||||
import org.springsource.ide.eclipse.commons.core.util.StringUtil;
|
||||
import org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager.DownloadManager;
|
||||
import org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager.UIThreadDownloadDisallowed;
|
||||
import org.springsource.ide.eclipse.commons.livexp.util.Log;
|
||||
|
||||
public class ReferenceApp extends GithubRepoContent {
|
||||
|
||||
/**
|
||||
* We expect a readme file of sorts, at the root of the
|
||||
* project, but the name may vary.
|
||||
*/
|
||||
public static final String[] readmes = {
|
||||
"README.md",
|
||||
"readme.md",
|
||||
"readme.MD",
|
||||
"README.MD",
|
||||
"readme.txt",
|
||||
"readme.TXT",
|
||||
"README.txt",
|
||||
"README.TXT",
|
||||
"readme",
|
||||
"README"
|
||||
};
|
||||
|
||||
/**
|
||||
* Optional additional metadata (if list of content was fetched from a random
|
||||
* url, it may contain supplementary data. E.g. a name or description that
|
||||
* is intended to override the description from the repo.
|
||||
*/
|
||||
private final ReferenceAppMetaData metadata;
|
||||
private final GithubClient github;
|
||||
|
||||
/**
|
||||
* The github repo containing the content of this ReferenceApp.
|
||||
* We initialize this lazyly because it requires a github api
|
||||
* call to create the repo instance.
|
||||
* <p>
|
||||
* If the metadata object contains enough info then we won't need
|
||||
* to actually create the repo instance to construct the data.
|
||||
*/
|
||||
private Repo repo;
|
||||
|
||||
/**
|
||||
* Lazy intialized reference to the CodeSet with all the content
|
||||
* of the reference app.
|
||||
*/
|
||||
private CodeSet codeset;
|
||||
|
||||
public static final String REFERENCE_APP_DESCRIPTION = "A reference app is a larger, complete application that shows how to use spring properly in a more realistic context.";
|
||||
|
||||
public ReferenceApp(ReferenceAppMetaData md, DownloadManager dl, GithubClient gh) {
|
||||
super(dl);
|
||||
Assert.isNotNull(md);
|
||||
Assert.isNotNull(md.getOwner());
|
||||
Assert.isNotNull(md.getRepo());
|
||||
this.metadata = md;
|
||||
this.github = gh;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
String name = metadata.getName();
|
||||
if (name!=null) {
|
||||
return name;
|
||||
}
|
||||
return metadata.getRepo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Repo getRepo() {
|
||||
if (this.repo==null) {
|
||||
this.repo = github.getRepo(metadata.getOwner(), metadata.getRepo());
|
||||
}
|
||||
return this.repo;
|
||||
}
|
||||
|
||||
public CodeSet getCodeSet() throws UIThreadDownloadDisallowed {
|
||||
if (codeset==null) {
|
||||
codeset = CodeSet.fromZip(getName(), getZip(), getRootPath());
|
||||
}
|
||||
return codeset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return path to readme file in the codeset.
|
||||
*/
|
||||
public String getReadme() throws UIThreadDownloadDisallowed {
|
||||
CodeSet cs = getCodeSet();
|
||||
for (String name : readmes) {
|
||||
if (cs.hasFile(name)) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<BuildType> getBuildTypes() throws UIThreadDownloadDisallowed {
|
||||
return getCodeSet().getBuildTypes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
//TODO: beatify the name
|
||||
return getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CodeSet> getCodeSets() throws UIThreadDownloadDisallowed {
|
||||
return Arrays.asList(getCodeSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBranch() {
|
||||
String b = this.metadata.getBranch();
|
||||
return StringUtil.hasText(b) ? b : "HEAD";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013 VMware, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* VMware, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.content;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* JSON metadata about reference apps retrieved from some external url.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class ReferenceAppMetaData {
|
||||
|
||||
@JsonProperty("name")
|
||||
private String name; //optional. If not provided reference app name will be repo name
|
||||
|
||||
@JsonProperty("type")
|
||||
private String type; //only legal value now is "github". In the future maybe we will allow other types of metadata
|
||||
// to define to other ways/places of obtaining the sample code.
|
||||
|
||||
@JsonProperty("description")
|
||||
private String description; //optional if not provided will use the github repo's description
|
||||
|
||||
@JsonProperty("owner")
|
||||
private String owner; //mandatory: github repo owner name
|
||||
|
||||
@JsonProperty("repo")
|
||||
private String repo; ///mandatory: repo name
|
||||
|
||||
@JsonProperty("branch")
|
||||
private String branch; ///optional: branch name (if not set, we'll assume 'master')
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
public String getOwner() {
|
||||
return owner;
|
||||
}
|
||||
public String getRepo() {
|
||||
return repo;
|
||||
}
|
||||
|
||||
public String getBranch() {
|
||||
return branch;
|
||||
}
|
||||
|
||||
public void setBranch(String branch) {
|
||||
this.branch = branch;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013 VMware, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* VMware, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.content;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.core.runtime.Assert;
|
||||
import org.eclipse.core.runtime.IPath;
|
||||
import org.eclipse.core.runtime.Path;
|
||||
import org.springframework.ide.eclipse.boot.wizard.BootWizardActivator;
|
||||
import org.springframework.ide.eclipse.boot.wizard.content.CodeSet.CodeSetEntry;
|
||||
import org.springframework.ide.eclipse.boot.wizard.github.Repo;
|
||||
import org.springsource.ide.eclipse.commons.core.preferences.StsProperties;
|
||||
import org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager.DownloadManager;
|
||||
import org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager.UIThreadDownloadDisallowed;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* Content for a Spring Tutorial provided via a Github Repo
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class TutorialGuide extends GithubRepoContent {
|
||||
|
||||
protected Repo repo;
|
||||
|
||||
private List<CodeSet> codesets;
|
||||
|
||||
private final String springGuidesUrl;
|
||||
|
||||
public static final String GUIDE_DESCRIPTION_TEXT =
|
||||
"A longer tutorial building a larger project in multiple steps. "
|
||||
+ "It has an 'initial' code set, several 'numbered' codesets."
|
||||
+ "The numbered 'codeset' represent the solution at the end of"
|
||||
+ "a tutorial step.";
|
||||
|
||||
/**
|
||||
* Relative path from the 'root' codeset to where the optional
|
||||
* metadata file is that describes codeset layout for projects
|
||||
* that don't follow the default layout.
|
||||
*/
|
||||
private static final String CODE_SET_METADATA = ".codesets.json";
|
||||
|
||||
// private static String[] _defaultCodesetNames;
|
||||
// public static String[] defaultCodesetNames() {
|
||||
// if (_defaultCodesetNames==null) {
|
||||
// String[] defaultCodesetNames = new String[defaultCodesets.length];
|
||||
// for (int i = 0; i < defaultCodesets.length; i++) {
|
||||
// defaultCodesetNames[i] = defaultCodesets[i].name;
|
||||
// }
|
||||
// _defaultCodesetNames = defaultCodesetNames;
|
||||
// }
|
||||
// return _defaultCodesetNames;
|
||||
// }
|
||||
|
||||
|
||||
public TutorialGuide(StsProperties props, Repo repo, DownloadManager dl) {
|
||||
super(dl);
|
||||
this.springGuidesUrl = props.get("spring.guides.url");
|
||||
this.repo = repo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getClass().getSimpleName()+"("+getName()+")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CodeSet> getCodeSets() throws UIThreadDownloadDisallowed {
|
||||
if (codesets==null) {
|
||||
CodeSet root = CodeSet.fromZip("ROOT", getZip(), getRootPath());
|
||||
if (root.hasFile(CODE_SET_METADATA)) {
|
||||
//TODO: duplicated in GettingStartedGuide
|
||||
try {
|
||||
//TODO: we have to parse the metadata file and extract the codeset names and locations from it.
|
||||
CodeSetMetaData[] metadata = root.readFileEntry(CODE_SET_METADATA, new CodeSet.Processor<CodeSetMetaData[]>() {
|
||||
@Override
|
||||
public CodeSetMetaData[] doit(CodeSetEntry e) throws Exception {
|
||||
InputStream in = e.getData();
|
||||
try {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
return mapper.readValue(in, CodeSetMetaData[].class);
|
||||
} finally {
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
if (metadata==null) {
|
||||
metadata = new CodeSetMetaData[0];
|
||||
}
|
||||
CodeSet[] array = new CodeSet[metadata.length];
|
||||
for (int i = 0; i < array.length; i++) {
|
||||
String name = metadata[i].name;
|
||||
String dir = metadata[i].dir;
|
||||
Assert.isLegal(name!=null, ".codesets.json objects must specify at least a 'name'.");
|
||||
if (dir==null) {
|
||||
dir = name; //Use the name as the default. The convention is that a codeset is in a sudirectory with the same name as
|
||||
// the codeset name.
|
||||
}
|
||||
//'dir' can't be null at this point because of the assert above and the default value computed from the name
|
||||
IPath zipPath = getRootPath().append(dir);
|
||||
array[i] = CodeSet.fromZip(name, getZip(), zipPath);
|
||||
}
|
||||
//Success parsing .codesets.json and initialising codesets field.
|
||||
codesets = Arrays.asList(array);
|
||||
return codesets;
|
||||
} catch (Throwable e) {
|
||||
BootWizardActivator.log(e);
|
||||
}
|
||||
}
|
||||
//We get here if either
|
||||
// - there's no .codeset.json
|
||||
// - .codeset.json is broken.
|
||||
codesets = Arrays.asList(defaultCodeSets(root));
|
||||
}
|
||||
return codesets;
|
||||
}
|
||||
|
||||
private CodeSet[] defaultCodeSets(CodeSet root) throws UIThreadDownloadDisallowed {
|
||||
List<CodeSet> codesets = new ArrayList<CodeSet>();
|
||||
codesets.add(CodeSet.fromZip("initial", getZip(), getRootPath().append("initial")));
|
||||
|
||||
int number = 1;
|
||||
while (root.hasFolder(new Path(""+number))) {
|
||||
Path codePath = new Path(number+"/complete");
|
||||
if (root.hasFolder(new Path(number+"/complete"))) {
|
||||
codesets.add(CodeSet.fromZip(number+"-complete", getZip(), getRootPath().append(codePath)));
|
||||
}
|
||||
number++;
|
||||
}
|
||||
return codesets.toArray(new CodeSet[codesets.size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Repo getRepo() {
|
||||
return this.repo;
|
||||
}
|
||||
|
||||
private String beatify(String name) {
|
||||
if (name.startsWith("tut-")) {
|
||||
name = name.substring(4);
|
||||
}
|
||||
String[] words = name.split("\\-");
|
||||
StringBuilder buf = new StringBuilder();
|
||||
for (String w : words) {
|
||||
if (w.length()>0) {
|
||||
buf.append(w.substring(0,1).toUpperCase());
|
||||
buf.append(w.substring(1));
|
||||
}
|
||||
buf.append(' ');
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getHomePage() {
|
||||
//Looks like this now:
|
||||
//https://${spring.guides.url}/tutorial/rest/
|
||||
try {
|
||||
String gsGuideName = getName();
|
||||
if (gsGuideName.startsWith("tut-")) {
|
||||
String guideName = gsGuideName.substring(4);
|
||||
return new URL(springGuidesUrl+"/tutorials/"+guideName);
|
||||
}
|
||||
} catch (MalformedURLException e) {
|
||||
BootWizardActivator.log(e);
|
||||
}
|
||||
//Fallback on default implementation if custom logic failed
|
||||
return super.getHomePage();
|
||||
}
|
||||
|
||||
/**
|
||||
* A more 'beautiful' name derived from the guide's repository name.
|
||||
*/
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return beatify(getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Metadata elements parsed from .codesets.json file are represented as instances of
|
||||
* this class.
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
static public class CodeSetMetaData {
|
||||
@JsonProperty
|
||||
public String name;
|
||||
@JsonProperty
|
||||
public String dir;
|
||||
|
||||
@JsonProperty
|
||||
public String description;
|
||||
|
||||
/**
|
||||
* No args constructor (needed for Jackson mapper).
|
||||
*/
|
||||
public CodeSetMetaData() {
|
||||
}
|
||||
|
||||
public CodeSetMetaData(String name) {
|
||||
this.name = name;
|
||||
this.dir = name;
|
||||
}
|
||||
|
||||
public CodeSetMetaData desciption(String d) {
|
||||
description = d;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CodeSetMD(name = "+name+", dir = "+dir+")";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013 VMware, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* VMware, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.content;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.ide.eclipse.boot.wizard.BootWizardActivator;
|
||||
import org.springframework.ide.eclipse.boot.wizard.github.auth.AuthenticatedDownloader;
|
||||
import org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager.DownloadManager;
|
||||
|
||||
/**
|
||||
* Abstract base class for implementing a content manager for a given type of
|
||||
* Content.
|
||||
*/
|
||||
class TypedContentManager<T> {
|
||||
|
||||
private T[] items; //In-memory cache of info about the content in this content manager.
|
||||
|
||||
private final DownloadManager downloader; //Manages file-system cache where downloaded content is stored.
|
||||
|
||||
private final ContentProvider<T> contentProvider;
|
||||
|
||||
protected TypedContentManager(DownloadManager downloader, ContentProvider<T> contentProvider) {
|
||||
this.downloader = downloader;
|
||||
this.contentProvider = contentProvider;
|
||||
}
|
||||
|
||||
public T[] getAll() {
|
||||
if (items==null) {
|
||||
items = fetch(downloader);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
private T[] fetch(DownloadManager downloader) {
|
||||
return contentProvider.fetch(downloader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a DownloadManager for a given content type name
|
||||
*/
|
||||
public static DownloadManager downloadManagerFor(String contentTypeName) throws IOException {
|
||||
return new DownloadManager(new AuthenticatedDownloader(),
|
||||
new File(BootWizardActivator.getDefault().getStateLocation().toFile(), "guides")
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013 GoPivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.content;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.zip.ZipException;
|
||||
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.zip.ZipFile;
|
||||
import org.eclipse.core.runtime.Assert;
|
||||
import org.eclipse.core.runtime.IPath;
|
||||
import org.eclipse.core.runtime.Path;
|
||||
import org.springframework.ide.eclipse.boot.wizard.BootWizardActivator;
|
||||
import org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager.DownloadableItem;
|
||||
import org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager.UIThreadDownloadDisallowed;
|
||||
|
||||
/**
|
||||
* A CodeSet stored in a Downloadable Zip File. The interesting data in the
|
||||
* code set may be somewhere inside the zip file and is pointed to by
|
||||
* a path relative to the zipfile root.
|
||||
*/
|
||||
public class ZipFileCodeSet extends CodeSet {
|
||||
|
||||
private static final boolean DEBUG = false; //(""+Platform.getLocation()).contains("kdvolder");
|
||||
|
||||
private void debug(String msg) {
|
||||
if (DEBUG) {
|
||||
System.out.println(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private final DownloadableItem zipDownload;
|
||||
private final IPath root;
|
||||
|
||||
private Map<String, CodeSetEntry> entries = null;
|
||||
|
||||
/**
|
||||
* Ensures that zip file is downloaded and entries are parsed
|
||||
* into a map. Only the first time this method is called do
|
||||
* we do any actual work. The zip entries are cached
|
||||
* after that.
|
||||
*/
|
||||
private synchronized void ensureEntryCache() throws Exception, UIThreadDownloadDisallowed {
|
||||
//Careful... if called in UIThred this may throw an exception because downloading content
|
||||
// in the UI thread is not allowed. Callers generally know how to deal with that but
|
||||
// we should take care not to accidentally leave an empty hashmap around as well.
|
||||
// I.e. take care not to install the map unless reading download etc of the
|
||||
// zip succeeded.
|
||||
if (entries==null) {
|
||||
debug(">>> caching codeset entries "+this);
|
||||
final HashMap<String, CodeSetEntry> newEntries = new HashMap<String, CodeSetEntry>(1024);
|
||||
each(new Processor<Void>() {
|
||||
@Override
|
||||
public Void doit(CodeSetEntry e) throws Exception {
|
||||
debug(e.getPath().toString());
|
||||
newEntries.put(e.getPath().toString(), e);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
//Nothing bad happened. The cache map is ready now.
|
||||
this.entries = newEntries;
|
||||
debug("<<< cached codeset entries "+entries.size());
|
||||
}
|
||||
}
|
||||
|
||||
ZipFileCodeSet(String name, DownloadableItem zip, IPath root) {
|
||||
super(name);
|
||||
this.zipDownload = zip;
|
||||
this.root = root.makeRelative();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ZipCodeSet("+name+", "+zipDownload.getURL()+"@"+root+")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() throws Exception {
|
||||
ensureEntryCache();
|
||||
return !entries.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasFile(IPath path) throws UIThreadDownloadDisallowed {
|
||||
try {
|
||||
ensureEntryCache();
|
||||
return entries.containsKey(fileKey(path));
|
||||
} catch (UIThreadDownloadDisallowed e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
BootWizardActivator.log(e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasFolder(IPath path) {
|
||||
try {
|
||||
ensureEntryCache();
|
||||
return entries.containsKey(folderKey(path));
|
||||
} catch (UIThreadDownloadDisallowed e) {
|
||||
//ignore. Sort of expected in some scenarios (waiting for download).
|
||||
} catch (Exception e) {
|
||||
BootWizardActivator.log(e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private String folderKey(IPath _path) {
|
||||
String path = fileKey(_path);
|
||||
if (!path.endsWith("/")) {
|
||||
//ZipEntries for dirs always end with a "/"
|
||||
path = path+"/";
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
private String fileKey(IPath path) {
|
||||
path = path.makeRelative();
|
||||
return path.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T each(Processor<T> processor) throws Exception {
|
||||
T result = null;
|
||||
ZipFile zip = new ZipFile(zipDownload.getFile());
|
||||
try {
|
||||
Enumeration<ZipArchiveEntry> iter = zip.getEntries();
|
||||
while (iter.hasMoreElements() && result==null) {
|
||||
ZipArchiveEntry el = iter.nextElement();
|
||||
Path zipPath = new Path(el.getName());
|
||||
if (root.isPrefixOf(zipPath)) {
|
||||
String key = zipPath.removeFirstSegments(root.segmentCount()).toString();
|
||||
if ("".equals(key)) {
|
||||
//path maches exactly, this means we hit the root of the
|
||||
// code set. Do not store it because the root of a codeset
|
||||
// is not actually an element of the codeset!
|
||||
} else {
|
||||
CodeSetEntry cse = csEntry(zip, el);
|
||||
result = processor.doit(cse);
|
||||
if (result!=null) {
|
||||
//Bail out early when result found
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} finally {
|
||||
try {
|
||||
zip.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T readFileEntry(String path, Processor<T> processor) throws Exception {
|
||||
ZipFile zip = new ZipFile(zipDownload.getFile());
|
||||
try {
|
||||
String entryName = root.append(path).toString();
|
||||
ZipArchiveEntry entry = zip.getEntry(entryName);
|
||||
return processor.doit(entry==null?null:csEntry(zip, entry));
|
||||
} finally {
|
||||
try {
|
||||
zip.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assertCorrectOutputLocation(java.nio.file.Path normalizedLocation, java.nio.file.Path normalizedTarget) throws Exception {
|
||||
if (!normalizedTarget.startsWith(normalizedLocation)) {
|
||||
throw new ZipException("The file " + normalizedTarget
|
||||
+ " is trying to leave the target output directory of " + normalizedLocation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a CodeSetEntry that wraps a ZipEntry
|
||||
*/
|
||||
private CodeSetEntry csEntry(final ZipFile zip, final ZipArchiveEntry e) {
|
||||
IPath zipPath = new Path(e.getName()); //path relative to zip file
|
||||
Assert.isTrue(root.isPrefixOf(zipPath));
|
||||
final IPath csPath = zipPath.removeFirstSegments(root.segmentCount());
|
||||
return new CodeSetEntry() {
|
||||
@Override
|
||||
public IPath getPath() {
|
||||
return csPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getPath()+" in "+zipDownload;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirectory() {
|
||||
return e.isDirectory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUnixMode() {
|
||||
return e.getUnixMode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getData() throws IOException {
|
||||
return zip.getInputStream(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
/user.properties
|
||||
@@ -0,0 +1,272 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013, 2019 GoPivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.github;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.reflect.Array;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.ide.eclipse.boot.core.BootActivator;
|
||||
import org.springframework.ide.eclipse.boot.wizard.BootWizardActivator;
|
||||
import org.springframework.ide.eclipse.boot.wizard.github.SimpleJsonRestClient.Response;
|
||||
import org.springframework.ide.eclipse.boot.wizard.github.SimpleJsonRestClient.WebTarget;
|
||||
import org.springframework.ide.eclipse.boot.wizard.github.auth.BasicAuthCredentials;
|
||||
import org.springframework.ide.eclipse.boot.wizard.github.auth.Credentials;
|
||||
import org.springframework.ide.eclipse.boot.wizard.github.auth.NullCredentials;
|
||||
import org.springsource.ide.eclipse.commons.frameworks.core.util.IOUtil;
|
||||
import org.springsource.ide.eclipse.commons.livexp.util.ExceptionUtil;
|
||||
import org.springsource.ide.eclipse.commons.livexp.util.Log;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
/**
|
||||
* A GithubClient instance needs to configured with some credentials and then it is able to
|
||||
* talk to github using its rest api to obtain information about github repos, users,
|
||||
* organisations etc.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class GithubClient {
|
||||
|
||||
private static final Pattern GITHUB_HOST = Pattern.compile("(.*\\.|)github\\.com");
|
||||
// pattern should match 'github.com' and api.github.com'
|
||||
|
||||
private static final int CONNECT_TIMEOUT = 10000;
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
// private static final boolean LOG_GITHUB_RATE_LIMIT = false;
|
||||
|
||||
private final Credentials credentials;
|
||||
private final SimpleJsonRestClient client;
|
||||
|
||||
/**
|
||||
* Create a GithubClient with default credentials. The default credentials
|
||||
* are a basic authentication username plus password read from a "user.properties"
|
||||
* fetched from the classloader.
|
||||
*/
|
||||
public GithubClient() {
|
||||
this(createDefaultCredentials());
|
||||
}
|
||||
|
||||
public GithubClient(Credentials c) {
|
||||
this.credentials = c;
|
||||
this.client = new SimpleJsonRestClient(c);
|
||||
}
|
||||
|
||||
public static Credentials createDefaultCredentials() {
|
||||
//Try system properties
|
||||
String username = System.getProperty("github.user.name");
|
||||
String password = System.getProperty("github.user.password");
|
||||
if (username!=null && password!=null) {
|
||||
return new BasicAuthCredentials(GITHUB_HOST, username, password);
|
||||
}
|
||||
//No credentials found. Try proceeding without credentials.
|
||||
return new NullCredentials();
|
||||
}
|
||||
|
||||
private String addHost(String path) {
|
||||
if (path.startsWith("http")) {
|
||||
return path;
|
||||
}
|
||||
if (!path.startsWith("/")) {
|
||||
path = "/"+path;
|
||||
}
|
||||
return "https://api.github.com"+path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch info about repos under a given organization.
|
||||
*/
|
||||
public Repo[] getOrgRepos(String orgName) {
|
||||
return get("/orgs/{orgName}/repos", Repo[].class, ImmutableMap.of("orgName", orgName));
|
||||
|
||||
// return get("/orgs/{orgName}/repos?per_page=100", Repo[].class, orgName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the remaining rate limit.
|
||||
*/
|
||||
public RateLimitResponse getRateLimit() throws IOException {
|
||||
return get("/rate_limit", RateLimitResponse.class);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fetch info about repos under a given user name
|
||||
*/
|
||||
public Repo[] getUserRepos(String userName) {
|
||||
return get("/users/{userName}/repos", Repo[].class, ImmutableMap.of("userName", userName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get repos for the authenticated user. This seems to be the only way to list private repos
|
||||
* associated with a user. This only works over an authenticated github connection.
|
||||
*/
|
||||
public Repo[] getMyRepos() {
|
||||
try {
|
||||
return get("/user/repos", Repo[].class);
|
||||
} catch (Throwable e) {
|
||||
BootWizardActivator.log(e);
|
||||
}
|
||||
return new Repo[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch info about a repo identified by an owner and a name
|
||||
*/
|
||||
public Repo getRepo(String owner, String repo) {
|
||||
return get("/repos/{owner}/{repo}", Repo.class, ImmutableMap.of(
|
||||
"owner", owner,
|
||||
"repo", repo
|
||||
));
|
||||
}
|
||||
|
||||
public <T> T get(String url, Class<T> type) {
|
||||
return get(url, type, ImmutableMap.of());
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to fetch json data from some url (or url template)
|
||||
* and parse the data into an object of a given type.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T get(String url, Class<T> type, Map<String, Object> vars) {
|
||||
try {
|
||||
url = addHost(url);
|
||||
if (type.isArray()) {
|
||||
Class<?> componentType = type.getComponentType();
|
||||
//Assume this means we have to support response pagination as described in:
|
||||
//https://developer.github.com/v3/#pagination
|
||||
ArrayList<Object> results = new ArrayList<>();
|
||||
WebTarget webtarget = client.target(url).resolveTemplates(vars);
|
||||
do {
|
||||
Response response = webtarget.get();
|
||||
Object[] pageResults = (Object[])response.readEntity(type);
|
||||
for (Object r : pageResults) {
|
||||
results.add(r);
|
||||
}
|
||||
url = getNextPageUrl(response);
|
||||
if (url!=null) {
|
||||
webtarget = client.target(url);
|
||||
} else {
|
||||
webtarget = null;
|
||||
}
|
||||
} while (webtarget!=null);
|
||||
return (T) results.toArray((Object[])Array.newInstance(componentType, results.size()));
|
||||
} else {
|
||||
return client.target(url).resolveTemplates(vars).get(type);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw ExceptionUtil.unchecked(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the url of the next page in a paginated result.
|
||||
* May return null if there is no next page.
|
||||
* <p>
|
||||
* See https://developer.github.com/v3/#pagination
|
||||
*/
|
||||
private static <T> String getNextPageUrl(Response response) {
|
||||
List<String> linkHeader = response.getHeaders().get("link");
|
||||
if (linkHeader==null) {
|
||||
linkHeader = response.getHeaders().get("Link");
|
||||
}
|
||||
if (linkHeader!=null) {
|
||||
//Example of header String:
|
||||
//<https://api.github.com/organizations/4161866/repos?page=2>; rel="next", <https://api.github.com/organizations/4161866/repos?page=2>; rel="last"
|
||||
Pattern nextPat = Pattern.compile("<([^<]*)>;\\s*rel=\"next\"");
|
||||
for (String string : linkHeader) {
|
||||
Matcher m = nextPat.matcher(string);
|
||||
if (m.find()) {
|
||||
return m.group(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null; //no pagination info found
|
||||
}
|
||||
|
||||
protected static String getNormalisedProtocol(String protocol) {
|
||||
return protocol.toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Download content from a url and save to an outputstream. Use same credentials as
|
||||
* other operations in this client. May need to use this to download stuff like
|
||||
* zip file from github if the repo it comes from is private.
|
||||
*/
|
||||
public void fetch(URL url, OutputStream writeTo) throws IOException {
|
||||
URLConnection conn = null;
|
||||
InputStream input = null;
|
||||
try {
|
||||
conn = BootActivator.getUrlConnectionFactory().createConnection(url);
|
||||
conn.setConnectTimeout(CONNECT_TIMEOUT);
|
||||
conn.setRequestProperty("Accept", "*/*");
|
||||
credentials.apply(conn);
|
||||
Log.info("Fetching content from: "+url);
|
||||
if (DEBUG) {
|
||||
Map<String, List<String>> reqHeaders = conn.getRequestProperties();
|
||||
System.out.println(">>> request: "+url);
|
||||
for (Entry<String, List<String>> e : reqHeaders.entrySet()) {
|
||||
System.out.println(e.getKey()+" = ");
|
||||
for (String s : e.getValue()) {
|
||||
System.out.println(" "+s);
|
||||
}
|
||||
}
|
||||
}
|
||||
conn.connect();
|
||||
if (DEBUG) {
|
||||
System.out.println(">>> response: "+url);
|
||||
Map<String, List<String>> headers = conn.getHeaderFields();
|
||||
for (Entry<String, List<String>> header : headers.entrySet()) {
|
||||
System.out.println(header.getKey()+":");
|
||||
for (String value : header.getValue()) {
|
||||
System.out.println(" "+value);
|
||||
}
|
||||
}
|
||||
System.out.println("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
|
||||
}
|
||||
|
||||
input = conn.getInputStream();
|
||||
IOUtil.pipe(input, writeTo);
|
||||
} finally {
|
||||
if (input!=null) {
|
||||
try {
|
||||
input.close();
|
||||
} catch (Throwable e) {
|
||||
//ignore.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For some quick add-hoc testing
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
GithubClient gh = new GithubClient();
|
||||
for (int i = 0; i < 5; i++) {
|
||||
Repo[] repos = gh.getOrgRepos("spring-guides");
|
||||
System.out.println(repos.length);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013 GoPivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.github;
|
||||
|
||||
/**
|
||||
* Abstract subperclass for enities that have an 'id'. This is pretty much every JSon object
|
||||
* returned from github rest API.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public abstract class IdAble {
|
||||
|
||||
private String id;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013 GoPivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.github;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class Owner extends IdAble {
|
||||
|
||||
@JsonProperty("login")
|
||||
private String login;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Owner("+login+")";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.github;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown=true)
|
||||
public class RateLimitResponse {
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown=true)
|
||||
public static class RateLimit {
|
||||
private int limit;
|
||||
private int remaining;
|
||||
private int reset;
|
||||
public int getLimit() {
|
||||
return limit;
|
||||
}
|
||||
public void setLimit(int limit) {
|
||||
this.limit = limit;
|
||||
}
|
||||
public int getRemaining() {
|
||||
return remaining;
|
||||
}
|
||||
public void setRemaining(int remaining) {
|
||||
this.remaining = remaining;
|
||||
}
|
||||
public int getReset() {
|
||||
return reset;
|
||||
}
|
||||
public void setReset(int reset) {
|
||||
this.reset = reset;
|
||||
}
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown=true)
|
||||
public static class RateLimits {
|
||||
|
||||
private RateLimit core;
|
||||
private RateLimit search;
|
||||
|
||||
public RateLimit getCore() {
|
||||
return core;
|
||||
}
|
||||
public void setCore(RateLimit core) {
|
||||
this.core = core;
|
||||
}
|
||||
public RateLimit getSearch() {
|
||||
return search;
|
||||
}
|
||||
public void setSearch(RateLimit search) {
|
||||
this.search = search;
|
||||
}
|
||||
}
|
||||
|
||||
private RateLimits resources;
|
||||
|
||||
public RateLimits getResources() {
|
||||
return resources;
|
||||
}
|
||||
|
||||
public void setResources(RateLimits resources) {
|
||||
this.resources = resources;
|
||||
}
|
||||
|
||||
public RateLimit getRate() {
|
||||
return getResources().getCore();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013 GoPivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.github;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown=true)
|
||||
public class Repo extends IdAble {
|
||||
|
||||
@JsonProperty("name")
|
||||
private String name;
|
||||
|
||||
@JsonProperty("full_name")
|
||||
private String fullName;
|
||||
|
||||
@JsonProperty("private")
|
||||
private boolean isPrivate;
|
||||
|
||||
@JsonProperty("html_url")
|
||||
private String htmlUrl;
|
||||
|
||||
@JsonProperty("description")
|
||||
private String description;
|
||||
|
||||
@JsonProperty("fork")
|
||||
private boolean isFork;
|
||||
|
||||
@JsonProperty("url")
|
||||
private String url;
|
||||
|
||||
private Owner owner;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getFullName() {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
public Owner getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Repo("+getName()+")";
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public String getHtmlUrl() {
|
||||
return htmlUrl;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013-2017 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* VMware, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.github;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.ProtocolException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.springframework.ide.eclipse.boot.wizard.github.auth.Credentials;
|
||||
import org.springsource.ide.eclipse.commons.livexp.util.ExceptionUtil;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* Poor man's rest client, just uses built-in JRE {@link URLConnection} apis instead of
|
||||
* more fancy libs like appache http client or jersey.
|
||||
*/
|
||||
public class SimpleJsonRestClient {
|
||||
|
||||
private Credentials credentials;
|
||||
|
||||
public class Response {
|
||||
|
||||
private HttpURLConnection conn;
|
||||
|
||||
byte[] buf = new byte[1024];
|
||||
|
||||
public Response(HttpURLConnection conn) {
|
||||
this.conn = conn;
|
||||
}
|
||||
|
||||
public <T> T readEntity(Class<T> type) throws IOException {
|
||||
return new ObjectMapper().readValue(conn.getInputStream(), type);
|
||||
}
|
||||
|
||||
public Map<String, List<String>> getHeaders() {
|
||||
return conn.getHeaderFields();
|
||||
}
|
||||
}
|
||||
|
||||
public class WebTarget {
|
||||
|
||||
private String url;
|
||||
|
||||
public WebTarget(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public WebTarget resolveTemplates(Map<String, Object> vars) {
|
||||
try {
|
||||
for (Entry<String, Object> entry : vars.entrySet()) {
|
||||
url = url.replace("{"+entry.getKey()+"}", URLEncoder.encode(entry.getValue().toString(), "UTF8"));
|
||||
}
|
||||
return this;
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
//shouldn't be possible... but anyhow
|
||||
throw ExceptionUtil.unchecked(e);
|
||||
}
|
||||
}
|
||||
|
||||
public Response get() {
|
||||
try {
|
||||
URL url = new URL(this.url);
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
credentials.apply(conn);
|
||||
conn.setRequestMethod("GET");
|
||||
conn.setRequestProperty("Accept", "application/json, text/plain");
|
||||
return new Response(conn);
|
||||
} catch (Exception e) {
|
||||
throw ExceptionUtil.unchecked(e);
|
||||
}
|
||||
}
|
||||
|
||||
public <T> T get(Class<T> type) throws IOException {
|
||||
return get().readEntity(type);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public SimpleJsonRestClient(Credentials c) {
|
||||
this.credentials = c;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public WebTarget target(String url) {
|
||||
return new WebTarget(url);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013 GoPivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.github.auth;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URL;
|
||||
|
||||
import org.springframework.ide.eclipse.boot.wizard.github.GithubClient;
|
||||
import org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager.DownloadManager.DownloadService;
|
||||
|
||||
/**
|
||||
* For now we need to authenticate to be able to download guides from github.
|
||||
* This is because the github repos for guides are private at the moment.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class AuthenticatedDownloader implements DownloadService {
|
||||
|
||||
GithubClient client = new GithubClient();
|
||||
|
||||
//@Override
|
||||
public void fetch(URL url, OutputStream writeTo) throws IOException {
|
||||
client.fetch(url, writeTo);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013-2017 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* VMware, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.github.auth;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLConnection;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.springframework.ide.eclipse.boot.wizard.BootWizardActivator;
|
||||
|
||||
/**
|
||||
* Uses basic authentication username and passwd to access github rest api.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class BasicAuthCredentials extends Credentials {
|
||||
|
||||
private final Pattern host;
|
||||
private final String username;
|
||||
private final String passwd;
|
||||
|
||||
public BasicAuthCredentials(Pattern host, String username, String passwd) {
|
||||
this.host = host;
|
||||
this.username = username;
|
||||
this.passwd = passwd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BasicAuthCredentials("+username+")";
|
||||
}
|
||||
|
||||
private String computeAuthString() throws UnsupportedEncodingException {
|
||||
String authorisation = username + ":" + passwd;
|
||||
byte[] encodedAuthorisation = Base64.encodeBase64(authorisation.getBytes("utf8"));
|
||||
String authString = "Basic " + new String(encodedAuthorisation);
|
||||
return authString;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(URLConnection conn) {
|
||||
try {
|
||||
if (matchHost(conn.getURL().getHost())) {
|
||||
conn.setRequestProperty("Authorization", computeAuthString());
|
||||
}
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
//Shouldn't really be possible...
|
||||
BootWizardActivator.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean matchHost(String host) {
|
||||
if (this.host!=null) {
|
||||
return this.host.matcher(host).matches();
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013 VMware, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* VMware, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.github.auth;
|
||||
|
||||
import java.net.URLConnection;
|
||||
|
||||
/**
|
||||
* Credentials provide a means to authenticate for using the gihub rest API.
|
||||
* <p>
|
||||
* Github support two mechanims basic authentication and oauth. Separate
|
||||
* subclasses can be created to implement these mechanisms. For now
|
||||
* only basic auth will be implemented.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public abstract class Credentials {
|
||||
|
||||
/**
|
||||
* Add authentication headers to a url connection.
|
||||
*/
|
||||
public abstract void apply(URLConnection conn);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013 GoPivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.github.auth;
|
||||
|
||||
import java.net.URLConnection;
|
||||
|
||||
/**
|
||||
* Accesses github rest apis without any credentials.
|
||||
* Severe rate limits will be in effect, but for some use cases that may be ok.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class NullCredentials extends Credentials {
|
||||
|
||||
@Override
|
||||
public void apply(URLConnection conn) {
|
||||
//No credentials so nothing to apply
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013 GoPivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.guides;
|
||||
|
||||
import org.eclipse.jface.viewers.ILabelProvider;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.IPageWithSections;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.SimpleLabelProvider;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.WizardPageSection;
|
||||
|
||||
public abstract class ChooseOneSection extends WizardPageSection {
|
||||
|
||||
protected ILabelProvider labelProvider = new SimpleLabelProvider();
|
||||
|
||||
public ChooseOneSection(IPageWithSections owner) {
|
||||
super(owner);
|
||||
}
|
||||
|
||||
public ChooseOneSection setLabelProvider(ILabelProvider p) {
|
||||
this.labelProvider = p;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013 GoPivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.guides;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.eclipse.jface.layout.GridDataFactory;
|
||||
import org.eclipse.jface.layout.GridLayoutFactory;
|
||||
import org.eclipse.jface.viewers.ArrayContentProvider;
|
||||
import org.eclipse.jface.viewers.ISelection;
|
||||
import org.eclipse.jface.viewers.ISelectionChangedListener;
|
||||
import org.eclipse.jface.viewers.IStructuredSelection;
|
||||
import org.eclipse.jface.viewers.SelectionChangedEvent;
|
||||
import org.eclipse.jface.viewers.StructuredSelection;
|
||||
import org.eclipse.jface.viewers.TableViewer;
|
||||
import org.eclipse.jface.viewers.Viewer;
|
||||
import org.eclipse.jface.viewers.ViewerFilter;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.events.ModifyEvent;
|
||||
import org.eclipse.swt.events.ModifyListener;
|
||||
import org.eclipse.swt.events.PaintEvent;
|
||||
import org.eclipse.swt.events.PaintListener;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Control;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
import org.eclipse.swt.widgets.Text;
|
||||
import org.springframework.ide.eclipse.boot.wizard.content.Describable;
|
||||
import org.springframework.ide.eclipse.boot.wizard.util.StringMatchers;
|
||||
import org.springsource.ide.eclipse.commons.core.util.StringUtil;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.LiveExpression;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.SelectionModel;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.ValidationResult;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.ChooseOneSectionCombo;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.IPageWithSections;
|
||||
|
||||
/**
|
||||
* Wizard section to choose one element from list of elements. Uses a table viewer to allow selecting
|
||||
* an element.
|
||||
* <p>
|
||||
* This class is very similar in functionality (from client's point of view) to {@link ChooseOneSectionCombo}.
|
||||
* It should be possible to use either one of these classes as functional replacements for one another.
|
||||
*/
|
||||
@SuppressWarnings("restriction")
|
||||
public class ChooseOneSectionTable<T> extends ChooseOneSection {
|
||||
|
||||
private class ChoicesFilter extends ViewerFilter {
|
||||
|
||||
private Predicate<String> matcher = null;
|
||||
|
||||
public ChoicesFilter() {
|
||||
if (searchBox!=null) {
|
||||
setSearchTerm(searchBox.getText());
|
||||
}
|
||||
}
|
||||
|
||||
public void setSearchTerm(String text) {
|
||||
matcher = StringMatchers.caseInsensitiveSubstring(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean select(Viewer viewer, Object parentElement, Object element) {
|
||||
if (matcher==null) {
|
||||
return true;
|
||||
} else {
|
||||
String label = labelProvider.getText(element);
|
||||
if (match(label)) {
|
||||
return true;
|
||||
} else if (element instanceof Describable) {
|
||||
String description = ((Describable) element).getDescription();
|
||||
return match(description);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean match(String text) {
|
||||
if (matcher==null) {
|
||||
return true; // Search term not set... anything is acceptable.
|
||||
} else {
|
||||
return matcher.test(text);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private SelectionModel<T> selection;
|
||||
private String label; //Descriptive Label for this section
|
||||
private T[] options; //The elements to choose from
|
||||
private Text searchBox;
|
||||
private ChoicesFilter filter;
|
||||
|
||||
public ChooseOneSectionTable(IPageWithSections owner, String label, SelectionModel<T> selection, T[] options) {
|
||||
super(owner);
|
||||
this.label = label;
|
||||
this.selection = selection;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public LiveExpression<ValidationResult> getValidator() {
|
||||
return selection.validator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createContents(Composite page) {
|
||||
Composite field = new Composite(page, SWT.NONE);
|
||||
int cols = label==null ? 1 : 2;
|
||||
GridLayout layout = GridLayoutFactory.fillDefaults().numColumns(cols).create();
|
||||
field.setLayout(layout);
|
||||
|
||||
searchBox = new Text(field, SWT.SINGLE | SWT.BORDER | SWT.SEARCH | SWT.ICON_CANCEL);
|
||||
GridDataFactory.fillDefaults().grab(true, false).applyTo(searchBox);
|
||||
|
||||
Label fieldNameLabel = null;
|
||||
if (label!=null) {
|
||||
fieldNameLabel = new Label(field, SWT.NONE);
|
||||
fieldNameLabel.setText(label);
|
||||
}
|
||||
|
||||
final TableViewer tv = new TableViewer(field, SWT.SINGLE|SWT.BORDER|SWT.V_SCROLL);
|
||||
tv.addFilter(filter = new ChoicesFilter());
|
||||
tv.setLabelProvider(labelProvider);
|
||||
tv.setContentProvider(ArrayContentProvider.getInstance());
|
||||
tv.setInput(options);
|
||||
|
||||
if (fieldNameLabel!=null) {
|
||||
GridDataFactory.fillDefaults().align(SWT.BEGINNING, SWT.BEGINNING).applyTo(fieldNameLabel);
|
||||
}
|
||||
GridDataFactory grab = GridDataFactory.fillDefaults().grab(true, true).hint(SWT.DEFAULT, 150);
|
||||
grab.applyTo(field);
|
||||
grab.applyTo(tv.getTable());
|
||||
|
||||
whenVisible(tv.getControl(), new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
T preSelect = selection.selection.getValue();
|
||||
if (preSelect!=null) {
|
||||
tv.setSelection(new StructuredSelection(preSelect), true);
|
||||
} else {
|
||||
tv.setSelection(StructuredSelection.EMPTY, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
tv.addSelectionChangedListener(new ISelectionChangedListener() {
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void selectionChanged(SelectionChangedEvent event) {
|
||||
ISelection sel = tv.getSelection();
|
||||
if (sel.isEmpty()) {
|
||||
selection.selection.setValue(null);
|
||||
} else if (sel instanceof IStructuredSelection){
|
||||
IStructuredSelection ss = (IStructuredSelection) sel;
|
||||
selection.selection.setValue((T)ss.getFirstElement());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
searchBox.addModifyListener(new ModifyListener() {
|
||||
@Override
|
||||
public void modifyText(ModifyEvent e) {
|
||||
filter.setSearchTerm(searchBox.getText());
|
||||
tv.refresh();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void whenVisible(final Control control, final Runnable runnable) {
|
||||
PaintListener l = new PaintListener() {
|
||||
@Override
|
||||
public void paintControl(PaintEvent e) {
|
||||
runnable.run();
|
||||
control.removePaintListener(this);
|
||||
}
|
||||
};
|
||||
control.addPaintListener(l);
|
||||
}
|
||||
|
||||
// private String[] getLabels() {
|
||||
// String[] labels = new String[options.length];
|
||||
// for (int i = 0; i < labels.length; i++) {
|
||||
// labels[i] = labelProvider.getText(options[i]);
|
||||
// }
|
||||
// return labels;
|
||||
// }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,442 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013, 2019 Pivotal Software, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal Software, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.guides;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import javax.inject.Provider;
|
||||
|
||||
import org.eclipse.jface.dialogs.Dialog;
|
||||
import org.eclipse.jface.layout.GridDataFactory;
|
||||
import org.eclipse.jface.layout.GridLayoutFactory;
|
||||
import org.eclipse.jface.resource.JFaceResources;
|
||||
import org.eclipse.jface.viewers.ISelection;
|
||||
import org.eclipse.jface.viewers.ISelectionChangedListener;
|
||||
import org.eclipse.jface.viewers.IStructuredSelection;
|
||||
import org.eclipse.jface.viewers.ITreeContentProvider;
|
||||
import org.eclipse.jface.viewers.LabelProvider;
|
||||
import org.eclipse.jface.viewers.SelectionChangedEvent;
|
||||
import org.eclipse.jface.viewers.StructuredSelection;
|
||||
import org.eclipse.jface.viewers.TreeViewer;
|
||||
import org.eclipse.jface.viewers.Viewer;
|
||||
import org.eclipse.jface.viewers.ViewerFilter;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.events.PaintEvent;
|
||||
import org.eclipse.swt.events.PaintListener;
|
||||
import org.eclipse.swt.graphics.Image;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Control;
|
||||
import org.eclipse.swt.widgets.Display;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
import org.springframework.ide.eclipse.boot.wizard.BootWizardActivator;
|
||||
import org.springframework.ide.eclipse.boot.wizard.SearchBoxSection;
|
||||
import org.springframework.ide.eclipse.boot.wizard.content.ContentManager;
|
||||
import org.springframework.ide.eclipse.boot.wizard.content.ContentManager.DownloadState;
|
||||
import org.springframework.ide.eclipse.boot.wizard.content.ContentType;
|
||||
import org.springframework.ide.eclipse.boot.wizard.content.Describable;
|
||||
import org.springframework.ide.eclipse.boot.wizard.content.DisplayNameable;
|
||||
import org.springframework.ide.eclipse.boot.wizard.content.GSContent;
|
||||
import org.springframework.ide.eclipse.boot.wizard.util.StringMatchers;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.LiveExpression;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.LiveVariable;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.SelectionModel;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.ValidationResult;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.ValueListener;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.IPageWithSections;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.WizardPageSection;
|
||||
import org.springsource.ide.eclipse.commons.livexp.util.ExceptionUtil;
|
||||
|
||||
/**
|
||||
* Allow choosing a guide in pull-down style combo box or table viewer.
|
||||
*
|
||||
* TODO: this was copied from the GuidesWizard but it should be changed
|
||||
* into or replaced by something that allows picking content from
|
||||
* a tree where the first level are the types of content available
|
||||
* and the second level are elements of that content type.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class ChooseTypedContentSection extends WizardPageSection {
|
||||
|
||||
private final PrefetchContentListener prefetchContentListener = new PrefetchContentListener();
|
||||
|
||||
public class PrefetchContentListener implements ValueListener<DownloadState> {
|
||||
@Override
|
||||
public void gotValue(LiveExpression<DownloadState> exp, DownloadState downloadState) {
|
||||
// if (downloadState == DownloadState.DOWNLOADED || downloadState == D) {
|
||||
Display.getDefault().asyncExec(() -> {
|
||||
if (treeviewer != null && !treeviewer.getTree().isDisposed()) {
|
||||
updateFilter();
|
||||
}
|
||||
});
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
private static class ContentProvider implements ITreeContentProvider {
|
||||
|
||||
private static final Object[] NO_ELEMENTS = {};
|
||||
private final ContentManager content;
|
||||
|
||||
public ContentProvider(ContentManager content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] getElements(Object e) {
|
||||
if (e==content) {
|
||||
ContentType<?>[] types = content.getTypes();
|
||||
if (types!=null) {
|
||||
if (types.length==1) {
|
||||
//If there's only one type of content. Then it looks better
|
||||
//to just show those elements uncategorized.
|
||||
return getChildren(types[0]);
|
||||
} else {
|
||||
return types;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NO_ELEMENTS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] getChildren(Object e) {
|
||||
try {
|
||||
if (e instanceof ContentType<?>) {
|
||||
return content.getWithPrefetchCheck((ContentType<?>)e);
|
||||
}
|
||||
return new Object[0];
|
||||
} catch (Throwable error) {
|
||||
BootWizardActivator.log(error);
|
||||
return new Object[] {error};
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getParent(Object e) {
|
||||
if (e instanceof GSContent) {
|
||||
return e.getClass();
|
||||
} else if (e instanceof Class<?>) {
|
||||
return content;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasChildren(Object e) {
|
||||
Object[] c = getChildren(e);
|
||||
return c!=null && c.length>0;
|
||||
}
|
||||
}
|
||||
|
||||
private static final LabelProvider labelProvider = new LabelProvider() {
|
||||
@Override
|
||||
public String getText(Object element) {
|
||||
if (element instanceof DisplayNameable) {
|
||||
DisplayNameable item = (DisplayNameable) element;
|
||||
return item.getDisplayName();
|
||||
} else if (element instanceof Throwable) {
|
||||
return ExceptionUtil.getMessage((Throwable) element);
|
||||
}
|
||||
return super.getText(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Image getImage(Object element) {
|
||||
if (element instanceof Throwable) {
|
||||
return JFaceResources.getImage(Dialog.DLG_IMG_MESSAGE_ERROR);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
private class ChoicesFilter extends ViewerFilter {
|
||||
|
||||
private Predicate<String> matcher = null;
|
||||
private final HashMap<Object, Boolean> cache = new HashMap<>();
|
||||
|
||||
public ChoicesFilter() {
|
||||
if (searchBox!=null && searchModel.getValue() != null) {
|
||||
setSearchTerm(searchModel.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
public void setSearchTerm(String text) {
|
||||
matcher = StringMatchers.caseInsensitiveSubstring(text);
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean select(Viewer viewer, Object parentElement, Object element) {
|
||||
if (matcher==null) {
|
||||
return true;
|
||||
}
|
||||
Boolean v = cache.get(element);
|
||||
if (v==null) {
|
||||
v = compute(viewer, parentElement, element);
|
||||
cache.put(element, v);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
public boolean compute(Viewer viewer, Object parentElement, Object e) {
|
||||
String label = labelProvider.getText(e);
|
||||
if (e instanceof ContentType<?>) {
|
||||
//Only search in the content (leaves). The contenttypes are selected if
|
||||
// any of their children (content) is selected.
|
||||
return matchChildren(viewer, e);
|
||||
} else if (match(()->label)) {
|
||||
return true;
|
||||
} else if (e instanceof Describable && match(()->((Describable) e).getDescription())) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean matchChildren(Viewer viewer, Object e) {
|
||||
Object[] children=contentProvider.getChildren(e);
|
||||
if (children!=null) {
|
||||
for (Object c : children) {
|
||||
if (select(viewer, e, c)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean match(Provider<String> provider) {
|
||||
if (matcher == null) {
|
||||
return true; // Search term not set... anything is acceptable.
|
||||
} else {
|
||||
String text = provider.get();
|
||||
if (text == null) {
|
||||
return false;
|
||||
} else {
|
||||
return matcher.test(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private String sectionLabel;
|
||||
private final SelectionModel<GSContent> selection;
|
||||
private SearchBoxSection searchBox;
|
||||
private final LiveVariable<String> searchModel = new LiveVariable<String>("");
|
||||
|
||||
private ChoicesFilter filter;
|
||||
private final ContentManager content;
|
||||
private final ContentProvider contentProvider;
|
||||
private final LiveVariable<Object> rawSelection;
|
||||
private TreeViewer treeviewer;
|
||||
private String initialFilterText;
|
||||
private String category;
|
||||
|
||||
public ChooseTypedContentSection(IPageWithSections owner, SelectionModel<GSContent> selection,
|
||||
LiveVariable<Object> rawSelection, ContentManager content) {
|
||||
super(owner);
|
||||
this.selection = selection;
|
||||
this.rawSelection = rawSelection;
|
||||
this.content = content;
|
||||
this.contentProvider = new ContentProvider(content);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public LiveExpression<ValidationResult> getValidator() {
|
||||
return selection.validator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createContents(Composite page) {
|
||||
|
||||
// PT 130652465 - Avoid downloading content (i.e. network I/O) in the UI thread.
|
||||
// Solution: downloading/prefetch content in the background to avoid blocking the UI in case
|
||||
// it takes a long time.
|
||||
prefetchProvidersAndContent();
|
||||
|
||||
Composite field = new Composite(page, SWT.NONE);
|
||||
int cols = sectionLabel==null ? 1 : 2;
|
||||
GridLayout layout = GridLayoutFactory.fillDefaults().numColumns(cols).create();
|
||||
field.setLayout(layout);
|
||||
|
||||
// PT 164654725 - Replace old SWT Text control with this search box section
|
||||
// that has "built in" focus handling
|
||||
searchBox = new SearchBoxSection(owner, searchModel) {
|
||||
@Override
|
||||
protected String getSearchHint() {
|
||||
return "Type pattern to match";
|
||||
}
|
||||
};
|
||||
|
||||
// PT 164654725 - IMPORTANT: set grab focus BEFORE creating content
|
||||
searchBox.grabFocus(true);
|
||||
searchBox.createContents(field);
|
||||
|
||||
Label fieldNameLabel = null;
|
||||
if (sectionLabel!=null) {
|
||||
fieldNameLabel = new Label(field, SWT.NONE);
|
||||
fieldNameLabel.setText(sectionLabel);
|
||||
}
|
||||
|
||||
treeviewer = new TreeViewer(field, SWT.SINGLE|SWT.BORDER|SWT.V_SCROLL);
|
||||
|
||||
treeviewer.addFilter(filter = new ChoicesFilter());
|
||||
treeviewer.setLabelProvider(labelProvider);
|
||||
treeviewer.setContentProvider(contentProvider);
|
||||
treeviewer.setInput(content);
|
||||
treeviewer.expandAll();
|
||||
|
||||
GridDataFactory grabHor = GridDataFactory.fillDefaults().grab(true, false);
|
||||
GridDataFactory fixHeight = GridDataFactory.fillDefaults().hint(SWT.DEFAULT, 200);
|
||||
grabHor.applyTo(field);
|
||||
fixHeight.applyTo(treeviewer.getControl());
|
||||
if (fieldNameLabel!=null) {
|
||||
GridDataFactory.fillDefaults().align(SWT.BEGINNING, SWT.BEGINNING).applyTo(fieldNameLabel);
|
||||
}
|
||||
|
||||
whenVisible(treeviewer.getControl(), new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
GSContent preSelect = selection.selection.getValue();
|
||||
if (preSelect!=null) {
|
||||
treeviewer.setSelection(new StructuredSelection(preSelect), true);
|
||||
} else {
|
||||
treeviewer.setSelection(StructuredSelection.EMPTY, true);
|
||||
}
|
||||
if (initialFilterText!=null) {
|
||||
searchModel.setValue(initialFilterText);
|
||||
}
|
||||
if (category!=null) {
|
||||
//System.out.println(category);
|
||||
ContentType<?> expand = null;
|
||||
ContentType<?>[] contentTypes = content.getTypes();
|
||||
for (ContentType<?> contentType : contentTypes) {
|
||||
//TODO: Miles(?) Searching by display name seems like a bad idea
|
||||
if (contentType.getDisplayName().equals(category)) {
|
||||
expand = contentType;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (expand!=null) {
|
||||
treeviewer.setExpandedElements(new Object[] { expand });
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
treeviewer.addSelectionChangedListener(new ISelectionChangedListener() {
|
||||
@Override
|
||||
public void selectionChanged(SelectionChangedEvent event) {
|
||||
ISelection sel = treeviewer.getSelection();
|
||||
if (sel.isEmpty()) {
|
||||
setSelection(null);
|
||||
} else if (sel instanceof IStructuredSelection){
|
||||
IStructuredSelection ss = (IStructuredSelection) sel;
|
||||
setSelection(ss.getFirstElement());
|
||||
} else {
|
||||
//Not expecting anything else. So ignore.
|
||||
}
|
||||
}
|
||||
|
||||
private void setSelection(Object e) {
|
||||
if (e == null) {
|
||||
selection.selection.setValue(null);
|
||||
rawSelection.setValue(null);
|
||||
} else {
|
||||
rawSelection.setValue(e);
|
||||
if (e instanceof GSContent) {
|
||||
selection.selection.setValue((GSContent) e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
searchModel.addListener((exp, val) -> {
|
||||
updateFilter();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private void prefetchProvidersAndContent() {
|
||||
LiveVariable<DownloadState> prefetchContentTracker = content.getPrefetchContentTracker();
|
||||
prefetchContentTracker.addListener(this.prefetchContentListener);
|
||||
content.prefetchInBackground(owner.getRunnableContext());
|
||||
}
|
||||
|
||||
private void whenVisible(final Control control, final Runnable runnable) {
|
||||
PaintListener l = new PaintListener() {
|
||||
@Override
|
||||
public void paintControl(PaintEvent e) {
|
||||
runnable.run();
|
||||
control.removePaintListener(this);
|
||||
}
|
||||
};
|
||||
control.addPaintListener(l);
|
||||
}
|
||||
|
||||
public void setFilterText(String text) {
|
||||
if (searchBox!=null) {
|
||||
// Don't set values into the search box directly. Do it
|
||||
// through the model. However, guard this against checks on the search box
|
||||
// existing, as no point in setting value in the model if there is
|
||||
// no actual search box. In faft, setting values in the model without
|
||||
// the search box existing probably will result in NPEs
|
||||
searchModel.setValue(text);
|
||||
} else {
|
||||
//UI isn't there yet so remember the text as intial filter text to be set when
|
||||
// ui is created later.
|
||||
this.initialFilterText = text;
|
||||
}
|
||||
}
|
||||
|
||||
public void setCategory(String category) {
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
private void updateFilter() {
|
||||
filter.setSearchTerm(searchModel.getValue());
|
||||
treeviewer.refresh();
|
||||
treeviewer.expandAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
if (content != null ) {
|
||||
if (prefetchContentListener != null) {
|
||||
// This may not be necessary, but to be safe explicitly remove the listener
|
||||
content.getPrefetchContentTracker().removeListener(prefetchContentListener);
|
||||
}
|
||||
content.disposePrefetchTrackingListeners();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// private String[] getLabels() {
|
||||
// String[] labels = new String[options.length];
|
||||
// for (int i = 0; i < labels.length; i++) {
|
||||
// labels[i] = labelProvider.getText(options[i]);
|
||||
// }
|
||||
// return labels;
|
||||
// }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013 GoPivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.guides;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
||||
import org.eclipse.jface.layout.GridDataFactory;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.events.SelectionEvent;
|
||||
import org.eclipse.swt.events.SelectionListener;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
import org.eclipse.swt.widgets.Button;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Control;
|
||||
import org.eclipse.swt.widgets.Group;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.LiveExpression;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.UIValueListener;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.ValidationResult;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.Validator;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.CommentSection;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.IPageWithSections;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.WizardPageSection;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.WizardPageWithSections;
|
||||
|
||||
/**
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class CodeSetCheckBoxesSection extends WizardPageSection {
|
||||
|
||||
public static class CheckBox extends WizardPageSection {
|
||||
|
||||
private final String name;
|
||||
private final MultiSelectionModel<String> model;
|
||||
private Button cb;
|
||||
|
||||
public CheckBox(IPageWithSections owner, String name, MultiSelectionModel<String> model) {
|
||||
super(owner);
|
||||
this.name = name;
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LiveExpression<ValidationResult> getValidator() {
|
||||
return Validator.OK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createContents(Composite page) {
|
||||
if (page!=null && !page.isDisposed()) {
|
||||
this.cb = new Button(page, SWT.CHECK);
|
||||
cb.setText(name);
|
||||
cb.setSelection(model.selecteds.contains(name));
|
||||
GridDataFactory.fillDefaults().grab(true, false).applyTo(cb);
|
||||
cb.addSelectionListener(new SelectionListener() {
|
||||
//@Override
|
||||
public void widgetSelected(SelectionEvent e) {
|
||||
handleSelection();
|
||||
}
|
||||
|
||||
//@Override
|
||||
public void widgetDefaultSelected(SelectionEvent e) {
|
||||
handleSelection();
|
||||
}
|
||||
|
||||
private void handleSelection() {
|
||||
boolean add = cb.getSelection();
|
||||
if (add) {
|
||||
model.selecteds.add(name);
|
||||
} else {
|
||||
model.selecteds.remove(name);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
if (cb!=null && !cb.isDisposed()) {
|
||||
cb.dispose();
|
||||
cb = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final LiveExpression<String[]> options;
|
||||
private final MultiSelectionModel<String> model;
|
||||
private Group group;
|
||||
|
||||
|
||||
public CodeSetCheckBoxesSection(WizardPageWithSections owner, LiveExpression<String[]> options, MultiSelectionModel<String> model) {
|
||||
super(owner);
|
||||
this.model = model;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
private WizardPageSection[] subsections;
|
||||
|
||||
protected GridLayout createLayout() {
|
||||
return new GridLayout(2, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LiveExpression<ValidationResult> getValidator() {
|
||||
return model.validator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createContents(Composite page) {
|
||||
this.group = new Group(page, SWT.NONE);
|
||||
this.group.setText("Code Sets");
|
||||
GridLayout layout = createLayout();
|
||||
group.setLayout(layout);
|
||||
GridDataFactory.fillDefaults().grab(true, false).applyTo(group);
|
||||
|
||||
//This section is a bit special in that the contents of the group is only
|
||||
// filled in dynamically in response to events from the 'options' LiveExpression.
|
||||
options.addListener(new UIValueListener<String[]>() {
|
||||
@Override
|
||||
public void uiGotValue(LiveExpression<String[]> exp, String[] names) {
|
||||
if (group==null || group.isDisposed()) {
|
||||
//Don't bother. The UI is already gone.
|
||||
options.removeListener(this);
|
||||
return;
|
||||
}
|
||||
if (names==null) {
|
||||
names = new String[0];
|
||||
}
|
||||
//Dispose the checkboxes and create new ones.
|
||||
if (subsections!=null) {
|
||||
for (WizardPageSection subsection : subsections) {
|
||||
subsection.dispose();
|
||||
}
|
||||
}
|
||||
subsections = new WizardPageSection[Math.max(1, names.length)];
|
||||
|
||||
// GridData gd = (GridData) group.getLayoutData();
|
||||
// boolean visible = checkboxes.length>0;
|
||||
// gd.exclude = !visible;
|
||||
|
||||
if (names.length==0) {
|
||||
//don't leave section empty it looks ugly
|
||||
subsections[0] = new CommentSection(owner, "No codesets");
|
||||
subsections[0].createContents(group);
|
||||
}
|
||||
GridLayout newLayout = createLayout();
|
||||
newLayout.numColumns = names.length>2 ? 3 : 2;
|
||||
group.setLayout(newLayout);
|
||||
for (int i = 0; i < names.length; i++) {
|
||||
subsections[i] = new CheckBox(owner, names[i], model);
|
||||
if (isNewName(names[i])) {
|
||||
model.selecteds.add(names[i]);
|
||||
}
|
||||
subsections[i].createContents(group);
|
||||
}
|
||||
//Note: code below removes invalid names from selection model.
|
||||
// Code has been commented out. We assume that any code
|
||||
// using the 'model.selecteds' will just ignore invalid
|
||||
// codeset names. That way we can leave the selecteds as is.
|
||||
// The benefit is that if one switches away from a guide
|
||||
// and then back again, then selected items will be 'preserved' even if
|
||||
// they weren't valid in between. (If the code below is reactivated invalid
|
||||
// names will be automatically cleared and the user will have to reselect them
|
||||
// when they return to the original guide).
|
||||
|
||||
// HashSet<String> validNameSet = new HashSet<String>(Arrays.asList(names));
|
||||
// for (String selectedName : model.selecteds.getValues()) {
|
||||
// if (!validNameSet.contains(selectedName)) {
|
||||
// model.selecteds.remove(selectedName);
|
||||
// }
|
||||
// };
|
||||
group.getParent().layout(true, true);
|
||||
}
|
||||
|
||||
private final HashSet<String> namesSeen = new HashSet<String>();
|
||||
|
||||
/**
|
||||
* Tracks codeset names that have (not) been seen before.
|
||||
* This method returns true the first time it is called with a given
|
||||
* name. Subsequent calls with the same name will return false
|
||||
*/
|
||||
private boolean isNewName(String name) {
|
||||
boolean seen = namesSeen.contains(name);
|
||||
if (!seen) {
|
||||
//Seen it now...next time its not new anymore
|
||||
namesSeen.add(name);
|
||||
}
|
||||
return !seen;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void clear(Group group) {
|
||||
Control[] children = group.getChildren();
|
||||
for (Control c : children) {
|
||||
c.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013, 2016 GoPivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.guides;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
import org.eclipse.core.runtime.IProgressMonitor;
|
||||
import org.eclipse.jface.dialogs.MessageDialog;
|
||||
import org.eclipse.jface.layout.GridDataFactory;
|
||||
import org.eclipse.jface.operation.IRunnableWithProgress;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.events.SelectionAdapter;
|
||||
import org.eclipse.swt.events.SelectionEvent;
|
||||
import org.eclipse.swt.widgets.Button;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.ui.PlatformUI;
|
||||
import org.springframework.ide.eclipse.boot.wizard.BootWizardActivator;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.LiveExpression;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.UIValueListener;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.ValidationResult;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.Validator;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.WizardPageSection;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.WizardPageWithSections;
|
||||
import org.springsource.ide.eclipse.commons.livexp.util.ExceptionUtil;
|
||||
|
||||
/**
|
||||
* A 'Download' button. Can be clicked to perform a download operation.
|
||||
* It is enabled or disabled (i.e. greyed out) based on whether the
|
||||
* content is already downloaded.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class DownloadButtonSection extends WizardPageSection {
|
||||
|
||||
public static abstract class DownloadableModel {
|
||||
|
||||
/**
|
||||
* Reflects the state of the downloadable content. True when it is already downloaded
|
||||
* false otherwise.
|
||||
*/
|
||||
public final LiveExpression<Boolean> isDownloaded;
|
||||
|
||||
public DownloadableModel(LiveExpression<Boolean> isDownloaded) {
|
||||
this.isDownloaded = isDownloaded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclass should implement to perform the download operation
|
||||
* and update isDownloaded LiveExp as appropriate upon completion.
|
||||
*/
|
||||
public abstract void performDownload(IProgressMonitor monitor);
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
private final DownloadableModel model;
|
||||
private Button button;
|
||||
|
||||
public DownloadButtonSection(
|
||||
WizardPageWithSections owner,
|
||||
DownloadableModel model) {
|
||||
super(owner);
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LiveExpression<ValidationResult> getValidator() {
|
||||
return Validator.OK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createContents(Composite page) {
|
||||
button = new Button(page, SWT.PUSH);
|
||||
button.setText("Download");
|
||||
enableDisable();
|
||||
GridDataFactory.fillDefaults().align(SWT.END, SWT.CENTER).grab(true, false).applyTo(button);
|
||||
model.isDownloaded.addListener(new UIValueListener<Boolean>() {
|
||||
@Override
|
||||
protected void uiGotValue(LiveExpression<Boolean> exp, Boolean value) {
|
||||
enableDisable();
|
||||
}
|
||||
});
|
||||
button.addSelectionListener(new SelectionAdapter() {
|
||||
@Override
|
||||
public void widgetSelected(SelectionEvent evt) {
|
||||
try {
|
||||
PlatformUI.getWorkbench().getProgressService().busyCursorWhile(new IRunnableWithProgress() {
|
||||
//@Override
|
||||
public void run(IProgressMonitor monitor) throws InvocationTargetException,
|
||||
InterruptedException {
|
||||
try {
|
||||
model.performDownload(monitor);
|
||||
} catch (Exception e) {
|
||||
throw new InvocationTargetException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (InvocationTargetException e) {
|
||||
MessageDialog.openError(button.getShell(), "Error downloading", ExceptionUtil.getMessage(e));
|
||||
BootWizardActivator.log(e);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update enablement state of the button. Button should be disabled if guide is
|
||||
* already downloaded.
|
||||
*/
|
||||
private void enableDisable() {
|
||||
Boolean isDown = model.isDownloaded.getValue();
|
||||
if (isDown!=null) {
|
||||
button.setEnabled(isDown);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013, 2016 Pivotal Software, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal Software, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.guides;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.core.runtime.IProgressMonitor;
|
||||
import org.eclipse.core.runtime.IStatus;
|
||||
import org.eclipse.core.runtime.Status;
|
||||
import org.eclipse.core.runtime.jobs.Job;
|
||||
import org.eclipse.jface.resource.ImageDescriptor;
|
||||
import org.eclipse.jface.viewers.IStructuredSelection;
|
||||
import org.eclipse.jface.wizard.Wizard;
|
||||
import org.eclipse.jface.wizard.WizardDialog;
|
||||
import org.eclipse.swt.widgets.Shell;
|
||||
import org.eclipse.ui.IImportWizard;
|
||||
import org.eclipse.ui.INewWizard;
|
||||
import org.eclipse.ui.IWorkbench;
|
||||
import org.springframework.ide.eclipse.boot.wizard.BootWizardImages;
|
||||
import org.springframework.ide.eclipse.boot.wizard.content.ContentManager;
|
||||
import org.springframework.ide.eclipse.boot.wizard.content.GSContent;
|
||||
import org.springframework.ide.eclipse.boot.wizard.content.GSZipFileCodeSet;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.DescriptionSection;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.WizardPageSection;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.WizardPageWithSections;
|
||||
import org.springsource.ide.eclipse.commons.livexp.util.ExceptionUtil;
|
||||
|
||||
/**
|
||||
* Generic import wizard for different types of getting started content.
|
||||
* <p>
|
||||
* The idea is that all content is shown together in single tree viewer,
|
||||
* grouped by type.
|
||||
* <p>
|
||||
* This is a generalization of the GuideImportWizard.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class GSImportWizard extends Wizard implements IImportWizard, INewWizard {
|
||||
|
||||
private static class DialogCallback {
|
||||
WizardDialog dialog;
|
||||
}
|
||||
|
||||
private DialogCallback callback;
|
||||
|
||||
private final GSImportWizardModel model = new GSImportWizardModel();
|
||||
|
||||
public GSImportWizard() {
|
||||
setNeedsProgressMonitor(true);
|
||||
setDefaultPageImageDescriptor(IMAGE);
|
||||
}
|
||||
|
||||
private static final ImageDescriptor IMAGE = BootWizardImages.GUIDES_WIZARD_ICON;
|
||||
//TODO: Get our own icon for GSG wizard
|
||||
|
||||
private final PageOne pageOne = new PageOne(model);
|
||||
private String focusItem;
|
||||
|
||||
public class PageOne extends WizardPageWithSections {
|
||||
|
||||
private final GSImportWizardModel model;
|
||||
private ChooseTypedContentSection contentChooser;
|
||||
|
||||
protected PageOne(GSImportWizardModel model) {
|
||||
super("Page One", "Import Getting Started Content", IMAGE);
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<WizardPageSection> createSections() {
|
||||
List<WizardPageSection> sections = new ArrayList<WizardPageSection>();
|
||||
|
||||
sections.add(getContentChooser());
|
||||
contentChooser.setCategory(focusItem);
|
||||
sections.add(new DescriptionSection(this, model.description));
|
||||
sections.add(new ImportStrategiesRadiosSection(this, model.getImportStrategyModel()));
|
||||
sections.add(new CodeSetCheckBoxesSection(this, model.validCodesetNames, model.getCodeSetModel()));
|
||||
sections.add(new OpenUrlSection(GSImportWizard.this, this, "Home Page", model.homePage, model.getEnableOpenHomePage()));
|
||||
return sections;
|
||||
}
|
||||
|
||||
private ChooseTypedContentSection getContentChooser() {
|
||||
if (contentChooser==null) {
|
||||
contentChooser = new ChooseTypedContentSection(this, model.getGSContentSelectionModel(),
|
||||
model.getRawSelection(), model.getContentManager());
|
||||
}
|
||||
return contentChooser;
|
||||
}
|
||||
|
||||
public void setFilterText(String text) {
|
||||
getContentChooser().setFilterText(text);
|
||||
}
|
||||
}
|
||||
|
||||
public void openHomePage(boolean close) {
|
||||
model.openHomePage();
|
||||
if (close) {
|
||||
callback.dialog.close();
|
||||
}
|
||||
}
|
||||
|
||||
// private PageTwo pageTwo = new PageTwo(model);
|
||||
//
|
||||
// public class PageTwo extends WizardPageWithSections {
|
||||
//
|
||||
// private GuideImportWizardModel model;
|
||||
//
|
||||
// protected PageTwo(GuideImportWizardModel model) {
|
||||
// super("Page Two", "Import Getting Started Guide", IMAGE);
|
||||
// this.model = model;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// protected List<WizardPageSection> createSections() {
|
||||
// List<WizardPageSection> sections = new ArrayList<WizardPageSection>();
|
||||
//
|
||||
// return sections;
|
||||
// }
|
||||
// }
|
||||
|
||||
public void init(IWorkbench workbench, IStructuredSelection selection) {
|
||||
// this.workbench = workbench;
|
||||
// super.init(workbench, selection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPages() {
|
||||
super.addPages();
|
||||
addPage(pageOne);
|
||||
|
||||
// addPage(pageTwo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performFinish() {
|
||||
Job job = new Job("Import Getting Started Content") {
|
||||
@Override
|
||||
protected IStatus run(IProgressMonitor mon) {
|
||||
try {
|
||||
model.performFinish(mon);
|
||||
return Status.OK_STATUS;
|
||||
} catch (Throwable e) {
|
||||
return ExceptionUtil.status(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
//job.setRule(ResourcesPlugin.getWorkspace().getRuleFactory().buildRule());
|
||||
job.setPriority(Job.BUILD);
|
||||
job.setUser(true); //shows progress in default eclipse config
|
||||
job.schedule();
|
||||
return true;
|
||||
}
|
||||
|
||||
//Old Version of performFinish that uses an "In Dialog Progress Bar". Dialog stays open until import is
|
||||
// complete:
|
||||
// @Override
|
||||
// public boolean performFinish() {
|
||||
// try {
|
||||
// //IMPORTANT: fork must be true or invalid thread access will ensue for gradle imports.
|
||||
// // This is because the gradle import will send progress events from worker threads.
|
||||
// // and the non-forked run will pass a UI-direct progress monitor to the runnable!
|
||||
// getContainer().run(true, false, new IRunnableWithProgress() {
|
||||
// @Override
|
||||
// public void run(IProgressMonitor mon) throws InvocationTargetException, InterruptedException {
|
||||
// model.performFinish(mon);
|
||||
// }
|
||||
// });
|
||||
// return true;
|
||||
// } catch (InterruptedException e) {
|
||||
// GettingStartedActivator.log(e);
|
||||
// } catch (InvocationTargetException e) {
|
||||
// String msg = ExceptionUtil.getMessage(e);
|
||||
// if (msg!=null && !"".equals(msg)) {
|
||||
// MessageDialog.openError(getShell(), "Error performing the import", msg);
|
||||
// }
|
||||
// GettingStartedActivator.log(e);
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Open the wizard and block until it is closed by the user. Returns the exit code of
|
||||
* the wizard (e.g. indicating OK or CANCEL).
|
||||
*/
|
||||
public static int open(Shell shell, GSContent guide) {
|
||||
return open(shell, guide, null, true, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the wizard and block until it is closed by the user. Returns the exit code of
|
||||
* the wizard (e.g. indicating OK or CANCEL).
|
||||
*/
|
||||
public static int open(Shell shell, String focusItem) {
|
||||
return open(shell, null, focusItem, true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the wizard and block until it is closed by the user. Returns the exit code of
|
||||
* the wizard (e.g. indicating OK or CANCEL).
|
||||
*/
|
||||
private static int open(Shell shell, GSContent guide, String focusItem, boolean synchronous, boolean enableOpenHomepage) {
|
||||
GSImportWizard wiz = new GSImportWizard();
|
||||
wiz.setEnableOpenHomePage(enableOpenHomepage);
|
||||
if (guide != null) {
|
||||
wiz.setItem(guide);
|
||||
}
|
||||
if (focusItem != null) {
|
||||
wiz.setFocusItem(focusItem);
|
||||
}
|
||||
WizardDialog dialog = new WizardDialog(shell, wiz);
|
||||
dialog.setBlockOnOpen(synchronous);
|
||||
wiz.callback = new DialogCallback();
|
||||
wiz.callback.dialog = dialog;
|
||||
return dialog.open();
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be called before opening the wizard to change the default value
|
||||
* for the option to open content homepage after importing.
|
||||
*/
|
||||
public void setEnableOpenHomePage(boolean enable) {
|
||||
model.getEnableOpenHomePage().setValue(enable);
|
||||
}
|
||||
|
||||
public static int open(Shell shell, ContentManager cm, GSZipFileCodeSet content) {
|
||||
GSImportWizard wiz = new GSImportWizard();
|
||||
wiz.setContentManager(cm); //All available content
|
||||
wiz.setItem(content); //Pre-selected content
|
||||
WizardDialog dialog = new WizardDialog(shell, wiz);
|
||||
dialog.setBlockOnOpen(true);
|
||||
return dialog.open();
|
||||
}
|
||||
|
||||
private void setContentManager(ContentManager cm) {
|
||||
model.setContentManager(cm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default selection for the content item that is going to be imported.
|
||||
*/
|
||||
public void setItem(GSContent guide) {
|
||||
this.model.setItem(guide);
|
||||
if (guide!=null) {
|
||||
pageOne.setFilterText(guide.getDisplayName());
|
||||
}
|
||||
}
|
||||
|
||||
public void setFocusItem(String focusItem) {
|
||||
this.focusItem = focusItem;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,521 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013, 2016 Pivotal Software, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal Software, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.guides;
|
||||
|
||||
import static org.springsource.ide.eclipse.commons.ui.UiUtil.openUrl;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.URL;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.core.runtime.IProgressMonitor;
|
||||
import org.eclipse.core.runtime.IStatus;
|
||||
import org.eclipse.core.runtime.Status;
|
||||
import org.eclipse.core.runtime.SubMonitor;
|
||||
import org.eclipse.core.runtime.jobs.Job;
|
||||
import org.eclipse.jface.operation.IRunnableWithProgress;
|
||||
import org.springframework.ide.eclipse.boot.wizard.BootWizardActivator;
|
||||
import org.springframework.ide.eclipse.boot.wizard.content.BuildType;
|
||||
import org.springframework.ide.eclipse.boot.wizard.content.CodeSet;
|
||||
import org.springframework.ide.eclipse.boot.wizard.content.ContentManager;
|
||||
import org.springframework.ide.eclipse.boot.wizard.content.ContentManager.DownloadState;
|
||||
import org.springframework.ide.eclipse.boot.wizard.content.Describable;
|
||||
import org.springframework.ide.eclipse.boot.wizard.content.GSContent;
|
||||
import org.springframework.ide.eclipse.boot.wizard.content.GettingStartedContent;
|
||||
import org.springframework.ide.eclipse.boot.wizard.content.GettingStartedGuide;
|
||||
import org.springframework.ide.eclipse.boot.wizard.importing.ImportConfiguration;
|
||||
import org.springframework.ide.eclipse.boot.wizard.importing.ImportStrategies;
|
||||
import org.springframework.ide.eclipse.boot.wizard.importing.ImportStrategy;
|
||||
import org.springframework.ide.eclipse.boot.wizard.importing.ImportUtils;
|
||||
import org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager.UIThreadDownloadDisallowed;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.LiveExpression;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.LiveSet;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.LiveVariable;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.SelectionModel;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.ValidationResult;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.Validator;
|
||||
import org.springsource.ide.eclipse.commons.livexp.util.ExceptionUtil;
|
||||
|
||||
/**
|
||||
* Core counterpart of <b>GSImportWizard</b> (essentially this is a 'model' for the wizard
|
||||
* UI.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
* @author Nieraj Singh
|
||||
*/
|
||||
public class GSImportWizardModel {
|
||||
|
||||
/**
|
||||
* ContentManager instance that provides all the content that this wizard can import.
|
||||
* By default this is content discovered automatically with the default content manager
|
||||
* instance. However it is possible to set the Content manager to browser / import
|
||||
* content provided another way.
|
||||
*/
|
||||
private ContentManager contentManager = GettingStartedContent.getInstance();
|
||||
|
||||
|
||||
// Tracks the download state of content from content manager
|
||||
private final LiveVariable<DownloadState> prefetchContentTracker = contentManager.getPrefetchContentTracker();
|
||||
|
||||
// Tracks the download state for content providers properties from content manager
|
||||
private final LiveVariable<DownloadState> prefetchContentProviderPropsTracker = contentManager.getPrefetchContentProviderPropertiesTracker();
|
||||
|
||||
/**
|
||||
* The chosen guide to import stuff from.
|
||||
*/
|
||||
private final LiveVariable<GSContent> guide = new LiveVariable<GSContent>();
|
||||
|
||||
public final LiveExpression<ValidationResult> downloadStatus = new Validator() {
|
||||
@Override
|
||||
protected ValidationResult compute() {
|
||||
GSContent g = guide.getValue();
|
||||
if (g == null) {
|
||||
return ValidationResult.OK;
|
||||
} else {
|
||||
return ValidationResult.from(g.getZip().getDownloadStatus());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final LiveExpression<ValidationResult> guideValidator = new Validator() {
|
||||
{
|
||||
dependsOn(guide);
|
||||
dependsOn(prefetchContentProviderPropsTracker);
|
||||
dependsOn(prefetchContentTracker);
|
||||
dependsOn(downloadStatus);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ValidationResult compute() {
|
||||
DownloadState state = prefetchContentProviderPropsTracker.getValue();
|
||||
if (state == DownloadState.IS_DOWNLOADING) {
|
||||
return ValidationResult.info("Registering content providers. Please wait...");
|
||||
} else {
|
||||
state = prefetchContentTracker.getValue();
|
||||
if (state == DownloadState.IS_DOWNLOADING) {
|
||||
return ValidationResult.info("Downloading all content. Please wait...");
|
||||
}
|
||||
}
|
||||
|
||||
ValidationResult downloadValidation = downloadStatus.getValue();
|
||||
if (guide.getValue() == null) {
|
||||
return ValidationResult.error("No GS content selected");
|
||||
} else if (downloadValidation != null && !downloadValidation.isOk()){
|
||||
return downloadValidation;
|
||||
} else {
|
||||
return ValidationResult.OK;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static final ValidationResult isDownloadingMessage(GSContent g) {
|
||||
return ValidationResult.info(g.getDisplayName()+" is downloading...");
|
||||
}
|
||||
|
||||
public class CodeSetValidator extends LiveExpression<ValidationResult> {
|
||||
|
||||
private final LiveVariable<GSContent> codesetProvider;
|
||||
private final LiveSet<String> selectedNames;
|
||||
private final LiveExpression<String[]> validCodesetNames;
|
||||
|
||||
public CodeSetValidator(LiveVariable<GSContent> guide, LiveSet<String> codesets, LiveExpression<String[]> validCodeSetNames) {
|
||||
this.codesetProvider = guide;
|
||||
this.selectedNames = codesets;
|
||||
this.validCodesetNames = validCodeSetNames;
|
||||
this.dependsOn(guide);
|
||||
this.dependsOn(codesets);
|
||||
this.dependsOn(validCodeSetNames);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ValidationResult compute() {
|
||||
try {
|
||||
GSContent g = codesetProvider.getValue();
|
||||
if (g!=null) { //Don't check or produce errors unless a content provider has been selected.
|
||||
boolean codesetSelected = false;
|
||||
try {
|
||||
Set<String> names = selectedNames.getValue();
|
||||
if (names != null && !names.isEmpty()) {
|
||||
for (String name : names) {
|
||||
CodeSet cs = g.getCodeSet(name);
|
||||
if (cs!=null) {
|
||||
codesetSelected = true;
|
||||
ImportConfiguration conf = ImportUtils.importConfig(g, cs);
|
||||
ValidationResult valid = ImportUtils.validateImportConfiguration(conf);
|
||||
if (!valid.isOk()) {
|
||||
return valid;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!codesetSelected) {
|
||||
//Selectiong nothing is only allowed if there is in fact nothing to select
|
||||
//otherwise at least on codeset must be selected for import.
|
||||
String[] validNames = validCodesetNames.getValue();
|
||||
if (validNames!=null && validNames.length>0) {
|
||||
return ValidationResult.error("At least one codeset should be selected");
|
||||
}
|
||||
}
|
||||
} catch (UIThreadDownloadDisallowed e) {
|
||||
scheduleDownloadJob();
|
||||
return isDownloadingMessage(g);
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
//Unexpected. So log it for more info but also try to create a sensible error message in
|
||||
// the wizard.
|
||||
BootWizardActivator.log(e);
|
||||
return ValidationResult.error(ExceptionUtil.getMessage(e));
|
||||
}
|
||||
return ValidationResult.OK;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Chosen element in the content picker whether it is an actual GSContent item
|
||||
* or a ContentType. Used to update description instead of 'c
|
||||
*/
|
||||
private final LiveVariable<Object> rawSelection = new LiveVariable<Object>();
|
||||
|
||||
/**
|
||||
* The names of the codesets selected for import.
|
||||
*/
|
||||
private final LiveSet<String> codesets = new LiveSet<String>(new HashSet<String>());
|
||||
{
|
||||
codesets.addAll(GettingStartedGuide.defaultCodesetNames()); //Select all codesets by default.
|
||||
}
|
||||
|
||||
/**
|
||||
* The valid codeset names w.r.t. the currently selected guide
|
||||
*/
|
||||
public final LiveExpression<String[]> validCodesetNames = new LiveExpression<String[]>(null) {
|
||||
|
||||
@Override
|
||||
protected String[] compute() {
|
||||
try {
|
||||
GSContent g = guide.getValue();
|
||||
if (g!=null) {
|
||||
List<CodeSet> validSets = g.getCodeSets();
|
||||
if (validSets!=null) {
|
||||
String[] names = new String[validSets.size()];
|
||||
for (int i = 0; i < names.length; i++) {
|
||||
names[i] = validSets.get(i).getName();
|
||||
}
|
||||
return names;
|
||||
}
|
||||
}
|
||||
} catch (UIThreadDownloadDisallowed e) {
|
||||
//Failed because content is not yet downloade but this is ok...
|
||||
//just schedule download to happen later and in the mean time return something sensible
|
||||
scheduleDownloadJob();
|
||||
} catch (Throwable e) {
|
||||
BootWizardActivator.log(e);
|
||||
}
|
||||
return GettingStartedGuide.defaultCodesetNames();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The import strategy chosen by user
|
||||
*/
|
||||
private final LiveVariable<ImportStrategy> importStrategy = new LiveVariable<ImportStrategy>(BuildType.DEFAULT.getDefaultStrategy());
|
||||
|
||||
private final LiveExpression<ValidationResult> codesetValidator = new CodeSetValidator(guide, codesets, validCodesetNames);
|
||||
private final LiveExpression<ValidationResult> importStrategyValidator = new Validator() {
|
||||
@Override
|
||||
protected ValidationResult compute() {
|
||||
GSContent g = guide.getValue();
|
||||
ImportStrategy bt = importStrategy.getValue();
|
||||
return validateImportStrategy(g, bt);
|
||||
}
|
||||
};
|
||||
|
||||
private ValidationResult validateImportStrategy(GSContent g, ImportStrategy importStrategy) {
|
||||
try {
|
||||
if (g!=null) {
|
||||
try {
|
||||
if (importStrategy==null) {
|
||||
return ValidationResult.error("No build type selected");
|
||||
} else {
|
||||
List<String> codesetNames = codesets.getValues();
|
||||
if (codesetNames!=null) {
|
||||
for (String csname : codesetNames) {
|
||||
CodeSet cs = g.getCodeSet(csname);
|
||||
if (cs!=null) {
|
||||
ValidationResult result = cs.validateBuildType(importStrategy.getBuildType());
|
||||
if (!result.isOk()) {
|
||||
return result.withMessage("CodeSet '"+csname+"': "+result.msg);
|
||||
}
|
||||
if (!importStrategy.isSupported()) {
|
||||
//This means some required STS component like m2e or gradle tooling is not installed
|
||||
return ValidationResult.error(importStrategy.getNotInstalledMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (UIThreadDownloadDisallowed e) {
|
||||
//Careful... check some of the validation will trigger downloads. This is not allowed in UI thread.
|
||||
scheduleDownloadJob();
|
||||
return isDownloadingMessage(g);
|
||||
}
|
||||
}
|
||||
return ValidationResult.OK;
|
||||
} catch (Throwable e) {
|
||||
BootWizardActivator.log(e);
|
||||
return ValidationResult.error(ExceptionUtil.getMessage(e));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public final LiveExpression<Boolean> isDownloaded = new LiveExpression<Boolean>(false) {
|
||||
@Override
|
||||
protected Boolean compute() {
|
||||
GSContent g = guide.getValue();
|
||||
return g == null || g.isDownloaded();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Tries to select a valid build type when a guide is selected.
|
||||
* The tricky bit is that validity of build types can not be determined until guide
|
||||
* content has been downloaded locally. At this point if user clicks around rapidly
|
||||
* another guide may already have been selected. Thus, this code should be run
|
||||
* any time the guide selection changes as well as any time the download
|
||||
* status changes.
|
||||
*/
|
||||
private final LiveExpression<Void> autoSelectBuildType = new LiveExpression<Void>() {
|
||||
{
|
||||
dependsOn(isDownloaded);
|
||||
dependsOn(guide);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void compute() {
|
||||
GSContent g = guide.getValue();
|
||||
if (g!=null) {
|
||||
if (g.isDownloaded()) {
|
||||
//Yes, we depend on the value of buildType but shouldn't respond to changes on it.
|
||||
// We do not want to autoselect a buildType when a user selects one. That would be
|
||||
// mighty annoying.
|
||||
ImportStrategy is = importStrategy.getValue();
|
||||
if (!validateImportStrategy(g, is).isOk()) {
|
||||
for (ImportStrategy other : ImportStrategies.all()) {
|
||||
if (other!=is) {
|
||||
if (validateImportStrategy(g,other).isOk()) {
|
||||
importStrategy.setValue(other);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* The description of the current guide.
|
||||
*/
|
||||
public final LiveExpression<String> description = new LiveExpression<String>("<no description>") {
|
||||
@Override
|
||||
protected String compute() {
|
||||
Object g = rawSelection.getValue();
|
||||
if (g!=null && g instanceof Describable) {
|
||||
return ((Describable) g).getDescription();
|
||||
}
|
||||
return "Select Getting Started Content to see its Description";
|
||||
}
|
||||
};
|
||||
|
||||
public final LiveExpression<URL> homePage = new LiveExpression<URL>(null) {
|
||||
@Override
|
||||
protected URL compute() {
|
||||
GSContent g = guide.getValue();
|
||||
if (g!=null) {
|
||||
return g.getHomePage();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Indicates whether the user has selected the option to open the home page.
|
||||
*/
|
||||
private final LiveVariable<Boolean> enableOpenHomePage = new LiveVariable<Boolean>(true);
|
||||
|
||||
{
|
||||
importStrategyValidator.dependsOn(guide);
|
||||
importStrategyValidator.dependsOn(isDownloaded);
|
||||
importStrategyValidator.dependsOn(importStrategy);
|
||||
importStrategyValidator.dependsOn(codesets);
|
||||
|
||||
isDownloaded.dependsOn(guide);
|
||||
downloadStatus.dependsOn(guide);
|
||||
|
||||
description.dependsOn(rawSelection);
|
||||
|
||||
homePage.dependsOn(guide);
|
||||
|
||||
|
||||
validCodesetNames.dependsOn(guide);
|
||||
validCodesetNames.dependsOn(isDownloaded);
|
||||
|
||||
codesetValidator.dependsOn(isDownloaded);
|
||||
//Note: some other dependsOn are registered inside CodeSetValidator class itself.
|
||||
// isDownloaded is an exception because is still null when CodeSetValidator class gets
|
||||
// instantiated.
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads currently selected guide content (if it is not already cached locally.
|
||||
*/
|
||||
public void performDownload(IProgressMonitor mon) {
|
||||
mon.beginTask("Downloading", 1);
|
||||
try {
|
||||
GSContent g = guide.getValue();
|
||||
if (g!=null) {
|
||||
g.getZip().getFile(); //This forces download
|
||||
}
|
||||
} catch (Exception e) {
|
||||
//Don't throw exceptions they are now tracked via downloadStatus.
|
||||
BootWizardActivator.log(e); //Log for more details than downloadStatus message (i.e. stack trace).
|
||||
} finally {
|
||||
isDownloaded.refresh();
|
||||
downloadStatus.refresh();
|
||||
mon.done();
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleDownloadJob() {
|
||||
Job job = new Job("Downloading guide content") {
|
||||
@Override
|
||||
protected IStatus run(IProgressMonitor mon) {
|
||||
try {
|
||||
performDownload(mon);
|
||||
} catch (Throwable e) {
|
||||
return ExceptionUtil.status(e);
|
||||
}
|
||||
return Status.OK_STATUS;
|
||||
}
|
||||
|
||||
};
|
||||
job.schedule();
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the final step of the wizard when user clicks on Finish button.
|
||||
* @throws InterruptedException
|
||||
* @throws InvocationTargetException
|
||||
*/
|
||||
public boolean performFinish(IProgressMonitor mon) throws InvocationTargetException, InterruptedException {
|
||||
//The import will be carried out with whatever the currently selected values are
|
||||
// in all the input fields / variables / widgets.
|
||||
GSContent g = guide.getValue();
|
||||
ImportStrategy is = importStrategy.getValue();
|
||||
Set<String> codesetNames = codesets.getValue();
|
||||
|
||||
mon.beginTask("Import guide content", codesetNames.size()+1);
|
||||
try {
|
||||
for (String name : codesetNames) {
|
||||
CodeSet cs = g.getCodeSet(name);
|
||||
if (cs==null) {
|
||||
//Ignore 'invalid' codesets. This is a bit of a hack so that we can retain selected codeset names
|
||||
// across guide selection changes. To do that we remember 'selected' cs names even if they
|
||||
// aren't valid for the current guide. That way the checkbox state stays consistent
|
||||
// when switching between guides (otherwise 'invalid' names would have to be cleared when switching to
|
||||
// a guide).
|
||||
mon.worked(1);
|
||||
} else {
|
||||
IRunnableWithProgress oper = is.createOperation(ImportUtils.importConfig(
|
||||
g,
|
||||
cs
|
||||
));
|
||||
oper.run(SubMonitor.convert(mon, 1));
|
||||
}
|
||||
}
|
||||
if (enableOpenHomePage.getValue()) {
|
||||
openHomePage();
|
||||
}
|
||||
return true;
|
||||
} catch (UIThreadDownloadDisallowed e) {
|
||||
//This shouldn't be possible... Finish button won't be enabled unless all is validated.
|
||||
//This implies the content has been downloaded (can't be validated otherwise).
|
||||
BootWizardActivator.log(e);
|
||||
return false;
|
||||
} finally {
|
||||
mon.done();
|
||||
}
|
||||
}
|
||||
|
||||
public void openHomePage() {
|
||||
URL url = homePage.getValue();
|
||||
if (url!=null) {
|
||||
openUrl(url.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// public void setGuide(GettingStartedGuide guide) {
|
||||
// this.guide.setValue(guide);
|
||||
// }
|
||||
//
|
||||
// public GettingStartedGuide getGuide() {
|
||||
// return guide.getValue();
|
||||
// }
|
||||
|
||||
public SelectionModel<ImportStrategy> getImportStrategyModel() {
|
||||
return new SelectionModel<ImportStrategy>(importStrategy, importStrategyValidator);
|
||||
}
|
||||
|
||||
public SelectionModel<GSContent> getGSContentSelectionModel() {
|
||||
return new SelectionModel<GSContent>(guide, guideValidator);
|
||||
}
|
||||
|
||||
public MultiSelectionModel<String> getCodeSetModel() {
|
||||
return new MultiSelectionModel<String>(codesets, codesetValidator);
|
||||
}
|
||||
|
||||
public LiveExpression<Boolean> isDownloaded() {
|
||||
return isDownloaded;
|
||||
}
|
||||
|
||||
public LiveVariable<Boolean> getEnableOpenHomePage() {
|
||||
return enableOpenHomePage;
|
||||
}
|
||||
|
||||
public void setItem(GSContent guide) {
|
||||
this.guide.setValue(guide);
|
||||
}
|
||||
|
||||
/**
|
||||
* The 'raw' selection in the UI will be sent here. I.e. selected object will
|
||||
* be sent whether it is actual content or a ContentTypeNode.
|
||||
* <p>
|
||||
* Normally clients shouldn't be interested in this selection. It is used
|
||||
* to allow the Description section of the UI to display descriptions for
|
||||
* any selected item, including non-content nodes in the tree.
|
||||
*/
|
||||
public LiveVariable<Object> getRawSelection() {
|
||||
return this.rawSelection;
|
||||
}
|
||||
|
||||
public ContentManager getContentManager() {
|
||||
return contentManager;
|
||||
}
|
||||
|
||||
public void setContentManager(ContentManager contentManager) {
|
||||
this.contentManager = contentManager;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013 GoPivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.guides;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jface.layout.GridDataFactory;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.events.SelectionEvent;
|
||||
import org.eclipse.swt.events.SelectionListener;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
import org.eclipse.swt.widgets.Button;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.springframework.ide.eclipse.boot.wizard.importing.ImportStrategies;
|
||||
import org.springframework.ide.eclipse.boot.wizard.importing.ImportStrategy;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.LiveExpression;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.LiveVariable;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.SelectionModel;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.UIValueListener;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.ValidationResult;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.Validator;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.GroupSection;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.IPageWithSections;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.WizardPageSection;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.WizardPageWithSections;
|
||||
|
||||
/**
|
||||
* Group of radio buttons that allows selection a ImportStrategy
|
||||
*/
|
||||
public class ImportStrategiesRadiosSection extends GroupSection {
|
||||
|
||||
private static class Choice extends WizardPageSection {
|
||||
|
||||
private final ImportStrategy strategy;
|
||||
private final LiveVariable<ImportStrategy> selection;
|
||||
|
||||
public Choice(IPageWithSections owner, ImportStrategy buildType, LiveVariable<ImportStrategy> selection) {
|
||||
super(owner);
|
||||
this.strategy = buildType;
|
||||
this.selection = selection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LiveExpression<ValidationResult> getValidator() {
|
||||
//This validator really doesn't matter because for a radio button group it makes
|
||||
// more sense to create a validator for the group rather than to compose it from
|
||||
// each component in the group.
|
||||
return Validator.constant(ValidationResult.OK);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createContents(Composite page) {
|
||||
final Button button = new Button(page, SWT.RADIO);
|
||||
button.setText(strategy.displayName());
|
||||
GridDataFactory.fillDefaults().grab(true, false).applyTo(button);
|
||||
selection.addListener(new UIValueListener<ImportStrategy>() {
|
||||
@Override
|
||||
protected void uiGotValue(LiveExpression<ImportStrategy> exp, ImportStrategy value) {
|
||||
button.setSelection(value==strategy);
|
||||
}
|
||||
});
|
||||
button.addSelectionListener(new SelectionListener() {
|
||||
|
||||
//@Override
|
||||
public void widgetSelected(SelectionEvent e) {
|
||||
if (button.getSelection()) {
|
||||
selection.setValue(strategy);
|
||||
}
|
||||
}
|
||||
|
||||
//@Override
|
||||
public void widgetDefaultSelected(SelectionEvent e) {
|
||||
if (button.getSelection()) {
|
||||
selection.setValue(strategy);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final SelectionModel<ImportStrategy> selection;
|
||||
|
||||
@Override
|
||||
protected GridLayout createLayout() {
|
||||
return new GridLayout(3, true);
|
||||
}
|
||||
|
||||
public ImportStrategiesRadiosSection(WizardPageWithSections owner, SelectionModel<ImportStrategy> selection) {
|
||||
super(owner, "Build Type", createSections(owner, selection));
|
||||
this.selection = selection;
|
||||
}
|
||||
|
||||
private static WizardPageSection[] createSections(WizardPageWithSections owner, SelectionModel<ImportStrategy> selection) {
|
||||
List<WizardPageSection> section = new ArrayList<>();
|
||||
for (ImportStrategy strat : ImportStrategies.all()) {
|
||||
section.add(new Choice(owner, strat, selection.selection));
|
||||
}
|
||||
return section.toArray(new WizardPageSection[section.size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LiveExpression<ValidationResult> getValidator() {
|
||||
return selection.validator;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013 GoPivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.guides;
|
||||
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.LiveExpression;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.LiveSet;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.ValidationResult;
|
||||
|
||||
/**
|
||||
* Model of a selection that can be thought of as a Set of elements somehow selected by the user.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class MultiSelectionModel<T> {
|
||||
|
||||
public final LiveSet<T> selecteds;
|
||||
public final LiveExpression<ValidationResult> validator;
|
||||
|
||||
public MultiSelectionModel(LiveSet<T> selecteds, LiveExpression<ValidationResult> validator) {
|
||||
this.selecteds = selecteds;
|
||||
this.validator = validator;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013 GoPivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.guides;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
import org.eclipse.jface.layout.GridDataFactory;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.events.SelectionAdapter;
|
||||
import org.eclipse.swt.events.SelectionEvent;
|
||||
import org.eclipse.swt.events.SelectionListener;
|
||||
import org.eclipse.swt.layout.GridLayout;
|
||||
import org.eclipse.swt.widgets.Button;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Control;
|
||||
import org.eclipse.swt.widgets.Group;
|
||||
import org.eclipse.swt.widgets.Link;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.LiveExpression;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.LiveVariable;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.ValidationResult;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.Validator;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.ValueListener;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.WizardPageSection;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.WizardPageWithSections;
|
||||
|
||||
public class OpenUrlSection extends WizardPageSection {
|
||||
|
||||
private final LiveExpression<URL> url;
|
||||
private final LiveVariable<Boolean> enableOpen;
|
||||
private final String sectionLabel;
|
||||
private GSImportWizard wizard;
|
||||
|
||||
public OpenUrlSection(GSImportWizard wizard, WizardPageWithSections owner, String sectionLabel,
|
||||
LiveExpression<URL> url,
|
||||
LiveVariable<Boolean> enableOpenOnFinish) {
|
||||
super(owner);
|
||||
this.wizard = wizard;
|
||||
this.sectionLabel = sectionLabel;
|
||||
this.url = url;
|
||||
this.enableOpen = enableOpenOnFinish;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LiveExpression<ValidationResult> getValidator() {
|
||||
return Validator.OK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createContents(Composite page) {
|
||||
final Composite comp;
|
||||
if (sectionLabel!=null) {
|
||||
Group group = new Group(page, SWT.NONE);
|
||||
group.setText(sectionLabel);
|
||||
comp = group;
|
||||
} else {
|
||||
comp = new Composite(page, SWT.NONE);
|
||||
}
|
||||
|
||||
comp.setLayout(new GridLayout(3, false));
|
||||
GridDataFactory.fillDefaults().grab(true, false).applyTo(comp);
|
||||
|
||||
final Link label = new Link(comp, SWT.NONE);
|
||||
final Button checkbox = new Button(comp, SWT.CHECK);
|
||||
checkbox.setText("Open");
|
||||
checkbox.setToolTipText("Open the url after finishing this wizard");
|
||||
checkbox.setSelection(enabled(enableOpen));
|
||||
GridDataFactory.fillDefaults().align(SWT.BEGINNING, SWT.CENTER).grab(true, false).applyTo(label);
|
||||
GridDataFactory.fillDefaults().align(SWT.END, SWT.CENTER).applyTo(checkbox);
|
||||
|
||||
label.addSelectionListener(new SelectionAdapter() {
|
||||
@Override
|
||||
public void widgetSelected(SelectionEvent e) {
|
||||
wizard.openHomePage(true);
|
||||
}
|
||||
});
|
||||
|
||||
url.addListener(new ValueListener<URL>() {
|
||||
public void gotValue(LiveExpression<URL> exp, URL value) {
|
||||
if (value == null ) {
|
||||
label.setText("(None)");
|
||||
} else {
|
||||
label.setText("<a href=\"" + value + "\">" + value + "</a>");
|
||||
}
|
||||
checkbox.setEnabled(value!=null);
|
||||
|
||||
//Size requirements for label may have changed:
|
||||
comp.layout(new Control[] {label});
|
||||
}
|
||||
});
|
||||
|
||||
checkbox.addSelectionListener(new SelectionListener() {
|
||||
public void widgetSelected(SelectionEvent e) {
|
||||
refresh();
|
||||
}
|
||||
public void widgetDefaultSelected(SelectionEvent e) {
|
||||
refresh();
|
||||
}
|
||||
private void refresh() {
|
||||
enableOpen.setValue(checkbox.getSelection());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static boolean enabled(LiveExpression<Boolean> var) {
|
||||
Boolean val = var.getValue();
|
||||
return val!=null && val;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013 GoPivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.guides;
|
||||
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.LiveExpression;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.ValidationResult;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.IPageWithSections;
|
||||
import org.springsource.ide.eclipse.commons.livexp.ui.WizardPageSection;
|
||||
|
||||
/**
|
||||
* Section that doesn't actually contribute any widgets to the page.
|
||||
* It only contributes validation logic to the overall page Validator.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class ValidatorSection extends WizardPageSection {
|
||||
|
||||
private LiveExpression<ValidationResult> validator;
|
||||
|
||||
public ValidatorSection(IPageWithSections owner, LiveExpression<ValidationResult> validator) {
|
||||
super(owner);
|
||||
this.validator = validator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LiveExpression<ValidationResult> getValidator() {
|
||||
return validator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createContents(Composite page) {
|
||||
//Nothing!
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013, 2016 GoPivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.importing;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.resources.IProjectDescription;
|
||||
import org.eclipse.core.resources.IWorkspace;
|
||||
import org.eclipse.core.resources.ResourcesPlugin;
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.core.runtime.IPath;
|
||||
import org.eclipse.core.runtime.IProgressMonitor;
|
||||
import org.eclipse.core.runtime.Path;
|
||||
import org.eclipse.core.runtime.Platform;
|
||||
import org.eclipse.core.runtime.SubProgressMonitor;
|
||||
import org.eclipse.jface.operation.IRunnableWithProgress;
|
||||
import org.springframework.ide.eclipse.boot.wizard.content.BuildType;
|
||||
import org.springframework.ide.eclipse.boot.wizard.content.CodeSet;
|
||||
import org.springsource.ide.eclipse.commons.livexp.util.ExceptionUtil;
|
||||
|
||||
/**
|
||||
* Importer strategy implementation for importing CodeSets into the workspace as a
|
||||
* general eclipse project (i.e. without any natures or builders).
|
||||
*
|
||||
* up to use Maven Tooling.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class GeneralProjectStrategy extends ImportStrategy {
|
||||
|
||||
public GeneralProjectStrategy(BuildType buildType, String name, String notInstalledMessage) {
|
||||
super(buildType, name, notInstalledMessage);
|
||||
}
|
||||
|
||||
public static class GeneralProjectImport implements IRunnableWithProgress {
|
||||
|
||||
private final String projectName;
|
||||
private final File location;
|
||||
private final CodeSet codeset;
|
||||
|
||||
public GeneralProjectImport(ImportConfiguration conf) {
|
||||
this.projectName = conf.getProjectName();
|
||||
this.location = new File(conf.getLocation());
|
||||
this.codeset = conf.getCodeSet();
|
||||
}
|
||||
|
||||
public void run(IProgressMonitor mon) throws InvocationTargetException, InterruptedException {
|
||||
mon.beginTask("Create General Project '"+projectName+"'", 2);
|
||||
try {
|
||||
//1: copy/isntantiate codeset data
|
||||
codeset.createAt(location);
|
||||
mon.worked(1);
|
||||
|
||||
//2: create project in workspace
|
||||
GeneralProjectImport.createGeneralProject(projectName, location, new SubProgressMonitor(mon, 1));
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
throw e;
|
||||
} catch (InvocationTargetException e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
throw new InvocationTargetException(e);
|
||||
}
|
||||
finally {
|
||||
mon.done();
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isDefaultProjectLocation(String projectName, File projectDir) {
|
||||
IPath workspaceLoc = Platform.getLocation();
|
||||
if (workspaceLoc!=null) {
|
||||
File defaultLoc = new File(workspaceLoc.toFile(), projectName);
|
||||
return defaultLoc.equals(projectDir);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a general eclipse project (no builders natures etc) with a given name and project contents
|
||||
* from a given location. The contents of the project will be linked, not copied.
|
||||
* @return
|
||||
*/
|
||||
public static IProject createGeneralProject(String projectName, File projectDir, IProgressMonitor mon) throws CoreException {
|
||||
mon.beginTask("Create project "+projectName, 3);
|
||||
try {
|
||||
//1
|
||||
IWorkspace ws = ResourcesPlugin.getWorkspace();
|
||||
IProjectDescription projectDescription = ws.newProjectDescription(projectName);
|
||||
Path projectLocation = new Path(projectDir.getAbsolutePath());
|
||||
if (!GeneralProjectImport.isDefaultProjectLocation(projectName, projectDir)) {
|
||||
projectDescription.setLocation(projectLocation);
|
||||
}
|
||||
//To improve error message... check validity of project location vs name
|
||||
//note: in import wizard use, this error is impossible since wizard validates this constraint.
|
||||
//Be careful that this constraint only needs to hold in a very specific case where the
|
||||
//location is nested exactly one level below the workspace location on disk.
|
||||
IPath wsLocation = ws.getRoot().getLocation();
|
||||
if (wsLocation.isPrefixOf(projectLocation) && wsLocation.segmentCount()+1==projectLocation.segmentCount()) {
|
||||
String expectedName = projectDir.getName();
|
||||
if (!expectedName.equals(projectName)) {
|
||||
throw ExceptionUtil.coreException("Project-name ("+projectName+") should match last segment of location ("+projectDir+")");
|
||||
}
|
||||
}
|
||||
mon.worked(1);
|
||||
|
||||
//2
|
||||
IProject project = ws.getRoot().getProject(projectName);
|
||||
project.create(projectDescription, new SubProgressMonitor(mon, 1));
|
||||
|
||||
//3
|
||||
project.open(new SubProgressMonitor(mon, 1));
|
||||
return project;
|
||||
} finally {
|
||||
mon.done();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IRunnableWithProgress createOperation(ImportConfiguration conf) {
|
||||
return new GeneralProjectImport(conf);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013 GoPivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.importing;
|
||||
|
||||
import org.springframework.ide.eclipse.boot.wizard.content.CodeSet;
|
||||
|
||||
/**
|
||||
* An object that contains the required data to import getting started contents
|
||||
* into the workspace using a particular build system.
|
||||
*/
|
||||
public interface ImportConfiguration {
|
||||
|
||||
/**
|
||||
* Location of where the project root should be placed or created in the file system.
|
||||
*/
|
||||
public String getLocation();
|
||||
|
||||
/**
|
||||
* The name of the project in the workspace.
|
||||
*/
|
||||
public String getProjectName();
|
||||
|
||||
/**
|
||||
* The data used to populate the project.
|
||||
*/
|
||||
public CodeSet getCodeSet();
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.importing;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
|
||||
import org.springframework.ide.eclipse.boot.wizard.content.BuildType;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
public class ImportStrategies {
|
||||
|
||||
/**
|
||||
* This method is provided to more easily wrap up the old import strategies, defined before
|
||||
* we provided an extension point. It is deprecated because stuff calling this method
|
||||
* should really be converted to use the extension point instead to contribute an ImportStrategy.
|
||||
*/
|
||||
@Deprecated
|
||||
public static ImportStrategyFactory forClass(final String className) {
|
||||
return new ImportStrategyFactory() {
|
||||
@Override
|
||||
public ImportStrategy create(BuildType buildType, String name, String notInstalledMessage) throws Exception {
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends ImportStrategy> klass = (Class<? extends ImportStrategy>) Class.forName(className);
|
||||
Constructor<? extends ImportStrategy> cons = klass.getConstructor(BuildType.class, String.class, String.class);
|
||||
return cons.newInstance(buildType, name, notInstalledMessage);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static Iterable<ImportStrategy> all() {
|
||||
@SuppressWarnings("unchecked")
|
||||
Iterable<ImportStrategy>[] perBuildType = new Iterable[BuildType.values().length];
|
||||
int i = 0;
|
||||
for (BuildType bt : BuildType.values()) {
|
||||
perBuildType[i++] = bt.getImportStrategies();
|
||||
}
|
||||
return Iterables.concat(perBuildType);
|
||||
}
|
||||
|
||||
public static ImportStrategy withId(String id) {
|
||||
for (ImportStrategy s : all()) {
|
||||
if (s.getId().equals(id)) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013 GoPivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.importing;
|
||||
|
||||
import org.eclipse.core.runtime.IPath;
|
||||
import org.eclipse.jface.operation.IRunnableWithProgress;
|
||||
import org.springframework.ide.eclipse.boot.wizard.content.BuildType;
|
||||
|
||||
/**
|
||||
* Strategy for importing a certain type of getting started content
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public abstract class ImportStrategy {
|
||||
|
||||
private final String name;
|
||||
private final BuildType buildType;
|
||||
private final String notInstalledMessage;
|
||||
|
||||
public ImportStrategy(BuildType buildType, String name, String notInstalledMessage) {
|
||||
this.name = name;
|
||||
this.buildType = buildType;
|
||||
this.notInstalledMessage = notInstalledMessage;
|
||||
}
|
||||
|
||||
public abstract IRunnableWithProgress createOperation(ImportConfiguration conf);
|
||||
|
||||
public boolean isSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses should override to provide more precise message
|
||||
*/
|
||||
public final String getNotInstalledMessage() {
|
||||
return "Can not import using "+displayName()+" because "+notInstalledMessage;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String displayName() {
|
||||
if (buildType.getImportStrategies().size()>1) {
|
||||
return buildType.displayName() + " ("+name+")";
|
||||
}
|
||||
return buildType.displayName();
|
||||
}
|
||||
|
||||
public IPath getBuildScript() {
|
||||
return buildType.getBuildScript();
|
||||
}
|
||||
|
||||
public BuildType getBuildType() {
|
||||
return buildType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (buildType.getImportStrategies().size()>1) {
|
||||
return buildType + "-"+name;
|
||||
}
|
||||
return buildType.toString();
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
String id = buildType.toString();
|
||||
if (buildType.getImportStrategies().size()>1) {
|
||||
id += "-" + name;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.importing;
|
||||
|
||||
import org.springframework.ide.eclipse.boot.wizard.content.BuildType;
|
||||
|
||||
/**
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public interface ImportStrategyFactory {
|
||||
ImportStrategy create(BuildType buildType,String name, String notInstalledMessage) throws Exception;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013, 2015 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.importing;
|
||||
|
||||
import org.springframework.ide.eclipse.boot.wizard.BootWizardActivator;
|
||||
import org.springframework.ide.eclipse.boot.wizard.content.BuildType;
|
||||
|
||||
/**
|
||||
* This is wrapper around a {@link ImportStrategyFactory}. It is repsonsible for creating
|
||||
* a single instance from the factory, and providing a fallback for when calling the
|
||||
* factory fails.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class ImportStrategyHolder {
|
||||
|
||||
private BuildType buildType;
|
||||
private ImportStrategyFactory factory;
|
||||
private String notInstalledMessage; //Message tailored to the particular tooling that is needed for an
|
||||
private String name; //Short name that can be used to identify strategy to the user (this is useful when more than one
|
||||
// strategy is available for a single build-type).
|
||||
|
||||
private ImportStrategy instance = null;
|
||||
|
||||
public ImportStrategyHolder(BuildType buildType, ImportStrategyFactory factory, String notInstalledMessage, String name) {
|
||||
this.buildType = buildType;
|
||||
this.factory = factory;
|
||||
this.notInstalledMessage = notInstalledMessage;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public ImportStrategy get() {
|
||||
if (instance == null) {
|
||||
try {
|
||||
this.instance = factory.create(buildType, name, notInstalledMessage);
|
||||
} catch (ClassNotFoundException e) {
|
||||
//ignore: happens when optional dependencies for strategy support are not installed
|
||||
} catch (Exception e) {
|
||||
//Somewhat unexpected, so log it.
|
||||
BootWizardActivator.log(e);
|
||||
}
|
||||
if (this.instance==null) {
|
||||
//Something prevented the creation of the import strategy.
|
||||
this.instance = new NullImportStrategy(buildType, name, notInstalledMessage);
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013, 2016 GoPivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.importing;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.resources.ResourcesPlugin;
|
||||
import org.eclipse.core.runtime.Assert;
|
||||
import org.eclipse.core.runtime.IPath;
|
||||
import org.eclipse.core.runtime.Platform;
|
||||
import org.springframework.ide.eclipse.boot.wizard.BootWizardActivator;
|
||||
import org.springframework.ide.eclipse.boot.wizard.content.CodeSet;
|
||||
import org.springframework.ide.eclipse.boot.wizard.content.GSContent;
|
||||
import org.springsource.ide.eclipse.commons.livexp.core.ValidationResult;
|
||||
import org.springsource.ide.eclipse.commons.livexp.util.ExceptionUtil;
|
||||
|
||||
/**
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class ImportUtils {
|
||||
|
||||
public static ImportConfiguration importConfig(final IPath location, final String projectName, final CodeSet codeset) {
|
||||
ImportConfiguration conf = new ImportConfiguration() {
|
||||
|
||||
// @Override
|
||||
public String getLocation() {
|
||||
return location.toString();
|
||||
}
|
||||
|
||||
// @Override
|
||||
public String getProjectName() {
|
||||
return projectName;
|
||||
}
|
||||
|
||||
// @Override
|
||||
public CodeSet getCodeSet() {
|
||||
return codeset;
|
||||
}
|
||||
};
|
||||
return conf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to create a import configuration that imports a particular codeset for a given guide into the
|
||||
* default location in the workspace.
|
||||
*/
|
||||
public static ImportConfiguration importConfig(GSContent guide, CodeSet codeset) {
|
||||
Assert.isNotNull(guide);
|
||||
Assert.isNotNull(codeset);
|
||||
String csName = codeset.getName();
|
||||
String projectName;
|
||||
if ("default".equals(csName)) {
|
||||
projectName = guide.getName();
|
||||
} else if (csName.equals(guide.getName())) {
|
||||
//Don't create silly names like 'spring-pet-clinic-spring-pet-clinic'
|
||||
projectName = csName;
|
||||
} else {
|
||||
projectName = guide.getName()+"-"+codeset.getName();
|
||||
}
|
||||
return ImportUtils.importConfig(
|
||||
/*location*/
|
||||
Platform.getLocation().append(projectName),
|
||||
/*name*/
|
||||
projectName,
|
||||
/*codeset*/
|
||||
codeset
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates whether import configuration doesn't clash with existing workspace or
|
||||
* file system content. I.e. proceeding with the import would result in possible
|
||||
* data loss (overwriting existing file system content) or Eclipse errors because of project name
|
||||
* clashes
|
||||
*/
|
||||
public static ValidationResult validateImportConfiguration(ImportConfiguration conf) {
|
||||
//Note: for now it is assumed all properties of the conf are set (i.e. not null) when this is called.
|
||||
// This true for current use cases but may not be true for more complex wizards
|
||||
// that allow specifying location and/or project name.
|
||||
|
||||
//Three things to check:
|
||||
try {
|
||||
//1: workspace project name is available
|
||||
String name = conf.getProjectName();
|
||||
IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(name);
|
||||
if (project.exists()) {
|
||||
return ValidationResult.error("Project '"+name+"' already exists in the workspace");
|
||||
}
|
||||
|
||||
//2: The target location is non-existent (otherwise data loss may ensue)
|
||||
File loc = new File(conf.getLocation());
|
||||
if (loc.exists()) {
|
||||
return ValidationResult.error("File or directory exists at '"+loc+"'");
|
||||
}
|
||||
|
||||
//3: default file system location in workspace is available. This must
|
||||
// be true even if that location won't actually be used. Eclipse simply will not allow creating a project
|
||||
// with this name unless the default location is available for use.
|
||||
File defaultLocation = Platform.getLocation().append(name).toFile();
|
||||
if (defaultLocation.exists()) {
|
||||
return ValidationResult.error("Project '"+name+"' unavailable: File exists at '"+defaultLocation+"'");
|
||||
}
|
||||
|
||||
return ValidationResult.OK;
|
||||
} catch (Throwable e) {
|
||||
//Unexpected error. But try to produce a message from it anyway
|
||||
BootWizardActivator.log(e);
|
||||
return ValidationResult.error(ExceptionUtil.getMessage(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013, 2016 GoPivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* GoPivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.importing;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.maven.model.Model;
|
||||
import org.eclipse.core.resources.ResourcesPlugin;
|
||||
import org.eclipse.core.runtime.Assert;
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.core.runtime.IProgressMonitor;
|
||||
import org.eclipse.core.runtime.Platform;
|
||||
import org.eclipse.core.runtime.Status;
|
||||
import org.eclipse.core.runtime.SubMonitor;
|
||||
import org.eclipse.core.runtime.SubProgressMonitor;
|
||||
import org.eclipse.core.runtime.jobs.ISchedulingRule;
|
||||
import org.eclipse.core.runtime.jobs.Job;
|
||||
import org.eclipse.jface.operation.IRunnableWithProgress;
|
||||
import org.eclipse.m2e.core.MavenPlugin;
|
||||
import org.eclipse.m2e.core.project.IMavenProjectImportResult;
|
||||
import org.eclipse.m2e.core.project.MavenProjectInfo;
|
||||
import org.eclipse.m2e.core.project.ProjectImportConfiguration;
|
||||
import org.eclipse.m2e.core.project.ResolverConfiguration;
|
||||
import org.eclipse.osgi.util.NLS;
|
||||
import org.springframework.ide.eclipse.boot.wizard.BootWizardActivator;
|
||||
import org.springframework.ide.eclipse.boot.wizard.content.BuildType;
|
||||
import org.springframework.ide.eclipse.boot.wizard.content.CodeSet;
|
||||
|
||||
/**
|
||||
* Importer strategy implementation for importing CodeSets into the workspace and set them
|
||||
* up to use Maven Tooling.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class MavenStrategy extends ImportStrategy {
|
||||
|
||||
public MavenStrategy(BuildType buildType, String name, String notInstalledMessage) {
|
||||
super(buildType, name, notInstalledMessage);
|
||||
Assert.isNotNull(Platform.getBundle("org.eclipse.m2e.core"), "M2E is not installed");
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the import by means of 'NewGradleProjectOperation'
|
||||
*/
|
||||
private static class MavenCodeSetImport implements IRunnableWithProgress {
|
||||
|
||||
//TODO: This import startegy doesn't even read projectName. The name actually comes from the
|
||||
// maven pom file. Actually makes sense for inport to determine projectName from project
|
||||
// content. So maybe projectName should not be in an ImportConfig at all!
|
||||
|
||||
private final String projectName;
|
||||
private final File location;
|
||||
private final CodeSet codeset;
|
||||
|
||||
public MavenCodeSetImport(ImportConfiguration conf) {
|
||||
this.projectName = conf.getProjectName();
|
||||
this.location = new File(conf.getLocation());
|
||||
this.codeset = conf.getCodeSet();
|
||||
}
|
||||
|
||||
public void run(IProgressMonitor mon) throws InvocationTargetException, InterruptedException {
|
||||
mon.beginTask("Create maven project "+projectName, 5);
|
||||
Job.getJobManager().beginRule(getRule(), new SubProgressMonitor(mon, 1));
|
||||
try {
|
||||
//1: 1 copy codeset data
|
||||
codeset.createAt(location);
|
||||
mon.worked(1);
|
||||
|
||||
//2..4: materialize eclipse project from pom.xml
|
||||
File pomFile = new File(location, "pom.xml");
|
||||
Assert.isTrue(pomFile.isFile(), "No pom file found: "+pomFile);
|
||||
Assert.isTrue(pomFile.length()>0, "Pom file contains no data: "+pomFile);
|
||||
createEclipseProjectFromExistingMavenProject(pomFile, new SubProgressMonitor(mon, 3));
|
||||
} catch (InterruptedException e) {
|
||||
throw e;
|
||||
} catch (InvocationTargetException e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
throw new InvocationTargetException(e);
|
||||
}
|
||||
finally {
|
||||
Job.getJobManager().endRule(getRule());
|
||||
mon.done();
|
||||
}
|
||||
}
|
||||
|
||||
private ISchedulingRule getRule() {
|
||||
return ResourcesPlugin.getWorkspace().getRoot();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IRunnableWithProgress createOperation(ImportConfiguration conf) {
|
||||
return new MavenCodeSetImport(conf);
|
||||
}
|
||||
|
||||
protected static void createEclipseProjectFromExistingMavenProject(File pomFile, IProgressMonitor monitor) throws CoreException {
|
||||
Model model = MavenPlugin.getMavenModelManager().readMavenModel(pomFile);
|
||||
String derivedProjectName = model.getName();
|
||||
if (derivedProjectName == null) {
|
||||
derivedProjectName = model.getArtifactId();
|
||||
}
|
||||
if (derivedProjectName == null) {
|
||||
String[] groupPieces = model.getGroupId().split("\\.");
|
||||
int lastIndex = groupPieces.length - 1;
|
||||
if (lastIndex >= 0) {
|
||||
derivedProjectName = groupPieces[lastIndex];
|
||||
} else {
|
||||
String message = NLS.bind("Bad pom.xml: no name, artifactId, or groupId.", null);
|
||||
throw new CoreException(new Status(Status.ERROR, BootWizardActivator.PLUGIN_ID, message));
|
||||
}
|
||||
}
|
||||
MavenProjectInfo parent = null;
|
||||
MavenProjectInfo projectInfo = new MavenProjectInfo(derivedProjectName, pomFile, model, parent);
|
||||
ArrayList<MavenProjectInfo> projectInfos = new ArrayList<MavenProjectInfo>();
|
||||
projectInfos.add(projectInfo);
|
||||
ResolverConfiguration resolverConfiguration = new ResolverConfiguration();
|
||||
String activeProfiles = "pom.xml";
|
||||
resolverConfiguration.setActiveProfiles(activeProfiles);
|
||||
ProjectImportConfiguration configuration = new ProjectImportConfiguration(resolverConfiguration);
|
||||
|
||||
List<IMavenProjectImportResult> importResults = MavenPlugin.getProjectConfigurationManager().importProjects(projectInfos, configuration,
|
||||
monitor);
|
||||
for (IMavenProjectImportResult importResult : importResults) {
|
||||
// skip projects which have not been properly imported
|
||||
if (importResult.getProject() != null)
|
||||
MavenPlugin.getProjectConfigurationManager().updateProjectConfiguration(importResult.getProject(), monitor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2013, 2015 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.wizard.importing;
|
||||
|
||||
import org.eclipse.jface.operation.IRunnableWithProgress;
|
||||
import org.springframework.ide.eclipse.boot.wizard.content.BuildType;
|
||||
|
||||
/**
|
||||
* Import stratgety used in place of a Strategy that could not be instantiated, presumably because
|
||||
* the required Eclipse plugins are not installed.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class NullImportStrategy extends ImportStrategy {
|
||||
|
||||
public NullImportStrategy(BuildType buildType, String name, String notInstalledMessage) {
|
||||
super(buildType, name, notInstalledMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IRunnableWithProgress createOperation(ImportConfiguration conf) {
|
||||
throw new IllegalStateException(getNotInstalledMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Spring Initializr</title>
|
||||
<link rel="stylesheet"
|
||||
href="/css/bootstrap.min.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="navbar">
|
||||
<div class="navbar-inner">
|
||||
<a class="brand"
|
||||
href="https://www.springsource.org">
|
||||
Spring
|
||||
</a>
|
||||
<ul class="nav">
|
||||
<li>
|
||||
<a href="/">
|
||||
Projects
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/spring.zip">
|
||||
Download
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/install.sh">
|
||||
Installer
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<h1>Spring Initializr</h1>
|
||||
<div>
|
||||
<form action="/starter.zip" method="get">
|
||||
<label for="groupId">Group:</label> <input id="groupId" type="text" value="org.demo" name="groupId"/>
|
||||
<label for="artifactId">Artifact:</label> <input id="artifactId" type="text" value="demo" name="artifactId"/>
|
||||
<label for="name">Name:</label> <input id="name" type="text" value="demo" name="name"/>
|
||||
<label for="name">Description:</label> <input id="description" type="text" value="Demo project" name="description"/>
|
||||
<label for="packageName">Package Name:</label> <input id="packageName" type="text" value="demo" name="packageName"/>
|
||||
<label>Styles:</label>
|
||||
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="style" value=""/>
|
||||
Standard
|
||||
</label>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="style" value="web"/>
|
||||
Web
|
||||
</label>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="style" value="actuator"/>
|
||||
Actuator
|
||||
</label>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="style" value="batch"/>
|
||||
Batch
|
||||
</label>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="style" value="jpa"/>
|
||||
JPA
|
||||
</label>
|
||||
<label>Type:</label>
|
||||
|
||||
<label class="radio">
|
||||
<input type="radio" name="type" value="pom" checked="true"/>
|
||||
Maven POM
|
||||
</label>
|
||||
<label class="radio">
|
||||
<input type="radio" name="type" value="pomproject"/>
|
||||
Maven Project
|
||||
</label>
|
||||
<button type="submit" class="btn">Generate</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user