Initial version of the JSON model APIs. Taken from bck2brwsr rev. be21afc3d48a
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/.hgignore Fri Apr 19 10:01:02 2013 +0200
1.3 @@ -0,0 +1,3 @@
1.4 +.*~
1.5 +.*\.orig$
1.6 +.*target/.*
2.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2.2 +++ b/COPYING Fri Apr 19 10:01:02 2013 +0200
2.3 @@ -0,0 +1,18 @@
2.4 +HTML via Java(tm) Language Bindings
2.5 +Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
2.6 +
2.7 +This program is free software: you can redistribute it and/or modify
2.8 +it under the terms of the GNU General Public License as published by
2.9 +the Free Software Foundation, version 2 of the License.
2.10 +
2.11 +This program is distributed in the hope that it will be useful,
2.12 +but WITHOUT ANY WARRANTY; without even the implied warranty of
2.13 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2.14 +GNU General Public License for more details. apidesign.org
2.15 +designates this particular file as subject to the
2.16 +"Classpath" exception as provided by apidesign.org
2.17 +in the License file that accompanied this code.
2.18 +
2.19 +You should have received a copy of the GNU General Public License
2.20 +along with this program. Look for COPYING file in the top folder.
2.21 +If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
3.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
3.2 +++ b/json/pom.xml Fri Apr 19 10:01:02 2013 +0200
3.3 @@ -0,0 +1,29 @@
3.4 +<?xml version="1.0"?>
3.5 +<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
3.6 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
3.7 + <modelVersion>4.0.0</modelVersion>
3.8 + <parent>
3.9 + <groupId>org.apidesign</groupId>
3.10 + <artifactId>html</artifactId>
3.11 + <version>0.1-SNAPSHOT</version>
3.12 + </parent>
3.13 + <groupId>org.apidesign.html</groupId>
3.14 + <artifactId>net.java.html.json</artifactId>
3.15 + <version>0.1-SNAPSHOT</version>
3.16 + <description>JSON Model in Java</description>
3.17 + <url>http://maven.apache.org</url>
3.18 + <properties>
3.19 + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
3.20 + </properties>
3.21 + <dependencies>
3.22 + <dependency>
3.23 + <groupId>org.testng</groupId>
3.24 + <artifactId>testng</artifactId>
3.25 + <scope>test</scope>
3.26 + </dependency>
3.27 + <dependency>
3.28 + <groupId>org.netbeans.api</groupId>
3.29 + <artifactId>org-openide-util-lookup</artifactId>
3.30 + </dependency>
3.31 + </dependencies>
3.32 +</project>
4.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
4.2 +++ b/json/src/main/java/net/java/html/json/ComputedProperty.java Fri Apr 19 10:01:02 2013 +0200
4.3 @@ -0,0 +1,41 @@
4.4 +/**
4.5 + * HTML via Java(tm) Language Bindings
4.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
4.7 + *
4.8 + * This program is free software: you can redistribute it and/or modify
4.9 + * it under the terms of the GNU General Public License as published by
4.10 + * the Free Software Foundation, version 2 of the License.
4.11 + *
4.12 + * This program is distributed in the hope that it will be useful,
4.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
4.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4.15 + * GNU General Public License for more details. apidesign.org
4.16 + * designates this particular file as subject to the
4.17 + * "Classpath" exception as provided by apidesign.org
4.18 + * in the License file that accompanied this code.
4.19 + *
4.20 + * You should have received a copy of the GNU General Public License
4.21 + * along with this program. Look for COPYING file in the top folder.
4.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
4.23 + */
4.24 +package net.java.html.json;
4.25 +
4.26 +import java.lang.annotation.ElementType;
4.27 +import java.lang.annotation.Retention;
4.28 +import java.lang.annotation.RetentionPolicy;
4.29 +import java.lang.annotation.Target;
4.30 +
4.31 +/** Can be used in classes annotated with {@link Page} annotation to
4.32 + * define a derived property. Value of derived property is based on values
4.33 + * of {@link Property} as enumerated by {@link Page#properties()}.
4.34 + * <p>
4.35 + * The name of the derived property is the name of the method. The arguments
4.36 + * of the method define the property names (from {@link Page#properties()} list)
4.37 + * the value of property depends on.
4.38 + *
4.39 + * @author Jaroslav Tulach <jtulach@netbeans.org>
4.40 + */
4.41 +@Retention(RetentionPolicy.SOURCE)
4.42 +@Target(ElementType.METHOD)
4.43 +public @interface ComputedProperty {
4.44 +}
5.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
5.2 +++ b/json/src/main/java/net/java/html/json/Function.java Fri Apr 19 10:01:02 2013 +0200
5.3 @@ -0,0 +1,37 @@
5.4 +/**
5.5 + * HTML via Java(tm) Language Bindings
5.6 + * Copyright (C) 2013 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. apidesign.org
5.16 + * designates this particular file as subject to the
5.17 + * "Classpath" exception as provided by apidesign.org
5.18 + * in the License file that accompanied this code.
5.19 + *
5.20 + * You should have received a copy of the GNU General Public License
5.21 + * along with this program. Look for COPYING file in the top folder.
5.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
5.23 + */
5.24 +package net.java.html.json;
5.25 +
5.26 +import java.lang.annotation.ElementType;
5.27 +import java.lang.annotation.Retention;
5.28 +import java.lang.annotation.RetentionPolicy;
5.29 +import java.lang.annotation.Target;
5.30 +
5.31 +/** Methods in class annotated by {@link Model} or {@link Page} can be
5.32 + * annotated by this annotation to signal that they should be available
5.33 + * as functions to users of the model classes.
5.34 + *
5.35 + * @author Jaroslav Tulach <jtulach@netbeans.org>
5.36 + */
5.37 +@Target(ElementType.METHOD)
5.38 +@Retention(RetentionPolicy.SOURCE)
5.39 +public @interface Function {
5.40 +}
6.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
6.2 +++ b/json/src/main/java/net/java/html/json/Model.java Fri Apr 19 10:01:02 2013 +0200
6.3 @@ -0,0 +1,47 @@
6.4 +/**
6.5 + * HTML via Java(tm) Language Bindings
6.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
6.7 + *
6.8 + * This program is free software: you can redistribute it and/or modify
6.9 + * it under the terms of the GNU General Public License as published by
6.10 + * the Free Software Foundation, version 2 of the License.
6.11 + *
6.12 + * This program is distributed in the hope that it will be useful,
6.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
6.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
6.15 + * GNU General Public License for more details. apidesign.org
6.16 + * designates this particular file as subject to the
6.17 + * "Classpath" exception as provided by apidesign.org
6.18 + * in the License file that accompanied this code.
6.19 + *
6.20 + * You should have received a copy of the GNU General Public License
6.21 + * along with this program. Look for COPYING file in the top folder.
6.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
6.23 + */
6.24 +package net.java.html.json;
6.25 +
6.26 +import java.lang.annotation.ElementType;
6.27 +import java.lang.annotation.Retention;
6.28 +import java.lang.annotation.RetentionPolicy;
6.29 +import java.lang.annotation.Target;
6.30 +
6.31 +/** Defines a model class named {@link #className()} which contains
6.32 + * defined {@link #properties()}. This class can have methods
6.33 + * annotated by {@link ComputedProperty} which define derived
6.34 + * properties in the model class.
6.35 + * <p>
6.36 + * The {@link #className() generated class}'s <code>toString</code>
6.37 + * converts the state of the object into
6.38 + * <a href="http://en.wikipedia.org/wiki/JSON">JSON</a> format.
6.39 + *
6.40 + * @author Jaroslav Tulach <jtulach@netbeans.org>
6.41 + */
6.42 +@Retention(RetentionPolicy.SOURCE)
6.43 +@Target(ElementType.TYPE)
6.44 +public @interface Model {
6.45 + /** Name of the model class */
6.46 + String className();
6.47 + /** List of properties in the model.
6.48 + */
6.49 + Property[] properties();
6.50 +}
7.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
7.2 +++ b/json/src/main/java/net/java/html/json/OnPropertyChange.java Fri Apr 19 10:01:02 2013 +0200
7.3 @@ -0,0 +1,41 @@
7.4 +/**
7.5 + * HTML via Java(tm) Language Bindings
7.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
7.7 + *
7.8 + * This program is free software: you can redistribute it and/or modify
7.9 + * it under the terms of the GNU General Public License as published by
7.10 + * the Free Software Foundation, version 2 of the License.
7.11 + *
7.12 + * This program is distributed in the hope that it will be useful,
7.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
7.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
7.15 + * GNU General Public License for more details. apidesign.org
7.16 + * designates this particular file as subject to the
7.17 + * "Classpath" exception as provided by apidesign.org
7.18 + * in the License file that accompanied this code.
7.19 + *
7.20 + * You should have received a copy of the GNU General Public License
7.21 + * along with this program. Look for COPYING file in the top folder.
7.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
7.23 + */
7.24 +package net.java.html.json;
7.25 +
7.26 +import java.lang.annotation.ElementType;
7.27 +import java.lang.annotation.Retention;
7.28 +import java.lang.annotation.RetentionPolicy;
7.29 +import java.lang.annotation.Target;
7.30 +
7.31 +/** Represents a property. Either in a generated model of an HTML
7.32 + * {@link Page} or in a class defined by {@link Model}.
7.33 + *
7.34 + * @author Jaroslav Tulach <jtulach@netbeans.org>
7.35 + */
7.36 +@Retention(RetentionPolicy.SOURCE)
7.37 +@Target(ElementType.METHOD)
7.38 +public @interface OnPropertyChange {
7.39 + /** Name(s) of the properties. One wishes to observe.
7.40 + *
7.41 + * @return valid java identifier
7.42 + */
7.43 + String[] value();
7.44 +}
8.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
8.2 +++ b/json/src/main/java/net/java/html/json/OnReceive.java Fri Apr 19 10:01:02 2013 +0200
8.3 @@ -0,0 +1,96 @@
8.4 +/**
8.5 + * HTML via Java(tm) Language Bindings
8.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
8.7 + *
8.8 + * This program is free software: you can redistribute it and/or modify
8.9 + * it under the terms of the GNU General Public License as published by
8.10 + * the Free Software Foundation, version 2 of the License.
8.11 + *
8.12 + * This program is distributed in the hope that it will be useful,
8.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
8.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8.15 + * GNU General Public License for more details. apidesign.org
8.16 + * designates this particular file as subject to the
8.17 + * "Classpath" exception as provided by apidesign.org
8.18 + * in the License file that accompanied this code.
8.19 + *
8.20 + * You should have received a copy of the GNU General Public License
8.21 + * along with this program. Look for COPYING file in the top folder.
8.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
8.23 + */
8.24 +package net.java.html.json;
8.25 +
8.26 +import java.lang.annotation.ElementType;
8.27 +import java.lang.annotation.Retention;
8.28 +import java.lang.annotation.RetentionPolicy;
8.29 +import java.lang.annotation.Target;
8.30 +
8.31 +/** Static methods in classes annotated by {@link Page}
8.32 + * can be marked by this annotation to establish a
8.33 + * <a href="http://en.wikipedia.org/wiki/JSON">JSON</a>
8.34 + * communication point.
8.35 + * The associated model page then gets new method to invoke a network
8.36 + * connection. Example follows:
8.37 + *
8.38 + * <pre>
8.39 + * {@link Page @Page}(className="MyModel", xhtml="page.html", properties={
8.40 + * {@link Property @Property}(name = "people", type=Person.class, array=true)
8.41 + * })
8.42 + * class MyModelImpl {
8.43 + * {@link Model @Model}(className="Person", properties={
8.44 + * {@link Property @Property}(name = "firstName", type=String.class),
8.45 + * {@link Property @Property}(name = "lastName", type=String.class)
8.46 + * })
8.47 + * static class PersonImpl {
8.48 + * {@link ComputedProperty @ComputedProperty}
8.49 + * static String fullName(String firstName, String lastName) {
8.50 + * return firstName + " " + lastName;
8.51 + * }
8.52 + * }
8.53 + *
8.54 + * {@link OnReceive @OnReceive}(url = "{protocol}://your.server.com/person/{name}")
8.55 + * static void getANewPerson(MyModel m, Person p) {
8.56 + * {@link Element#alert Element.alert}("Adding " + p.getFullName() + '!');
8.57 + * m.getPeople().add(p);
8.58 + * }
8.59 + *
8.60 + * // the above will generate method <code>getANewPerson</code> in class <code>MyModel</code>.
8.61 + * // with <code>protocol</code> and <code>name</code> arguments
8.62 + * // which asynchronously contacts the server and in case of success calls
8.63 + * // your {@link OnReceive @OnReceive} with parsed in data
8.64 + *
8.65 + * {@link On @On}(event={@link OnEvent#CLICK OnEvent.CLICK}, id="rqst")
8.66 + * static void requestSmith(MyModel m) {
8.67 + * m.getANewPerson("http", "Smith");
8.68 + * }
8.69 + * }
8.70 + * </pre>
8.71 + * When the server returns <code>{ "firstName" : "John", "lastName" : "Smith" }</code>
8.72 + * the browser will show alert message <em>Adding John Smith!</em>.
8.73 + *
8.74 + * @author Jaroslav Tulach <jtulach@netbeans.org>
8.75 + * @since 0.6
8.76 + */
8.77 +@Retention(RetentionPolicy.SOURCE)
8.78 +@Target(ElementType.METHOD)
8.79 +public @interface OnReceive {
8.80 + /** The URL to connect to. Can contain variable names surrounded by '{' and '}'.
8.81 + * Those parameters will then become variables of the associated method.
8.82 + *
8.83 + * @return the (possibly parametrized) url to connect to
8.84 + */
8.85 + String url();
8.86 +
8.87 + /** Support for <a href="http://en.wikipedia.org/wiki/JSONP">JSONP</a> requires
8.88 + * a callback from the server generated page to a function defined in the
8.89 + * system. The name of such function is usually specified as a property
8.90 + * (of possibly different names). By defining the <code>jsonp</code> attribute
8.91 + * one turns on the <a href="http://en.wikipedia.org/wiki/JSONP">JSONP</a>
8.92 + * transmission and specifies the name of the property. The property should
8.93 + * also be used in the {@link #url()} attribute on appropriate place.
8.94 + *
8.95 + * @return name of a property to carry the name of <a href="http://en.wikipedia.org/wiki/JSONP">JSONP</a>
8.96 + * callback function.
8.97 + */
8.98 + String jsonp() default "";
8.99 +}
9.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
9.2 +++ b/json/src/main/java/net/java/html/json/Property.java Fri Apr 19 10:01:02 2013 +0200
9.3 @@ -0,0 +1,58 @@
9.4 +/**
9.5 + * HTML via Java(tm) Language Bindings
9.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
9.7 + *
9.8 + * This program is free software: you can redistribute it and/or modify
9.9 + * it under the terms of the GNU General Public License as published by
9.10 + * the Free Software Foundation, version 2 of the License.
9.11 + *
9.12 + * This program is distributed in the hope that it will be useful,
9.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
9.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9.15 + * GNU General Public License for more details. apidesign.org
9.16 + * designates this particular file as subject to the
9.17 + * "Classpath" exception as provided by apidesign.org
9.18 + * in the License file that accompanied this code.
9.19 + *
9.20 + * You should have received a copy of the GNU General Public License
9.21 + * along with this program. Look for COPYING file in the top folder.
9.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
9.23 + */
9.24 +package net.java.html.json;
9.25 +
9.26 +import java.lang.annotation.Retention;
9.27 +import java.lang.annotation.RetentionPolicy;
9.28 +import java.lang.annotation.Target;
9.29 +import java.util.List;
9.30 +
9.31 +/** Represents a property. Either in a generated model of an HTML
9.32 + * {@link Page} or in a class defined by {@link Model}.
9.33 + *
9.34 + * @author Jaroslav Tulach <jtulach@netbeans.org>
9.35 + */
9.36 +@Retention(RetentionPolicy.SOURCE)
9.37 +@Target({})
9.38 +public @interface Property {
9.39 + /** Name of the property. Will be used to define proper getter and setter
9.40 + * in the associated class.
9.41 + *
9.42 + * @return valid java identifier
9.43 + */
9.44 + String name();
9.45 +
9.46 + /** Type of the property. Can either be primitive type (like <code>int.class</code>,
9.47 + * <code>double.class</code>, etc.), {@link String} or complex model
9.48 + * class (defined by {@link Model} property).
9.49 + *
9.50 + * @return the class of the property
9.51 + */
9.52 + Class<?> type();
9.53 +
9.54 + /** Is this property an array of the {@link #type()} or a single value?
9.55 + * If the property is an array, only its getter (returning mutable {@link List} of
9.56 + * the boxed {@link #type()}).
9.57 + *
9.58 + * @return true, if this is supposed to be an array of values.
9.59 + */
9.60 + boolean array() default false;
9.61 +}
10.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
10.2 +++ b/json/src/main/java/org/apidesign/html/json/impl/PageProcessor.java Fri Apr 19 10:01:02 2013 +0200
10.3 @@ -0,0 +1,1180 @@
10.4 +/**
10.5 + * HTML via Java(tm) Language Bindings
10.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
10.7 + *
10.8 + * This program is free software: you can redistribute it and/or modify
10.9 + * it under the terms of the GNU General Public License as published by
10.10 + * the Free Software Foundation, version 2 of the License.
10.11 + *
10.12 + * This program is distributed in the hope that it will be useful,
10.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
10.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10.15 + * GNU General Public License for more details. apidesign.org
10.16 + * designates this particular file as subject to the
10.17 + * "Classpath" exception as provided by apidesign.org
10.18 + * in the License file that accompanied this code.
10.19 + *
10.20 + * You should have received a copy of the GNU General Public License
10.21 + * along with this program. Look for COPYING file in the top folder.
10.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
10.23 + */
10.24 +package org.apidesign.html.json.impl;
10.25 +
10.26 +import java.io.IOException;
10.27 +import java.io.InputStream;
10.28 +import java.io.OutputStreamWriter;
10.29 +import java.io.StringWriter;
10.30 +import java.io.Writer;
10.31 +import java.lang.annotation.AnnotationTypeMismatchException;
10.32 +import java.lang.annotation.IncompleteAnnotationException;
10.33 +import java.lang.reflect.Method;
10.34 +import java.util.ArrayList;
10.35 +import java.util.Collection;
10.36 +import java.util.Collections;
10.37 +import java.util.HashMap;
10.38 +import java.util.HashSet;
10.39 +import java.util.LinkedHashSet;
10.40 +import java.util.List;
10.41 +import java.util.Map;
10.42 +import java.util.Set;
10.43 +import java.util.WeakHashMap;
10.44 +import javax.annotation.processing.AbstractProcessor;
10.45 +import javax.annotation.processing.ProcessingEnvironment;
10.46 +import javax.annotation.processing.Processor;
10.47 +import javax.annotation.processing.RoundEnvironment;
10.48 +import javax.annotation.processing.SupportedAnnotationTypes;
10.49 +import javax.lang.model.element.AnnotationMirror;
10.50 +import javax.lang.model.element.AnnotationValue;
10.51 +import javax.lang.model.element.Element;
10.52 +import javax.lang.model.element.ElementKind;
10.53 +import javax.lang.model.element.ExecutableElement;
10.54 +import javax.lang.model.element.Modifier;
10.55 +import javax.lang.model.element.PackageElement;
10.56 +import javax.lang.model.element.TypeElement;
10.57 +import javax.lang.model.element.VariableElement;
10.58 +import javax.lang.model.type.ArrayType;
10.59 +import javax.lang.model.type.MirroredTypeException;
10.60 +import javax.lang.model.type.TypeKind;
10.61 +import javax.lang.model.type.TypeMirror;
10.62 +import javax.lang.model.util.Elements;
10.63 +import javax.lang.model.util.Types;
10.64 +import javax.tools.Diagnostic;
10.65 +import javax.tools.FileObject;
10.66 +import javax.tools.StandardLocation;
10.67 +import net.java.html.json.ComputedProperty;
10.68 +import net.java.html.json.Model;
10.69 +import net.java.html.json.Function;
10.70 +import net.java.html.json.OnPropertyChange;
10.71 +import net.java.html.json.OnReceive;
10.72 +import net.java.html.json.Property;
10.73 +import org.openide.util.lookup.ServiceProvider;
10.74 +
10.75 +/** Annotation processor to process an XHTML page and generate appropriate
10.76 + * "id" file.
10.77 + *
10.78 + * @author Jaroslav Tulach <jtulach@netbeans.org>
10.79 + */
10.80 +@ServiceProvider(service=Processor.class)
10.81 +@SupportedAnnotationTypes({
10.82 + "net.java.html.model.Model",
10.83 + "net.java.html.model.Function",
10.84 + "net.java.html.model.OnReceive",
10.85 + "net.java.html.model.OnPropertyChange",
10.86 + "net.java.html.model.ComputedProperty",
10.87 + "net.java.html.model.Property"
10.88 +})
10.89 +public final class PageProcessor extends AbstractProcessor {
10.90 + private final Map<Element,String> models = new WeakHashMap<>();
10.91 + private final Map<Element,Prprt[]> verify = new WeakHashMap<>();
10.92 + @Override
10.93 + public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
10.94 + boolean ok = true;
10.95 + for (Element e : roundEnv.getElementsAnnotatedWith(Model.class)) {
10.96 + if (!processModel(e)) {
10.97 + ok = false;
10.98 + }
10.99 + }
10.100 + if (roundEnv.processingOver()) {
10.101 + models.clear();
10.102 + for (Map.Entry<Element, Prprt[]> entry : verify.entrySet()) {
10.103 + TypeElement te = (TypeElement)entry.getKey();
10.104 + String fqn = processingEnv.getElementUtils().getBinaryName(te).toString();
10.105 + Element finalElem = processingEnv.getElementUtils().getTypeElement(fqn);
10.106 + if (finalElem == null) {
10.107 + continue;
10.108 + }
10.109 + Prprt[] props;
10.110 + Model m = finalElem.getAnnotation(Model.class);
10.111 + props = Prprt.wrap(processingEnv, finalElem, m.properties());
10.112 + for (Prprt p : props) {
10.113 + boolean[] isModel = { false };
10.114 + boolean[] isEnum = { false };
10.115 + boolean[] isPrimitive = { false };
10.116 + String t = checkType(p, isModel, isEnum, isPrimitive);
10.117 + if (isEnum[0]) {
10.118 + continue;
10.119 + }
10.120 + if (isPrimitive[0]) {
10.121 + continue;
10.122 + }
10.123 + if (isModel[0]) {
10.124 + continue;
10.125 + }
10.126 + if ("java.lang.String".equals(t)) {
10.127 + continue;
10.128 + }
10.129 + error("The type " + t + " should be defined by @Model annotation", entry.getKey());
10.130 + }
10.131 + }
10.132 + verify.clear();
10.133 + }
10.134 + return ok;
10.135 + }
10.136 +
10.137 + private InputStream openStream(String pkg, String name) throws IOException {
10.138 + try {
10.139 + FileObject fo = processingEnv.getFiler().getResource(
10.140 + StandardLocation.SOURCE_PATH, pkg, name);
10.141 + return fo.openInputStream();
10.142 + } catch (IOException ex) {
10.143 + return processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, pkg, name).openInputStream();
10.144 + }
10.145 + }
10.146 +
10.147 + private void error(String msg, Element e) {
10.148 + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, msg, e);
10.149 + }
10.150 +
10.151 + private boolean processModel(Element e) {
10.152 + boolean ok = true;
10.153 + Model m = e.getAnnotation(Model.class);
10.154 + if (m == null) {
10.155 + return true;
10.156 + }
10.157 + String pkg = findPkgName(e);
10.158 + Writer w;
10.159 + String className = m.className();
10.160 + models.put(e, className);
10.161 + try {
10.162 + StringWriter body = new StringWriter();
10.163 + List<String> propsGetSet = new ArrayList<>();
10.164 + List<String> functions = new ArrayList<>();
10.165 + Map<String, Collection<String>> propsDeps = new HashMap<>();
10.166 + Map<String, Collection<String>> functionDeps = new HashMap<>();
10.167 + Prprt[] props = createProps(e, m.properties());
10.168 +
10.169 + if (!generateComputedProperties(body, props, e.getEnclosedElements(), propsGetSet, propsDeps)) {
10.170 + ok = false;
10.171 + }
10.172 + if (!generateOnChange(e, propsDeps, props, className, functionDeps)) {
10.173 + ok = false;
10.174 + }
10.175 + if (!generateProperties(e, body, props, propsGetSet, propsDeps, functionDeps)) {
10.176 + ok = false;
10.177 + }
10.178 + if (!generateFunctions(e, body, className, e.getEnclosedElements(), functions)) {
10.179 + ok = false;
10.180 + }
10.181 + FileObject java = processingEnv.getFiler().createSourceFile(pkg + '.' + className, e);
10.182 + w = new OutputStreamWriter(java.openOutputStream());
10.183 + try {
10.184 + w.append("package " + pkg + ";\n");
10.185 + w.append("import org.apidesign.bck2brwsr.htmlpage.api.*;\n");
10.186 + w.append("import org.apidesign.bck2brwsr.htmlpage.KOList;\n");
10.187 + w.append("import org.apidesign.bck2brwsr.core.JavaScriptOnly;\n");
10.188 + w.append("public final class ").append(className).append(" implements Cloneable {\n");
10.189 + w.append(" private boolean locked;\n");
10.190 + w.append(" private org.apidesign.bck2brwsr.htmlpage.Knockout ko;\n");
10.191 + w.append(body.toString());
10.192 + w.append(" private static Class<" + inPckName(e) + "> modelFor() { return null; }\n");
10.193 + w.append(" public ").append(className).append("() {\n");
10.194 + w.append(" };\n");
10.195 + w.append(" private org.apidesign.bck2brwsr.htmlpage.Knockout intKnckt() {\n");
10.196 + w.append(" if (ko != null) return ko;\n");
10.197 + w.append(" return ko = org.apidesign.bck2brwsr.htmlpage.Knockout.applyBindings(this, ");
10.198 + writeStringArray(propsGetSet, w);
10.199 + w.append(", ");
10.200 + writeStringArray(functions, w);
10.201 + w.append(" );\n");
10.202 + w.append(" };\n");
10.203 + w.append(" ").append(className).append("(Object json) {\n");
10.204 + int values = 0;
10.205 + for (int i = 0; i < propsGetSet.size(); i += 4) {
10.206 + Prprt p = findPrprt(props, propsGetSet.get(i));
10.207 + if (p == null) {
10.208 + continue;
10.209 + }
10.210 + values++;
10.211 + }
10.212 + w.append(" Object[] ret = new Object[" + values + "];\n");
10.213 + w.append(" org.apidesign.bck2brwsr.htmlpage.ConvertTypes.extractJSON(json, new String[] {\n");
10.214 + for (int i = 0; i < propsGetSet.size(); i += 4) {
10.215 + Prprt p = findPrprt(props, propsGetSet.get(i));
10.216 + if (p == null) {
10.217 + continue;
10.218 + }
10.219 + w.append(" \"").append(propsGetSet.get(i)).append("\",\n");
10.220 + }
10.221 + w.append(" }, ret);\n");
10.222 + for (int i = 0, cnt = 0, prop = 0; i < propsGetSet.size(); i += 4) {
10.223 + final String pn = propsGetSet.get(i);
10.224 + Prprt p = findPrprt(props, pn);
10.225 + if (p == null) {
10.226 + continue;
10.227 + }
10.228 + boolean[] isModel = { false };
10.229 + boolean[] isEnum = { false };
10.230 + boolean isPrimitive[] = { false };
10.231 + String type = checkType(props[prop++], isModel, isEnum, isPrimitive);
10.232 + if (p.array()) {
10.233 + w.append("if (ret[" + cnt + "] instanceof Object[]) {\n");
10.234 + w.append(" for (Object e : ((Object[])ret[" + cnt + "])) {\n");
10.235 + if (isModel[0]) {
10.236 + w.append(" this.prop_").append(pn).append(".add(new ");
10.237 + w.append(type).append("(e));\n");
10.238 + } else if (isEnum[0]) {
10.239 + w.append(" this.prop_").append(pn);
10.240 + w.append(".add(e == null ? null : ");
10.241 + w.append(type).append(".valueOf((String)e));\n");
10.242 + } else {
10.243 + if (isPrimitive(type)) {
10.244 + w.append(" this.prop_").append(pn).append(".add(((Number)e).");
10.245 + w.append(type).append("Value());\n");
10.246 + } else {
10.247 + w.append(" this.prop_").append(pn).append(".add((");
10.248 + w.append(type).append(")e);\n");
10.249 + }
10.250 + }
10.251 + w.append(" }\n");
10.252 + w.append("}\n");
10.253 + } else {
10.254 + if (isEnum[0]) {
10.255 + w.append(" this.prop_").append(pn);
10.256 + w.append(" = ret[" + cnt + "] == null ? null : ");
10.257 + w.append(type).append(".valueOf((String)ret[" + cnt + "]);\n");
10.258 + } else if (isPrimitive(type)) {
10.259 + w.append(" this.prop_").append(pn);
10.260 + w.append(" = ((Number)").append("ret[" + cnt + "]).");
10.261 + w.append(type).append("Value();\n");
10.262 + } else {
10.263 + w.append(" this.prop_").append(pn);
10.264 + w.append(" = (").append(type).append(')');
10.265 + w.append("ret[" + cnt + "];\n");
10.266 + }
10.267 + }
10.268 + cnt++;
10.269 + }
10.270 + w.append(" };\n");
10.271 + writeToString(props, w);
10.272 + writeClone(className, props, w);
10.273 + w.append(" public Object koData() {\n");
10.274 + w.append(" return intKnckt().koData();\n");
10.275 + w.append(" }\n");
10.276 + w.append("}\n");
10.277 + } finally {
10.278 + w.close();
10.279 + }
10.280 + } catch (IOException ex) {
10.281 + error("Can't create " + className + ".java", e);
10.282 + return false;
10.283 + }
10.284 + return ok;
10.285 + }
10.286 +
10.287 + private static String type(String tag) {
10.288 + if (tag.equals("title")) {
10.289 + return "Title";
10.290 + }
10.291 + if (tag.equals("button")) {
10.292 + return "Button";
10.293 + }
10.294 + if (tag.equals("input")) {
10.295 + return "Input";
10.296 + }
10.297 + if (tag.equals("canvas")) {
10.298 + return "Canvas";
10.299 + }
10.300 + if (tag.equals("img")) {
10.301 + return "Image";
10.302 + }
10.303 + return "Element";
10.304 + }
10.305 +
10.306 + private boolean generateProperties(
10.307 + Element where,
10.308 + Writer w, Prprt[] properties,
10.309 + Collection<String> props,
10.310 + Map<String,Collection<String>> deps,
10.311 + Map<String,Collection<String>> functionDeps
10.312 + ) throws IOException {
10.313 + boolean ok = true;
10.314 + for (Prprt p : properties) {
10.315 + final String tn;
10.316 + tn = typeName(where, p);
10.317 + String[] gs = toGetSet(p.name(), tn, p.array());
10.318 +
10.319 + if (p.array()) {
10.320 + w.write("private KOList<" + tn + "> prop_" + p.name() + " = new KOList<" + tn + ">(\""
10.321 + + p.name() + "\"");
10.322 + Collection<String> dependants = deps.get(p.name());
10.323 + if (dependants != null) {
10.324 + for (String depProp : dependants) {
10.325 + w.write(", ");
10.326 + w.write('\"');
10.327 + w.write(depProp);
10.328 + w.write('\"');
10.329 + }
10.330 + }
10.331 + w.write(")");
10.332 +
10.333 + dependants = functionDeps.get(p.name());
10.334 + if (dependants != null) {
10.335 + w.write(".onChange(new Runnable() { public void run() {\n");
10.336 + for (String call : dependants) {
10.337 + w.append(call);
10.338 + }
10.339 + w.write("}})");
10.340 + }
10.341 + w.write(";\n");
10.342 +
10.343 + w.write("public java.util.List<" + tn + "> " + gs[0] + "() {\n");
10.344 + w.write(" if (locked) throw new IllegalStateException();\n");
10.345 + w.write(" prop_" + p.name() + ".assign(ko);\n");
10.346 + w.write(" return prop_" + p.name() + ";\n");
10.347 + w.write("}\n");
10.348 + } else {
10.349 + w.write("private " + tn + " prop_" + p.name() + ";\n");
10.350 + w.write("public " + tn + " " + gs[0] + "() {\n");
10.351 + w.write(" if (locked) throw new IllegalStateException();\n");
10.352 + w.write(" return prop_" + p.name() + ";\n");
10.353 + w.write("}\n");
10.354 + w.write("public void " + gs[1] + "(" + tn + " v) {\n");
10.355 + w.write(" if (locked) throw new IllegalStateException();\n");
10.356 + w.write(" prop_" + p.name() + " = v;\n");
10.357 + w.write(" if (ko != null) {\n");
10.358 + w.write(" ko.valueHasMutated(\"" + p.name() + "\");\n");
10.359 + Collection<String> dependants = deps.get(p.name());
10.360 + if (dependants != null) {
10.361 + for (String depProp : dependants) {
10.362 + w.write(" ko.valueHasMutated(\"" + depProp + "\");\n");
10.363 + }
10.364 + }
10.365 + w.write(" }\n");
10.366 + dependants = functionDeps.get(p.name());
10.367 + if (dependants != null) {
10.368 + for (String call : dependants) {
10.369 + w.append(call);
10.370 + }
10.371 + }
10.372 + w.write("}\n");
10.373 + }
10.374 +
10.375 + props.add(p.name());
10.376 + props.add(gs[2]);
10.377 + props.add(gs[3]);
10.378 + props.add(gs[0]);
10.379 + }
10.380 + return ok;
10.381 + }
10.382 +
10.383 + private boolean generateComputedProperties(
10.384 + Writer w, Prprt[] fixedProps,
10.385 + Collection<? extends Element> arr, Collection<String> props,
10.386 + Map<String,Collection<String>> deps
10.387 + ) throws IOException {
10.388 + boolean ok = true;
10.389 + for (Element e : arr) {
10.390 + if (e.getKind() != ElementKind.METHOD) {
10.391 + continue;
10.392 + }
10.393 + if (e.getAnnotation(ComputedProperty.class) == null) {
10.394 + continue;
10.395 + }
10.396 + ExecutableElement ee = (ExecutableElement)e;
10.397 + final TypeMirror rt = ee.getReturnType();
10.398 + final Types tu = processingEnv.getTypeUtils();
10.399 + TypeMirror ert = tu.erasure(rt);
10.400 + String tn = fqn(ert, ee);
10.401 + boolean array = false;
10.402 + if (tn.equals("java.util.List")) {
10.403 + array = true;
10.404 + }
10.405 +
10.406 + final String sn = ee.getSimpleName().toString();
10.407 + String[] gs = toGetSet(sn, tn, array);
10.408 +
10.409 + w.write("public " + tn + " " + gs[0] + "() {\n");
10.410 + w.write(" if (locked) throw new IllegalStateException();\n");
10.411 + int arg = 0;
10.412 + for (VariableElement pe : ee.getParameters()) {
10.413 + final String dn = pe.getSimpleName().toString();
10.414 +
10.415 + if (!verifyPropName(pe, dn, fixedProps)) {
10.416 + ok = false;
10.417 + }
10.418 +
10.419 + final String dt = fqn(pe.asType(), ee);
10.420 + String[] call = toGetSet(dn, dt, false);
10.421 + w.write(" " + dt + " arg" + (++arg) + " = ");
10.422 + w.write(call[0] + "();\n");
10.423 +
10.424 + Collection<String> depends = deps.get(dn);
10.425 + if (depends == null) {
10.426 + depends = new LinkedHashSet<>();
10.427 + deps.put(dn, depends);
10.428 + }
10.429 + depends.add(sn);
10.430 + }
10.431 + w.write(" try {\n");
10.432 + w.write(" locked = true;\n");
10.433 + w.write(" return " + fqn(ee.getEnclosingElement().asType(), ee) + '.' + e.getSimpleName() + "(");
10.434 + String sep = "";
10.435 + for (int i = 1; i <= arg; i++) {
10.436 + w.write(sep);
10.437 + w.write("arg" + i);
10.438 + sep = ", ";
10.439 + }
10.440 + w.write(");\n");
10.441 + w.write(" } finally {\n");
10.442 + w.write(" locked = false;\n");
10.443 + w.write(" }\n");
10.444 + w.write("}\n");
10.445 +
10.446 + props.add(e.getSimpleName().toString());
10.447 + props.add(gs[2]);
10.448 + props.add(null);
10.449 + props.add(gs[0]);
10.450 + }
10.451 +
10.452 + return ok;
10.453 + }
10.454 +
10.455 + private static String[] toGetSet(String name, String type, boolean array) {
10.456 + String n = Character.toUpperCase(name.charAt(0)) + name.substring(1);
10.457 + String bck2brwsrType = "L" + type.replace('.', '_') + "_2";
10.458 + if ("int".equals(type)) {
10.459 + bck2brwsrType = "I";
10.460 + }
10.461 + if ("double".equals(type)) {
10.462 + bck2brwsrType = "D";
10.463 + }
10.464 + String pref = "get";
10.465 + if ("boolean".equals(type)) {
10.466 + pref = "is";
10.467 + bck2brwsrType = "Z";
10.468 + }
10.469 + final String nu = n.replace('.', '_');
10.470 + if (array) {
10.471 + return new String[] {
10.472 + "get" + n,
10.473 + null,
10.474 + "get" + nu + "__Ljava_util_List_2",
10.475 + null
10.476 + };
10.477 + }
10.478 + return new String[]{
10.479 + pref + n,
10.480 + "set" + n,
10.481 + pref + nu + "__" + bck2brwsrType,
10.482 + "set" + nu + "__V" + bck2brwsrType
10.483 + };
10.484 + }
10.485 +
10.486 + private String typeName(Element where, Prprt p) {
10.487 + String ret;
10.488 + boolean[] isModel = { false };
10.489 + boolean[] isEnum = { false };
10.490 + boolean isPrimitive[] = { false };
10.491 + ret = checkType(p, isModel, isEnum, isPrimitive);
10.492 + if (p.array()) {
10.493 + String bt = findBoxedType(ret);
10.494 + if (bt != null) {
10.495 + return bt;
10.496 + }
10.497 + }
10.498 + return ret;
10.499 + }
10.500 +
10.501 + private static String findBoxedType(String ret) {
10.502 + if (ret.equals("boolean")) {
10.503 + return Boolean.class.getName();
10.504 + }
10.505 + if (ret.equals("byte")) {
10.506 + return Byte.class.getName();
10.507 + }
10.508 + if (ret.equals("short")) {
10.509 + return Short.class.getName();
10.510 + }
10.511 + if (ret.equals("char")) {
10.512 + return Character.class.getName();
10.513 + }
10.514 + if (ret.equals("int")) {
10.515 + return Integer.class.getName();
10.516 + }
10.517 + if (ret.equals("long")) {
10.518 + return Long.class.getName();
10.519 + }
10.520 + if (ret.equals("float")) {
10.521 + return Float.class.getName();
10.522 + }
10.523 + if (ret.equals("double")) {
10.524 + return Double.class.getName();
10.525 + }
10.526 + return null;
10.527 + }
10.528 +
10.529 + private boolean verifyPropName(Element e, String propName, Prprt[] existingProps) {
10.530 + StringBuilder sb = new StringBuilder();
10.531 + String sep = "";
10.532 + for (Prprt Prprt : existingProps) {
10.533 + if (Prprt.name().equals(propName)) {
10.534 + return true;
10.535 + }
10.536 + sb.append(sep);
10.537 + sb.append('"');
10.538 + sb.append(Prprt.name());
10.539 + sb.append('"');
10.540 + sep = ", ";
10.541 + }
10.542 + error(
10.543 + propName + " is not one of known properties: " + sb
10.544 + , e
10.545 + );
10.546 + return false;
10.547 + }
10.548 +
10.549 + private static String findPkgName(Element e) {
10.550 + for (;;) {
10.551 + if (e.getKind() == ElementKind.PACKAGE) {
10.552 + return ((PackageElement)e).getQualifiedName().toString();
10.553 + }
10.554 + e = e.getEnclosingElement();
10.555 + }
10.556 + }
10.557 +
10.558 + private boolean generateFunctions(
10.559 + Element clazz, StringWriter body, String className,
10.560 + List<? extends Element> enclosedElements, List<String> functions
10.561 + ) {
10.562 + for (Element m : enclosedElements) {
10.563 + if (m.getKind() != ElementKind.METHOD) {
10.564 + continue;
10.565 + }
10.566 + ExecutableElement e = (ExecutableElement)m;
10.567 + Function onF = e.getAnnotation(Function.class);
10.568 + if (onF == null) {
10.569 + continue;
10.570 + }
10.571 + if (!e.getModifiers().contains(Modifier.STATIC)) {
10.572 + error("@OnFunction method needs to be static", e);
10.573 + return false;
10.574 + }
10.575 + if (e.getModifiers().contains(Modifier.PRIVATE)) {
10.576 + error("@OnFunction method cannot be private", e);
10.577 + return false;
10.578 + }
10.579 + if (e.getReturnType().getKind() != TypeKind.VOID) {
10.580 + error("@OnFunction method should return void", e);
10.581 + return false;
10.582 + }
10.583 + String n = e.getSimpleName().toString();
10.584 + body.append("private void ").append(n).append("(Object data, Object ev) {\n");
10.585 + body.append(" ").append(clazz.getSimpleName()).append(".").append(n).append("(");
10.586 + body.append(wrapParams(e, null, className, "ev", "data"));
10.587 + body.append(");\n");
10.588 + body.append("}\n");
10.589 +
10.590 + functions.add(n);
10.591 + functions.add(n + "__VLjava_lang_Object_2Ljava_lang_Object_2");
10.592 + }
10.593 + return true;
10.594 + }
10.595 +
10.596 + private boolean generateOnChange(Element clazz, Map<String,Collection<String>> propDeps,
10.597 + Prprt[] properties, String className,
10.598 + Map<String, Collection<String>> functionDeps
10.599 + ) {
10.600 + for (Element m : clazz.getEnclosedElements()) {
10.601 + if (m.getKind() != ElementKind.METHOD) {
10.602 + continue;
10.603 + }
10.604 + ExecutableElement e = (ExecutableElement) m;
10.605 + OnPropertyChange onPC = e.getAnnotation(OnPropertyChange.class);
10.606 + if (onPC == null) {
10.607 + continue;
10.608 + }
10.609 + for (String pn : onPC.value()) {
10.610 + if (findPrprt(properties, pn) == null && findDerivedFrom(propDeps, pn).isEmpty()) {
10.611 + error("No Prprt named '" + pn + "' in the model", clazz);
10.612 + return false;
10.613 + }
10.614 + }
10.615 + if (!e.getModifiers().contains(Modifier.STATIC)) {
10.616 + error("@OnPrprtChange method needs to be static", e);
10.617 + return false;
10.618 + }
10.619 + if (e.getModifiers().contains(Modifier.PRIVATE)) {
10.620 + error("@OnPrprtChange method cannot be private", e);
10.621 + return false;
10.622 + }
10.623 + if (e.getReturnType().getKind() != TypeKind.VOID) {
10.624 + error("@OnPrprtChange method should return void", e);
10.625 + return false;
10.626 + }
10.627 + String n = e.getSimpleName().toString();
10.628 +
10.629 +
10.630 + for (String pn : onPC.value()) {
10.631 + StringBuilder call = new StringBuilder();
10.632 + call.append(" ").append(clazz.getSimpleName()).append(".").append(n).append("(");
10.633 + call.append(wrapPropName(e, className, "name", pn));
10.634 + call.append(");\n");
10.635 +
10.636 + Collection<String> change = functionDeps.get(pn);
10.637 + if (change == null) {
10.638 + change = new ArrayList<>();
10.639 + functionDeps.put(pn, change);
10.640 + }
10.641 + change.add(call.toString());
10.642 + for (String dpn : findDerivedFrom(propDeps, pn)) {
10.643 + change = functionDeps.get(dpn);
10.644 + if (change == null) {
10.645 + change = new ArrayList<>();
10.646 + functionDeps.put(dpn, change);
10.647 + }
10.648 + change.add(call.toString());
10.649 + }
10.650 + }
10.651 + }
10.652 + return true;
10.653 + }
10.654 +
10.655 + private boolean generateReceive(
10.656 + Element clazz, StringWriter body, String className,
10.657 + List<? extends Element> enclosedElements, List<String> functions
10.658 + ) {
10.659 + for (Element m : enclosedElements) {
10.660 + if (m.getKind() != ElementKind.METHOD) {
10.661 + continue;
10.662 + }
10.663 + ExecutableElement e = (ExecutableElement)m;
10.664 + OnReceive onR = e.getAnnotation(OnReceive.class);
10.665 + if (onR == null) {
10.666 + continue;
10.667 + }
10.668 + if (!e.getModifiers().contains(Modifier.STATIC)) {
10.669 + error("@OnReceive method needs to be static", e);
10.670 + return false;
10.671 + }
10.672 + if (e.getModifiers().contains(Modifier.PRIVATE)) {
10.673 + error("@OnReceive method cannot be private", e);
10.674 + return false;
10.675 + }
10.676 + if (e.getReturnType().getKind() != TypeKind.VOID) {
10.677 + error("@OnReceive method should return void", e);
10.678 + return false;
10.679 + }
10.680 + String modelClass = null;
10.681 + boolean expectsList = false;
10.682 + List<String> args = new ArrayList<>();
10.683 + {
10.684 + for (VariableElement ve : e.getParameters()) {
10.685 + TypeMirror modelType = null;
10.686 + if (ve.asType().toString().equals(className)) {
10.687 + args.add(className + ".this");
10.688 + } else if (isModel(ve.asType())) {
10.689 + modelType = ve.asType();
10.690 + } else if (ve.asType().getKind() == TypeKind.ARRAY) {
10.691 + modelType = ((ArrayType)ve.asType()).getComponentType();
10.692 + expectsList = true;
10.693 + }
10.694 + if (modelType != null) {
10.695 + if (modelClass != null) {
10.696 + error("There can be only one model class among arguments", e);
10.697 + } else {
10.698 + modelClass = modelType.toString();
10.699 + if (expectsList) {
10.700 + args.add("arr");
10.701 + } else {
10.702 + args.add("arr[0]");
10.703 + }
10.704 + }
10.705 + }
10.706 + }
10.707 + }
10.708 + if (modelClass == null) {
10.709 + error("The method needs to have one @Model class as parameter", e);
10.710 + }
10.711 + String n = e.getSimpleName().toString();
10.712 + body.append("public void ").append(n).append("(");
10.713 + StringBuilder assembleURL = new StringBuilder();
10.714 + String jsonpVarName = null;
10.715 + {
10.716 + String sep = "";
10.717 + boolean skipJSONP = onR.jsonp().isEmpty();
10.718 + for (String p : findParamNames(e, onR.url(), assembleURL)) {
10.719 + if (!skipJSONP && p.equals(onR.jsonp())) {
10.720 + skipJSONP = true;
10.721 + jsonpVarName = p;
10.722 + continue;
10.723 + }
10.724 + body.append(sep);
10.725 + body.append("String ").append(p);
10.726 + sep = ", ";
10.727 + }
10.728 + if (!skipJSONP) {
10.729 + error(
10.730 + "Name of jsonp attribute ('" + onR.jsonp() +
10.731 + "') is not used in url attribute '" + onR.url() + "'", e
10.732 + );
10.733 + }
10.734 + }
10.735 + body.append(") {\n");
10.736 + body.append(" final Object[] result = { null };\n");
10.737 + body.append(
10.738 + " class ProcessResult implements Runnable {\n" +
10.739 + " @Override\n" +
10.740 + " public void run() {\n" +
10.741 + " Object value = result[0];\n");
10.742 + body.append(
10.743 + " " + modelClass + "[] arr;\n");
10.744 + body.append(
10.745 + " if (value instanceof Object[]) {\n" +
10.746 + " Object[] data = ((Object[])value);\n" +
10.747 + " arr = new " + modelClass + "[data.length];\n" +
10.748 + " for (int i = 0; i < data.length; i++) {\n" +
10.749 + " arr[i] = new " + modelClass + "(data[i]);\n" +
10.750 + " }\n" +
10.751 + " } else {\n" +
10.752 + " arr = new " + modelClass + "[1];\n" +
10.753 + " arr[0] = new " + modelClass + "(value);\n" +
10.754 + " }\n"
10.755 + );
10.756 + {
10.757 + body.append(clazz.getSimpleName()).append(".").append(n).append("(");
10.758 + String sep = "";
10.759 + for (String arg : args) {
10.760 + body.append(sep);
10.761 + body.append(arg);
10.762 + sep = ", ";
10.763 + }
10.764 + body.append(");\n");
10.765 + }
10.766 + body.append(
10.767 + " }\n" +
10.768 + " }\n"
10.769 + );
10.770 + body.append(" ProcessResult pr = new ProcessResult();\n");
10.771 + if (jsonpVarName != null) {
10.772 + body.append(" String ").append(jsonpVarName).
10.773 + append(" = org.apidesign.bck2brwsr.htmlpage.ConvertTypes.createJSONP(result, pr);\n");
10.774 + }
10.775 + body.append(" org.apidesign.bck2brwsr.htmlpage.ConvertTypes.loadJSON(\n ");
10.776 + body.append(assembleURL);
10.777 + body.append(", result, pr, ").append(jsonpVarName).append("\n );\n");
10.778 +// body.append(" ").append(clazz.getSimpleName()).append(".").append(n).append("(");
10.779 +// body.append(wrapParams(e, null, className, "ev", "data"));
10.780 +// body.append(");\n");
10.781 + body.append("}\n");
10.782 + }
10.783 + return true;
10.784 + }
10.785 +
10.786 + private CharSequence wrapParams(
10.787 + ExecutableElement ee, String id, String className, String evName, String dataName
10.788 + ) {
10.789 + TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType();
10.790 + StringBuilder params = new StringBuilder();
10.791 + boolean first = true;
10.792 + for (VariableElement ve : ee.getParameters()) {
10.793 + if (!first) {
10.794 + params.append(", ");
10.795 + }
10.796 + first = false;
10.797 + String toCall = null;
10.798 + if (ve.asType() == stringType) {
10.799 + if (ve.getSimpleName().contentEquals("id")) {
10.800 + params.append('"').append(id).append('"');
10.801 + continue;
10.802 + }
10.803 + toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toString(";
10.804 + }
10.805 + if (ve.asType().getKind() == TypeKind.DOUBLE) {
10.806 + toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toDouble(";
10.807 + }
10.808 + if (ve.asType().getKind() == TypeKind.INT) {
10.809 + toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toInt(";
10.810 + }
10.811 + if (dataName != null && ve.getSimpleName().contentEquals(dataName) && isModel(ve.asType())) {
10.812 + toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toModel(" + ve.asType() + ".class, ";
10.813 + }
10.814 +
10.815 + if (toCall != null) {
10.816 + params.append(toCall);
10.817 + if (dataName != null && ve.getSimpleName().contentEquals(dataName)) {
10.818 + params.append(dataName);
10.819 + params.append(", null");
10.820 + } else {
10.821 + if (evName == null) {
10.822 + final StringBuilder sb = new StringBuilder();
10.823 + sb.append("Unexpected string parameter name.");
10.824 + if (dataName != null) {
10.825 + sb.append(" Try \"").append(dataName).append("\"");
10.826 + }
10.827 + error(sb.toString(), ee);
10.828 + }
10.829 + params.append(evName);
10.830 + params.append(", \"");
10.831 + params.append(ve.getSimpleName().toString());
10.832 + params.append("\"");
10.833 + }
10.834 + params.append(")");
10.835 + continue;
10.836 + }
10.837 + String rn = fqn(ve.asType(), ee);
10.838 + int last = rn.lastIndexOf('.');
10.839 + if (last >= 0) {
10.840 + rn = rn.substring(last + 1);
10.841 + }
10.842 + if (rn.equals(className)) {
10.843 + params.append(className).append(".this");
10.844 + continue;
10.845 + }
10.846 + error(
10.847 + "@On method can only accept String named 'id' or " + className + " arguments",
10.848 + ee
10.849 + );
10.850 + }
10.851 + return params;
10.852 + }
10.853 +
10.854 +
10.855 + private CharSequence wrapPropName(
10.856 + ExecutableElement ee, String className, String propName, String propValue
10.857 + ) {
10.858 + TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType();
10.859 + StringBuilder params = new StringBuilder();
10.860 + boolean first = true;
10.861 + for (VariableElement ve : ee.getParameters()) {
10.862 + if (!first) {
10.863 + params.append(", ");
10.864 + }
10.865 + first = false;
10.866 + if (ve.asType() == stringType) {
10.867 + if (propName != null && ve.getSimpleName().contentEquals(propName)) {
10.868 + params.append('"').append(propValue).append('"');
10.869 + } else {
10.870 + error("Unexpected string parameter name. Try \"" + propName + "\".", ee);
10.871 + }
10.872 + continue;
10.873 + }
10.874 + String rn = fqn(ve.asType(), ee);
10.875 + int last = rn.lastIndexOf('.');
10.876 + if (last >= 0) {
10.877 + rn = rn.substring(last + 1);
10.878 + }
10.879 + if (rn.equals(className)) {
10.880 + params.append(className).append(".this");
10.881 + continue;
10.882 + }
10.883 + error(
10.884 + "@OnPrprtChange method can only accept String or " + className + " arguments",
10.885 + ee);
10.886 + }
10.887 + return params;
10.888 + }
10.889 +
10.890 + private boolean isModel(TypeMirror tm) {
10.891 + final Element e = processingEnv.getTypeUtils().asElement(tm);
10.892 + if (e == null) {
10.893 + return false;
10.894 + }
10.895 + for (Element ch : e.getEnclosedElements()) {
10.896 + if (ch.getKind() == ElementKind.METHOD) {
10.897 + ExecutableElement ee = (ExecutableElement)ch;
10.898 + if (ee.getParameters().isEmpty() && ee.getSimpleName().contentEquals("modelFor")) {
10.899 + return true;
10.900 + }
10.901 + }
10.902 + }
10.903 + return models.values().contains(e.getSimpleName().toString());
10.904 + }
10.905 +
10.906 + private void writeStringArray(List<String> strings, Writer w) throws IOException {
10.907 + w.write("new String[] {\n");
10.908 + String sep = "";
10.909 + for (String n : strings) {
10.910 + w.write(sep);
10.911 + if (n == null) {
10.912 + w.write(" null");
10.913 + } else {
10.914 + w.write(" \"" + n + "\"");
10.915 + }
10.916 + sep = ",\n";
10.917 + }
10.918 + w.write("\n }");
10.919 + }
10.920 +
10.921 + private void writeToString(Prprt[] props, Writer w) throws IOException {
10.922 + w.write(" public String toString() {\n");
10.923 + w.write(" StringBuilder sb = new StringBuilder();\n");
10.924 + w.write(" sb.append('{');\n");
10.925 + String sep = "";
10.926 + for (Prprt p : props) {
10.927 + w.write(sep);
10.928 + w.append(" sb.append(\"" + p.name() + ": \");\n");
10.929 + w.append(" sb.append(org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toJSON(prop_");
10.930 + w.append(p.name()).append("));\n");
10.931 + sep = " sb.append(',');\n";
10.932 + }
10.933 + w.write(" sb.append('}');\n");
10.934 + w.write(" return sb.toString();\n");
10.935 + w.write(" }\n");
10.936 + }
10.937 + private void writeClone(String className, Prprt[] props, Writer w) throws IOException {
10.938 + w.write(" public " + className + " clone() {\n");
10.939 + w.write(" " + className + " ret = new " + className + "();\n");
10.940 + for (Prprt p : props) {
10.941 + if (!p.array()) {
10.942 + boolean isModel[] = { false };
10.943 + boolean isEnum[] = { false };
10.944 + boolean isPrimitive[] = { false };
10.945 + checkType(p, isModel, isEnum, isPrimitive);
10.946 + if (!isModel[0]) {
10.947 + w.write(" ret.prop_" + p.name() + " = prop_" + p.name() + ";\n");
10.948 + continue;
10.949 + }
10.950 + w.write(" ret.prop_" + p.name() + " = prop_" + p.name() + ".clone();\n");
10.951 + } else {
10.952 + w.write(" ret.prop_" + p.name() + " = prop_" + p.name() + ".clone();\n");
10.953 + }
10.954 + }
10.955 +
10.956 + w.write(" return ret;\n");
10.957 + w.write(" }\n");
10.958 + }
10.959 +
10.960 + private String inPckName(Element e) {
10.961 + StringBuilder sb = new StringBuilder();
10.962 + while (e.getKind() != ElementKind.PACKAGE) {
10.963 + if (sb.length() == 0) {
10.964 + sb.append(e.getSimpleName());
10.965 + } else {
10.966 + sb.insert(0, '.');
10.967 + sb.insert(0, e.getSimpleName());
10.968 + }
10.969 + e = e.getEnclosingElement();
10.970 + }
10.971 + return sb.toString();
10.972 + }
10.973 +
10.974 + private String fqn(TypeMirror pt, Element relative) {
10.975 + if (pt.getKind() == TypeKind.ERROR) {
10.976 + final Elements eu = processingEnv.getElementUtils();
10.977 + PackageElement pckg = eu.getPackageOf(relative);
10.978 + return pckg.getQualifiedName() + "." + pt.toString();
10.979 + }
10.980 + return pt.toString();
10.981 + }
10.982 +
10.983 + private String checkType(Prprt p, boolean[] isModel, boolean[] isEnum, boolean[] isPrimitive) {
10.984 + TypeMirror tm;
10.985 + try {
10.986 + String ret = p.typeName(processingEnv);
10.987 + TypeElement e = processingEnv.getElementUtils().getTypeElement(ret);
10.988 + if (e == null) {
10.989 + isModel[0] = true;
10.990 + isEnum[0] = false;
10.991 + isPrimitive[0] = false;
10.992 + return ret;
10.993 + }
10.994 + tm = e.asType();
10.995 + } catch (MirroredTypeException ex) {
10.996 + tm = ex.getTypeMirror();
10.997 + }
10.998 + tm = processingEnv.getTypeUtils().erasure(tm);
10.999 + isPrimitive[0] = tm.getKind().isPrimitive();
10.1000 + final Element e = processingEnv.getTypeUtils().asElement(tm);
10.1001 + final Model m = e == null ? null : e.getAnnotation(Model.class);
10.1002 +
10.1003 + String ret;
10.1004 + if (m != null) {
10.1005 + ret = findPkgName(e) + '.' + m.className();
10.1006 + isModel[0] = true;
10.1007 + models.put(e, m.className());
10.1008 + } else if (findModelForMthd(e)) {
10.1009 + ret = ((TypeElement)e).getQualifiedName().toString();
10.1010 + isModel[0] = true;
10.1011 + } else {
10.1012 + ret = tm.toString();
10.1013 + }
10.1014 + TypeMirror enm = processingEnv.getElementUtils().getTypeElement("java.lang.Enum").asType();
10.1015 + enm = processingEnv.getTypeUtils().erasure(enm);
10.1016 + isEnum[0] = processingEnv.getTypeUtils().isSubtype(tm, enm);
10.1017 + return ret;
10.1018 + }
10.1019 +
10.1020 + private static boolean findModelForMthd(Element clazz) {
10.1021 + if (clazz == null) {
10.1022 + return false;
10.1023 + }
10.1024 + for (Element e : clazz.getEnclosedElements()) {
10.1025 + if (e.getKind() == ElementKind.METHOD) {
10.1026 + ExecutableElement ee = (ExecutableElement)e;
10.1027 + if (
10.1028 + ee.getSimpleName().contentEquals("modelFor") &&
10.1029 + ee.getParameters().isEmpty()
10.1030 + ) {
10.1031 + return true;
10.1032 + }
10.1033 + }
10.1034 + }
10.1035 + return false;
10.1036 + }
10.1037 +
10.1038 + private Iterable<String> findParamNames(Element e, String url, StringBuilder assembleURL) {
10.1039 + List<String> params = new ArrayList<>();
10.1040 +
10.1041 + for (int pos = 0; ;) {
10.1042 + int next = url.indexOf('{', pos);
10.1043 + if (next == -1) {
10.1044 + assembleURL.append('"')
10.1045 + .append(url.substring(pos))
10.1046 + .append('"');
10.1047 + return params;
10.1048 + }
10.1049 + int close = url.indexOf('}', next);
10.1050 + if (close == -1) {
10.1051 + error("Unbalanced '{' and '}' in " + url, e);
10.1052 + return params;
10.1053 + }
10.1054 + final String paramName = url.substring(next + 1, close);
10.1055 + params.add(paramName);
10.1056 + assembleURL.append('"')
10.1057 + .append(url.substring(pos, next))
10.1058 + .append("\" + ").append(paramName).append(" + ");
10.1059 + pos = close + 1;
10.1060 + }
10.1061 + }
10.1062 +
10.1063 + private static Prprt findPrprt(Prprt[] properties, String propName) {
10.1064 + for (Prprt p : properties) {
10.1065 + if (propName.equals(p.name())) {
10.1066 + return p;
10.1067 + }
10.1068 + }
10.1069 + return null;
10.1070 + }
10.1071 +
10.1072 + private boolean isPrimitive(String type) {
10.1073 + return
10.1074 + "int".equals(type) ||
10.1075 + "double".equals(type) ||
10.1076 + "long".equals(type) ||
10.1077 + "short".equals(type) ||
10.1078 + "byte".equals(type) ||
10.1079 + "float".equals(type);
10.1080 + }
10.1081 +
10.1082 + private static Collection<String> findDerivedFrom(Map<String, Collection<String>> propsDeps, String derivedProp) {
10.1083 + Set<String> names = new HashSet<>();
10.1084 + for (Map.Entry<String, Collection<String>> e : propsDeps.entrySet()) {
10.1085 + if (e.getValue().contains(derivedProp)) {
10.1086 + names.add(e.getKey());
10.1087 + }
10.1088 + }
10.1089 + return names;
10.1090 + }
10.1091 +
10.1092 + private Prprt[] createProps(Element e, Property[] arr) {
10.1093 + Prprt[] ret = Prprt.wrap(processingEnv, e, arr);
10.1094 + Prprt[] prev = verify.put(e, ret);
10.1095 + if (prev != null) {
10.1096 + error("Two sets of properties for ", e);
10.1097 + }
10.1098 + return ret;
10.1099 + }
10.1100 +
10.1101 + private static class Prprt {
10.1102 + private final Element e;
10.1103 + private final AnnotationMirror tm;
10.1104 + private final Property p;
10.1105 +
10.1106 + public Prprt(Element e, AnnotationMirror tm, Property p) {
10.1107 + this.e = e;
10.1108 + this.tm = tm;
10.1109 + this.p = p;
10.1110 + }
10.1111 +
10.1112 + String name() {
10.1113 + return p.name();
10.1114 + }
10.1115 +
10.1116 + boolean array() {
10.1117 + return p.array();
10.1118 + }
10.1119 +
10.1120 + String typeName(ProcessingEnvironment env) {
10.1121 + try {
10.1122 + return p.type().getName();
10.1123 + } catch (IncompleteAnnotationException | AnnotationTypeMismatchException ex) {
10.1124 + for (Object v : getAnnoValues(env)) {
10.1125 + String s = v.toString().replace(" ", "");
10.1126 + if (s.startsWith("type=") && s.endsWith(".class")) {
10.1127 + return s.substring(5, s.length() - 6);
10.1128 + }
10.1129 + }
10.1130 + throw ex;
10.1131 + }
10.1132 + }
10.1133 +
10.1134 +
10.1135 + static Prprt[] wrap(ProcessingEnvironment pe, Element e, Property[] arr) {
10.1136 + if (arr.length == 0) {
10.1137 + return new Prprt[0];
10.1138 + }
10.1139 +
10.1140 + if (e.getKind() != ElementKind.CLASS) {
10.1141 + throw new IllegalStateException("" + e.getKind());
10.1142 + }
10.1143 + TypeElement te = (TypeElement)e;
10.1144 + List<? extends AnnotationValue> val = null;
10.1145 + for (AnnotationMirror an : te.getAnnotationMirrors()) {
10.1146 + for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : an.getElementValues().entrySet()) {
10.1147 + if (entry.getKey().getSimpleName().contentEquals("properties")) {
10.1148 + val = (List)entry.getValue().getValue();
10.1149 + break;
10.1150 + }
10.1151 + }
10.1152 + }
10.1153 + if (val == null || val.size() != arr.length) {
10.1154 + pe.getMessager().printMessage(Diagnostic.Kind.ERROR, "" + val, e);
10.1155 + return new Prprt[0];
10.1156 + }
10.1157 + Prprt[] ret = new Prprt[arr.length];
10.1158 + BIG: for (int i = 0; i < ret.length; i++) {
10.1159 + AnnotationMirror am = (AnnotationMirror)val.get(i).getValue();
10.1160 + ret[i] = new Prprt(e, am, arr[i]);
10.1161 +
10.1162 + }
10.1163 + return ret;
10.1164 + }
10.1165 +
10.1166 + private List<? extends Object> getAnnoValues(ProcessingEnvironment pe) {
10.1167 + try {
10.1168 + Class<?> trees = Class.forName("com.sun.tools.javac.api.JavacTrees");
10.1169 + Method m = trees.getMethod("instance", ProcessingEnvironment.class);
10.1170 + Object instance = m.invoke(null, pe);
10.1171 + m = instance.getClass().getMethod("getPath", Element.class, AnnotationMirror.class);
10.1172 + Object path = m.invoke(instance, e, tm);
10.1173 + m = path.getClass().getMethod("getLeaf");
10.1174 + Object leaf = m.invoke(path);
10.1175 + m = leaf.getClass().getMethod("getArguments");
10.1176 + return (List)m.invoke(leaf);
10.1177 + } catch (Exception ex) {
10.1178 + return Collections.emptyList();
10.1179 + }
10.1180 + }
10.1181 + }
10.1182 +
10.1183 +}
11.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
11.2 +++ b/json/src/test/java/net/java/html/json/ModelTest.java Fri Apr 19 10:01:02 2013 +0200
11.3 @@ -0,0 +1,241 @@
11.4 +/**
11.5 + * HTML via Java(tm) Language Bindings
11.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
11.7 + *
11.8 + * This program is free software: you can redistribute it and/or modify
11.9 + * it under the terms of the GNU General Public License as published by
11.10 + * the Free Software Foundation, version 2 of the License.
11.11 + *
11.12 + * This program is distributed in the hope that it will be useful,
11.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
11.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11.15 + * GNU General Public License for more details. apidesign.org
11.16 + * designates this particular file as subject to the
11.17 + * "Classpath" exception as provided by apidesign.org
11.18 + * in the License file that accompanied this code.
11.19 + *
11.20 + * You should have received a copy of the GNU General Public License
11.21 + * along with this program. Look for COPYING file in the top folder.
11.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
11.23 + */
11.24 +package net.java.html.json;
11.25 +
11.26 +import java.util.ArrayList;
11.27 +import java.util.Collections;
11.28 +import java.util.Iterator;
11.29 +import java.util.List;
11.30 +import java.util.ListIterator;
11.31 +import static org.testng.Assert.*;
11.32 +import org.testng.annotations.BeforeMethod;
11.33 +import org.testng.annotations.Test;
11.34 +
11.35 +/**
11.36 + *
11.37 + * @author Jaroslav Tulach <jtulach@netbeans.org>
11.38 + */
11.39 +@Model(className = "Modelik", properties = {
11.40 + @Property(name = "value", type = int.class),
11.41 + @Property(name = "count", type = int.class),
11.42 + @Property(name = "unrelated", type = long.class),
11.43 + @Property(name = "names", type = String.class, array = true),
11.44 + @Property(name = "values", type = int.class, array = true),
11.45 + @Property(name = "people", type = Person.class, array = true),
11.46 + @Property(name = "changedProperty", type=String.class)
11.47 +})
11.48 +public class ModelTest {
11.49 + private Modelik model;
11.50 + private static Modelik leakedModel;
11.51 +
11.52 + @BeforeMethod
11.53 + public void createModel() {
11.54 + model = new Modelik();
11.55 + }
11.56 +
11.57 + @Test public void classGeneratedWithSetterGetter() {
11.58 + model.setValue(10);
11.59 + assertEquals(10, model.getValue(), "Value changed");
11.60 + }
11.61 +
11.62 + @Test public void computedMethod() {
11.63 + model.setValue(4);
11.64 + assertEquals(16, model.getPowerValue());
11.65 + }
11.66 +
11.67 + @Test public void arrayIsMutable() {
11.68 + assertEquals(model.getNames().size(), 0, "Is empty");
11.69 + model.getNames().add("Jarda");
11.70 + assertEquals(model.getNames().size(), 1, "One element");
11.71 + }
11.72 +/*
11.73 + @Test public void arrayChangesNotified() {
11.74 + MockKnockout my = new MockKnockout();
11.75 + MockKnockout.next = my;
11.76 +
11.77 + model.applyBindings();
11.78 +
11.79 + model.getNames().add("Hello");
11.80 +
11.81 + assertFalse(my.mutated.isEmpty(), "There was a change" + my.mutated);
11.82 + assertTrue(my.mutated.contains("names"), "Change in names property: " + my.mutated);
11.83 +
11.84 + my.mutated.clear();
11.85 +
11.86 + Iterator<String> it = model.getNames().iterator();
11.87 + assertEquals(it.next(), "Hello");
11.88 + it.remove();
11.89 +
11.90 + assertFalse(my.mutated.isEmpty(), "There was a change" + my.mutated);
11.91 + assertTrue(my.mutated.contains("names"), "Change in names property: " + my.mutated);
11.92 +
11.93 + my.mutated.clear();
11.94 +
11.95 + ListIterator<String> lit = model.getNames().listIterator();
11.96 + lit.add("Jarda");
11.97 +
11.98 + assertFalse(my.mutated.isEmpty(), "There was a change" + my.mutated);
11.99 + assertTrue(my.mutated.contains("names"), "Change in names property: " + my.mutated);
11.100 + }
11.101 +
11.102 + @Test public void autoboxedArray() {
11.103 + MockKnockout my = new MockKnockout();
11.104 + MockKnockout.next = my;
11.105 +
11.106 + model.applyBindings();
11.107 +
11.108 + model.getValues().add(10);
11.109 +
11.110 + assertEquals(model.getValues().get(0), Integer.valueOf(10), "Really ten");
11.111 + }
11.112 +
11.113 + @Test public void derivedArrayProp() {
11.114 + MockKnockout my = new MockKnockout();
11.115 + MockKnockout.next = my;
11.116 +
11.117 + model.applyBindings();
11.118 +
11.119 + model.setCount(10);
11.120 +
11.121 + List<String> arr = model.getRepeat();
11.122 + assertEquals(arr.size(), 10, "Ten items: " + arr);
11.123 +
11.124 + my.mutated.clear();
11.125 +
11.126 + model.setCount(5);
11.127 +
11.128 + arr = model.getRepeat();
11.129 + assertEquals(arr.size(), 5, "Five items: " + arr);
11.130 +
11.131 + assertEquals(my.mutated.size(), 2, "Two properties changed: " + my.mutated);
11.132 + assertTrue(my.mutated.contains("repeat"), "Array is in there: " + my.mutated);
11.133 + assertTrue(my.mutated.contains("count"), "Count is in there: " + my.mutated);
11.134 + }
11.135 +
11.136 + @Test public void derivedPropertiesAreNotified() {
11.137 + MockKnockout my = new MockKnockout();
11.138 + MockKnockout.next = my;
11.139 +
11.140 + model.applyBindings();
11.141 +
11.142 + model.setValue(33);
11.143 +
11.144 + // not interested in change of this property
11.145 + my.mutated.remove("changedProperty");
11.146 +
11.147 + assertEquals(my.mutated.size(), 2, "Two properties changed: " + my.mutated);
11.148 + assertTrue(my.mutated.contains("powerValue"), "Power value is in there: " + my.mutated);
11.149 + assertTrue(my.mutated.contains("value"), "Simple value is in there: " + my.mutated);
11.150 +
11.151 + my.mutated.clear();
11.152 +
11.153 + model.setUnrelated(44);
11.154 +
11.155 +
11.156 + // not interested in change of this property
11.157 + my.mutated.remove("changedProperty");
11.158 + assertEquals(my.mutated.size(), 1, "One property changed: " + my.mutated);
11.159 + assertTrue(my.mutated.contains("unrelated"), "Its name is unrelated");
11.160 + }
11.161 + */
11.162 + @Test public void computedPropertyCannotWriteToModel() {
11.163 + leakedModel = model;
11.164 + try {
11.165 + String res = model.getNotAllowedWrite();
11.166 + fail("We should not be allowed to write to the model: " + res);
11.167 + } catch (IllegalStateException ex) {
11.168 + // OK, we can't read
11.169 + }
11.170 + }
11.171 +
11.172 + @Test public void computedPropertyCannotReadToModel() {
11.173 + leakedModel = model;
11.174 + try {
11.175 + String res = model.getNotAllowedRead();
11.176 + fail("We should not be allowed to read from the model: " + res);
11.177 + } catch (IllegalStateException ex) {
11.178 + // OK, we can't read
11.179 + }
11.180 + }
11.181 +
11.182 + @Function
11.183 + static void doSomething() {
11.184 + }
11.185 +
11.186 + @ComputedProperty
11.187 + static int powerValue(int value) {
11.188 + return value * value;
11.189 + }
11.190 +
11.191 + @OnPropertyChange({ "powerValue", "unrelated" })
11.192 + static void aPropertyChanged(Modelik m, String name) {
11.193 + m.setChangedProperty(name);
11.194 + }
11.195 +
11.196 + @OnPropertyChange({ "values" })
11.197 + static void anArrayPropertyChanged(String name, Modelik m) {
11.198 + m.setChangedProperty(name);
11.199 + }
11.200 +
11.201 + @Test public void changeAnything() {
11.202 + model.setCount(44);
11.203 + assertNull(model.getChangedProperty(), "No observed value change");
11.204 + }
11.205 + @Test public void changeValue() {
11.206 + model.setValue(33);
11.207 + assertEquals(model.getChangedProperty(), "powerValue", "power property changed");
11.208 + }
11.209 + @Test public void changeUnrelated() {
11.210 + model.setUnrelated(333);
11.211 + assertEquals(model.getChangedProperty(), "unrelated", "unrelated changed");
11.212 + }
11.213 +
11.214 + @Test public void changeInArray() {
11.215 + model.getValues().add(10);
11.216 + assertEquals(model.getChangedProperty(), "values", "Something added into the array");
11.217 + }
11.218 +
11.219 + @ComputedProperty
11.220 + static String notAllowedRead() {
11.221 + return "Not allowed callback: " + leakedModel.getUnrelated();
11.222 + }
11.223 +
11.224 + @ComputedProperty
11.225 + static String notAllowedWrite() {
11.226 + leakedModel.setUnrelated(11);
11.227 + return "Not allowed callback!";
11.228 + }
11.229 +
11.230 + @ComputedProperty
11.231 + static List<String> repeat(int count) {
11.232 + return Collections.nCopies(count, "Hello");
11.233 + }
11.234 +
11.235 + public @Test void hasPersonPropertyAndComputedFullName() {
11.236 + List<Person> arr = model.getPeople();
11.237 + assertEquals(arr.size(), 0, "By default empty");
11.238 + Person p = null;
11.239 + if (p != null) {
11.240 + String fullNameGenerated = p.getFullName();
11.241 + assertNotNull(fullNameGenerated);
11.242 + }
11.243 + }
11.244 +}
12.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
12.2 +++ b/json/src/test/java/net/java/html/json/PersonImpl.java Fri Apr 19 10:01:02 2013 +0200
12.3 @@ -0,0 +1,60 @@
12.4 +/**
12.5 + * HTML via Java(tm) Language Bindings
12.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
12.7 + *
12.8 + * This program is free software: you can redistribute it and/or modify
12.9 + * it under the terms of the GNU General Public License as published by
12.10 + * the Free Software Foundation, version 2 of the License.
12.11 + *
12.12 + * This program is distributed in the hope that it will be useful,
12.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
12.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12.15 + * GNU General Public License for more details. apidesign.org
12.16 + * designates this particular file as subject to the
12.17 + * "Classpath" exception as provided by apidesign.org
12.18 + * in the License file that accompanied this code.
12.19 + *
12.20 + * You should have received a copy of the GNU General Public License
12.21 + * along with this program. Look for COPYING file in the top folder.
12.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
12.23 + */
12.24 +package net.java.html.json;
12.25 +
12.26 +/**
12.27 + *
12.28 + * @author Jaroslav Tulach <jtulach@netbeans.org>
12.29 + */
12.30 +@Model(className = "Person", properties = {
12.31 + @Property(name = "firstName", type = String.class),
12.32 + @Property(name = "lastName", type = String.class),
12.33 + @Property(name = "sex", type = Sex.class)
12.34 +})
12.35 +final class PersonImpl {
12.36 + @ComputedProperty
12.37 + public static String fullName(String firstName, String lastName) {
12.38 + return firstName + " " + lastName;
12.39 + }
12.40 +
12.41 + @ComputedProperty
12.42 + public static String sexType(Sex sex) {
12.43 + return sex == null ? "unknown" : sex.toString();
12.44 + }
12.45 +
12.46 + @Function
12.47 + static void changeSex(Person p) {
12.48 + if (p.getSex() == Sex.MALE) {
12.49 + p.setSex(Sex.FEMALE);
12.50 + } else {
12.51 + p.setSex(Sex.MALE);
12.52 + }
12.53 + }
12.54 +
12.55 + @Model(className = "People", properties = {
12.56 + @Property(array = true, name = "info", type = Person.class),
12.57 + @Property(array = true, name = "nicknames", type = String.class),
12.58 + @Property(array = true, name = "age", type = int.class),
12.59 + @Property(array = true, name = "sex", type = Sex.class)
12.60 + })
12.61 + public class PeopleImpl {
12.62 + }
12.63 +}
13.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
13.2 +++ b/json/src/test/java/net/java/html/json/Sex.java Fri Apr 19 10:01:02 2013 +0200
13.3 @@ -0,0 +1,29 @@
13.4 +/**
13.5 + * HTML via Java(tm) Language Bindings
13.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
13.7 + *
13.8 + * This program is free software: you can redistribute it and/or modify
13.9 + * it under the terms of the GNU General Public License as published by
13.10 + * the Free Software Foundation, version 2 of the License.
13.11 + *
13.12 + * This program is distributed in the hope that it will be useful,
13.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
13.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13.15 + * GNU General Public License for more details. apidesign.org
13.16 + * designates this particular file as subject to the
13.17 + * "Classpath" exception as provided by apidesign.org
13.18 + * in the License file that accompanied this code.
13.19 + *
13.20 + * You should have received a copy of the GNU General Public License
13.21 + * along with this program. Look for COPYING file in the top folder.
13.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
13.23 + */
13.24 +package net.java.html.json;
13.25 +
13.26 +/**
13.27 + *
13.28 + * @author Jaroslav Tulach <jtulach@netbeans.org>
13.29 + */
13.30 +public enum Sex {
13.31 + MALE, FEMALE;
13.32 +}
14.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
14.2 +++ b/pom.xml Fri Apr 19 10:01:02 2013 +0200
14.3 @@ -0,0 +1,248 @@
14.4 +<?xml version="1.0" encoding="UTF-8"?>
14.5 +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
14.6 + <modelVersion>4.0.0</modelVersion>
14.7 + <groupId>org.apidesign</groupId>
14.8 + <artifactId>html</artifactId>
14.9 + <version>0.1-SNAPSHOT</version>
14.10 + <packaging>pom</packaging>
14.11 + <name>HTML APIs via Java</name>
14.12 + <parent>
14.13 + <groupId>net.java</groupId>
14.14 + <artifactId>jvnet-parent</artifactId>
14.15 + <version>3</version>
14.16 + </parent>
14.17 + <properties>
14.18 + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
14.19 + <netbeans.version>RELEASE73</netbeans.version>
14.20 + <license>COPYING</license>
14.21 + </properties>
14.22 + <modules>
14.23 + <module>json</module>
14.24 + </modules>
14.25 + <licenses>
14.26 + <license>
14.27 + <name>GPL-2.0wCPexc</name>
14.28 + <url>http://opensource.org/licenses/GPL-2.0</url>
14.29 + <distribution>repo</distribution>
14.30 + </license>
14.31 + </licenses>
14.32 + <organization>
14.33 + <name>API Design</name>
14.34 + <url>http://apidesign.org</url>
14.35 + </organization>
14.36 + <scm>
14.37 + <connection>scm:hg:https://hg.java.net/hg/html~html4j</connection>
14.38 + <developerConnection>scm:hg:https://hg.java.net/hg/html~html4j</developerConnection>
14.39 + <url>https://hg.java.net/hg/html~html4j</url>
14.40 + <tag>default</tag>
14.41 + </scm>
14.42 + <repositories>
14.43 + <repository>
14.44 + <id>netbeans</id>
14.45 + <name>NetBeans</name>
14.46 + <url>http://bits.netbeans.org/maven2/</url>
14.47 + </repository>
14.48 + </repositories>
14.49 + <pluginRepositories>
14.50 + <pluginRepository>
14.51 + <id>mc-release</id>
14.52 + <name>Local Maven repository of releases</name>
14.53 + <url>http://mc-repo.googlecode.com/svn/maven2/releases</url>
14.54 + <snapshots>
14.55 + <enabled>false</enabled>
14.56 + </snapshots>
14.57 + <releases>
14.58 + <enabled>true</enabled>
14.59 + </releases>
14.60 + </pluginRepository>
14.61 + </pluginRepositories>
14.62 + <build>
14.63 + <plugins>
14.64 + <plugin>
14.65 + <inherited>false</inherited>
14.66 + <groupId>com.mycila.maven-license-plugin</groupId>
14.67 + <artifactId>maven-license-plugin</artifactId>
14.68 + <version>1.9.0</version>
14.69 + <executions>
14.70 + <execution>
14.71 + <id>blah</id>
14.72 + <goals>
14.73 + <goal>check</goal>
14.74 + </goals>
14.75 + </execution>
14.76 + </executions>
14.77 + <configuration>
14.78 + <aggregate>true</aggregate>
14.79 + <basedir>${basedir}</basedir>
14.80 + <header>COPYING</header>
14.81 + <strictCheck>true</strictCheck>
14.82 + <excludes>
14.83 + <exclude>*</exclude>
14.84 + </excludes>
14.85 + </configuration>
14.86 + </plugin>
14.87 + <plugin>
14.88 + <artifactId>maven-release-plugin</artifactId>
14.89 + <version>2.4</version>
14.90 + <configuration>
14.91 + <mavenExecutorId>forked-path</mavenExecutorId>
14.92 + <useReleaseProfile>false</useReleaseProfile>
14.93 + <arguments>-Pjvnet-release -Pgpg</arguments>
14.94 + <tag>release-${releaseVersion}</tag>
14.95 + </configuration>
14.96 + </plugin>
14.97 + </plugins>
14.98 + <pluginManagement>
14.99 + <plugins>
14.100 + <plugin>
14.101 + <groupId>org.apache.maven.plugins</groupId>
14.102 + <artifactId>maven-surefire-plugin</artifactId>
14.103 + <version>2.13</version>
14.104 + </plugin>
14.105 + <plugin>
14.106 + <groupId>org.apache.maven.plugins</groupId>
14.107 + <artifactId>maven-javadoc-plugin</artifactId>
14.108 + <version>2.9</version>
14.109 + <configuration>
14.110 + <skip>true</skip>
14.111 + </configuration>
14.112 + </plugin>
14.113 + <plugin>
14.114 + <groupId>org.apache.maven.plugins</groupId>
14.115 + <artifactId>maven-compiler-plugin</artifactId>
14.116 + <version>2.3.2</version>
14.117 + <configuration>
14.118 + <source>1.7</source>
14.119 + <target>1.7</target>
14.120 + </configuration>
14.121 + </plugin>
14.122 + </plugins>
14.123 + </pluginManagement>
14.124 + </build>
14.125 + <dependencyManagement>
14.126 + <dependencies>
14.127 + <dependency>
14.128 + <groupId>org.testng</groupId>
14.129 + <artifactId>testng</artifactId>
14.130 + <version>6.7</version>
14.131 + <scope>test</scope>
14.132 + <exclusions>
14.133 + <exclusion>
14.134 + <artifactId>junit</artifactId>
14.135 + <groupId>junit</groupId>
14.136 + </exclusion>
14.137 + </exclusions>
14.138 + </dependency>
14.139 + <dependency>
14.140 + <groupId>org.netbeans.api</groupId>
14.141 + <artifactId>org-netbeans-modules-classfile</artifactId>
14.142 + <version>${netbeans.version}</version>
14.143 + <type>jar</type>
14.144 + </dependency>
14.145 + <dependency>
14.146 + <groupId>org.netbeans.api</groupId>
14.147 + <artifactId>org-openide-util-lookup</artifactId>
14.148 + <version>${netbeans.version}</version>
14.149 + <scope>compile</scope>
14.150 + <type>jar</type>
14.151 + </dependency>
14.152 + <dependency>
14.153 + <groupId>org.netbeans.api</groupId>
14.154 + <artifactId>org-netbeans-api-annotations-common</artifactId>
14.155 + <version>${netbeans.version}</version>
14.156 + </dependency>
14.157 + <dependency>
14.158 + <groupId>org.netbeans.api</groupId>
14.159 + <artifactId>org-netbeans-modules-java-source</artifactId>
14.160 + <version>${netbeans.version}</version>
14.161 + </dependency>
14.162 + <dependency>
14.163 + <groupId>org.netbeans.api</groupId>
14.164 + <artifactId>org-netbeans-libs-javacapi</artifactId>
14.165 + <version>${netbeans.version}</version>
14.166 + </dependency>
14.167 + <dependency>
14.168 + <groupId>org.netbeans.api</groupId>
14.169 + <artifactId>org-netbeans-spi-java-hints</artifactId>
14.170 + <version>${netbeans.version}</version>
14.171 + </dependency>
14.172 + <dependency>
14.173 + <groupId>org.netbeans.api</groupId>
14.174 + <artifactId>org-netbeans-modules-parsing-api</artifactId>
14.175 + <version>${netbeans.version}</version>
14.176 + </dependency>
14.177 + <dependency>
14.178 + <groupId>org.netbeans.api</groupId>
14.179 + <artifactId>org-netbeans-spi-editor-hints</artifactId>
14.180 + <version>${netbeans.version}</version>
14.181 + </dependency>
14.182 + <dependency>
14.183 + <groupId>org.netbeans.api</groupId>
14.184 + <artifactId>org-openide-util</artifactId>
14.185 + <version>${netbeans.version}</version>
14.186 + </dependency>
14.187 + <dependency>
14.188 + <groupId>org.netbeans.api</groupId>
14.189 + <artifactId>org-netbeans-modules-java-lexer</artifactId>
14.190 + <version>${netbeans.version}</version>
14.191 + </dependency>
14.192 + <dependency>
14.193 + <groupId>org.netbeans.api</groupId>
14.194 + <artifactId>org-netbeans-modules-lexer</artifactId>
14.195 + <version>${netbeans.version}</version>
14.196 + </dependency>
14.197 + <dependency>
14.198 + <groupId>org.netbeans.api</groupId>
14.199 + <artifactId>org-netbeans-modules-java-hints-test</artifactId>
14.200 + <version>${netbeans.version}</version>
14.201 + </dependency>
14.202 + <dependency>
14.203 + <groupId>org.netbeans.api</groupId>
14.204 + <artifactId>org-netbeans-libs-junit4</artifactId>
14.205 + <version>${netbeans.version}</version>
14.206 + </dependency>
14.207 + <dependency>
14.208 + <groupId>org.netbeans.modules</groupId>
14.209 + <artifactId>org-netbeans-lib-nbjavac</artifactId>
14.210 + <version>${netbeans.version}</version>
14.211 + </dependency>
14.212 + <dependency>
14.213 + <groupId>org.netbeans.modules</groupId>
14.214 + <artifactId>org-netbeans-modules-web-browser-api</artifactId>
14.215 + <version>${netbeans.version}</version>
14.216 + <exclusions>
14.217 + <exclusion>
14.218 + <artifactId>org-netbeans-core</artifactId>
14.219 + <groupId>org.netbeans.modules</groupId>
14.220 + </exclusion>
14.221 + <exclusion>
14.222 + <artifactId>org-netbeans-core-multiview</artifactId>
14.223 + <groupId>org.netbeans.api</groupId>
14.224 + </exclusion>
14.225 + <exclusion>
14.226 + <artifactId>org-netbeans-libs-lucene</artifactId>
14.227 + <groupId>org.netbeans.api</groupId>
14.228 + </exclusion>
14.229 + <exclusion>
14.230 + <artifactId>org-netbeans-modules-diff</artifactId>
14.231 + <groupId>org.netbeans.api</groupId>
14.232 + </exclusion>
14.233 + <exclusion>
14.234 + <artifactId>org-netbeans-modules-editor-fold</artifactId>
14.235 + <groupId>org.netbeans.api</groupId>
14.236 + </exclusion>
14.237 + <exclusion>
14.238 + <artifactId>org-netbeans-modules-editor-guards</artifactId>
14.239 + <groupId>org.netbeans.api</groupId>
14.240 + </exclusion>
14.241 + </exclusions>
14.242 + </dependency>
14.243 + <dependency>
14.244 + <artifactId>org-netbeans-modules-projectapi</artifactId>
14.245 + <groupId>org.netbeans.api</groupId>
14.246 + <type>jar</type>
14.247 + <version>${netbeans.version}</version>
14.248 + </dependency>
14.249 + </dependencies>
14.250 + </dependencyManagement>
14.251 +</project>