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:
Martin Lippert
2020-09-29 14:00:01 +02:00
parent 8edfab1a16
commit de16884b60
2210 changed files with 160282 additions and 43 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 (&quot;Content&quot;). Unless otherwise indicated below, the
Content is provided to you under the terms and conditions of the Eclipse
Public License Version 1.0 (&quot;EPL&quot;). 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, &quot;Program&quot; 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
(&quot;Redistributor&quot;) 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>

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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() {
}
}

View File

@@ -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));
}
}

View File

@@ -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());
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}
}

View File

@@ -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)));
}
}

View File

@@ -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;
}
}

View File

@@ -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 "??";
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}
});
}
}

View File

@@ -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);
});
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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");
}
});
}
}

View File

@@ -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");
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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];
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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
}
}

View File

@@ -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);
}

View File

@@ -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();
};
}

View File

@@ -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"));
}
}

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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;
// }
}

View File

@@ -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+")";
}
}
}

View File

@@ -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();
}
}

View File

@@ -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";
}
}

View File

@@ -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;
}
}

View File

@@ -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+")";
}
}
}

View File

@@ -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")
);
}
}

View File

@@ -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);
}
};
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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+")";
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}

View File

@@ -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
}
}

View File

@@ -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;
}
}

View File

@@ -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;
// }
}

View File

@@ -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;
// }
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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!
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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));
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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