From 660ae68f6c3b98ea790086175bf27a80dddbd2aa Mon Sep 17 00:00:00 2001 From: nathan Date: Mon, 20 Jul 2015 16:16:58 +0200 Subject: [PATCH] Updated license information, general code cleanup --- .../src/dorkbox/util/entropy/Entropy.java | 104 +++++++++ .../serializers/ArraysAsListSerializer.java | 112 ++++++++++ .../FieldAnnotationAwareSerializer.java | 202 ++++++++++++++++++ .../UnmodifiableCollectionsSerializer.java | 186 ++++++++++++++++ 4 files changed, 604 insertions(+) create mode 100644 Dorkbox-Util/src/dorkbox/util/entropy/Entropy.java create mode 100644 Dorkbox-Util/src/dorkbox/util/serializers/ArraysAsListSerializer.java create mode 100644 Dorkbox-Util/src/dorkbox/util/serializers/FieldAnnotationAwareSerializer.java create mode 100644 Dorkbox-Util/src/dorkbox/util/serializers/UnmodifiableCollectionsSerializer.java diff --git a/Dorkbox-Util/src/dorkbox/util/entropy/Entropy.java b/Dorkbox-Util/src/dorkbox/util/entropy/Entropy.java new file mode 100644 index 0000000..fd0866b --- /dev/null +++ b/Dorkbox-Util/src/dorkbox/util/entropy/Entropy.java @@ -0,0 +1,104 @@ +/* + * Copyright 2010 dorkbox, llc + * + * 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 dorkbox.util.entropy; + +import dorkbox.network.util.exceptions.InitializationException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Method; + +public final +class Entropy { + + @SuppressWarnings("StaticNonFinalField") + private static EntropyProvider provider = null; + + /** + * Starts the process, and gets, the next amount of entropy bytes + */ + public static + byte[] get(String messageForUser) throws InitializationException { + synchronized (Entropy.class) { + try { + if (provider == null) { + Entropy.init(SimpleEntropy.class); + } + + return provider.get(messageForUser); + } catch (Exception e) { + Logger logger = LoggerFactory.getLogger(Entropy.class); + String error = "Unable to get entropy bytes for " + provider.getClass(); + logger.error(error, e); + throw new InitializationException(error); + } + } + } + + /** + * Will only set the Entropy provider if it has not ALREADY been set! + */ + public static + void init(Class providerClass, Object... args) throws InitializationException { + synchronized (Entropy.class) { + if (provider == null) { + Exception exception = null; + + // use reflection to create the provider. + try { + Method createMethod = null; + Method[] declaredMethods = providerClass.getDeclaredMethods(); + for (Method m : declaredMethods) { + if (m.getName() + .equals("create")) { + createMethod = m; + break; + } + } + + if (createMethod != null) { + createMethod.setAccessible(true); + + if (args.length == 0) { + provider = (EntropyProvider) createMethod.invoke(null); + } + else { + provider = (EntropyProvider) createMethod.invoke(null, args); + } + return; + } + } catch (Exception e) { + exception = e; + } + + Logger logger = LoggerFactory.getLogger(Entropy.class); + String error = "Unable to create entropy provider for " + providerClass + " with " + args.length + " args"; + if (exception != null) { + logger.error(error, exception); + } + else { + logger.error(error); + } + + throw new InitializationException(error); + } + } + } + + private + Entropy() { + } +} diff --git a/Dorkbox-Util/src/dorkbox/util/serializers/ArraysAsListSerializer.java b/Dorkbox-Util/src/dorkbox/util/serializers/ArraysAsListSerializer.java new file mode 100644 index 0000000..3db9cc2 --- /dev/null +++ b/Dorkbox-Util/src/dorkbox/util/serializers/ArraysAsListSerializer.java @@ -0,0 +1,112 @@ +/* + * Copyright 2010 Martin Grotzke + * + * 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 dorkbox.util.serializers; + +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.List; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.Serializer; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; + +/** + * A kryo {@link Serializer} for lists created via {@link Arrays#asList(Object...)}. + *

+ * Note: This serializer does not support cyclic references, so if one of the objects + * gets set the list as attribute this might cause an error during deserialization. + *

+ * + * @author Martin Grotzke + */ +public class ArraysAsListSerializer extends Serializer> { + + private Field _arrayField; + + public ArraysAsListSerializer() { + try { + _arrayField = Class.forName( "java.util.Arrays$ArrayList" ).getDeclaredField( "a" ); + _arrayField.setAccessible( true ); + } catch ( final Exception e ) { + throw new RuntimeException( e ); + } + // Immutable causes #copy(obj) to return the original object + setImmutable(true); + } + + @Override + public List read(final Kryo kryo, final Input input, final Class> type) { + final int length = input.readInt(true); + Class componentType = kryo.readClass( input ).getType(); + if (componentType.isPrimitive()) { + componentType = getPrimitiveWrapperClass(componentType); + } + try { + final Object items = Array.newInstance( componentType, length ); + for( int i = 0; i < length; i++ ) { + Array.set(items, i, kryo.readClassAndObject( input )); + } + return Arrays.asList( (Object[])items ); + } catch ( final Exception e ) { + throw new RuntimeException( e ); + } + } + + @Override + public void write(final Kryo kryo, final Output output, final List obj) { + try { + final Object[] array = (Object[]) _arrayField.get( obj ); + output.writeInt(array.length, true); + final Class componentType = array.getClass().getComponentType(); + kryo.writeClass( output, componentType ); + for( final Object item : array ) { + kryo.writeClassAndObject( output, item ); + } + } catch ( final RuntimeException e ) { + // Don't eat and wrap RuntimeExceptions because the ObjectBuffer.write... + // handles SerializationException specifically (resizing the buffer)... + throw e; + } catch ( final Exception e ) { + throw new RuntimeException( e ); + } + } + + private static Class getPrimitiveWrapperClass(final Class c) { + if (c.isPrimitive()) { + if (c.equals(Long.TYPE)) { + return Long.class; + } else if (c.equals(Integer.TYPE)) { + return Integer.class; + } else if (c.equals(Double.TYPE)) { + return Double.class; + } else if (c.equals(Float.TYPE)) { + return Float.class; + } else if (c.equals(Boolean.TYPE)) { + return Boolean.class; + } else if (c.equals(Character.TYPE)) { + return Character.class; + } else if (c.equals(Short.TYPE)) { + return Short.class; + } else if (c.equals(Byte.TYPE)) { + return Byte.class; + } + } + return c; + } +} diff --git a/Dorkbox-Util/src/dorkbox/util/serializers/FieldAnnotationAwareSerializer.java b/Dorkbox-Util/src/dorkbox/util/serializers/FieldAnnotationAwareSerializer.java new file mode 100644 index 0000000..5ea6dca --- /dev/null +++ b/Dorkbox-Util/src/dorkbox/util/serializers/FieldAnnotationAwareSerializer.java @@ -0,0 +1,202 @@ +/* + * Copyright 2010 Martin Grotzke + * + * 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 dorkbox.util.serializers; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.Serializer; +import com.esotericsoftware.kryo.factories.SerializerFactory; +import com.esotericsoftware.kryo.serializers.FieldSerializer; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +/** + * A kryo {@link FieldSerializer} that allows to exclusively include or exclude fields that + * are attributed with user-specific annotations. This can be for example useful when serializing beans that carry + * references to a dependency injection framework. As an example for Spring: + *

+ *

+ * {@code
+ * Set> marks = new HashSet<>();
+ * marks.add(Autowired.class);
+ * SerializerFactory disregardingFactory = new FieldAnnotationAwareSerializer.Factory(marks, true);
+ * Kryo kryo = new Kryo();
+ * kryo.setDefaultSerializer(factory);
+ * }
+ * 
+ *

+ * The resulting {@link Kryo} instance would ignore all fields that are annotated with Spring's {@code @Autowired} + * annotation. + *

+ * Similarly, it is possible to created a serializer which does the opposite such that the resulting serializer + * would only serialize fields that are annotated with the specified annotations. + * + * @author Rafael Winterhalter + * @author Martin Grotzke + */ +public class FieldAnnotationAwareSerializer extends FieldSerializer { + + /** + * A factory for creating instances of {@link FieldAnnotationAwareSerializer}. + */ + public static class Factory implements SerializerFactory { + + private final Collection> marked; + private final boolean disregarding; + + /** + * Creates a new factory. See {@link FieldAnnotationAwareSerializer#FieldAnnotationAwareSerializer( + *com.esotericsoftware.kryo.Kryo, Class, java.util.Collection, boolean)} + * for additional information on the constructor parameters. + * + * @param marked The annotations that will be considered of the resulting converter. + * @param disregarding If {@code true}, the serializer will ignore all annotated fields, + * if set to {@code false} it will exclusively look at annotated fields. + */ + public Factory(final Collection> marked, final boolean disregarding) { + this.marked = marked; + this.disregarding = disregarding; + } + + @Override + public Serializer makeSerializer(final Kryo kryo, final Class type) { + return new FieldAnnotationAwareSerializer(kryo, type, this.marked, this.disregarding); + } + } + + private final Set> marked; + + /** + * Determines whether annotated fields should be excluded from serialization. + *

+ * {@code true} if annotated fields should be excluded from serialization, + * {@code false} if only annotated fields should be included from serialization. + */ + private final boolean disregarding; + + /** + * Creates a new field annotation aware serializer. + * + * @param kryo The {@link Kryo} instace. + * @param type The type of the class being serialized. + * @param marked The annotations this serializer considers for its serialization process. Be aware tha + * a serializer with {@code disregarding} set to {@code false} will never be able to + * serialize fields that are not annotated with any of these annotations since it is not + * possible to add fields to a {@link FieldSerializer} once it is created. See the + * documentation to {@link FieldAnnotationAwareSerializer#addAnnotation(Class)} and + * {@link FieldAnnotationAwareSerializer#removeAnnotation(Class)} for further information. + * @param disregarding If {@code true}, the serializer will ignore all annotated fields, + * if set to {@code false} it will exclusively look at annotated fields. + */ + public FieldAnnotationAwareSerializer(final Kryo kryo, + final Class type, + final Collection> marked, + final boolean disregarding) { + super(kryo, type); + this.disregarding = disregarding; + this.marked = new HashSet>(marked); + rebuildCachedFields(); + } + + @Override + protected void rebuildCachedFields() { + // In order to avoid rebuilding the cached fields twice, the super constructor's call + // to this method will be suppressed. This can be done by a simple check of the initialization + // state of a property of this subclass. + if (this.marked == null) { + return; + } + super.rebuildCachedFields(); + removeFields(); + } + + private void removeFields() { + final CachedField[] cachedFields = getFields(); + for (final CachedField cachedField : cachedFields) { + final Field field = cachedField.getField(); + if (isRemove(field)) { +// if (TRACE) { +// trace("kryo", String.format("Ignoring field %s tag: %s", this.disregarding ? "without" : "with", cachedField)); +// } + super.removeField(field.getName()); + } + } + } + + private boolean isRemove(final Field field) { + return !isMarked(field) ^ this.disregarding; + } + + private boolean isMarked(final Field field) { + for (final Annotation annotation : field.getAnnotations()) { + final Class annotationType = annotation.annotationType(); + if (this.marked.contains(annotationType)) { + return true; + } + } + return false; + } + + /** + * Adds an annotation to the annotations that are considered by this serializer. + *

+ * Important: This will not have an effect if the serializer was configured + * to exclusively serialize annotated fields by setting {@code disregarding} to + * {@code false}. This is similar to the contract of this serializer's superclass + * {@link FieldSerializer} which does not allow to add fields that were formerly + * removed. If this was possible, instances that were serialized before this field + * was added could not longer be properly deserialized. In order to make this contract + * break explicit, you need to create a new instance of this serializer if you want to + * include new fields to a serializer that exclusively serializes annotated fields. + * + * @param clazz The annotation class to be added. + * @return {@code true} if the method call had an effect. + */ + public boolean addAnnotation(final Class clazz) { + if (this.disregarding && this.marked.add(clazz)) { + initializeCachedFields(); + return true; + } + return false; + } + + /** + * Removes an annotation to the annotations that are considered by this serializer. + *

+ * Important: This will not have an effect if the serializer was configured + * to not serialize annotated fields by setting {@code disregarding} to + * {@code true}. This is similar to the contract of this serializer's superclass + * {@link FieldSerializer} which does not allow to add fields that were formerly + * removed. If this was possible, instances that were serialized before this field + * was added could not longer be properly deserialized. In order to make this contract + * break explicit, you need to create a new instance of this serializer if you want to + * include new fields to a serializer that ignores annotated fields for serialization. + * + * @param clazz The annotation class to be removed. + * @return {@code true} if the method call had an effect. + */ + public boolean removeAnnotation(final Class clazz) { + if (!this.disregarding && this.marked.remove(clazz)) { + initializeCachedFields(); + return true; + } + return false; + } +} diff --git a/Dorkbox-Util/src/dorkbox/util/serializers/UnmodifiableCollectionsSerializer.java b/Dorkbox-Util/src/dorkbox/util/serializers/UnmodifiableCollectionsSerializer.java new file mode 100644 index 0000000..e2d1830 --- /dev/null +++ b/Dorkbox-Util/src/dorkbox/util/serializers/UnmodifiableCollectionsSerializer.java @@ -0,0 +1,186 @@ +/* + * Copyright 2010 Martin Grotzke + * + * 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 dorkbox.util.serializers; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.Serializer; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; +import dorkbox.util.exceptions.NetException; +import dorkbox.util.SerializationManager; + +import java.lang.reflect.Field; +import java.util.*; + +/** + * A kryo {@link Serializer} for unmodifiable {@link Collection}s and {@link Map}s + * created via {@link Collections}. + * + * @author Martin Grotzke + */ +public class UnmodifiableCollectionsSerializer extends Serializer { + + private static final Field SOURCE_COLLECTION_FIELD; + private static final Field SOURCE_MAP_FIELD; + + static { + try { + SOURCE_COLLECTION_FIELD = Class.forName("java.util.Collections$UnmodifiableCollection" ) + .getDeclaredField("c"); + SOURCE_COLLECTION_FIELD.setAccessible( true ); + + + SOURCE_MAP_FIELD = Class.forName("java.util.Collections$UnmodifiableMap" ) + .getDeclaredField("m"); + SOURCE_MAP_FIELD.setAccessible( true ); + } catch ( final Exception e ) { + throw new NetException("Could not access source collection" + + " field in java.util.Collections$UnmodifiableCollection.", e ); + } + } + + @Override + public Object read(final Kryo kryo, final Input input, final Class clazz) { + final int ordinal = input.readInt( true ); + final UnmodifiableCollection unmodifiableCollection = UnmodifiableCollection.values()[ordinal]; + final Object sourceCollection = kryo.readClassAndObject( input ); + return unmodifiableCollection.create( sourceCollection ); + } + + @Override + public void write(final Kryo kryo, final Output output, final Object object) { + try { + final UnmodifiableCollection unmodifiableCollection = UnmodifiableCollection.valueOfType( object.getClass() ); + // the ordinal could be replaced by s.th. else (e.g. a explicitely managed "id") + output.writeInt( unmodifiableCollection.ordinal(), true ); + kryo.writeClassAndObject( output, unmodifiableCollection.sourceCollectionField.get( object ) ); + } catch ( final RuntimeException e ) { + // Don't eat and wrap RuntimeExceptions because the ObjectBuffer.write... + // handles SerializationException specifically (resizing the buffer)... + throw e; + } catch ( final Exception e ) { + throw new RuntimeException( e ); + } + } + + @Override + public Object copy(Kryo kryo, Object original) { + try { + final UnmodifiableCollection unmodifiableCollection = UnmodifiableCollection.valueOfType( original.getClass() ); + Object sourceCollectionCopy = kryo.copy(unmodifiableCollection.sourceCollectionField.get(original)); + return unmodifiableCollection.create( sourceCollectionCopy ); + } catch ( final RuntimeException e ) { + // Don't eat and wrap RuntimeExceptions + throw e; + } catch ( final Exception e ) { + throw new RuntimeException( e ); + } + } + + private static enum UnmodifiableCollection { + COLLECTION( Collections.unmodifiableCollection( Arrays.asList( "" ) ).getClass(), SOURCE_COLLECTION_FIELD ){ + @Override + public Object create( final Object sourceCollection ) { + return Collections.unmodifiableCollection( (Collection) sourceCollection ); + } + }, + RANDOM_ACCESS_LIST( Collections.unmodifiableList( new ArrayList() ).getClass(), SOURCE_COLLECTION_FIELD ){ + @Override + public Object create( final Object sourceCollection ) { + return Collections.unmodifiableList( (List) sourceCollection ); + } + }, + LIST( Collections.unmodifiableList( new LinkedList() ).getClass(), SOURCE_COLLECTION_FIELD ){ + @Override + public Object create( final Object sourceCollection ) { + return Collections.unmodifiableList( (List) sourceCollection ); + } + }, + SET( Collections.unmodifiableSet( new HashSet() ).getClass(), SOURCE_COLLECTION_FIELD ){ + @Override + public Object create( final Object sourceCollection ) { + return Collections.unmodifiableSet( (Set) sourceCollection ); + } + }, + SORTED_SET( Collections.unmodifiableSortedSet( new TreeSet() ).getClass(), SOURCE_COLLECTION_FIELD ){ + @Override + public Object create( final Object sourceCollection ) { + return Collections.unmodifiableSortedSet( (SortedSet) sourceCollection ); + } + }, + MAP( Collections.unmodifiableMap( new HashMap() ).getClass(), SOURCE_MAP_FIELD ) { + + @Override + public Object create( final Object sourceCollection ) { + return Collections.unmodifiableMap( (Map) sourceCollection ); + } + + }, + SORTED_MAP( Collections.unmodifiableSortedMap( new TreeMap() ).getClass(), SOURCE_MAP_FIELD ) { + @Override + public Object create( final Object sourceCollection ) { + return Collections.unmodifiableSortedMap( (SortedMap) sourceCollection ); + } + }; + + private final Class type; + private final Field sourceCollectionField; + + private UnmodifiableCollection( final Class type, final Field sourceCollectionField ) { + this.type = type; + this.sourceCollectionField = sourceCollectionField; + } + + /** + * @param sourceCollection + */ + public abstract Object create( Object sourceCollection ); + + static UnmodifiableCollection valueOfType( final Class type ) { + for( final UnmodifiableCollection item : values() ) { + if ( item.type.equals( type ) ) { + return item; + } + } + throw new IllegalArgumentException( "The type " + type + " is not supported." ); + } + + } + + /** + * Creates a new {@link UnmodifiableCollectionsSerializer} and registers its serializer + * for the several unmodifiable Collections that can be created via {@link Collections}, + * including {@link Map}s. + * + * @param manager the {@link SerializationManager} instance to set the serializer on. + * + * @see Collections#unmodifiableCollection(Collection) + * @see Collections#unmodifiableList(List) + * @see Collections#unmodifiableSet(Set) + * @see Collections#unmodifiableSortedSet(SortedSet) + * @see Collections#unmodifiableMap(Map) + * @see Collections#unmodifiableSortedMap(SortedMap) + */ + public static void registerSerializers( final SerializationManager manager ) { + final UnmodifiableCollectionsSerializer serializer = new UnmodifiableCollectionsSerializer(); + UnmodifiableCollection.values(); + for ( final UnmodifiableCollection item : UnmodifiableCollection.values() ) { + manager.register( item.type, serializer ); + } + } + +}