http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/json/src/main/java/org/netbeans/html/json/impl/PropertyBindingAccessor.java ---------------------------------------------------------------------- diff --git a/json/src/main/java/org/netbeans/html/json/impl/PropertyBindingAccessor.java b/json/src/main/java/org/netbeans/html/json/impl/PropertyBindingAccessor.java new file mode 100644 index 0000000..bf545fa --- /dev/null +++ b/json/src/main/java/org/netbeans/html/json/impl/PropertyBindingAccessor.java @@ -0,0 +1,109 @@ +/** + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ +package org.netbeans.html.json.impl; + +import net.java.html.BrwsrCtx; +import org.netbeans.html.json.spi.JSONCall; +import org.netbeans.html.json.spi.PropertyBinding; +import org.netbeans.html.json.spi.Proto; + +/** + * + * @author Jaroslav Tulach + */ +public abstract class PropertyBindingAccessor { + private static PropertyBindingAccessor DEFAULT; + + protected PropertyBindingAccessor() { + if (DEFAULT != null) throw new IllegalStateException(); + DEFAULT = this; + } + + static { + JSON.initClass(PropertyBinding.class); + } + + protected abstract <M> PropertyBinding newBinding( + Proto.Type<M> access, Bindings<?> bindings, String name, int index, M model, byte propertyType); + protected abstract JSONCall newCall( + BrwsrCtx ctx, RcvrJSON callback, + String headers, String urlBefore, String urlAfter, + String method, Object data + ); + + protected abstract Bindings bindings(Proto proto, boolean initialize, Object copyFrom); + protected abstract void notifyChange(Proto proto, int propIndex); + protected abstract Proto findProto(Proto.Type<?> type, Object object); + protected abstract <Model> Model cloneTo(Proto.Type<Model> type, Model model, BrwsrCtx c); + protected abstract Object read(Proto.Type<?> from, BrwsrCtx c, Object data); + + static Bindings getBindings(Proto proto, boolean initialize, Object copyFrom) { + return DEFAULT.bindings(proto, initialize, copyFrom); + } + + static void notifyProtoChange(Proto proto, int propIndex) { + DEFAULT.notifyChange(proto, propIndex); + } + + static <M> PropertyBinding create( + Proto.Type<M> access, Bindings<?> bindings, String name, int index, M model , byte propertyType + ) { + return DEFAULT.newBinding(access, bindings, name, index, model, propertyType); + } + public static JSONCall createCall( + BrwsrCtx ctx, RcvrJSON callback, + String headers, String urlBefore, String urlAfter, + String method, Object data + ) { + return DEFAULT.newCall(ctx, callback, headers, urlBefore, urlAfter, method, data); + } + static Proto protoFor(Proto.Type<?> type, Object object) { + return DEFAULT.findProto(type, object); + } + static <Model> Model clone(Proto.Type<Model> type, Model model, BrwsrCtx c) { + return DEFAULT.cloneTo(type, model, c); + } + static Object readFrom(Proto.Type<?> from, BrwsrCtx c, Object data) { + return DEFAULT.read(from, c, data); + } +}
http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/json/src/main/java/org/netbeans/html/json/impl/RcvrJSON.java ---------------------------------------------------------------------- diff --git a/json/src/main/java/org/netbeans/html/json/impl/RcvrJSON.java b/json/src/main/java/org/netbeans/html/json/impl/RcvrJSON.java new file mode 100644 index 0000000..d3764f9 --- /dev/null +++ b/json/src/main/java/org/netbeans/html/json/impl/RcvrJSON.java @@ -0,0 +1,136 @@ +/** + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ +package org.netbeans.html.json.impl; + +import java.util.concurrent.Callable; + +/** Super type for those who wish to receive JSON messages. + * + * @author Jaroslav Tulach + */ +public abstract class RcvrJSON { + protected void onOpen(MsgEvnt msg) {} + protected abstract void onMessage(MsgEvnt msg); + protected void onClose(MsgEvnt msg) {} + protected abstract void onError(MsgEvnt msg); + + public abstract static class MsgEvnt { + MsgEvnt() { + } + + public Throwable getError() { + return null; + } + + public final Exception getException() { + Throwable t = getError(); + if (t instanceof Exception) { + return (Exception)t; + } + if (t == null) { + return null; + } + return new Exception(t); + } + + public Object[] getValues() { + return null; + } + + public abstract void dispatch(RcvrJSON r); + + public static MsgEvnt createError(final Throwable t) { + return new MsgEvnt() { + @Override + public Throwable getError() { + return t; + } + + @Override + public void dispatch(RcvrJSON r) { + r.onError(this); + } + }; + } + + public static MsgEvnt createMessage(final Object value) { + return new MsgEvnt() { + private Object val = value; + + @Override + public Object[] getValues() { + if (val instanceof Callable) { + try { + val = ((Callable)val).call(); + } catch (Exception ex) { + throw new IllegalStateException("Cannot compute " + val, ex); + } + } + return val instanceof Object[] ? (Object[])val : new Object[] { val }; + } + + @Override + public void dispatch(RcvrJSON r) { + r.onMessage(this); + } + }; + } + + public static MsgEvnt createOpen() { + return new MsgEvnt() { + @Override + public void dispatch(RcvrJSON r) { + r.onOpen(this); + } + }; + } + + public static MsgEvnt createClose() { + return new MsgEvnt() { + @Override + public void dispatch(RcvrJSON r) { + r.onClose(this); + } + }; + } + } } http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/json/src/main/java/org/netbeans/html/json/impl/Transitive.java ---------------------------------------------------------------------- diff --git a/json/src/main/java/org/netbeans/html/json/impl/Transitive.java b/json/src/main/java/org/netbeans/html/json/impl/Transitive.java new file mode 100644 index 0000000..e7713ed --- /dev/null +++ b/json/src/main/java/org/netbeans/html/json/impl/Transitive.java @@ -0,0 +1,60 @@ +/** + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ +package org.netbeans.html.json.impl; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** A way to control {@link ComputedProperty} behavior - whether it tracks + * deeply or not. Not public yet, maybe it won't be needed in the API at all. + * Used in tests only. + * + * @author Jaroslav Tulach + */ +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.METHOD) +@interface Transitive { + boolean deep() default false; +} http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/json/src/main/java/org/netbeans/html/json/spi/FunctionBinding.java ---------------------------------------------------------------------- diff --git a/json/src/main/java/org/netbeans/html/json/spi/FunctionBinding.java b/json/src/main/java/org/netbeans/html/json/spi/FunctionBinding.java new file mode 100644 index 0000000..10bdc53 --- /dev/null +++ b/json/src/main/java/org/netbeans/html/json/spi/FunctionBinding.java @@ -0,0 +1,162 @@ +/** + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ +package org.netbeans.html.json.spi; + +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import net.java.html.BrwsrCtx; +import net.java.html.json.Function; +import net.java.html.json.Model; + +/** Describes a function provided by the {@link Model} and + * annotated by {@link Function} annotation. + * + * @author Jaroslav Tulach + */ +public abstract class FunctionBinding { + FunctionBinding() { + } + + /** Returns name of the function. + * @return function name + */ + public abstract String getFunctionName(); + + /** + * Calls the function provided data associated with current element, as well + * as information about the event that triggered the event. + * + * @param data data associated with selected element + * @param ev event (with additional properties) that triggered the event + */ + public abstract void call(Object data, Object ev); + + /** Returns identical version of the binding, but one that holds on the + * original model object via weak reference. + * + * @return binding that uses weak reference + * @since 1.1 + */ + public abstract FunctionBinding weak(); + + static <M> FunctionBinding registerFunction(String name, int index, M model, Proto.Type<M> access) { + return new Impl<M>(model, name, index, access); + } + + private static abstract class AImpl<M> extends FunctionBinding { + final String name; + final Proto.Type<M> access; + final int index; + + public AImpl(String name, int index, Proto.Type<M> access) { + this.name = name; + this.index = index; + this.access = access; + } + + protected abstract M model(); + + @Override + public String getFunctionName() { + return name; + } + + @Override + public void call(final Object data, final Object ev) { + final M model = model(); + if (model == null) { + return; + } + BrwsrCtx ctx = access.protoFor(model).getContext(); + class Dispatch implements Runnable { + @Override + public void run() { + try { + access.call(model, index, data, ev); + } catch (Throwable ex) { + ex.printStackTrace(); + } + } + } + ctx.execute(new Dispatch()); + } + } + + private static final class Impl<M> extends AImpl<M> { + private final M model; + + public Impl(M model, String name, int index, Proto.Type<M> access) { + super(name, index, access); + this.model = model; + } + + @Override + protected M model() { + return model; + } + + @Override + public FunctionBinding weak() { + return new Weak(model, name, index, access); + } + } + + private static final class Weak<M> extends AImpl<M> { + private final Reference<M> ref; + + public Weak(M model, String name, int index, Proto.Type<M> access) { + super(name, index, access); + this.ref = new WeakReference<M>(model); + } + + @Override + protected M model() { + return ref.get(); + } + + @Override + public FunctionBinding weak() { + return this; + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/json/src/main/java/org/netbeans/html/json/spi/JSONCall.java ---------------------------------------------------------------------- diff --git a/json/src/main/java/org/netbeans/html/json/spi/JSONCall.java b/json/src/main/java/org/netbeans/html/json/spi/JSONCall.java new file mode 100644 index 0000000..bf1a649 --- /dev/null +++ b/json/src/main/java/org/netbeans/html/json/spi/JSONCall.java @@ -0,0 +1,152 @@ +/** + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ +package org.netbeans.html.json.spi; + +import java.io.IOException; +import java.io.OutputStream; +import net.java.html.BrwsrCtx; +import org.netbeans.html.json.impl.RcvrJSON; + +/** Description of a JSON call request that is supposed to be processed + * by {@link Transfer#loadJSON(org.netbeans.html.json.spi.JSONCall)} implementors. + * + * @author Jaroslav Tulach + */ +public final class JSONCall { + private final RcvrJSON whenDone; + private final String headers; + private final String urlBefore; + private final String urlAfter; + private final String method; + private final Object data; + private final BrwsrCtx ctx; + + JSONCall( + BrwsrCtx ctx, RcvrJSON whenDone, + String headers, String urlBefore, String urlAfter, + String method, Object data + ) { + this.ctx = ctx; + this.whenDone = whenDone; + this.headers = headers; + this.urlBefore = urlBefore; + this.urlAfter = urlAfter; + this.method = method; + this.data = data; + } + + /** Do we have some data to send? Can the {@link #writeData(java.io.OutputStream)} method be + * called? + * + * @return true, if the call has some data to send + */ + public boolean isDoOutput() { + return this.data != null; + } + + public void writeData(OutputStream os) throws IOException { + if (this.data == null) { + throw new IOException("No data!"); + } + os.write(this.data.toString().getBytes("UTF-8")); + os.flush(); + } + + /** Additional headers to be included in the request. + * Usually multiline string to be appended into the header. + * + * @return <code>null</code> or string with prepared (HTTP) request headers + * @since 1.2 + */ + public String getHeaders() { + return headers; + } + + public String getMethod() { + return method; + } + + public boolean isJSONP() { + return urlAfter != null; + } + + public String composeURL(String jsonpCallback) { + if ((urlAfter == null) != (jsonpCallback == null)) { + throw new IllegalStateException(); + } + if (urlAfter != null) { + return urlBefore + jsonpCallback + urlAfter; + } else { + return urlBefore; + } + } + + public void notifySuccess(Object result) { + if (result == null) { + dispatch(RcvrJSON.MsgEvnt.createOpen()); + } else { + dispatch(RcvrJSON.MsgEvnt.createMessage(result)); + } + } + + public void notifyError(Throwable error) { + if (error == null) { + dispatch(RcvrJSON.MsgEvnt.createClose()); + } else { + dispatch(RcvrJSON.MsgEvnt.createError(error)); + } + } + + private void dispatch(final RcvrJSON.MsgEvnt ev) { + ctx.execute(new Runnable() { + @Override + public void run() { + ev.dispatch(whenDone); + } + }); + } + + public String getMessage() { + return this.data.toString(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/json/src/main/java/org/netbeans/html/json/spi/Observers.java ---------------------------------------------------------------------- diff --git a/json/src/main/java/org/netbeans/html/json/spi/Observers.java b/json/src/main/java/org/netbeans/html/json/spi/Observers.java new file mode 100644 index 0000000..cb12ac1 --- /dev/null +++ b/json/src/main/java/org/netbeans/html/json/spi/Observers.java @@ -0,0 +1,233 @@ +/** + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ +package org.netbeans.html.json.spi; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +/** + * + * @author Jaroslav Tulach + */ +final class Observers { + private static final LinkedList<Watcher> GLOBAL = new LinkedList<Watcher>(); + private final List<Watcher> watchers = new ArrayList<Watcher>(); + private final List<Ref> observers = new ArrayList<Ref>(); + + Observers() { + assert Thread.holdsLock(GLOBAL); + } + + static void beginComputing(Proto p, String name) { + synchronized (GLOBAL) { + verifyUnlocked(p); + final Watcher nw = new Watcher(p, name); + GLOBAL.push(nw); + } + } + + static void verifyUnlocked(Proto p) { + synchronized (GLOBAL) { + for (Watcher w : GLOBAL) { + if (w.proto == p) { + if (w.owner == Thread.currentThread()) { + throw new IllegalStateException("Re-entrant attempt to access " + p); + } + } + } + } + } + + static void accessingValue(Proto p, String propName) { + synchronized (GLOBAL) { + verifyUnlocked(p); + for (Watcher w : GLOBAL) { + Observers mine = p.observers(true); + mine.add(w, new Ref(w, propName)); + } + } + } + + static void finishComputing(Proto p) { + synchronized (GLOBAL) { + boolean found = false; + Iterator<Watcher> it = GLOBAL.iterator(); + while (it.hasNext()) { + Watcher w = it.next(); + if (w.proto == p && w.owner == Thread.currentThread()) { + if (w.prop != null) { + Observers mine = p.observers(true); + mine.add(w); + } + found = true; + it.remove(); + } + } + if (!found) { + throw new IllegalStateException("Cannot find " + p + " in " + GLOBAL); + } + } + } + + private static final class Ref extends WeakReference<Watcher> { + private final String prop; + + public Ref(Watcher ref, String prop) { + super(ref); + this.prop = prop; + } + + final Watcher watcher() { + Watcher w = get(); + if (w == null) { + return null; + } + final Observers o = w.proto.observers(false); + if (o == null) { + return null; + } + if (o.find(w.prop) == w) { + return w; + } + return null; + } + } + + private Watcher find(String prop) { + if (prop == null) { + return null; + } + for (Watcher w : watchers) { + if (prop.equals(w.prop)) { + return w; + } + } + return null; + } + + final void add(Watcher w) { + for (int i = 0; i < watchers.size(); i++) { + Watcher ith = watchers.get(i); + if (w.prop == null) { + if (ith.prop == null) { + watchers.set(i, w); + return; + } + } else if (w.prop.equals(ith.prop)) { + watchers.set(i, w); + return; + } + } + watchers.add(w); + } + + static final void valueHasMutated(Proto p, String propName) { + List<Watcher> mutated = new LinkedList<Watcher>(); + synchronized (GLOBAL) { + Observers mine = p.observers(false); + if (mine == null) { + return; + } + Iterator<Ref> it = mine.observers.iterator(); + while (it.hasNext()) { + Ref ref = it.next(); + if (ref.get() == null) { + it.remove(); + continue; + } + if (ref.prop.equals(propName)) { + Watcher w = ref.watcher(); + if (w != null) { + mutated.add(w); + } + } + } + } + for (Watcher w : mutated) { + w.proto.valueHasMutated(w.prop); + } + } + + void add(Watcher w, Ref r) { + Thread.holdsLock(GLOBAL); + if (w == null) { + return; + } + Iterator<Ref> it = observers.iterator(); + while (it.hasNext()) { + Ref ref = it.next(); + if (r == ref) { + return; + } + final Watcher rw = ref.get(); + if (rw == null) { + it.remove(); + continue; + } + if (rw == w && r.prop.equals(r.prop)) { + return; + } + } + observers.add(r); + } + + private static final class Watcher { + final Thread owner; + final Proto proto; + final String prop; + + Watcher(Proto proto, String prop) { + this.owner = Thread.currentThread(); + this.proto = proto; + this.prop = prop; + } + + @Override + public String toString() { + return "Watcher: " + proto + ", " + prop; + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/json/src/main/java/org/netbeans/html/json/spi/PropertyBinding.java ---------------------------------------------------------------------- diff --git a/json/src/main/java/org/netbeans/html/json/spi/PropertyBinding.java b/json/src/main/java/org/netbeans/html/json/spi/PropertyBinding.java new file mode 100644 index 0000000..c43217d --- /dev/null +++ b/json/src/main/java/org/netbeans/html/json/spi/PropertyBinding.java @@ -0,0 +1,236 @@ +/** + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ +package org.netbeans.html.json.spi; + +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import net.java.html.BrwsrCtx; +import net.java.html.json.ComputedProperty; +import org.netbeans.html.json.impl.Bindings; +import org.netbeans.html.json.impl.JSON; +import org.netbeans.html.json.impl.PropertyBindingAccessor; +import org.netbeans.html.json.impl.RcvrJSON; + +/** Describes a property when one is asked to + * bind it + * + * @author Jaroslav Tulach + */ +public abstract class PropertyBinding { + PropertyBinding() { + } + + static { + new PropertyBindingAccessor() { + @Override + protected JSONCall newCall(BrwsrCtx ctx, RcvrJSON callback, String headers, String urlBefore, String urlAfter, String method, Object data) { + return new JSONCall(ctx, callback, headers, urlBefore, urlAfter, method, data); + } + + @Override + protected Bindings bindings(Proto proto, boolean initialize, Object copyFrom) { + return initialize ? proto.initBindings(copyFrom) : proto.getBindings(); + } + + @Override + protected void notifyChange(Proto proto, int propIndex) { + proto.onChange(propIndex); + } + + @Override + protected Proto findProto(Proto.Type<?> type, Object object) { + return type.protoFor(object); + } + + @Override + protected <Model> Model cloneTo(Proto.Type<Model> type, Model model, BrwsrCtx c) { + return type.cloneTo(model, c); + } + + @Override + protected Object read(Proto.Type<?> from, BrwsrCtx c, Object data) { + return from.read(c, data); + } + + @Override + protected <M> PropertyBinding newBinding( + Proto.Type<M> access, Bindings<?> bindings, String name, int index, M model, byte propertyType) { + return new Impl(model, bindings, name, index, access, propertyType); + } + }; + } + + /** Name of the property this binding represents. + * @return name of the property + */ + public abstract String getPropertyName(); + + /** Changes value of the property. Can be called only on dedicated + * thread. See {@link Technology#runSafe(java.lang.Runnable)}. + * + * @param v new value of the property + */ + public abstract void setValue(Object v); + + /** Obtains current value of the property this binding represents. + * Can be called only on dedicated + * thread. See {@link Technology#runSafe(java.lang.Runnable)}. + * + * @return the value or <code>null</code> + */ + public abstract Object getValue(); + + /** Is this property read only?. Or can one call {@link #setValue(java.lang.Object)}? + * The property can still change, but only as a result of other + * properties being changed, just like {@link ComputedProperty} can. + * + * @return true, if this property is read only + */ + public abstract boolean isReadOnly(); + + /** Is this property constant?. If a property is constant, than its + * value cannot changed after it is read. + * + * @return true, if this property is constant + * @since 1.3 + */ + public abstract boolean isConstant(); + + /** Returns identical version of the binding, but one that holds on the + * original model object via weak reference. + * + * @return binding that uses weak reference + * @since 1.1 + */ + public abstract PropertyBinding weak(); + + private static abstract class AImpl<M> extends PropertyBinding { + public final String name; + public final byte propertyType; + final Proto.Type<M> access; + final Bindings<?> bindings; + final int index; + + public AImpl(Bindings<?> bindings, String name, int index, Proto.Type<M> access, byte propertyType) { + this.bindings = bindings; + this.name = name; + this.index = index; + this.access = access; + this.propertyType = propertyType; + } + + protected abstract M model(); + + @Override + public void setValue(Object v) { + M model = model(); + if (model == null) { + return; + } + access.setValue(model, index, v); + } + + @Override + public Object getValue() { + M model = model(); + if (model == null) { + return null; + } + Object v = access.getValue(model, index); + Object r = JSON.find(v, bindings); + return r == null ? v : r; + } + + @Override + public boolean isReadOnly() { + return (propertyType & 1) != 0; + } + + @Override + public boolean isConstant() { + return (propertyType & 2) != 0; + } + + @Override + public String getPropertyName() { + return name; + } + } // end of PBData + + private static final class Impl<M> extends AImpl<M> { + private final M model; + + public Impl(M model, Bindings<?> bindings, String name, int index, Proto.Type<M> access, byte propertyType) { + super(bindings, name, index, access, propertyType); + this.model = model; + } + + @Override + protected M model() { + return model; + } + + @Override + public PropertyBinding weak() { + return new Weak(model, bindings, name, index, access, propertyType); + } + } + + private static final class Weak<M> extends AImpl<M> { + private final Reference<M> ref; + public Weak(M model, Bindings<?> bindings, String name, int index, Proto.Type<M> access, byte propertyType) { + super(bindings, name, index, access, propertyType); + this.ref = new WeakReference<M>(model); + } + + @Override + protected M model() { + return ref.get(); + } + + @Override + public PropertyBinding weak() { + return this; + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/json/src/main/java/org/netbeans/html/json/spi/Proto.java ---------------------------------------------------------------------- diff --git a/json/src/main/java/org/netbeans/html/json/spi/Proto.java b/json/src/main/java/org/netbeans/html/json/spi/Proto.java new file mode 100644 index 0000000..73c36e7 --- /dev/null +++ b/json/src/main/java/org/netbeans/html/json/spi/Proto.java @@ -0,0 +1,953 @@ +/** + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved. + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + */ +package org.netbeans.html.json.spi; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import net.java.html.BrwsrCtx; +import net.java.html.json.ComputedProperty; +import net.java.html.json.Model; +import net.java.html.json.Property; +import org.netbeans.html.json.impl.Bindings; +import org.netbeans.html.json.impl.JSON; +import org.netbeans.html.json.impl.JSON.WS; +import org.netbeans.html.json.impl.JSONList; +import org.netbeans.html.json.impl.PropertyBindingAccessor; +import org.netbeans.html.json.impl.RcvrJSON; +import org.netbeans.html.json.impl.RcvrJSON.MsgEvnt; + +/** Object associated with one instance of a model generated by the + * {@link Model} annotation. Contains methods the generated class can + * use to communicate with behind the scene associated {@link Technology}. + * Each {@link Proto} object is associated with <a href="http://wiki.apidesign.org/wiki/Singletonizer"> + * singletonizer</a>-like interface {@link Type} which provides the + * associated {@link Technology} the necessary information about the + * generated {@link Model} class. + * + * @author Jaroslav Tulach + * @since 0.7 + */ +public final class Proto { + private final Object obj; + private final Type type; + private final net.java.html.BrwsrCtx context; + private org.netbeans.html.json.impl.Bindings ko; + private Observers observers; + + Proto(Object obj, Type type, BrwsrCtx context) { + this.obj = obj; + this.type = type; + this.context = context; + } + + /** Browser context this proto object and its associated model + * are operating-in. + * + * @return the associated context + */ + public BrwsrCtx getContext() { + return context; + } + + /** Acquires global lock to compute a {@link ComputedProperty derived property} + * on this proto object. This proto object must not be locked yet. No + * dependency tracking is performed. + * + * @throws IllegalStateException if already locked + */ + public void acquireLock() throws IllegalStateException { + acquireLock(null); + } + + /** Acquires global lock to compute a {@link ComputedProperty derived property} + * on this proto object. This proto object must not be locked yet. The + * name of the property is used to track dependencies on own + * properties of other proto objects - when they are changed, this + * {@link #valueHasMutated(java.lang.String) property is changed too}. + * + * @param propName name of property we are about to compute + * @throws IllegalStateException thrown when there is a cyclic + * call is detected + * @since 0.9 + */ + public void acquireLock(String propName) throws IllegalStateException { + Observers.beginComputing(this, propName); + } + + /** A property on this proto object is about to be accessed. Verifies + * whether this proto object is accessible - e.g. it has not been + * {@link #acquireLock() locked yet}. If everything is OK, the + * <code>propName</code> is recorded in the chain of dependencies + * tracked by {@link #acquireLock(java.lang.String)} and watched by + * {@link #valueHasMutated(java.lang.String)}. + * + * @param propName name of the property that is requested + * @throws IllegalStateException if the model is locked + * @since 0.9 + */ + public void accessProperty(String propName) throws IllegalStateException { + Observers.accessingValue(this, propName); + } + + /** Verifies the model is not locked otherwise throws an exception. + * @throws IllegalStateException if the model is locked + */ + public void verifyUnlocked() throws IllegalStateException { + Observers.verifyUnlocked(this); + } + + /** When modifications are over, the model is switched into + * unlocked state by calling this method. + */ + public void releaseLock() { + Observers.finishComputing(this); + } + + /** Whenever model changes a property. It should notify the + * associated technology by calling this method. + * Since 0.8.3: This method may be called by any thread - it reschedules + * its actual execution into appropriate one by using + * {@link BrwsrCtx#execute(java.lang.Runnable)}. + * + * @param propName name of the changed property + */ + public void valueHasMutated(final String propName) { + context.execute(new Runnable() { + @Override + public void run() { + if (ko != null) { + ko.valueHasMutated(propName, null, null); + } + Observers.valueHasMutated(Proto.this, propName); + } + }); + } + + /** Whenever model changes a propertyit should notify the + * associated technology. Either by calling this method + * (if the new value is known and different to the old one) or + * via (slightly ineffective) {@link #valueHasMutated(java.lang.String)} + * method. + * Since 0.8.3: This method may be called by any thread - it reschedules + * its actual execution into appropriate one by using + * {@link BrwsrCtx#execute(java.lang.Runnable)}. + * + * @param propName name of the changed property + * @param oldValue provides previous value of the property + * @param newValue provides new value of the property + * @since 0.7.6 + */ + public void valueHasMutated( + final String propName, final Object oldValue, final Object newValue + ) { + context.execute(new Runnable() { + @Override + public void run() { + if (ko != null) { + ko.valueHasMutated(propName, oldValue, newValue); + } + Observers.valueHasMutated(Proto.this, propName); + } + }); + } + + /** Initializes the associated model in the current {@link #getContext() context}. + * In case of <em>knockout.js</em> technology, applies given bindings + * of the current model to the <em>body</em> element of the page. + */ + public void applyBindings() { + initBindings(null).applyBindings(null); + } + + /** Initializes the associated model to the specified element's subtree. + * The technology is taken from the current {@link #getContext() context} and + * in case of <em>knockout.js</em> applies given bindings + * of the current model to the element of the page with 'id' attribute + * set to the specified <code>id</code> value. + * + * @param id the id of element to apply the binding to + * @since 1.1 + * @see Technology.ApplyId + */ + public void applyBindings(String id) { + initBindings(null).applyBindings(id); + } + + /** Invokes the provided runnable in the {@link #getContext() context} + * of the browser. If the caller is already on the right thread, the + * <code>run.run()</code> is invoked immediately and synchronously. + * Otherwise the method returns immediately and the <code>run()</code> + * method is performed later + * + * @param run the action to execute + */ + public void runInBrowser(Runnable run) { + context.execute(run); + } + + /** Invokes the specified function index in the {@link #getContext() context} + * of the browser. If the caller is already on the right thread, the + * index-th function is invoked immediately and synchronously. + * Otherwise the method returns immediately and the function is invoked + * later. + * + * @param index the index of the function as will be passed to + * {@link Type#call(java.lang.Object, int, java.lang.Object, java.lang.Object)} + * method + * @param args array of arguments that will be passed as + * <code>data</code> argument of the <code>call</code> method. + * @since 0.7.6 + */ + public void runInBrowser(final int index, final Object... args) { + context.execute(new Runnable() { + @Override + public void run() { + try { + type.call(obj, index, args, null); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + }); + } + + /** Initializes the provided collection with a content of the <code>array</code>. + * The initialization can only be done soon after the the collection + * is created, otherwise an exception is throw + * + * @param to the collection to initialize (assumed to be empty) + * @param array the array to add to the collection + * @throws IllegalStateException if the system has already been initialized + */ + public void initTo(Collection<?> to, Object array) { + if (ko != null) { + throw new IllegalStateException(); + } + if (to instanceof JSONList) { + ((JSONList)to).init(array); + } else { + JSONList.init(to, array); + } + } + + /** Takes an object representing JSON result and extract some of its + * properties. It is assumed that the <code>props</code> and + * <code>values</code> arrays have the same length. + * + * @param json the JSON object (actual type depends on the associated + * {@link Technology}) + * @param props list of properties to extract + * @param values array that will be filled with extracted values + */ + public void extract(Object json, String[] props, Object[] values) { + JSON.extract(context, json, props, values); + } + + /** Converts raw JSON <code>data</code> into a Java {@link Model} class. + * + * @param <T> type of the model class + * @param modelClass the type of the class to create + * @param data the raw JSON data + * @return newly created instance of the model class + */ + public <T> T read(Class<T> modelClass, Object data) { + return JSON.read(context, modelClass, data); + } + + /** Initializes asynchronous JSON connection to specified URL. Delegates + * to {@link #loadJSON(int, java.lang.String, java.lang.String, java.lang.String, java.lang.Object, java.lang.Object...) } + * with no extra parameters. + * + * @param index the callback index to be used when a reply is received + * to call {@link Type#onMessage(java.lang.Object, int, int, java.lang.Object)}. + * + * @param urlBefore the part of the URL before JSON-P callback parameter + * @param urlAfter the rest of the URL or <code>null</code> if no JSON-P is used + * @param method method to use for connection to the server + * @param data string, number or a {@link Model} generated class to send to + * the server when doing a query + */ + public void loadJSON(final int index, + String urlBefore, String urlAfter, String method, + final Object data + ) { + loadJSON(index, urlBefore, urlAfter, method, data, new Object[0]); + } + + /** Initializes asynchronous JSON connection to specified URL. The + * method returns immediately and later does callback later. + * + * @param index the callback index to be used when a reply is received + * to call {@link Type#onMessage(java.lang.Object, int, int, java.lang.Object)}. + * + * @param urlBefore the part of the URL before JSON-P callback parameter + * @param urlAfter the rest of the URL or <code>null</code> if no JSON-P is used + * @param method method to use for connection to the server + * @param data string, number or a {@link Model} generated class to send to + * the server when doing a query + * @param params extra params to pass back when calling + * {@link Type#onMessage(java.lang.Object, int, int, java.lang.Object, java.lang.Object[])} + * @since 0.8.1 + */ + public void loadJSON(final int index, + String urlBefore, String urlAfter, String method, + final Object data, final Object... params + ) { + loadJSONWithHeaders(index, null, urlBefore, urlAfter, method, data, params); + } + + /** Initializes asynchronous JSON connection to specified URL. The + * method returns immediately and later does callback later. + * + * @param index the callback index to be used when a reply is received + * to call {@link Type#onMessage(java.lang.Object, int, int, java.lang.Object)}. + * + * @param headers headers to use for the request or <code>null</code> to use default ones + * @param urlBefore the part of the URL before JSON-P callback parameter + * @param urlAfter the rest of the URL or <code>null</code> if no JSON-P is used + * @param method method to use for connection to the server + * @param data string, number or a {@link Model} generated class to send to + * the server when doing a query + * @param params extra params to pass back when calling + * {@link Type#onMessage(java.lang.Object, int, int, java.lang.Object, java.lang.Object[])} + * @since 1.2 + */ + public void loadJSONWithHeaders(final int index, + String headers, + String urlBefore, String urlAfter, String method, + final Object data, final Object... params + ) { + class Rcvr extends RcvrJSON { + @Override + protected void onMessage(MsgEvnt msg) { + type.onMessage(obj, index, 1, msg.getValues(), params); + } + + @Override + protected void onError(MsgEvnt msg) { + type.onMessage(obj, index, 2, msg.getException(), params); + } + } + JSONCall call = PropertyBindingAccessor.createCall( + context, new Rcvr(), headers, urlBefore, urlAfter, method, data + ); + Transfer t = JSON.findTransfer(context); + t.loadJSON(call); + } + + /** Opens new WebSocket connection to the specified URL. + * + * @param index the index to use later during callbacks to + * {@link Type#onMessage(java.lang.Object, int, int, java.lang.Object)} + * @param url the <code>ws://</code> or <code>wss://</code> URL to connect to + * @param data data to send to server (usually <code>null</code>) + * @return returns a non-null object representing the socket + * which can be used when calling {@link #wsSend(java.lang.Object, java.lang.String, java.lang.Object) } + */ + public Object wsOpen(final int index, String url, Object data) { + class WSrcvr extends RcvrJSON { + @Override + protected void onError(MsgEvnt msg) { + type.onMessage(obj, index, 2, msg.getException()); + } + + @Override + protected void onMessage(MsgEvnt msg) { + type.onMessage(obj, index, 1, msg.getValues()); + } + + @Override + protected void onClose(MsgEvnt msg) { + type.onMessage(obj, index, 3, null); + } + + @Override + protected void onOpen(MsgEvnt msg) { + type.onMessage(obj, index, 0, null); + } + } + WS ws = WS.create(JSON.findWSTransfer(context), new WSrcvr()); + ws.send(context, null, url, data); + return ws; + } + + /** Sends a message to existing socket. + * + * @param webSocket the socket to send message to + * @param url the <code>ws://</code> or <code>wss://</code> URL to connect to, + * preferably the same as the one used when the socket was + * {@link #wsOpen(int, java.lang.String, java.lang.Object) opened} + * @param data the data to send or <code>null</code> if the socket is + * supposed to be closed + */ + public void wsSend(Object webSocket, String url, Object data) { + ((JSON.WS)webSocket).send(context, null, url, data); + } + + /** Converts raw data (one of its properties) to string representation. + * + * @param data the object + * @param propName the name of object property or <code>null</code> + * if the whole object should be converted + * @return the string representation of the object or its property + */ + public String toString(Object data, String propName) { + return JSON.toString(context, data, propName); + } + + /** Converts raw data (one of its properties) to a number representation. + * + * @param data the object + * @param propName the name of object property or <code>null</code> + * if the whole object should be converted + * @return the number representation of the object or its property + */ + public Number toNumber(Object data, String propName) { + return JSON.toNumber(context, data, propName); + } + + /** Converts raw JSON data into a {@link Model} class representation. + * + * @param <T> type of the model to create + * @param type class of the model to create + * @param data raw JSON data (depends on associated {@link Technology}) + * @return new instances of the model class filled with values from the + * <code>data</code> object + */ + public <T> T toModel(Class<T> type, Object data) { + return JSON.toModel(context, type, data, null); + } + + /** Creates new JSON like observable list. + * + * @param <T> the type of the list elements + * @param propName name of a property this list is associated with + * @param onChange index of the property to use when the list is modified + * during callback to {@link Type#onChange(java.lang.Object, int)}. + * If the value is {@link Integer#MIN_VALUE}, then the list is + * not fully {@link Property#mutable()} and throws {@link UnsupportedOperationException} + * on such attempts. + * @param dependingProps the array of {@link ComputedProperty derived properties} + * that depend on the value of the list + * @return new, empty list associated with this proto-object and its model + */ + public <T> List<T> createList(String propName, int onChange, String... dependingProps) { + return new JSONList<T>(this, propName, onChange, dependingProps); + } + + /** Copies content of one collection to another, re-assigning all its + * elements from their current context to the new <code>ctx</code>. + * + * @param <T> type of the collections + * @param to the target collection to be filled with cloned values + * @param ctx context for the new collection + * @param from original collection with its data + */ + public <T> void cloneList(Collection<T> to, BrwsrCtx ctx, Collection<T> from) { + Boolean isModel = null; + for (T t : from) { + if (isModel == null) { + isModel = JSON.isModel(t.getClass()); + } + if (isModel) { + to.add(JSON.bindTo(t, ctx)); + } else { + to.add(t); + } + } + } + + // + // internal state + // + + final String toStr() { + return "Proto[" + obj + "]@" + Integer.toHexString(System.identityHashCode(this)); + } + + final Bindings initBindings(Object originalObject) { + if (ko == null) { + Bindings b = Bindings.apply(context); + PropertyBinding[] pb = new PropertyBinding[type.propertyNames.length]; + for (int i = 0; i < pb.length; i++) { + pb[i] = b.registerProperty( + type.propertyNames[i], i, obj, type, type.propertyType[i] + ); + } + FunctionBinding[] fb = new FunctionBinding[type.functions.length]; + for (int i = 0; i < fb.length; i++) { + fb[i] = FunctionBinding.registerFunction( + type.functions[i], i, obj, type + ); + } + ko = b; + b.finish(obj, originalObject, pb, fb); + } + return ko; + } + + final Bindings getBindings() { + return ko; + } + + final void onChange(int index) { + type.onChange(obj, index); + } + + final Observers observers(boolean create) { + if (create && observers == null) { + observers = new Observers(); + } + return observers; + } + + /** Functionality used by the code generated by annotation + * processor for the {@link net.java.html.json.Model} annotation. + * + * @param <Model> the generated class + * @since 0.7 + */ + public static abstract class Type<Model> { + private final Class<Model> clazz; + private final String[] propertyNames; + private final byte[] propertyType; + private final String[] functions; + + /** Constructor for subclasses generated by the annotation processor + * associated with {@link net.java.html.json.Model} annotation. + * + * @param clazz the generated model class + * @param modelFor the original class annotated by the {@link net.java.html.json.Model} annotation. + * @param properties number of properties the class has + * @param functions number of functions the class has + */ + protected Type( + Class<Model> clazz, Class<?> modelFor, int properties, int functions + ) { + assert getClass().getName().endsWith("$Html4JavaType"); + try { + assert getClass().getDeclaringClass() == clazz; + } catch (SecurityException ex) { + // OK, no check + } + this.clazz = clazz; + this.propertyNames = new String[properties]; + this.propertyType = new byte[properties]; + this.functions = new String[functions]; + JSON.register(clazz, this); + } + + /** Registers property for the type. It is expected each index + * is initialized only once. + * + * @param name name of the property + * @param index index of the property + * @param readOnly is the property read only? + */ + protected final void registerProperty(String name, int index, boolean readOnly) { + assert propertyNames[index] == null; + propertyNames[index] = name; + propertyType[index] = (byte) (readOnly ? 1 : 0); + } + + /** Registers property for the type. It is expected each index + * is initialized only once. The difference between <code>readOnly</code> + * and <code>constant</code> is: The <code>constant</code> value is + * assigned only at the beginning and never changed then - like the + * {@link Property#mutable() non-mutable} property. On the other + * hand, a <code>readOnly</code> property can change its value, + * but not via a setter - just like {@link ComputedProperty}. + * + * @param name name of the property + * @param index index of the property + * @param readOnly is the property read only? + * @param constant is the property assigned once and never changed again? + * @since 1.3 + */ + protected final void registerProperty( + String name, int index, boolean readOnly, boolean constant + ) { + assert propertyNames[index] == null; + propertyNames[index] = name; + propertyType[index] = (byte) ((readOnly ? 1 : 0) | (constant ? 2 : 0)); + } + + /** Registers function of given name at given index. + * + * @param name name of the function + * @param index name of the type + */ + protected final void registerFunction(String name, int index) { + assert functions[index] == null; + functions[index] = name; + } + + /** Creates new proto-object for given {@link Model} class bound to + * provided context. + * + * @param obj instance of appropriate {@link Model} class + * @param context the browser context + * @return new proto-object that the generated class can use for + * communication with the infrastructure + */ + public Proto createProto(Object obj, BrwsrCtx context) { + return new Proto(obj, this, context); + } + + // + // Implemented by subclasses + // + + /** Sets value of a {@link #registerProperty(java.lang.String, int, boolean) registered property} + * to new value. + * + * @param model the instance of {@link Model model class} + * @param index index of the property used during registration + * @param value the value to set the property to + */ + protected abstract void setValue(Model model, int index, Object value); + + /** Obtains and returns value of a + * {@link #registerProperty(java.lang.String, int, boolean) registered property}. + * + * @param model the instance of {@link Model model class} + * @param index index of the property used during registration + * @return current value of the property + */ + protected abstract Object getValue(Model model, int index); + + /** Invokes a {@link #registerFunction(java.lang.String, int)} registered function + * on given object. + * + * @param model the instance of {@link Model model class} + * @param index index of the property used during registration + * @param data the currently selected object the function is about to operate on + * @param event the event that triggered the event + * @throws Exception the method can throw exception which is then logged + */ + protected abstract void call(Model model, int index, Object data, Object event) + throws Exception; + + /** Re-binds the model object to new browser context. + * + * @param model the instance of {@link Model model class} + * @param ctx browser context to clone the object to + * @return new instance of the model suitable for new context + */ + protected abstract Model cloneTo(Model model, BrwsrCtx ctx); + + /** Reads raw JSON data and converts them to our model class. + * + * @param c the browser context to work in + * @param json raw JSON data to get values from + * @return new instance of model class filled by the data + */ + protected abstract Model read(BrwsrCtx c, Object json); + + /** Called when a {@link #registerProperty(java.lang.String, int, boolean) registered property} + * changes its value. + * + * @param model the object that has the property + * @param index the index of the property during registration + */ + protected abstract void onChange(Model model, int index); + + /** Finds out if there is an associated proto-object for given + * object. + * + * @param object an object, presumably (but not necessarily) instance of Model class + * @return associated proto-object or <code>null</code> + */ + protected abstract Proto protoFor(Object object); + + /** Called to report results of asynchronous over-the-wire + * communication. Result of calling {@link Proto#wsOpen(int, java.lang.String, java.lang.Object)} + * or {@link Proto#loadJSON(int, java.lang.String, java.lang.String, java.lang.String, java.lang.Object, java.lang.Object...)}. + * + * @param model the instance of the model class + * @param index index used during initiating the communication (via <code>loadJSON</code> or <code>wsOpen</code> calls) + * @param type type of the message: 0 - onOpen, 1 - onMessage, 2 - onError, 3 - onClose - + * not all messages are applicable to all communication protocols (JSON has only 1 and 2). + * @param data <code>null</code> or string, number or a {@link Model} class + * obtained to the server as a response + */ + protected void onMessage(Model model, int index, int type, Object data) { + onMessage(model, index, type, data, new Object[0]); + } + + /** Called to report results of asynchronous over-the-wire + * communication. Result of calling {@link Proto#wsOpen(int, java.lang.String, java.lang.Object)} + * or {@link Proto#loadJSON(int, java.lang.String, java.lang.String, java.lang.String, java.lang.Object, java.lang.Object...)}. + * + * @param model the instance of the model class + * @param index index used during initiating the communication (via <code>loadJSON</code> or <code>wsOpen</code> calls) + * @param type type of the message: 0 - onOpen, 1 - onMessage, 2 - onError, 3 - onClose - + * not all messages are applicable to all communication protocols (JSON has only 1 and 2). + * @param data <code>null</code> or string, number or a {@link Model} class + * obtained to the server as a response + * @param params extra parameters as passed for example to + * {@link Proto#loadJSON(int, java.lang.String, java.lang.String, java.lang.String, java.lang.Object, java.lang.Object...)} + * method + * @since 0.8.1 + */ + protected void onMessage(Model model, int index, int type, Object data, Object[] params) { + onMessage(model, index, type, data); + } + + // + // Various support methods the generated classes use + // + + /** Converts and array of raw JSON objects into an array of typed + * Java {@link Model} classes. + * + * @param <T> the type of the destination array + * @param context browser context to use + * @param src array of raw JSON objects + * @param destType type of the individual array elements + * @param dest array to be filled with read type instances + */ + public <T> void copyJSON(BrwsrCtx context, Object[] src, Class<T> destType, T[] dest) { + for (int i = 0; i < src.length && i < dest.length; i++) { + dest[i] = org.netbeans.html.json.impl.JSON.read(context, destType, src[i]); + } + } + + /** Compares two objects that can be converted to integers. + * @param a first value + * @param b second value + * @return true if they are the same + */ + public final boolean isSame(int a, int b) { + return a == b; + } + + /** Compares two objects that can be converted to (floating point) + * numbers. + * @param a first value + * @param b second value + * @return true if they are the same + */ + public final boolean isSame(double a, double b) { + return a == b; + } + + /** Compares two objects for being the same - e.g. either <code>==</code> + * or <code>equals</code>. + * @param a first value + * @param b second value + * @return true if they are equals + */ + public final boolean isSame(Object a, Object b) { + if (a == b) { + return true; + } + if (a == null || b == null) { + return false; + } + return a.equals(b); + } + + /** Cumulative hash function. Adds hashcode of the object to the + * previous value. + * @param o the object (or <code>null</code>) + * @param h the previous value of the hash + * @return new hash - the old one xor the object's one + */ + public final int hashPlus(Object o, int h) { + return o == null ? h : h ^ o.hashCode(); + } + + /** Converts an object to its JSON value. + * + * @param obj the object to convert + * @return JSON representation of the object + */ + public final String toJSON(Object obj) { + return JSON.toJSON(obj); + } + + /** Converts the value to string. + * + * @param val the value + * @return the converted value + */ + public final String stringValue(Object val) { + return JSON.stringValue(val); + } + + /** Converts the value to number. + * + * @param val the value + * @return the converted value + */ + public final Number numberValue(Object val) { + return JSON.numberValue(val); + } + + /** Converts the value to character. + * + * @param val the value + * @return the converted value + */ + public final Character charValue(Object val) { + return JSON.charValue(val); + } + + /** Converts the value to boolean. + * + * @param val the value + * @return the converted value + */ + public final Boolean boolValue(Object val) { + return JSON.boolValue(val); + } + + /** Extracts value of specific type from given object. + * + * @param <T> the type of object one is interested in + * @param type the type + * @param val the object to convert to type + * @return the converted value + */ + public final <T> T extractValue(Class<T> type, Object val) { + if (Number.class.isAssignableFrom(type)) { + val = numberValue(val); + } + if (Boolean.class == type) { + val = boolValue(val); + } + if (String.class == type) { + val = stringValue(val); + } + if (Character.class == type) { + val = charValue(val); + } + if (Integer.class == type) { + val = val instanceof Number ? ((Number) val).intValue() : 0; + } + if (Long.class == type) { + val = val instanceof Number ? ((Number) val).longValue() : 0; + } + if (Short.class == type) { + val = val instanceof Number ? ((Number) val).shortValue() : 0; + } + if (Byte.class == type) { + val = val instanceof Number ? ((Number) val).byteValue() : 0; + } + if (Double.class == type) { + val = val instanceof Number ? ((Number) val).doubleValue() : Double.NaN; + } + if (Float.class == type) { + val = val instanceof Number ? ((Number) val).floatValue() : Float.NaN; + } + if (type.isEnum() && val instanceof String) { + val = Enum.valueOf(type.asSubclass(Enum.class), (String)val); + } + return type.cast(val); + } + + /** Special dealing with array & {@link List} values. This method + * takes the provided collection, empties it and fills it again + * with values extracted from <code>value</code> (which is supposed + * to be an array). + * + * @param <T> the type of list elements + * @param arr collection to fill with elements in value + * @param type the type of elements in the collection + * @param value array of elements to put into the collecition. If + * value is not an array it is wrapped into array with only element + * @since 1.0 + */ + public final <T> void replaceValue(Collection<? super T> arr, Class<T> type, Object value) { + List<T> tmp = new ArrayList<T>(); + if (value instanceof Object[]) { + for (Object e : (Object[]) value) { + tmp.add(extractValue(type, e)); + } + } else if (value instanceof byte[]) { + for (Object e : (byte[]) value) { + tmp.add(extractValue(type, e)); + } + } else if (value instanceof short[]) { + for (Object e : (short[]) value) { + tmp.add(extractValue(type, e)); + } + } else if (value instanceof int[]) { + for (Object e : (int[]) value) { + tmp.add(extractValue(type, e)); + } + } else if (value instanceof char[]) { + for (Object e : (char[]) value) { + tmp.add(extractValue(type, e)); + } + } else if (value instanceof long[]) { + for (Object e : (long[]) value) { + tmp.add(extractValue(type, e)); + } + } else if (value instanceof float[]) { + for (Object e : (float[]) value) { + tmp.add(extractValue(type, e)); + } + } else if (value instanceof double[]) { + for (Object e : (double[]) value) { + tmp.add(extractValue(type, e)); + } + } else if (value instanceof boolean[]) { + for (Object e : (boolean[]) value) { + tmp.add(extractValue(type, e)); + } + } else { + tmp.add(extractValue(type, value)); + } + if (arr instanceof JSONList) { + JSONList jsList = (JSONList) arr; + jsList.fastReplace(tmp); + } else { + arr.clear(); + arr.addAll(tmp); + } + } + } +}
