/*
* Copyright (C) 2011 Rhegium Team
*
* 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 org.rhegium.internal.serialization;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.rhegium.api.serialization.Attribute;
import org.rhegium.api.serialization.AttributeAccessorException;
import org.rhegium.api.serialization.IllegalOptionalAttributeException;
import org.rhegium.api.serialization.Marshaller;
import org.rhegium.api.serialization.Unmarshaller;
import org.rhegium.api.serialization.accessor.Accessor;
import org.rhegium.api.serialization.accessor.AccessorService;
import com.google.inject.Inject;
class DefaultPojoMarshaller<O> implements Marshaller<O>, Unmarshaller<O> {
@Inject
private AccessorService accessorService;
private static final Map<Class<?>, Set<AttributeDescriptor>> ATTRIBUTE_CACHE = new HashMap<Class<?>, Set<AttributeDescriptor>>();
private static final Comparator<AttributeDescriptor> ATTRIBUTE_COMPARATOR = new AttributeComparator();
@Override
public void unmarshal(O object, DataInput stream) throws IOException {
final Collection<AttributeDescriptor> attributes = getAttributes(object.getClass());
for (final AttributeDescriptor attribute : attributes) {
writeAttributeValue(stream, attribute, object);
}
}
@Override
public void marshal(O object, DataOutput stream) throws IOException {
final Collection<AttributeDescriptor> attributes = getAttributes(object.getClass());
for (final AttributeDescriptor attribute : attributes) {
readAttributeValue(stream, attribute, object);
}
}
private static void quickCheckLegalOptionalAttribute(Collection<AttributeDescriptor> attributes) {
int index = 0;
final int length = attributes.size();
for (final AttributeDescriptor attribute : attributes) {
if (index < length - 1 && attribute.isOptional()) {
throw new IllegalOptionalAttributeException(String.format(
"Optional attributes can only be the last attribute in a packet: %s", attribute));
}
index++;
}
}
private void writeAttributeValue(DataInput input, AttributeDescriptor attribute, Object object) throws IOException {
final Class<?> type = attribute.getType();
// Reserved for later use to preserve space in protocol data
@SuppressWarnings("unused")
final int length = attribute.getProtocolAttribute().length();
final Accessor<?> accessor = accessorService.resolveAccessor(type);
if (accessor == null) {
throw new AttributeAccessorException(String.format("Accessor for attribute %s with type %s not found",
attribute, type));
}
accessor.run(input, object, attribute, AccessorType.Get);
}
private void readAttributeValue(DataOutput output, AttributeDescriptor attribute, Object object) throws IOException {
final Class<?> type = attribute.getType();
// Reserved for later use to preserve space in protocol data
@SuppressWarnings("unused")
final int length = attribute.getProtocolAttribute().length();
final Accessor<?> accessor = accessorService.resolveAccessor(type);
if (accessor == null) {
throw new AttributeAccessorException(String.format("Accessor for attribute %s with type %s not found",
attribute, type));
}
accessor.run(output, object, attribute, AccessorType.Set);
}
private Collection<AttributeDescriptor> getAttributes(Class<?> clazz) {
// Is class info cached?
final Set<AttributeDescriptor> attributes = ATTRIBUTE_CACHE.get(clazz);
if (attributes != null) {
// Let's sort the attributes and return them
final List<AttributeDescriptor> result = new ArrayList<AttributeDescriptor>(attributes);
Collections.sort(result, ATTRIBUTE_COMPARATOR);
return Collections.unmodifiableList(result);
}
// It isn't so just analyze class
try {
final Set<AttributeDescriptor> foundAttributes = new HashSet<AttributeDescriptor>();
final Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(Attribute.class)) {
foundAttributes.add(new AttributeDescriptor(field.getAnnotation(Attribute.class), field,
accessorService));
}
}
// Cache value for next run - we do not need to synchronize this
// since multiple runs result in same result
ATTRIBUTE_CACHE.put(clazz, foundAttributes);
// Let's sort the attributes and return them
final List<AttributeDescriptor> result = new ArrayList<AttributeDescriptor>(foundAttributes);
Collections.sort(result, ATTRIBUTE_COMPARATOR);
// Test if an optional attribute is not at last position
quickCheckLegalOptionalAttribute(foundAttributes);
return Collections.unmodifiableList(result);
}
catch (Exception e) {
throw new IllegalStateException("Message class could not be analysed", e);
}
}
}