|
|
|
This utility standardizes the process to use files as blueprints to create new files.
Question (arch-overall): Describe the overall architecture. Answer:API allows to create new files based on templates. Scripting engines can be specified for processing the template, or custom Handlers may be registered to process certain templates.
A template can use places substituable with parameter values; certain well-known parameters are predefined, if the caller does not provide its custom values.
Question (arch-usecases): Describe the main use cases of the new API. Who will use it under what circumstances? What kind of code would typically need to be written to use the module? Answer:An existing file can be used as a boilerplate for creation of a new file. The boiler plate can contain necessary skeleton, comments, content. As the boilerplate resides on config filesystem, it is also customizable by the user and the user can eventually develop custom templates.
In previous NetBeans versions, templating system was built into DataSystemsAPI.
Often many people require ability to create a "clever" template - e.g. write piece of simple text and at the time of its processing do some advanced changes to it using either scripting or templating languages.
This traditionally used to be a bit complicated task (hacking into DataObject implementation), however since version 6.1 there are interface in org.openide.loaders.CreateFromTemplateHandler - DataSystem API and finally org.netbeans.api.templates.CreateFromTemplateHandler - that can be registered as a services in a lookup and it is reponsible for handling the whole copy of the template file(s) to the destination folder.
Runtime or project-related values may be supplied by org.openide.loaders.CreateFromTemplateAttributes - that can be registered as a services in a lookup and it is reponsible for providing "hints" - e.g. map mapping strings to various objects. and these interfaces allow anyone to extend the behaviour during creation of new files.
The CreateFromTemplateAttribute implementation knows which template is being used, where the outcome should be placed, so it can derive appropriate values for both the template and the target location.
There is a built in support for scripting languages
in the NetBeans Platform. If a template is annotated with
javax.script.ScriptEngine
-
a property that can be associated to templates that either should
return real instance of ScriptEngine
interface or
a String
name of the engine that is then used to
search for it in the javax.script.ScriptEngineManager
.
Usually the freemarker engine is the one that is
supported by the NetBeans IDE - if your module wants to use it
then include a token dependency OpenIDE-Module-Needs: javax.script.ScriptEngine.freemarker
in your manifest file (also accessible through project customizer GUI)
to indicate to the system that you need it.
then the scripting engine is then used to process the template and
generate the output file. While running the engine one can rely
on few predefined properties:
String
representing the current day like 23. 3. 2007
String
the current time like 17:18:30
java.util.Date
representing current data and time like
String
the file encoding of the template instance
Other properties can indeed be provided by
CreateFromTemplateAttributess.
After processing, the output is also sent to appropriate
org.openide.text.IndentEngine
associated
with the mime type of the template, for formating.
Smart Templating Quick How-To
First of all create a file in your module layer located somewhere
under the Templates/
folder. Make it a template by
adding <attr name="template" boolvalue="true"/>. Associate
this template with a scripting language, for example by
<attr name="javax.script.ScriptEngine" stringvalue="freemarker"/>.
Now make sure that the scripting language integration is also available
by requesting a token in standard format, for freemarker just put
OpenIDE-Module-Needs: javax.script.ScriptEngine.freemarker
in your manifest. This tells the NetBeans module system that a
module providing integration with such scripting engine has to be
enabled. Now you can use regular script language tags inside of
your template file. When you write your instantiate
method in your wizard, you can create a Map<String,Object> and
fill it with parameters collected from your wizard and then pass it
to
createFromTemplate(targetFolder, targetName, mapWithParameters)
. This will invoke the scripting language and make the
mapWithParameters
values available to it. Beyond this
there is few standard parameters predefined including name
, user
, date
, time
, etc.
and also additional parameters are collected from all registered
CreateFromTemplateAttributesProviders.
A CreateFromTemplateHandler should be able to create multiple files, one of them important so it will open after user initiates the creation action. The template of set of related files may be represented by a folder with a handler attached, and the operation deploys multiple files in the target directory.
There is a way to create a portable wizard (e.g. one that can
be executed inside of NetBeans as well as in a browser). The
most portable UI these days is written in HTML. To
register such HTML based wizard with your file template,
use @TemplateRegistration
annotation and include page()
attribute referencing
your own HTML page:
public class X {
@TemplateRegistration(
page = "x.html",
scriptEngine = "freemarker",
displayName = "JS Wizard",
folder = "Other",
content = "x.fmk"
)
public static String jsWizard() {
return "yourInitializationCode();";
}
}
the return value of the annotated method (named jsWizard
)
should be of type String and its content should be snippet of
JavaScript code to execute inside of your specified HTML page
(e.g. x.html
) to create an instance of
KnockoutJS model to
drive the wizard. Here is a sample code for the model:
function yourInitializationCode() {
var ok = ko.observable(false);
var msg = ko.observable('');
var current = ko.observable('Init');
var data = {
'errorCode': ko.computed(function() {
if ('Init' == current()) return 0;
if (!ok()) return 1;
if (msg()) return 0;
return 2;
}),
'current': current,
'ok': ok,
'msg' : msg
}
ko.applyBindings(data);
return data;
}
The model defines wizard composed of few panels (defined in following
HTML file) and a verification function (registered as errorCode
)
to check if everything is OK. In addition to that it defines
proprietary text value msg
which is
going to be filled by the wizard and cannot be empty. Each
page of the wizard is registered using a custom
Knockout.js binding called
step
. Here is an HTML page defining three steps:
<section data-bind="step: { 'id' : 'init', text : 'Initial Page'}" >
<p>
Write your UI in portable HTML and display it in NetBeans
or on web! Read more at <a href="http://wiki.netbeans.org/HtmlUIForTemplates">our wiki</a>...
</p>
</section>
<section data-bind="step: 'info'" >
<p>
Use <a href="http://knockoutjs.com">knockout.js</a> bindings
to isolate your view model from the actual look of your HTML
page. Bind your view to model written in Java or JavaScript.
</p>
<h3>Is everything OK?</h3>
<input type="checkbox" data-bind="checked: ok"/>
<h3>How do you feel?</h3>
<input type='text' data-bind="textInput: msg"/>
</section>
<section data-bind="step: { 'id' : 'summary' }" >
<p>
You are feeling <span data-bind="text: msg"></span>!
Let's proceed to create a file which will express your
feeling by using <a href="http://freemarker.org/">Freemarker</a>
templating engine and values filled in this wizard.
</p>
</section>
The Next/Finish buttons are controlled by the errorCode
property.
If it is non-zero, there is an error and these buttons are disabled.
Also once can use that inside of the HTML page to display user related errors:
<div data-bind="visible: errorCode() == 1">
<span style="color: red">Please check you are OK!</span>
</div>
<div data-bind="visible: errorCode() == 2">
<span style="color: red">Tell us how do you feel!</span>
</div>
The L10N of the wizard is done on the level of HTML pages.
The whole page gets translated into different language with appropriate
suffix like x_cs.html
and it is then
selected instead of the default one, when user runs in such locale.
When the wizard is successfully finished, all the values
specified in the model (except system ones like current
,
errorCode
, etc.) are transfered to the templating engine,
so they can influence the content of created files.
Here is a sample x.fmt
content which reuses the msg
value provided by the wizard:
Hi, I am Freemarker. I feel ${wizard.msg}.
When such file is instantiated, the ${wizard.msg}
is
replaced by the actual value taken from the wizard.
Some people would rather use Java instead of Java script while getting the portability of the HTML. There is a simple way to rewrite the HTML and JavaScript sample to Java (and possibly run it in a plugin-less browser via bck2brwsr VM). Keep the same HTML, Freemarker, etc. files - just instead of encoding the logic in JavaScript use Java:
@Model(className = "JavaWizard", properties = {
@Property(name = "current", type = String.class),
@Property(name = "ok", type = boolean.class),
@Property(name = "msg", type = String.class)
})
public class JavaWizardCntrl {
@ComputedProperty static int errorCode(
String current, boolean ok, String msg
) {
if ("Init".equals(current)) return 0;
if (!ok) return 1;
if (msg == null || msg.isEmpty()) return 2;
return 0;
}
@TemplateRegistration(
page = "x.html",
scriptEngine = "freemarker",
displayName = "HTML/Java Wizard",
folder = "Java",
content = "x.fmk"
)
public static JavaWizard javaWizardFactory() {
return new JavaWizard("Init", false, "");
}
}
The return value of the annotated method is now an HTML/Java model class which can naturally represent the essential Knockout.js objects in Java.
It is very common that the HTML file creation wizards (either controled by JavaScript or by Java) need to allow user to specify target location of their file. To simplify such common task and to ensure its UI is consistent with the rest of the environment, one can just include following code snippet in the HTML file and leave its actual rendering on the system:
<section data-bind="step: 'targetChooser'" >
</section>
Such section will then be replaced by a panel which provides appropriate UI for choosing target directory as well as name for the newly created file.
In case one prefers more Java-like chooser, it is possible to use
'targetChooser:java'
as name of the step. Then all
Java source groups in target project will be listed and presented
in a typical Java package view selection mode. Once can use different
suffix than java
to list other types of source groups.
This feature requires presence of org.netbeans.modules.java.project.ui
module, otherwise the target chooser falls back to classical one.
There is a way to create a portable wizard (with logic either in JavaScript or in Java) to instantiate a Maven archetype. This way one merges the project templating functionality of Maven with flexible and tailored UI provided by HTML and JS/Java.
The definition of the UI is the same as in
previous
cases, just as a target chooser
one can also use dedicated Maven one - just use
'targetChooser:archetype'
to get a panel
with options to select directory, archetypeId
,
groupId
and version
of the project to
create.
This feature requires presence of org.netbeans.api.maven
module, otherwise the target chooser falls back to classical one.
The archetype registration is then a classical one. Here is an
example of the UI being in x.html
and the archetype
described in x.archetype
one:
@TemplateRegistration(
page = "x.html",
displayName = "HTML/Java Wizard",
folder = "Java",
content = "x.archetype"
)
public static MavenWizard mavenArchetype() {
return new MavenWizard(/*...*/);
}
The x.archetype
file describes the archetype to use
and has following properties-like syntax:
archetypeGroupId=org.codehaus.mojo.archetypes archetypeArtifactId=javafx archetypeVersion=0.6 archetypeBuild=false # or true to build the project once created archetypeOpen=src/main/java/.*/Main.java,src/main/resources/default.config # regexp to # find files that should be opened once the project is created
The values archetypeArtifactId
,
archetypeGroupId
and archetypeVersion
are by default taken from the archetype definition file, but the
wizard model can define these properties as well and in such case
they would take precedence. These values define what Maven
archetype will be used to initialized the project structure.
Any properties defined by the model (in the above example
the MavenWizard
) are going to be passed into
Maven archetype execution and can thus influence the
the behavior of the archetype - this is the way to write an
HTML UI for Maven archetype.
October 2014
Question (arch-quality): How will the quality of your code be tested and how are future regressions going to be prevented? Answer:The feature will be fully covered by unit tests.
Question (arch-where): Where one can find sources for your module? Answer:
The sources for the module are in the Apache Git repositories or in the GitHub repositories.
These modules are required in project.xml:
None.
Question (dep-platform): On which platforms does your module run? Does it run in the same way on each? Answer:No native platform dependencies.
Question (dep-jre): Which version of JRE do you need (1.2, 1.3, 1.4, etc.)? Answer:Requires JRE 7, for implementation reasons (AutoCloseable).
Question (dep-jrejdk): Do you require the JDK or is the JRE enough? Answer:JRE
JARs only.
Question (deploy-nbm): Can you deploy an NBM via the Update Center? Answer:Yes.
Question (deploy-shared): Do you need to be installed in the shared location only, or in the user directory only, or can your module be installed anywhere? Answer:Anywhere.
Question (deploy-packages): Are packages of your module made inaccessible by not declaring them public? Answer:Yes, except API.
Question (deploy-dependencies): What do other modules need to do to declare a dependency on this one, in addition to or instead of the normal module dependency declaration (e.g. tokens to require)? Answer:No specific deploy dependencies.
Yes.
Question (compat-standards): Does the module implement or define any standards? Is the implementation exact or does it deviate somehow? Answer:No.
Question (compat-version): Can your module coexist with earlier and future versions of itself? Can you correctly read all old settings? Will future versions be able to read your current settings? Can you read or politely ignore settings stored by a future version? Answer:Yes.
Question (compat-deprecation): How the introduction of your project influences functionality provided by previous version of the product? Answer:This module replaces some implementation in DataSystem APIs so the implementation is usable even without DataSystems API itself. DataSystems API will use this library.
java.io.File
directly?
Answer:
No.
Question (resources-layer): Does your module provide own layer? Does it create any files or folders in it? What it is trying to communicate by that and with which components? Answer:No.
Question (resources-read): Does your module read any resources from layers? For what purpose? Answer:No.
Question (resources-mask): Does your module mask/hide/override any resources provided by other modules in their layers? Answer:No.
Question (resources-preferences): Does your module uses preferences via Preferences API? Does your module use NbPreferences or or regular JDK Preferences ? Does it read, write or both ? Does it share preferences with other modules ? If so, then why ? Answer:No.
org.openide.util.Lookup
or any similar technology to find any components to communicate with? Which ones?
Answer:
Yes, looks up CreateFromTemplateAttributes and CreateFromTemplateHandler.
Question (lookup-register): Do you register anything into lookup for other code to find? Answer:
Registers handler, which integrates scripting engines using javax.script
API. Provides an annotation
processor, so it is easy to register - see
TemplateRegistration annotation.
No.
System.getProperty
) property?
On a similar note, is there something interesting that you
pass to java.util.logging.Logger
? Or do you observe
what others log?
Answer:
org.netbeans.api.templates.IndentEngine
-
A special ScriptEngine type is required to perform indentation on the produced sources.
The ScriptEngine must provide a name "org.netbeans.api.templates.IndentEngine
".
The only attribute property passed to the ScriptContext is mimeType
of the
text being formatted.
freeFileExtension - A parameter for template creation, possibly specified as a template file layer attribute that controls how the extension for the new file is computed. See CreateDescriptor javadoc for the details.
org-netbeans-modules-java-preformattedSource - A parameter for template creation, possibly specified as a template file layer attribute that controls formatting of the produced text. See CreateDescriptor javadoc for the details.
Question (exec-component): Is execution of your code influenced by any (string) property of any of your components? Answer:No.
Question (exec-ant-tasks): Do you define or register any ant tasks that other can use? Answer:No.
Question (exec-classloader): Does your code create its own class loader(s)? Answer:No.
Question (exec-reflection): Does your code use Java Reflection to execute other code? Answer:
JavaPackageChooser
-
Uses reflection to access JavaTemplates
from module
org.netbeans.modules.java.project.ui
in order to
create Java-like package chooser. If the module is not available,
the wizard falls back to classical target chooser.
No.
Question (exec-process): Do you execute an external process from your module? How do you ensure that the result is the same on different platforms? Do you parse output? Do you depend on result code? Answer:No.
Question (exec-introspection): Does your module use any kind of runtime type information (instanceof
,
work with java.lang.Class
, etc.)?
Answer:
No.
Question (exec-threading): What threading models, if any, does your module adhere to? How the project behaves with respect to threading? Answer:Yes.
Question (security-policy): Does your functionality require modifications to the standard policy file? Answer:No.
Question (security-grant): Does your code grant additional rights to some other code? Answer:No.
None.
Question (format-dnd): Which protocols (if any) does your code understand during Drag & Drop? Answer:None.
Question (format-clipboard): Which data flavors (if any) does your code read from or insert to the clipboard (by access to clipboard on means calling methods onjava.awt.datatransfer.Transferable
?
Answer:
None.
No.
Question (perf-exit): Does your module run any code on exit? Answer:No.
Question (perf-scale): Which external criteria influence the performance of your program (size of file in editor, number of files in menu, in source directory, etc.) and how well your code scales? Answer:XXX no answer for perf-scale
Question (perf-limit): Are there any hard-coded or practical limits in the number or size of elements your code can handle? Answer:Files are processed in-memory in documents; practical limits are imposed by the platform's Document implementation.
Question (perf-mem): How much memory does your component consume? Estimate with a relation to the number of windows, etc. Answer:See 'perf-limit'
Question (perf-wakeup): Does any piece of your code wake up periodically and do something even when the system is otherwise idle (no user interaction)? Answer:No.
Question (perf-progress): Does your module execute any long-running tasks? Answer:No.
Question (perf-huge_dialogs): Does your module contain any dialogs or wizards with a large number of GUI controls such as combo boxes, lists, trees, or text areas? Answer:No UI.
Question (perf-menus): Does your module use dynamically updated context menus, or context-sensitive actions with complicated and slow enablement logic? Answer:No UI.
Question (perf-spi): How the performance of the plugged in code will be enforced? Answer:No practical enforcement.