/*
* Copyright 2015 Odnoklassniki Ltd, Mail.Ru Group
*
* 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 one.nio.serial;
import one.nio.gen.BytecodeGenerator;
import one.nio.serial.gen.Delegate;
import one.nio.serial.gen.DelegateGenerator;
import one.nio.serial.gen.StubGenerator;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
public class GeneratedSerializer extends Serializer {
static final AtomicInteger missedLocalFields = new AtomicInteger();
static final AtomicInteger missedStreamFields = new AtomicInteger();
static final AtomicInteger migratedFields = new AtomicInteger();
static final AtomicInteger renamedFields = new AtomicInteger();
static final AtomicInteger unsupportedFields = new AtomicInteger();
private FieldDescriptor[] fds;
private ArrayList<Field> defaultFields;
private Delegate delegate;
GeneratedSerializer(Class cls) {
super(cls);
Field[] ownFields = getSerializableFields();
this.fds = new FieldDescriptor[ownFields.length / 2];
for (int i = 0; i < ownFields.length; i += 2) {
fds[i / 2] = new FieldDescriptor(ownFields[i], ownFields[i + 1]);
}
checkFieldTypes();
this.delegate = BytecodeGenerator.INSTANCE.instantiate(code(), Delegate.class);
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
super.writeExternal(out);
out.writeShort(fds.length);
for (FieldDescriptor fd : fds) {
fd.write(out);
}
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.fds = new FieldDescriptor[in.readUnsignedShort()];
for (int i = 0; i < fds.length; i++) {
fds[i] = FieldDescriptor.read(in);
}
try {
super.readExternal(in);
} catch (ClassNotFoundException e) {
if ((Repository.getOptions() & Repository.CUSTOM_STUBS) == 0) throw e;
if (isException()) {
this.cls = StubGenerator.generateRegular(uniqueName("Ex"), "java/lang/Exception", fds);
} else {
this.cls = StubGenerator.generateRegular(uniqueName("Stub"), "java/lang/Object", fds);
}
this.origin = Origin.GENERATED;
}
Field[] ownFields = getSerializableFields();
for (FieldDescriptor fd : fds) {
int found = findField(ownFields, fd);
if (found >= 0) {
fd.assignField(ownFields[found], ownFields[found + 1]);
ownFields[found] = null;
}
}
this.defaultFields = new ArrayList<Field>();
for (int i = 0; i < ownFields.length; i += 2) {
Field f = ownFields[i];
if (f != null) {
logFieldMismatch("Local field is missed in stream", f.getType(), f.getDeclaringClass(), f.getName());
missedLocalFields.incrementAndGet();
if (f.getAnnotation(Default.class) != null) {
defaultFields.add(f);
}
}
}
checkFieldTypes();
this.delegate = BytecodeGenerator.INSTANCE.instantiate(code(), Delegate.class);
}
@Override
public void skipExternal(ObjectInput in) throws IOException, ClassNotFoundException {
int fds = in.readUnsignedShort();
for (int i = 0; i < fds; i++) {
in.skipBytes(in.readUnsignedShort());
if (in.readByte() < 0) {
in.skipBytes(in.readUnsignedShort());
}
}
}
@Override
public byte[] code() {
return DelegateGenerator.generate(cls, fds, defaultFields);
}
@Override
public void calcSize(Object obj, CalcSizeStream css) throws IOException {
delegate.calcSize(obj, css);
}
@Override
public void write(Object obj, DataStream out) throws IOException {
delegate.write(obj, out);
}
@Override
public Object read(DataStream in) throws IOException, ClassNotFoundException {
return delegate.read(in);
}
@Override
public void skip(DataStream in) throws IOException, ClassNotFoundException {
delegate.skip(in);
}
@Override
public void toJson(Object obj, StringBuilder builder) throws IOException {
delegate.toJson(obj, builder);
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder(super.toString());
builder.append("Fields:\n");
for (FieldDescriptor fd : fds) {
builder.append(" - Name: ").append(fd.name()).append('\n');
builder.append(" Type: ").append(fd.type()).append('\n');
if (fd.parentField() != null) {
builder.append(" Parent: ").append(fd.parentField().getName()).append('\n');
}
}
return builder.toString();
}
private boolean isException() {
return fds.length >= 3
&& fds[0].is("detailMessage", "java.lang.String")
&& fds[1].is("cause", "java.lang.Throwable")
&& fds[2].is("stackTrace", "[Ljava.lang.StackTraceElement;");
}
private int findField(Field[] ownFields, FieldDescriptor fd) {
String name = fd.name();
Class type = fd.type().resolve();
String oldName = null;
int p = name.indexOf('|');
if (p >= 0) {
oldName = name.substring(p + 1);
name = name.substring(0, p);
}
// 1. Find exact match
for (int i = 0; i < ownFields.length; i += 2) {
Field f = ownFields[i];
if (f != null && f.getType() == type && f.getName().equals(name)) {
return i;
}
}
// 2. Find exact match by locally old name
for (int i = 0; i < ownFields.length; i += 2) {
Field f = ownFields[i];
if (f != null && f.getType() == type) {
Renamed renamed = f.getAnnotation(Renamed.class);
if (renamed != null && renamed.from().equals(name)) {
logFieldMismatch("Local field renamed from " + renamed.from(), f.getType(), f.getDeclaringClass(), f.getName());
renamedFields.incrementAndGet();
return i;
}
}
}
// 3. Find exact match by remotely old name
if (oldName != null) {
for (int i = 0; i < ownFields.length; i += 2) {
Field f = ownFields[i];
if (f != null && f.getType() == type && f.getName().equals(oldName)) {
logFieldMismatch("Remote field renamed from " + oldName, f.getType(), f.getDeclaringClass(), f.getName());
renamedFields.incrementAndGet();
return i;
}
}
}
// 4. Find match by name only
for (int i = 0; i < ownFields.length; i += 2) {
Field f = ownFields[i];
if (f != null && (f.getName().equals(name) || f.getName().equals(oldName))) {
logFieldMismatch("Field type migrated from " + type.getName(), f.getType(), f.getDeclaringClass(), f.getName());
migratedFields.incrementAndGet();
return i;
}
}
logFieldMismatch("Stream field is missed locally", type, cls, name);
missedStreamFields.incrementAndGet();
return -1;
}
private Field[] getSerializableFields() {
ArrayList<Field> list = new ArrayList<Field>();
getSerializableFields(cls, null, list);
return list.toArray(new Field[list.size()]);
}
private void getSerializableFields(Class cls, Field parentField, ArrayList<Field> list) {
if (cls != null) {
getSerializableFields(cls.getSuperclass(), parentField, list);
for (Field f : cls.getDeclaredFields()) {
if ((f.getModifiers() & (Modifier.STATIC | Modifier.TRANSIENT)) == 0) {
list.add(f);
list.add(parentField);
} else if ((f.getModifiers() & Modifier.STATIC) == 0
&& Repository.hasOptions(f.getType(), Repository.INLINE)
&& parentField == null) {
logFieldMismatch("Inlining field", f.getType(), cls, f.getName());
getSerializableFields(f.getType(), f, list);
}
}
}
}
private void checkFieldTypes() {
for (FieldDescriptor fd : fds) {
Field f = fd.ownField();
if (f != null) {
Class type = f.getType();
if (Externalizable.class.isAssignableFrom(type) || Repository.hasOptions(type, Repository.FIELD_SERIALIZATION)) {
continue;
}
if (Collection.class.isAssignableFrom(type) && !CollectionSerializer.isValidType(type)
|| Map.class.isAssignableFrom(type) && !MapSerializer.isValidType(type)) {
generateUid();
logFieldMismatch("Unsupported field type", type, cls, f.getName());
unsupportedFields.incrementAndGet();
}
}
}
}
private void logFieldMismatch(String msg, Class type, Class holder, String name) {
Repository.log.warn("[" + Long.toHexString(uid) + "] " + msg + ": " + type.getName() + ' ' + holder.getName() + '.' + name);
}
}