1.1 --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java Wed Feb 20 18:14:59 2013 +0100
1.2 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java Fri Feb 22 08:59:40 2013 +0100
1.3 @@ -20,7 +20,9 @@
1.4 import java.io.IOException;
1.5 import java.io.InputStream;
1.6 import java.io.OutputStreamWriter;
1.7 +import java.io.StringWriter;
1.8 import java.io.Writer;
1.9 +import java.lang.annotation.Annotation;
1.10 import java.util.ArrayList;
1.11 import java.util.Collection;
1.12 import java.util.Collections;
1.13 @@ -37,6 +39,7 @@
1.14 import javax.annotation.processing.RoundEnvironment;
1.15 import javax.annotation.processing.SupportedAnnotationTypes;
1.16 import javax.lang.model.element.AnnotationMirror;
1.17 +import javax.lang.model.element.AnnotationValue;
1.18 import javax.lang.model.element.Element;
1.19 import javax.lang.model.element.ElementKind;
1.20 import javax.lang.model.element.ExecutableElement;
1.21 @@ -51,6 +54,7 @@
1.22 import javax.tools.FileObject;
1.23 import javax.tools.StandardLocation;
1.24 import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty;
1.25 +import org.apidesign.bck2brwsr.htmlpage.api.Model;
1.26 import org.apidesign.bck2brwsr.htmlpage.api.On;
1.27 import org.apidesign.bck2brwsr.htmlpage.api.Page;
1.28 import org.apidesign.bck2brwsr.htmlpage.api.Property;
1.29 @@ -63,6 +67,7 @@
1.30 */
1.31 @ServiceProvider(service=Processor.class)
1.32 @SupportedAnnotationTypes({
1.33 + "org.apidesign.bck2brwsr.htmlpage.api.Model",
1.34 "org.apidesign.bck2brwsr.htmlpage.api.Page",
1.35 "org.apidesign.bck2brwsr.htmlpage.api.On"
1.36 })
1.37 @@ -70,85 +75,14 @@
1.38 @Override
1.39 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
1.40 boolean ok = true;
1.41 + for (Element e : roundEnv.getElementsAnnotatedWith(Model.class)) {
1.42 + if (!processModel(e)) {
1.43 + ok = false;
1.44 + }
1.45 + }
1.46 for (Element e : roundEnv.getElementsAnnotatedWith(Page.class)) {
1.47 - Page p = e.getAnnotation(Page.class);
1.48 - if (p == null) {
1.49 - continue;
1.50 - }
1.51 - PackageElement pe = (PackageElement)e.getEnclosingElement();
1.52 - String pkg = pe.getQualifiedName().toString();
1.53 -
1.54 - ProcessPage pp;
1.55 - try {
1.56 - InputStream is = openStream(pkg, p.xhtml());
1.57 - pp = ProcessPage.readPage(is);
1.58 - is.close();
1.59 - } catch (IOException iOException) {
1.60 - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Can't read " + p.xhtml(), e);
1.61 + if (!processPage(e)) {
1.62 ok = false;
1.63 - pp = null;
1.64 - }
1.65 - Writer w;
1.66 - String className = p.className();
1.67 - if (className.isEmpty()) {
1.68 - int indx = p.xhtml().indexOf('.');
1.69 - className = p.xhtml().substring(0, indx);
1.70 - }
1.71 - try {
1.72 - FileObject java = processingEnv.getFiler().createSourceFile(pkg + '.' + className, e);
1.73 - w = new OutputStreamWriter(java.openOutputStream());
1.74 - try {
1.75 - w.append("package " + pkg + ";\n");
1.76 - w.append("import org.apidesign.bck2brwsr.htmlpage.api.*;\n");
1.77 - w.append("import org.apidesign.bck2brwsr.htmlpage.KOList;\n");
1.78 - w.append("final class ").append(className).append(" {\n");
1.79 - w.append(" private boolean locked;\n");
1.80 - if (!initializeOnClick(className, (TypeElement) e, w, pp)) {
1.81 - ok = false;
1.82 - } else {
1.83 - for (String id : pp.ids()) {
1.84 - String tag = pp.tagNameForId(id);
1.85 - String type = type(tag);
1.86 - w.append(" ").append("public final ").
1.87 - append(type).append(' ').append(cnstnt(id)).append(" = new ").
1.88 - append(type).append("(\"").append(id).append("\");\n");
1.89 - }
1.90 - }
1.91 - List<String> propsGetSet = new ArrayList<String>();
1.92 - Map<String,Collection<String>> propsDeps = new HashMap<String, Collection<String>>();
1.93 - if (!generateComputedProperties(w, p.properties(), e.getEnclosedElements(), propsGetSet, propsDeps)) {
1.94 - ok = false;
1.95 - }
1.96 - generateProperties(w, p.properties(), propsGetSet, propsDeps);
1.97 - w.append(" private org.apidesign.bck2brwsr.htmlpage.Knockout ko;\n");
1.98 - if (!propsGetSet.isEmpty()) {
1.99 - w.write("public " + className + " applyBindings() {\n");
1.100 - w.write(" ko = org.apidesign.bck2brwsr.htmlpage.Knockout.applyBindings(");
1.101 - w.write(className + ".class, this, ");
1.102 - w.write("new String[] {\n");
1.103 - String sep = "";
1.104 - for (String n : propsGetSet) {
1.105 - w.write(sep);
1.106 - if (n == null) {
1.107 - w.write(" null");
1.108 - } else {
1.109 - w.write(" \"" + n + "\"");
1.110 - }
1.111 - sep = ",\n";
1.112 - }
1.113 - w.write("\n });\n return this;\n}\n");
1.114 -
1.115 - w.write("public void triggerEvent(Element e, OnEvent ev) {\n");
1.116 - w.write(" org.apidesign.bck2brwsr.htmlpage.Knockout.triggerEvent(e.getId(), ev.getElementPropertyName());\n");
1.117 - w.write("}\n");
1.118 - }
1.119 - w.append("}\n");
1.120 - } finally {
1.121 - w.close();
1.122 - }
1.123 - } catch (IOException ex) {
1.124 - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Can't create " + className + ".java", e);
1.125 - return false;
1.126 }
1.127 }
1.128 return ok;
1.129 @@ -163,6 +97,134 @@
1.130 return processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, pkg, name).openInputStream();
1.131 }
1.132 }
1.133 +
1.134 + private boolean processModel(Element e) {
1.135 + boolean ok = true;
1.136 + Model m = e.getAnnotation(Model.class);
1.137 + if (m == null) {
1.138 + return true;
1.139 + }
1.140 + String pkg = findPkgName(e);
1.141 + Writer w;
1.142 + String className = m.className();
1.143 + try {
1.144 + StringWriter body = new StringWriter();
1.145 + List<String> propsGetSet = new ArrayList<>();
1.146 + Map<String, Collection<String>> propsDeps = new HashMap<>();
1.147 + if (!generateComputedProperties(body, m.properties(), e.getEnclosedElements(), propsGetSet, propsDeps)) {
1.148 + ok = false;
1.149 + }
1.150 + if (!generateProperties(body, m.properties(), propsGetSet, propsDeps)) {
1.151 + ok = false;
1.152 + }
1.153 + FileObject java = processingEnv.getFiler().createSourceFile(pkg + '.' + className, e);
1.154 + w = new OutputStreamWriter(java.openOutputStream());
1.155 + try {
1.156 + w.append("package " + pkg + ";\n");
1.157 + w.append("import org.apidesign.bck2brwsr.htmlpage.api.*;\n");
1.158 + w.append("import org.apidesign.bck2brwsr.htmlpage.KOList;\n");
1.159 + w.append("final class ").append(className).append(" {\n");
1.160 + w.append(" private Object json;\n");
1.161 + w.append(" private boolean locked;\n");
1.162 + w.append(" private org.apidesign.bck2brwsr.htmlpage.Knockout ko;\n");
1.163 + w.append(body.toString());
1.164 + w.append("}\n");
1.165 + } finally {
1.166 + w.close();
1.167 + }
1.168 + } catch (IOException ex) {
1.169 + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Can't create " + className + ".java", e);
1.170 + return false;
1.171 + }
1.172 + return ok;
1.173 + }
1.174 +
1.175 + private boolean processPage(Element e) {
1.176 + boolean ok = true;
1.177 + Page p = e.getAnnotation(Page.class);
1.178 + if (p == null) {
1.179 + return true;
1.180 + }
1.181 + String pkg = findPkgName(e);
1.182 +
1.183 + ProcessPage pp;
1.184 + try (InputStream is = openStream(pkg, p.xhtml())) {
1.185 + pp = ProcessPage.readPage(is);
1.186 + is.close();
1.187 + } catch (IOException iOException) {
1.188 + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Can't read " + p.xhtml(), e);
1.189 + ok = false;
1.190 + pp = null;
1.191 + }
1.192 + Writer w;
1.193 + String className = p.className();
1.194 + if (className.isEmpty()) {
1.195 + int indx = p.xhtml().indexOf('.');
1.196 + className = p.xhtml().substring(0, indx);
1.197 + }
1.198 + try {
1.199 + StringWriter body = new StringWriter();
1.200 + List<String> propsGetSet = new ArrayList<>();
1.201 + Map<String, Collection<String>> propsDeps = new HashMap<>();
1.202 + if (!generateComputedProperties(body, p.properties(), e.getEnclosedElements(), propsGetSet, propsDeps)) {
1.203 + ok = false;
1.204 + }
1.205 + if (!generateProperties(body, p.properties(), propsGetSet, propsDeps)) {
1.206 + ok = false;
1.207 + }
1.208 +
1.209 + FileObject java = processingEnv.getFiler().createSourceFile(pkg + '.' + className, e);
1.210 + w = new OutputStreamWriter(java.openOutputStream());
1.211 + try {
1.212 + w.append("package " + pkg + ";\n");
1.213 + w.append("import org.apidesign.bck2brwsr.htmlpage.api.*;\n");
1.214 + w.append("import org.apidesign.bck2brwsr.htmlpage.KOList;\n");
1.215 + w.append("final class ").append(className).append(" {\n");
1.216 + w.append(" private boolean locked;\n");
1.217 + if (!initializeOnClick(className, (TypeElement) e, w, pp)) {
1.218 + ok = false;
1.219 + } else {
1.220 + for (String id : pp.ids()) {
1.221 + String tag = pp.tagNameForId(id);
1.222 + String type = type(tag);
1.223 + w.append(" ").append("public final ").
1.224 + append(type).append(' ').append(cnstnt(id)).append(" = new ").
1.225 + append(type).append("(\"").append(id).append("\");\n");
1.226 + }
1.227 + }
1.228 + w.append(" private org.apidesign.bck2brwsr.htmlpage.Knockout ko;\n");
1.229 + w.append(body.toString());
1.230 + if (!propsGetSet.isEmpty()) {
1.231 + w.write("public " + className + " applyBindings() {\n");
1.232 + w.write(" ko = org.apidesign.bck2brwsr.htmlpage.Knockout.applyBindings(");
1.233 + w.write(className + ".class, this, ");
1.234 + w.write("new String[] {\n");
1.235 + String sep = "";
1.236 + for (String n : propsGetSet) {
1.237 + w.write(sep);
1.238 + if (n == null) {
1.239 + w.write(" null");
1.240 + } else {
1.241 + w.write(" \"" + n + "\"");
1.242 + }
1.243 + sep = ",\n";
1.244 + }
1.245 + w.write("\n });\n return this;\n}\n");
1.246 +
1.247 + w.write("public void triggerEvent(Element e, OnEvent ev) {\n");
1.248 + w.write(" org.apidesign.bck2brwsr.htmlpage.Knockout.triggerEvent(e.getId(), ev.getElementPropertyName());\n");
1.249 + w.write("}\n");
1.250 + }
1.251 + w.append("}\n");
1.252 + } finally {
1.253 + w.close();
1.254 + }
1.255 + } catch (IOException ex) {
1.256 + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Can't create " + className + ".java", e);
1.257 + return false;
1.258 + }
1.259 + return ok;
1.260 + }
1.261
1.262 private static String type(String tag) {
1.263 if (tag.equals("title")) {
1.264 @@ -295,8 +357,7 @@
1.265
1.266 Element cls = findClass(element);
1.267 Page p = cls.getAnnotation(Page.class);
1.268 - PackageElement pe = (PackageElement) cls.getEnclosingElement();
1.269 - String pkg = pe.getQualifiedName().toString();
1.270 + String pkg = findPkgName(cls);
1.271 ProcessPage pp;
1.272 try {
1.273 InputStream is = openStream(pkg, p.xhtml());
1.274 @@ -306,7 +367,7 @@
1.275 return Collections.emptyList();
1.276 }
1.277
1.278 - List<Completion> cc = new ArrayList<Completion>();
1.279 + List<Completion> cc = new ArrayList<>();
1.280 userText = userText.substring(1);
1.281 for (String id : pp.ids()) {
1.282 if (id.startsWith(userText)) {
1.283 @@ -327,12 +388,14 @@
1.284 return e.getEnclosingElement();
1.285 }
1.286
1.287 - private void generateProperties(
1.288 - Writer w, Property[] properties, Collection<String> props,
1.289 - Map<String,Collection<String>> deps
1.290 + private boolean generateProperties(
1.291 + Writer w, Property[] properties,
1.292 + Collection<String> props, Map<String,Collection<String>> deps
1.293 ) throws IOException {
1.294 + boolean ok = true;
1.295 for (Property p : properties) {
1.296 - final String tn = typeName(p);
1.297 + final String tn;
1.298 + tn = typeName(p);
1.299 String[] gs = toGetSet(p.name(), tn, p.array());
1.300
1.301 if (p.array()) {
1.302 @@ -382,6 +445,7 @@
1.303 props.add(gs[3]);
1.304 props.add(gs[0]);
1.305 }
1.306 + return ok;
1.307 }
1.308
1.309 private boolean generateComputedProperties(
1.310 @@ -427,7 +491,7 @@
1.311
1.312 Collection<String> depends = deps.get(dn);
1.313 if (depends == null) {
1.314 - depends = new LinkedHashSet<String>();
1.315 + depends = new LinkedHashSet<>();
1.316 deps.put(dn, depends);
1.317 }
1.318 depends.add(sn);
1.319 @@ -494,11 +558,19 @@
1.320
1.321 private String typeName(Property p) {
1.322 String ret;
1.323 + boolean isModel = false;
1.324 try {
1.325 ret = p.type().getName();
1.326 } catch (MirroredTypeException ex) {
1.327 TypeMirror tm = processingEnv.getTypeUtils().erasure(ex.getTypeMirror());
1.328 - ret = tm.toString();
1.329 + final Element e = processingEnv.getTypeUtils().asElement(tm);
1.330 + final Model m = e == null ? null : e.getAnnotation(Model.class);
1.331 + if (m != null) {
1.332 + ret = findPkgName(e) + '.' + m.className();
1.333 + isModel = true;
1.334 + } else {
1.335 + ret = tm.toString();
1.336 + }
1.337 }
1.338 if (p.array()) {
1.339 String bt = findBoxedType(ret);
1.340 @@ -506,14 +578,12 @@
1.341 return bt;
1.342 }
1.343 }
1.344 - if ("java.lang.String".equals(ret)) {
1.345 - return ret;
1.346 + if (!isModel && !"java.lang.String".equals(ret)) {
1.347 + String bt = findBoxedType(ret);
1.348 + if (bt == null) {
1.349 + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Only primitive types supported in the mapping. Not " + ret);
1.350 + }
1.351 }
1.352 - String bt = findBoxedType(ret);
1.353 - if (bt != null) {
1.354 - return ret;
1.355 - }
1.356 - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Only primitive types supported in the mapping. Not " + ret);
1.357 return ret;
1.358 }
1.359
1.360 @@ -561,4 +631,13 @@
1.361 );
1.362 return false;
1.363 }
1.364 +
1.365 + private static String findPkgName(Element e) {
1.366 + for (;;) {
1.367 + if (e.getKind() == ElementKind.PACKAGE) {
1.368 + return ((PackageElement)e).getQualifiedName().toString();
1.369 + }
1.370 + e = e.getEnclosingElement();
1.371 + }
1.372 + }
1.373 }
2.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2.2 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/Model.java Fri Feb 22 08:59:40 2013 +0100
2.3 @@ -0,0 +1,43 @@
2.4 +/**
2.5 + * Back 2 Browser Bytecode Translator
2.6 + * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
2.7 + *
2.8 + * This program is free software: you can redistribute it and/or modify
2.9 + * it under the terms of the GNU General Public License as published by
2.10 + * the Free Software Foundation, version 2 of the License.
2.11 + *
2.12 + * This program is distributed in the hope that it will be useful,
2.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
2.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2.15 + * GNU General Public License for more details.
2.16 + *
2.17 + * You should have received a copy of the GNU General Public License
2.18 + * along with this program. Look for COPYING file in the top folder.
2.19 + * If not, see http://opensource.org/licenses/GPL-2.0.
2.20 + */
2.21 +package org.apidesign.bck2brwsr.htmlpage.api;
2.22 +
2.23 +import java.lang.annotation.ElementType;
2.24 +import java.lang.annotation.Retention;
2.25 +import java.lang.annotation.RetentionPolicy;
2.26 +import java.lang.annotation.Target;
2.27 +
2.28 +/** Defines a model class named {@link #className()} which contains
2.29 + * defined {@link #properties()}. This class can have methods
2.30 + * annotated by {@link ComputedProperty} which define derived
2.31 + * properties in the model class.
2.32 + * <p>
2.33 + * The {@link #className() generated class} will have methods
2.34 + * to convert the object <code>toJSON</code> and <code>fromJSON</code>.
2.35 + *
2.36 + * @author Jaroslav Tulach <jtulach@netbeans.org>
2.37 + */
2.38 +@Retention(RetentionPolicy.SOURCE)
2.39 +@Target(ElementType.TYPE)
2.40 +public @interface Model {
2.41 + /** Name of the model class */
2.42 + String className();
2.43 + /** List of properties in the model.
2.44 + */
2.45 + Property[] properties();
2.46 +}
3.1 --- a/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/Property.java Wed Feb 20 18:14:59 2013 +0100
3.2 +++ b/javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/api/Property.java Fri Feb 22 08:59:40 2013 +0100
3.3 @@ -20,16 +20,36 @@
3.4 import java.lang.annotation.Retention;
3.5 import java.lang.annotation.RetentionPolicy;
3.6 import java.lang.annotation.Target;
3.7 +import java.util.List;
3.8
3.9 -/** Represents a property in a generated model of an HTML
3.10 - * {@link Page}.
3.11 +/** Represents a property. Either in a generated model of an HTML
3.12 + * {@link Page} or in a class defined by {@link Model}.
3.13 *
3.14 * @author Jaroslav Tulach <jtulach@netbeans.org>
3.15 */
3.16 @Retention(RetentionPolicy.SOURCE)
3.17 @Target({})
3.18 public @interface Property {
3.19 + /** Name of the property. Will be used to define proper getter and setter
3.20 + * in the associated class.
3.21 + *
3.22 + * @return valid java identifier
3.23 + */
3.24 String name();
3.25 +
3.26 + /** Type of the property. Can either be primitive type (like <code>int.class</code>,
3.27 + * <code>double.class</code>, etc.), {@link String} or complex model
3.28 + * class (defined by {@link Model} property).
3.29 + *
3.30 + * @return the class of the property
3.31 + */
3.32 Class<?> type();
3.33 +
3.34 + /** Is this property an array of the {@link #type()} or a single value?
3.35 + * If the property is an array, only its getter (returning mutable {@link List} of
3.36 + * the boxed {@link #type()}).
3.37 + *
3.38 + * @return true, if this is supposed to be an array of values.
3.39 + */
3.40 boolean array() default false;
3.41 }
4.1 --- a/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ModelTest.java Wed Feb 20 18:14:59 2013 +0100
4.2 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/ModelTest.java Fri Feb 22 08:59:40 2013 +0100
4.3 @@ -33,20 +33,21 @@
4.4 *
4.5 * @author Jaroslav Tulach <jtulach@netbeans.org>
4.6 */
4.7 -@Page(xhtml = "Empty.html", className = "Model", properties = {
4.8 +@Page(xhtml = "Empty.html", className = "Modelik", properties = {
4.9 @Property(name = "value", type = int.class),
4.10 @Property(name = "count", type = int.class),
4.11 @Property(name = "unrelated", type = long.class),
4.12 @Property(name = "names", type = String.class, array = true),
4.13 - @Property(name = "values", type = int.class, array = true)
4.14 + @Property(name = "values", type = int.class, array = true),
4.15 + @Property(name = "people", type = PersonImpl.class, array = true)
4.16 })
4.17 public class ModelTest {
4.18 - private Model model;
4.19 - private static Model leakedModel;
4.20 + private Modelik model;
4.21 + private static Modelik leakedModel;
4.22
4.23 @BeforeMethod
4.24 public void createModel() {
4.25 - model = new Model();
4.26 + model = new Modelik();
4.27 }
4.28
4.29 @Test public void classGeneratedWithSetterGetter() {
4.30 @@ -189,11 +190,21 @@
4.31 }
4.32
4.33 static class MockKnockout extends Knockout {
4.34 - List<String> mutated = new ArrayList<String>();
4.35 + List<String> mutated = new ArrayList<>();
4.36
4.37 @Override
4.38 public void valueHasMutated(String prop) {
4.39 mutated.add(prop);
4.40 }
4.41 }
4.42 +
4.43 + public @Test void hasPersonPropertyAndComputedFullName() {
4.44 + List<Person> arr = model.getPeople();
4.45 + assertEquals(arr.size(), 0, "By default empty");
4.46 + Person p = null;
4.47 + if (p != null) {
4.48 + String fullNameGenerated = p.getFullName();
4.49 + assertNotNull(fullNameGenerated);
4.50 + }
4.51 + }
4.52 }
5.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
5.2 +++ b/javaquery/api/src/test/java/org/apidesign/bck2brwsr/htmlpage/PersonImpl.java Fri Feb 22 08:59:40 2013 +0100
5.3 @@ -0,0 +1,37 @@
5.4 +/**
5.5 + * Back 2 Browser Bytecode Translator
5.6 + * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
5.7 + *
5.8 + * This program is free software: you can redistribute it and/or modify
5.9 + * it under the terms of the GNU General Public License as published by
5.10 + * the Free Software Foundation, version 2 of the License.
5.11 + *
5.12 + * This program is distributed in the hope that it will be useful,
5.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
5.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
5.15 + * GNU General Public License for more details.
5.16 + *
5.17 + * You should have received a copy of the GNU General Public License
5.18 + * along with this program. Look for COPYING file in the top folder.
5.19 + * If not, see http://opensource.org/licenses/GPL-2.0.
5.20 + */
5.21 +package org.apidesign.bck2brwsr.htmlpage;
5.22 +
5.23 +import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty;
5.24 +import org.apidesign.bck2brwsr.htmlpage.api.Model;
5.25 +import org.apidesign.bck2brwsr.htmlpage.api.Property;
5.26 +
5.27 +/**
5.28 + *
5.29 + * @author Jaroslav Tulach <jtulach@netbeans.org>
5.30 + */
5.31 +@Model(className = "Person", properties = {
5.32 + @Property(name = "firstName", type = String.class),
5.33 + @Property(name = "lastName", type = String.class)
5.34 +})
5.35 +final class PersonImpl {
5.36 + @ComputedProperty
5.37 + public static String fullName(String firstName, String lastName) {
5.38 + return firstName + " " + lastName;
5.39 + }
5.40 +}