io.undertow.websockets.jsr.EncodingFactory Maven / Gradle / Ivy
/*
* JBoss, Home of Professional Open Source.
* Copyright 2014 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.undertow.websockets.jsr;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.websocket.Decoder;
import javax.websocket.DeploymentException;
import javax.websocket.Encoder;
import javax.websocket.EndpointConfig;
import io.undertow.servlet.api.ClassIntrospecter;
import io.undertow.servlet.api.InstanceFactory;
import io.undertow.servlet.api.InstanceHandle;
/**
* Factory class that produces encoding instances for an endpoint. This also provides static
* methods about the capabilities of encoders.
*
* These classes also perform implicit encodings for java primitives
*
* @author Stuart Douglas
*/
public class EncodingFactory {
/**
* An encoding factory that can deal with primitive types.
*/
public static final EncodingFactory DEFAULT = new EncodingFactory(Collections.EMPTY_MAP, Collections.EMPTY_MAP, Collections.EMPTY_MAP, Collections.EMPTY_MAP);
private final Map, List>> binaryEncoders;
private final Map, List>> binaryDecoders;
private final Map, List>> textEncoders;
private final Map, List>> textDecoders;
public EncodingFactory(final Map, List>> binaryEncoders, final Map, List>> binaryDecoders, final Map, List>> textEncoders, final Map, List>> textDecoders) {
this.binaryEncoders = binaryEncoders;
this.binaryDecoders = binaryDecoders;
this.textEncoders = textEncoders;
this.textDecoders = textDecoders;
}
public boolean canEncodeText(final Class> type) {
if (isPrimitiveOrBoxed(type)) {
return true;
}
return textEncoders.containsKey(type);
}
public boolean canDecodeText(final Class> type) {
if (isPrimitiveOrBoxed(type)) {
return true;
}
return textDecoders.containsKey(type);
}
public boolean canEncodeBinary(final Class> type) {
return binaryEncoders.containsKey(type);
}
public boolean canDecodeBinary(final Class> type) {
return binaryDecoders.containsKey(type);
}
public Encoding createEncoding(final EndpointConfig endpointConfig) {
try {
Map, List>> binaryEncoders = this.binaryEncoders.isEmpty() ? Collections., List>>emptyMap() : new HashMap, List>>();
Map, List>> binaryDecoders = this.binaryDecoders.isEmpty() ? Collections., List>>emptyMap() : new HashMap, List>>();
Map, List>> textEncoders = this.textEncoders.isEmpty() ? Collections., List>>emptyMap() : new HashMap, List>>();
Map, List>> textDecoders = this.textDecoders.isEmpty() ? Collections., List>>emptyMap() : new HashMap, List>>();
for (Map.Entry, List>> entry : this.binaryEncoders.entrySet()) {
final List> val = new ArrayList<>(entry.getValue().size());
binaryEncoders.put(entry.getKey(), val);
for (InstanceFactory extends Encoder> factory : entry.getValue()) {
InstanceHandle extends Encoder> instance = factory.createInstance();
instance.getInstance().init(endpointConfig);
val.add(instance);
}
}
for (Map.Entry, List>> entry : this.binaryDecoders.entrySet()) {
final List> val = new ArrayList<>(entry.getValue().size());
binaryDecoders.put(entry.getKey(), val);
for (InstanceFactory extends Decoder> factory : entry.getValue()) {
InstanceHandle extends Decoder> instance = factory.createInstance();
instance.getInstance().init(endpointConfig);
val.add(instance);
}
}
for (Map.Entry, List>> entry : this.textEncoders.entrySet()) {
final List> val = new ArrayList<>(entry.getValue().size());
textEncoders.put(entry.getKey(), val);
for (InstanceFactory extends Encoder> factory : entry.getValue()) {
InstanceHandle extends Encoder> instance = factory.createInstance();
instance.getInstance().init(endpointConfig);
val.add(instance);
}
}
for (Map.Entry, List>> entry : this.textDecoders.entrySet()) {
final List> val = new ArrayList<>(entry.getValue().size());
textDecoders.put(entry.getKey(), val);
for (InstanceFactory extends Decoder> factory : entry.getValue()) {
InstanceHandle extends Decoder> instance = factory.createInstance();
instance.getInstance().init(endpointConfig);
val.add(instance);
}
}
return new Encoding(binaryEncoders, binaryDecoders, textEncoders, textDecoders);
} catch (InstantiationException e) {
throw new RuntimeException(e);
}
}
public static EncodingFactory createFactory(final ClassIntrospecter classIntrospecter, final Class extends Decoder>[] decoders, final Class extends Encoder>[] encoders) throws DeploymentException {
return createFactory(classIntrospecter, Arrays.asList(decoders), Arrays.asList(encoders));
}
public static EncodingFactory createFactory(final ClassIntrospecter classIntrospecter, final List> decoders, final List> encoders) throws DeploymentException {
final Map, List>> binaryEncoders = new HashMap<>();
final Map, List>> binaryDecoders = new HashMap<>();
final Map, List>> textEncoders = new HashMap<>();
final Map, List>> textDecoders = new HashMap<>();
for (Class extends Decoder> decoder : decoders) {
if (Decoder.Binary.class.isAssignableFrom(decoder)) {
try {
Method method = decoder.getMethod("decode", ByteBuffer.class);
final Class> type = method.getReturnType();
List> list = binaryDecoders.get(type);
if (list == null) {
binaryDecoders.put(type, list = new ArrayList<>());
}
list.add(classIntrospecter.createInstanceFactory(decoder));
} catch (NoSuchMethodException e) {
throw JsrWebSocketMessages.MESSAGES.couldNotDetermineTypeOfDecodeMethodForClass(decoder, e);
}
} else if (Decoder.BinaryStream.class.isAssignableFrom(decoder)) {
try {
Method method = decoder.getMethod("decode", InputStream.class);
final Class> type = method.getReturnType();
List> list = binaryDecoders.get(type);
if (list == null) {
binaryDecoders.put(type, list = new ArrayList<>());
}
list.add(classIntrospecter.createInstanceFactory(decoder));
} catch (NoSuchMethodException e) {
throw JsrWebSocketMessages.MESSAGES.couldNotDetermineTypeOfDecodeMethodForClass(decoder, e);
}
} else if (Decoder.Text.class.isAssignableFrom(decoder)) {
try {
Method method = decoder.getMethod("decode", String.class);
final Class> type = resolveReturnType(method, decoder);
List> list = textDecoders.get(type);
if (list == null) {
textDecoders.put(type, list = new ArrayList<>());
}
list.add(classIntrospecter.createInstanceFactory(decoder));
} catch (NoSuchMethodException e) {
throw JsrWebSocketMessages.MESSAGES.couldNotDetermineTypeOfDecodeMethodForClass(decoder, e);
}
} else if (Decoder.TextStream.class.isAssignableFrom(decoder)) {
try {
Method method = decoder.getMethod("decode", Reader.class);
final Class> type = method.getReturnType();
List> list = textDecoders.get(type);
if (list == null) {
textDecoders.put(type, list = new ArrayList<>());
}
list.add(createInstanceFactory(classIntrospecter, decoder));
} catch (NoSuchMethodException e) {
throw JsrWebSocketMessages.MESSAGES.couldNotDetermineTypeOfDecodeMethodForClass(decoder, e);
}
} else {
throw JsrWebSocketMessages.MESSAGES.didNotImplementKnownDecoderSubclass(decoder);
}
}
for (Class extends Encoder> encoder : encoders) {
if (Encoder.Binary.class.isAssignableFrom(encoder)) {
final Class> type = findEncodeMethod(encoder, ByteBuffer.class);
List> list = binaryEncoders.get(type);
if (list == null) {
binaryEncoders.put(type, list = new ArrayList<>());
}
list.add(createInstanceFactory(classIntrospecter, encoder));
} else if (Encoder.BinaryStream.class.isAssignableFrom(encoder)) {
final Class> type = findEncodeMethod(encoder, void.class, OutputStream.class);
List> list = binaryEncoders.get(type);
if (list == null) {
binaryEncoders.put(type, list = new ArrayList<>());
}
list.add(createInstanceFactory(classIntrospecter, encoder));
} else if (Encoder.Text.class.isAssignableFrom(encoder)) {
final Class> type = findEncodeMethod(encoder, String.class);
List> list = textEncoders.get(type);
if (list == null) {
textEncoders.put(type, list = new ArrayList<>());
}
list.add(createInstanceFactory(classIntrospecter, encoder));
} else if (Encoder.TextStream.class.isAssignableFrom(encoder)) {
final Class> type = findEncodeMethod(encoder, void.class, Writer.class);
List> list = textEncoders.get(type);
if (list == null) {
textEncoders.put(type, list = new ArrayList<>());
}
list.add(createInstanceFactory(classIntrospecter, encoder));
}
}
return new EncodingFactory(binaryEncoders, binaryDecoders, textEncoders, textDecoders);
}
private static Class> resolveReturnType(Method method, Class extends Decoder> decoder) {
Type genericReturnType = method.getGenericReturnType();
if (genericReturnType instanceof Class) {
return (Class>) genericReturnType;
} else if (genericReturnType instanceof TypeVariable) {
TypeVariable type = ((TypeVariable) genericReturnType);
List classes = new ArrayList<>();
Class c = decoder;
while (c != method.getDeclaringClass() && c != null) {
classes.add(c);
c = c.getSuperclass();
}
Collections.reverse(classes);
String currentName = type.getName();
int currentPos = -1;
for (Class clz : classes) {
for (int i = 0; i < clz.getSuperclass().getTypeParameters().length; ++i) {
TypeVariable extends Class>> param = clz.getSuperclass().getTypeParameters()[i];
if (param.getName().equals(currentName)) {
currentPos = i;
break;
}
}
Type gs = clz.getGenericSuperclass();
if (gs instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) gs;
Type at = pt.getActualTypeArguments()[currentPos];
if (at instanceof Class) {
return (Class>) at;
} else if (at instanceof TypeVariable) {
TypeVariable tv = (TypeVariable) at;
currentName = tv.getName();
}
}
}
//todo: should we throw an exception here? It should never actually be reached
return method.getReturnType();
} else {
return method.getReturnType();
}
}
private static InstanceFactory extends T> createInstanceFactory(final ClassIntrospecter classIntrospecter, final Class extends T> decoder) throws DeploymentException {
try {
return classIntrospecter.createInstanceFactory(decoder);
} catch (NoSuchMethodException e) {
throw JsrWebSocketMessages.MESSAGES.classDoesNotHaveDefaultConstructor(decoder, e);
}
}
private static Class> findEncodeMethod(final Class extends Encoder> encoder, final Class> returnType, Class>... otherParameters) throws DeploymentException {
for (Method method : encoder.getMethods()) {
if (method.getName().equals("encode") && !method.isBridge() &&
method.getParameterTypes().length == 1 + otherParameters.length &&
method.getReturnType() == returnType) {
boolean ok = true;
for (int i = 1; i < method.getParameterTypes().length; ++i) {
if (method.getParameterTypes()[i] != otherParameters[i - 1]) {
ok = false;
break;
}
}
if (ok) {
return method.getParameterTypes()[0];
}
}
}
throw JsrWebSocketMessages.MESSAGES.couldNotDetermineTypeOfEncodeMethodForClass(encoder);
}
static boolean isPrimitiveOrBoxed(final Class> clazz) {
return clazz.isPrimitive() ||
clazz == Boolean.class ||
clazz == Byte.class ||
clazz == Character.class ||
clazz == Short.class ||
clazz == Integer.class ||
clazz == Long.class ||
clazz == Float.class ||
clazz == Double.class;//we don't care about void
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy