1.1 --- a/geo/src/main/java/net/java/html/geo/Position.java Wed Aug 27 18:45:51 2014 +0200
1.2 +++ b/geo/src/main/java/net/java/html/geo/Position.java Thu Aug 28 14:16:08 2014 +0200
1.3 @@ -48,9 +48,9 @@
1.4 import java.util.logging.Logger;
1.5 import net.java.html.BrwsrCtx;
1.6 import org.netbeans.html.context.spi.Contexts;
1.7 +import org.netbeans.html.geo.impl.Accessor;
1.8 import org.netbeans.html.geo.impl.JsGLProvider;
1.9 import org.netbeans.html.geo.spi.GLProvider;
1.10 -import org.netbeans.html.geo.spi.GLProvider.Callback;
1.11
1.12 /** Class that represents a geolocation position provided as a callback
1.13 * to {@link Handle#onLocation(net.java.html.geo.Position)} method. The
1.14 @@ -320,12 +320,14 @@
1.15 return temp.watch == null ? null : temp;
1.16 }
1.17
1.18 - private final class JsH<Watch> extends GLProvider<?, Watch>.Callback {
1.19 + private final class JsH<Watch> extends Accessor {
1.20 private final Watch watch;
1.21 + private final GLProvider<?, Watch> provider;
1.22
1.23 public JsH(GLProvider<?, Watch> p) {
1.24 - p.super();
1.25 - this.watch = start(oneTime, enableHighAccuracy, timeout, maximumAge);
1.26 + super(true);
1.27 + this.watch = Accessor.SPI.start(p, this, oneTime, enableHighAccuracy, timeout, maximumAge);
1.28 + this.provider = p;
1.29 }
1.30
1.31 @Override
1.32 @@ -349,7 +351,7 @@
1.33 return;
1.34 }
1.35 if (oneTime) {
1.36 - stop(watch);
1.37 + stop();
1.38 }
1.39 try {
1.40 Handle.this.onError(err);
1.41 @@ -359,8 +361,23 @@
1.42 }
1.43
1.44 protected final void stop() {
1.45 - stop(watch);
1.46 + Accessor.SPI.stop(provider, watch);
1.47 }
1.48 - }
1.49 +
1.50 + @Override
1.51 + public <Watch> Watch start(
1.52 + GLProvider<?, Watch> p, Accessor peer,
1.53 + boolean oneTime, boolean enableHighAccuracy,
1.54 + long timeout, long maximumAge
1.55 + ) {
1.56 + throw new UnsupportedOperationException();
1.57 + }
1.58 +
1.59 + @Override
1.60 + public <Watch> void stop(GLProvider<?, Watch> p, Watch w) {
1.61 + throw new UnsupportedOperationException();
1.62 + }
1.63 +
1.64 + } // end of JsH
1.65 }
1.66 }
2.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
2.2 +++ b/geo/src/main/java/org/netbeans/html/geo/impl/Accessor.java Thu Aug 28 14:16:08 2014 +0200
2.3 @@ -0,0 +1,76 @@
2.4 +/**
2.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
2.6 + *
2.7 + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
2.8 + *
2.9 + * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
2.10 + * Other names may be trademarks of their respective owners.
2.11 + *
2.12 + * The contents of this file are subject to the terms of either the GNU
2.13 + * General Public License Version 2 only ("GPL") or the Common
2.14 + * Development and Distribution License("CDDL") (collectively, the
2.15 + * "License"). You may not use this file except in compliance with the
2.16 + * License. You can obtain a copy of the License at
2.17 + * http://www.netbeans.org/cddl-gplv2.html
2.18 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
2.19 + * specific language governing permissions and limitations under the
2.20 + * License. When distributing the software, include this License Header
2.21 + * Notice in each file and include the License file at
2.22 + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
2.23 + * particular file as subject to the "Classpath" exception as provided
2.24 + * by Oracle in the GPL Version 2 section of the License file that
2.25 + * accompanied this code. If applicable, add the following below the
2.26 + * License Header, with the fields enclosed by brackets [] replaced by
2.27 + * your own identifying information:
2.28 + * "Portions Copyrighted [year] [name of copyright owner]"
2.29 + *
2.30 + * Contributor(s):
2.31 + *
2.32 + * The Original Software is NetBeans. The Initial Developer of the Original
2.33 + * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved.
2.34 + *
2.35 + * If you wish your version of this file to be governed by only the CDDL
2.36 + * or only the GPL Version 2, indicate your decision by adding
2.37 + * "[Contributor] elects to include this software in this distribution
2.38 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
2.39 + * single choice of license, a recipient has the option to distribute
2.40 + * your version of this file under either the CDDL, the GPL Version 2 or
2.41 + * to extend the choice of license to its licensees as provided above.
2.42 + * However, if you add GPL Version 2 code and therefore, elected the GPL
2.43 + * Version 2 license, then the option applies only if the new code is
2.44 + * made subject to such option by the copyright holder.
2.45 + */
2.46 +package org.netbeans.html.geo.impl;
2.47 +
2.48 +import net.java.html.geo.Position;
2.49 +import org.netbeans.html.geo.spi.GLProvider;
2.50 +
2.51 +/** Connection between API and SPI parts of the module.
2.52 + *
2.53 + * @author Jaroslav Tulach <jtulach@netbeans.org>
2.54 + */
2.55 +public abstract class Accessor {
2.56 + public static Accessor SPI;
2.57 + static {
2.58 + JsGLProvider initGLProviderClass = new JsGLProvider();
2.59 + }
2.60 +
2.61 + protected Accessor(boolean api) {
2.62 + if (!api) {
2.63 + assert SPI == null;
2.64 + SPI = this;
2.65 + }
2.66 + }
2.67 +
2.68 + public abstract <Watch> Watch start(
2.69 + GLProvider<?, Watch> p, Accessor peer,
2.70 + boolean oneTime, boolean enableHighAccuracy,
2.71 + long timeout, long maximumAge
2.72 + );
2.73 +
2.74 + public abstract <Watch> void stop(GLProvider<?, Watch> p, Watch w);
2.75 +
2.76 + public abstract void onError(Exception ex);
2.77 +
2.78 + public abstract void onLocation(Position position);
2.79 +}
3.1 --- a/geo/src/main/java/org/netbeans/html/geo/impl/JsGLProvider.java Wed Aug 27 18:45:51 2014 +0200
3.2 +++ b/geo/src/main/java/org/netbeans/html/geo/impl/JsGLProvider.java Thu Aug 28 14:16:08 2014 +0200
3.3 @@ -82,7 +82,7 @@
3.4 "}\n"
3.5 )
3.6 private long doStart(
3.7 - Callback c,
3.8 + Query c,
3.9 boolean onlyOnce,
3.10 boolean enableHighAccuracy,
3.11 long timeout,
3.12 @@ -95,15 +95,15 @@
3.13 }
3.14
3.15 @Override
3.16 - public Long start(Callback c, boolean oneTime, boolean enableHighAccuracy, long timeout, long maximumAge) {
3.17 + public Long start(Query c) {
3.18 if (!hasGeolocation()) {
3.19 return null;
3.20 }
3.21 - return doStart(c, oneTime, enableHighAccuracy, timeout, maximumAge);
3.22 + return doStart(c, c.isOneTime(), c.isHighAccuracy(), c.getTimeout(), c.getMaximumAge());
3.23 }
3.24
3.25 final void onLocation(Object c, Object p) {
3.26 - callback((Callback)c, timeStamp(p), p, null);
3.27 + callback((Query)c, timeStamp(p), p, null);
3.28 }
3.29
3.30 final void onError(Object c, final String msg, int code) {
3.31 @@ -113,7 +113,7 @@
3.32 return msg;
3.33 }
3.34 };
3.35 - callback((Callback)c, 0L, null, err);
3.36 + callback((Query)c, 0L, null, err);
3.37 }
3.38
3.39 @Override
4.1 --- a/geo/src/main/java/org/netbeans/html/geo/spi/GLProvider.java Wed Aug 27 18:45:51 2014 +0200
4.2 +++ b/geo/src/main/java/org/netbeans/html/geo/spi/GLProvider.java Thu Aug 28 14:16:08 2014 +0200
4.3 @@ -48,6 +48,7 @@
4.4 import net.java.html.geo.Position.Handle;
4.5 import net.java.html.geo.Position.Coordinates;
4.6 import org.netbeans.html.context.spi.Contexts;
4.7 +import org.netbeans.html.geo.impl.Accessor;
4.8 import org.openide.util.lookup.ServiceProvider;
4.9
4.10 /** SPI for those who wish to provide their own way of obtaining geolocation.
4.11 @@ -66,12 +67,12 @@
4.12 * <ol>
4.13 * <li>
4.14 * It handles a geolocation request and creates a "watch" to represent it -
4.15 - * to do so implement the {@link #start(org.netbeans.html.geo.spi.GLProvider.Callback, boolean, boolean, long, long) start}
4.16 + * to do so implement the {@link #start(org.netbeans.html.geo.spi.GLProvider.Query) start}
4.17 * method and the {@link #stop(java.lang.Object) stop} method.
4.18 * </li>
4.19 * <li>
4.20 * Once the location is found, the provider needs to
4.21 - * {@link #callback(org.netbeans.html.geo.spi.GLProvider.Callback, long, java.lang.Object, java.lang.Exception) call back}
4.22 + * {@link #callback(org.netbeans.html.geo.spi.GLProvider.Query, long, java.lang.Object, java.lang.Exception) call back}
4.23 * with appropriate location information which can be extracted
4.24 * later via {@link #latitude(java.lang.Object)} {@link #longitude(java.lang.Object)}, and
4.25 * other methods in this that also need to be implemented.
4.26 @@ -93,7 +94,7 @@
4.27 *
4.28 * @author Jaroslav Tulach
4.29 * @param <Watch> your choosen type to represent one query (one time) or watch (repeated) request -
4.30 - * this type is used in {@link #start(org.netbeans.html.geo.spi.GLProvider.Callback, boolean, boolean, long, long) start}
4.31 + * this type is used in {@link #start(org.netbeans.html.geo.spi.GLProvider.Query) start}
4.32 * and {@link #stop(java.lang.Object) stop} methods.
4.33 *
4.34 * @param <Coords> your choosen type to represent geolocation coordinates -
4.35 @@ -103,46 +104,58 @@
4.36 * @since 1.0
4.37 */
4.38 public abstract class GLProvider<Coords,Watch> {
4.39 + static {
4.40 + Accessor initChannel = new Accessor(false) {
4.41 + @Override
4.42 + public <Watch> Watch start(GLProvider<?, Watch> p, Accessor peer, boolean oneTime, boolean enableHighAccuracy, long timeout, long maximumAge) {
4.43 + return p.start(new Query(peer, oneTime, enableHighAccuracy, timeout, maximumAge));
4.44 + }
4.45 +
4.46 + @Override
4.47 + public <Watch> void stop(GLProvider<?, Watch> p, Watch w) {
4.48 + p.stop(w);
4.49 + }
4.50 +
4.51 + @Override
4.52 + public void onError(Exception ex) {
4.53 + throw new UnsupportedOperationException();
4.54 + }
4.55 +
4.56 + @Override
4.57 + public void onLocation(Position position) {
4.58 + throw new UnsupportedOperationException();
4.59 + }
4.60 + };
4.61 + }
4.62 /** Start obtaining geolocation.
4.63 * When the client {@link Handle#start() requests location} (and
4.64 * your provider is found) this method should initialize the request or
4.65 * return <code>null</code> to give chance to another provider.
4.66 *
4.67 - * @param c the callback to {@link #callback(org.netbeans.html.geo.spi.GLProvider.Callback, long, java.lang.Object, java.lang.Exception) use when location is found} -
4.68 + * @param c the query describing the request and
4.69 + * to {@link #callback(org.netbeans.html.geo.spi.GLProvider.Query, long, java.lang.Object, java.lang.Exception) use when location is found} -
4.70 * keep it, you'll need it later
4.71 - * @param oneTime one time request vs. repeated requests
4.72 - * - mimics value provided in {@link Handle#Handle(boolean) constructor}
4.73 - * @param enableHighAccuracy mimics value of
4.74 - * {@link Handle#setHighAccuracy(boolean)}
4.75 - * @param timeout mimics value of
4.76 - * {@link Handle#setTimeout(long)}
4.77 - * @param maximumAge mimics value of
4.78 - * {@link Handle#setMaximumAge(long)}
4.79 *
4.80 * @return an object representing the request (so it can be {@link #stop(java.lang.Object) stopped} later)
4.81 * or <code>null</code> if this provider was unable to start the request
4.82 */
4.83 - protected abstract Watch start(
4.84 - Callback c,
4.85 - boolean oneTime, boolean enableHighAccuracy,
4.86 - long timeout, long maximumAge
4.87 - );
4.88 + protected abstract Watch start(Query c);
4.89
4.90 /** Called when a geolocation request should be stopped.
4.91 *
4.92 - * @param watch the watch returned when {@link #start(org.netbeans.html.geo.spi.GLProvider.Callback, boolean, boolean, long, long) starting}
4.93 + * @param watch the watch returned when {@link #start(org.netbeans.html.geo.spi.GLProvider.Query) starting}
4.94 * the request
4.95 */
4.96 protected abstract void stop(Watch watch);
4.97
4.98 - /** Invoke this method when your provider obtained request location.
4.99 + /** Invoke this method when your provider obtained requested location.
4.100 * This single method is used for notification of success (when <code>ex</code>
4.101 * argument is <code>null</code> and <code>position</code> is provided) or
4.102 * a failure (when <code>ex</code> argument is non-<code>null</code>).
4.103 - * A successful requests leads in call to {@link Handle#onLocation(net.java.html.geo.Position)}
4.104 - * while an error report leads to call to {@link Handle#onError(java.lang.Exception)}.
4.105 + * A successful requests leads in a call to {@link Handle#onLocation(net.java.html.geo.Position)}
4.106 + * while an error report leads to a call to {@link Handle#onError(java.lang.Exception)}.
4.107 *
4.108 - * @param c the callback as provided when {@link #start(org.netbeans.html.geo.spi.GLProvider.Callback, boolean, boolean, long, long) starting}
4.109 + * @param c the query as provided when {@link #start(org.netbeans.html.geo.spi.GLProvider.Query) starting}
4.110 * the request
4.111 * @param timestamp milliseconds since epoch when the location has been obtained
4.112 * @param position your own, internal, representation of geolocation
4.113 @@ -153,14 +166,14 @@
4.114 * when one notifies the successfully obtained <code>position</code>
4.115 */
4.116 protected final void callback(
4.117 - Callback c,
4.118 + Query c,
4.119 long timestamp, Coords position,
4.120 Exception ex
4.121 ) {
4.122 if (ex == null) {
4.123 - c.onLocation(new Position(timestamp, new CoordImpl<Coords>(position, this)));
4.124 + c.peer.onLocation(new Position(timestamp, new CoordImpl<Coords>(position, this)));
4.125 } else {
4.126 - c.onError(ex);
4.127 + c.peer.onError(ex);
4.128 }
4.129 }
4.130
4.131 @@ -219,52 +232,65 @@
4.132 */
4.133 protected abstract Double speed(Coords coords);
4.134
4.135 - /** A callback interface used by {@link GLProvider} to notify back
4.136 + /** Holds parameters describing the location query and is used by {@link GLProvider} to notify back
4.137 * results of its findings.
4.138 */
4.139 - public abstract class Callback {
4.140 - /** Restricted constructor.
4.141 - * @throws IllegalStateException most of the time
4.142 + public static final class Query {
4.143 + private final boolean oneTime;
4.144 + private final boolean enableHighAccuracy;
4.145 + private final long timeout;
4.146 + private final long maximumAge;
4.147 + final Accessor peer;
4.148 +
4.149 + Query(Accessor peer, boolean oneTime, boolean enableHighAccuracy, long timeout, long maximumAge) {
4.150 + this.peer = peer;
4.151 + this.oneTime = oneTime;
4.152 + this.enableHighAccuracy = enableHighAccuracy;
4.153 + this.timeout = timeout;
4.154 + this.maximumAge = maximumAge;
4.155 + }
4.156 +
4.157 + /**
4.158 + * Is this one time or repeated request? Mimics value provided in
4.159 + * {@link Handle#Handle(boolean) constructor}.
4.160 + *
4.161 + * @return true if this is one time request, false if the request is
4.162 + * permanent (up until {@link Handle#stop() } is called).
4.163 */
4.164 - protected Callback() {
4.165 - if (!getClass().getName().equals("net.java.html.geo.Position$Handle$JsH")) {
4.166 - throw new IllegalStateException();
4.167 - }
4.168 + public final boolean isOneTime() {
4.169 + return oneTime;
4.170 }
4.171
4.172 - /** Initiates a geolocation request.
4.173 - *
4.174 - * @param oneTime one time request vs. repeated requests
4.175 - * - mimics value provided in {@link Handle#Handle(boolean) constructor}
4.176 - * @param enableHighAccuracy mimics value of
4.177 - * {@link Handle#setHighAccuracy(boolean)}
4.178 - * @param timeout mimics value of
4.179 - * {@link Handle#setTimeout(long)}
4.180 - * @param maximumAge mimics value of
4.181 - * {@link Handle#setMaximumAge(long)}
4.182 - * @return an object representing the request (so it can be {@link #stop(java.lang.Object) stopped} later)
4.183 - * or <code>null</code> if this provider was unable to start the request
4.184 + /**
4.185 + * Turns on high accuracy mode as specified by the
4.186 + * <a href="http://www.w3.org/TR/2012/PRgeolocationAPI20120510/">
4.187 + * W3C's Geolocation API</a>. By default the mode is disabled. Mimics
4.188 + * value of {@link Handle#setHighAccuracy(boolean)}.
4.189 + *
4.190 + * @return enable <code>true</code> or <code>false</code>
4.191 */
4.192 - protected final Watch start(boolean oneTime, boolean enableHighAccuracy, long timeout, long maximumAge) {
4.193 - return GLProvider.this.start(this, oneTime, enableHighAccuracy, timeout, maximumAge);
4.194 + public final boolean isHighAccuracy() {
4.195 + return this.enableHighAccuracy;
4.196 }
4.197 -
4.198 - /** Stops the watch request.
4.199 - * @param w the watch
4.200 +
4.201 + /**
4.202 + * The amount of milliseconds to wait for a result. Mimics value of
4.203 + * {@link Handle#setTimeout(long)}.
4.204 + *
4.205 + * @return time in milliseconds to wait for a result.
4.206 */
4.207 - protected final void stop(Watch w) {
4.208 - GLProvider.this.stop(w);
4.209 + public final long getTimeout() {
4.210 + return this.timeout;
4.211 }
4.212 -
4.213 - /** Called when a position is successfully obtained.
4.214 - *
4.215 - * @param p the position
4.216 +
4.217 + /**
4.218 + * Sets maximum age of cached results which are acceptable to be
4.219 + * returned. Mimics value of {@link Handle#setMaximumAge(long)}.
4.220 + *
4.221 + * @return time in milliseconds of acceptable cached results
4.222 */
4.223 - protected abstract void onLocation(Position p);
4.224 -
4.225 - /** Called on an error during obtaining of the position.
4.226 - * @param ex exception describing the error
4.227 - */
4.228 - protected abstract void onError(Exception ex);
4.229 + public final long getMaximumAge() {
4.230 + return this.maximumAge;
4.231 + }
4.232 }
4.233 }
5.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
5.2 +++ b/geo/src/test/java/org/netbeans/html/geo/impl/JsGLProviderTest.java Thu Aug 28 14:16:08 2014 +0200
5.3 @@ -0,0 +1,76 @@
5.4 +/**
5.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
5.6 + *
5.7 + * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
5.8 + *
5.9 + * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
5.10 + * Other names may be trademarks of their respective owners.
5.11 + *
5.12 + * The contents of this file are subject to the terms of either the GNU
5.13 + * General Public License Version 2 only ("GPL") or the Common
5.14 + * Development and Distribution License("CDDL") (collectively, the
5.15 + * "License"). You may not use this file except in compliance with the
5.16 + * License. You can obtain a copy of the License at
5.17 + * http://www.netbeans.org/cddl-gplv2.html
5.18 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
5.19 + * specific language governing permissions and limitations under the
5.20 + * License. When distributing the software, include this License Header
5.21 + * Notice in each file and include the License file at
5.22 + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
5.23 + * particular file as subject to the "Classpath" exception as provided
5.24 + * by Oracle in the GPL Version 2 section of the License file that
5.25 + * accompanied this code. If applicable, add the following below the
5.26 + * License Header, with the fields enclosed by brackets [] replaced by
5.27 + * your own identifying information:
5.28 + * "Portions Copyrighted [year] [name of copyright owner]"
5.29 + *
5.30 + * Contributor(s):
5.31 + *
5.32 + * The Original Software is NetBeans. The Initial Developer of the Original
5.33 + * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved.
5.34 + *
5.35 + * If you wish your version of this file to be governed by only the CDDL
5.36 + * or only the GPL Version 2, indicate your decision by adding
5.37 + * "[Contributor] elects to include this software in this distribution
5.38 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
5.39 + * single choice of license, a recipient has the option to distribute
5.40 + * your version of this file under either the CDDL, the GPL Version 2 or
5.41 + * to extend the choice of license to its licensees as provided above.
5.42 + * However, if you add GPL Version 2 code and therefore, elected the GPL
5.43 + * Version 2 license, then the option applies only if the new code is
5.44 + * made subject to such option by the copyright holder.
5.45 + */
5.46 +package org.netbeans.html.geo.impl;
5.47 +
5.48 +import net.java.html.geo.Position;
5.49 +import static org.testng.Assert.*;
5.50 +import org.testng.annotations.Test;
5.51 +
5.52 +/**
5.53 + *
5.54 + * @author Jaroslav Tulach <jtulach@netbeans.org>
5.55 + */
5.56 +public class JsGLProviderTest extends Position.Handle {
5.57 + public JsGLProviderTest() {
5.58 + super(true);
5.59 + }
5.60 +
5.61 + @Test public void checkWhetherWeCanInstantiate() {
5.62 + assertNotNull(new JsGLProvider());
5.63 + }
5.64 +
5.65 + @Test public void canCallIsSupported() {
5.66 + assertFalse(isSupported(), "Well, it is not, as we are not in a browser context");
5.67 + }
5.68 +
5.69 + @Override
5.70 + protected void onLocation(Position p) throws Throwable {
5.71 + throw new UnsupportedOperationException();
5.72 + }
5.73 +
5.74 + @Override
5.75 + protected void onError(Exception ex) throws Throwable {
5.76 + throw new UnsupportedOperationException();
5.77 + }
5.78 +
5.79 +}
6.1 --- a/geo/src/test/java/org/netbeans/html/geo/spi/CoordImplTest.java Wed Aug 27 18:45:51 2014 +0200
6.2 +++ b/geo/src/test/java/org/netbeans/html/geo/spi/CoordImplTest.java Thu Aug 28 14:16:08 2014 +0200
6.3 @@ -44,10 +44,6 @@
6.4 package org.netbeans.html.geo.spi;
6.5
6.6 import static org.testng.Assert.*;
6.7 -import org.testng.annotations.AfterClass;
6.8 -import org.testng.annotations.AfterMethod;
6.9 -import org.testng.annotations.BeforeClass;
6.10 -import org.testng.annotations.BeforeMethod;
6.11 import org.testng.annotations.Test;
6.12
6.13 /**
6.14 @@ -64,7 +60,7 @@
6.15 }
6.16
6.17 @Override
6.18 - protected Object start(Callback c, boolean oneTime, boolean enableHighAccuracy, long timeout, long maximumAge) {
6.19 + protected Object start(Query c) {
6.20 throw new UnsupportedOperationException();
6.21 }
6.22