instanceof and gson's reflect method giving different results

Hi,
I have the following code :
private String toJson(MedusaEvent event) {
        Type classType = getClassType(getEventType(event));
        return _gson.toJson(event, classType);
    }

    @EventType
    private int getEventType(MedusaEvent event) {
        if (event instanceof AllApps) {
            return EVENT_ALL_APPS;
        } else if (event instanceof AppInstall) {
            return EVENT_APP_INSTALL;
        } else if (event instanceof AppUninstall) {
            return EVENT_APP_UNINSTALL;
        } else if (event instanceof SearchKeyword) {
            return EVENT_SEARCH_KEYWORD;
        } else if (event instanceof SearchCountEvent) {
            return EVENT_SEARCH_COUNT;
        } else {
            return EVENT_NONE;
        }
    }

 private Type getClassType(@EventType int eventType) {
        switch (eventType) {
            case EVENT_ALL_APPS:
                return AllApps.class;
            case EVENT_APP_INSTALL:
                return AppInstall.class;
            case EVENT_APP_UNINSTALL:
                return AppUninstall.class;
            case EVENT_SEARCH_KEYWORD:
                return SearchKeyword.class;
            case EVENT_SEARCH_COUNT:
                return SearchCountEvent.class;
            default:
                throw new ClassCastException("Unknown medusa event");
        }
    }

Open in new window


At some point from some another file the function toJson gets called and my code gives the following exception
at line _gson.toJson(event, c
lassType)

Exact Exception :java.lang.IllegalArgumentException: Expected receiver of type co.riva.vader.medusa.event.SearchKeyword, but got co.riva.vader.medusa.event.SearchCountEvent
       at java.lang.reflect.Field.get(Field.java)
       at java.lang.reflect.Field.get(Field.java:279)
       at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.writeField(ReflectiveTypeAdapterFactory.java:110)
       at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:217)
       at com.google.gson.Gson.toJson(Gson.java:600)
       at com.google.gson.Gson.toJson(Gson.java:579)
       at com.google.gson.Gson.toJson(Gson.java:534)
       at co.riva.vader.medusa.store.MedusaEventStore.toJson(MedusaEventStore.java:158)
       at co.riva.vader.medusa.store.MedusaEventStore.add(MedusaEventStore.java:63)
       at co.riva.vader.medusa.store.MedusaEventStore.addAll(MedusaEventStore.java:78)
       at co.riva.vader.medusa.MedusaService$1.run(MedusaService.java:38)
       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
       at java.lang.Thread.run(Thread.java:818)

Open in new window


This means the in the line _gson.toJson(event, classType);  The classType is different from what the gson class deduced using reflection. This is surprising. How can this be possible as i am explicity checking with instanceof operator.

Thanks
Rohit BajajAsked:
Who is Participating?

[Product update] Infrastructure Analysis Tool is now available with Business Accounts.Learn More

x
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

gurpsbassiCommented:
I think you're missing break statements in your switch construct so its coming back with SearchCountEvent every time.
Rohit BajajAuthor Commented:
HI,
That probably cannot be the case As this error is coming randomly.
Also i tried an example to check whether return statement in switch can cause a problem with the following code :

public class Test2 {

    public static void main(String[] args) {
        for(int i=1;i<=1000;i++){
            int k = i%5+1;
            System.out.println("k = "+k+"  type = "+getType(k));
        }

    }

    private static int getType(int type)
    {
        switch(type)
        {
            case 1:
                return 1;
            case 2:
                return 2;
            case 3:
                return 3;
            case 4:
                return 4;
            case 5:
                return 5;
            default:
                return 10;

        }
    }
}

Open in new window


It seems to run fine.
gurpsbassiCommented:
ah sorry yes you're right.
I didn't appreciate they were return statements.
Why Diversity in Tech Matters

Kesha Williams, certified professional and software developer, explores the imbalance of diversity in the world of technology -- especially when it comes to hiring women. She showcases ways she's making a difference through the Colors of STEM program.

gurpsbassiCommented:
What is the class hierarchy of all these event types?
Rohit BajajAuthor Commented:
One more strange thing i encountered which is similar to this is :
java.lang.ClassCastException: Unknown medusa event
       at co.riva.vader.medusa.store.MedusaEventStore.getClassType(MedusaEventStore.java:197)
       at co.riva.vader.medusa.store.MedusaEventStore.toJson(MedusaEventStore.java:166)
       at co.riva.vader.medusa.store.MedusaEventStore.add(MedusaEventStore.java:58)
       at co.riva.vader.medusa.store.MedusaEventStore.addAll(MedusaEventStore.java:73)
       at co.riva.vader.medusa.MedusaService$1.run(MedusaService.java:38)
       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
       at java.lang.Thread.run(Thread.java:818)

Open in new window


Now the above happens only when an Event of type EVENT_NONE is passed. Which is strange as i am never passing this type of event. Again something to do with switch statement and instanceof operator ... But cannt figure out why this was happening. The above error also occurs in the same tojson function which i posted earlier

I am including all the event files here:


package co.riva.vader.medusa.event;

import com.google.gson.annotations.SerializedName;

import java.util.List;

public class AllApps extends AppCount {
    private static final String EVENT_NAME = "all_apps";

    @SerializedName("packageNames")
    private final List<String> _packageNames;

    public AllApps(List<String> packageNames, int appCount) {
        super(EVENT_NAME, appCount);
        _packageNames = packageNames;
    }

    public List<String> getPackageNames() {
        return _packageNames;
    }
}

Open in new window


package co.riva.vader.medusa.event;

import com.google.gson.annotations.SerializedName;

public abstract class AppCount extends MedusaEvent {
    @SerializedName("appCount")
    private final int _appCount;

    public AppCount(String eventName, int appCount) {
        super(eventName);
        _appCount = appCount;
    }
}

Open in new window


package co.riva.vader.medusa.event;

import com.google.gson.annotations.SerializedName;

public class AppUninstall extends AppCount {
    private static final String EVENT_NAME = "app_uninstall";

    @SerializedName("packageName")
    private final String _packageName;

    public AppUninstall(String packageName, int appCount) {
        super(EVENT_NAME, appCount);
        _packageName = packageName;
    }

    public String getPackageName() {
        return _packageName;
    }
}

Open in new window


package co.riva.vader.medusa.event;

import com.google.gson.annotations.SerializedName;

public class SearchKeyword extends MedusaEvent {
    private static final String EVENT_NAME = "search_keyword";

    @SerializedName("searchKeyword")
    private final String _searchKeyword;

    public SearchKeyword(String searchKeyword) {
        super(EVENT_NAME);
        _searchKeyword = searchKeyword;
    }

    public String getSearchKeyword() {
        return _searchKeyword;
    }
}

Open in new window


package co.riva.vader.medusa.event;

public class SearchCountEvent extends MedusaEvent {
    private static final String EVENT_NAME = "search_count";

    public SearchCountEvent() {
        super(EVENT_NAME);
        _enabled = true;
    }
}

Open in new window


package co.riva.vader.medusa.event;

import com.google.gson.annotations.SerializedName;

public class AppInstall extends AppCount {
    private static final String EVENT_NAME = "app_install";

    @SerializedName("packageName")
    private final String _packageName;

    public AppInstall(String packageName, int appCount) {
        super(EVENT_NAME, appCount);
        _packageName = packageName;
    }

    public String getPackageName() {
        return _packageName;
    }
}

Open in new window


package co.riva.vader.medusa.event;

import android.support.annotation.NonNull;

import com.google.gson.annotations.SerializedName;

public abstract class MedusaEvent implements Comparable<MedusaEvent> {
    @SerializedName("eventName")
    private final String _eventName;
    @SerializedName("eventTs")
    private final long _eventTs;
    protected transient boolean _enabled = false;

    public MedusaEvent(String eventName) {
        _eventName = eventName;
        _eventTs = System.currentTimeMillis();
    }

    public boolean isEnabled() {
        return _enabled;
    }

    public long getEventTime() {
        return _eventTs;
    }

    @Override
    public int compareTo(@NonNull MedusaEvent another) {
        if (_eventTs > another._eventTs) {
            return 1;
        } else if (_eventTs < another._eventTs) {
            return -1;
        } else {
            return 0;
        }
    }
}

Open in new window

gurpsbassiCommented:
Looking at another post you have created:

It seems you are reading the data from a database.

Can you confirm the columns are coming back in the same order as you would expect? it seems you are relying on the column index meta data to be consistent with the constant values in your class. e.g. EVENT_ALL_APPS must be column 0 in the result set meta data.

Can you also post your Columns.java class?
Rohit BajajAuthor Commented:
HI,
Following are 2 java classes which will give all the code :
This contains the definition for Columns :
Its an enum :
 private enum Columns {
        EVENT, TIMESTAMP, TYPE
    }

package co.riva.vader.medusa.store;

import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.support.annotation.IntDef;

import com.google.gson.Gson;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import co.riva.vader.medusa.event.AllApps;
import co.riva.vader.medusa.event.AppInstall;
import co.riva.vader.medusa.event.AppUninstall;
import co.riva.vader.medusa.event.MedusaEvent;
import co.riva.vader.medusa.event.SearchCountEvent;
import co.riva.vader.medusa.event.SearchKeyword;
import to.talk.logging.Logger;
import to.talk.logging.LoggerFactory;

public class MedusaEventStore {
    private static final String TABLE_NAME = "medusa";
    private static final Logger _logger = LoggerFactory
            .getTrimmer(MedusaEventStore.class, "EventStore");
    private static final int EVENT_NONE = -1;
    private static final int EVENT_ALL_APPS = 0;
    private static final int EVENT_APP_INSTALL = 1;
    private static final int EVENT_APP_UNINSTALL = 2;
    private static final int EVENT_SEARCH_KEYWORD = 3;
    private static final int EVENT_SEARCH_COUNT = 4;
    private final SQLiteDatabase _database;
    private final List<MedusaEvent> _eventList;
    private final Gson _gson = new Gson();
    private final ReadWriteLock _lock = new ReentrantReadWriteLock();

    public MedusaEventStore(SQLiteDatabase database) {
        _database = database;
        _eventList = loadFromDb();
    }

    public static List<String> getCreateQuery() {
        String tableCreate = String.format("CREATE TABLE %s ( %s TEXT NOT NULL, " +
                        "%s INTEGER NOT NULL, %s INTEGER NOT NULL)",
                TABLE_NAME,
                Columns.EVENT,
                Columns.TYPE,
                Columns.TIMESTAMP);
        return Collections.singletonList(tableCreate);
    }

    public static String getDeleteQuery() {
        return "DROP TABLE IF EXISTS " + TABLE_NAME;
    }

    public void add(MedusaEvent event) {
        _lock.writeLock().lock();
        try {
            ContentValues cv = new ContentValues();
            if (!(getEventType(event) == EVENT_NONE)) {
                String json = toJson(event);
                cv.put(Columns.EVENT.toString(), json);
                cv.put(Columns.TIMESTAMP.toString(), event.getEventTime());
                cv.put(Columns.TYPE.toString(), getEventType(event));
                _eventList.add(event);
                _database.replace(TABLE_NAME, null, cv);
            } else {
                _logger.info("MedusaEvent of type none encountered : {}",event);
            }
        } finally {
            _lock.writeLock().unlock();
        }
    }

    public void addAll(List<MedusaEvent> events) {
        _lock.writeLock().lock();
        try {
            for (MedusaEvent event : events) {
                add(event);
            }
        } finally {
            _lock.writeLock().unlock();
        }
    }

    public void clear() {
        _lock.writeLock().lock();
        try {
            _eventList.clear();
            _database.delete(TABLE_NAME, null, null);
        } finally {
            _lock.writeLock().unlock();
        }
    }

    public List<MedusaEvent> getAll() {
        _lock.readLock().lock();
        try {
            return new ArrayList<>(_eventList);
        } finally {
            _lock.readLock().unlock();
        }
    }

    public void removeAllBefore(long timeMillis) {
        _lock.writeLock().lock();
        try {
            Iterator<MedusaEvent> iterator = _eventList.iterator();
            while (iterator.hasNext()) {
                long timestamp = iterator.next().getEventTime();
                if (timestamp <= timeMillis) {
                    iterator.remove();
                }
            }
            String where = Columns.TIMESTAMP + "<= ?";
            String[] whereArgs = new String[]{String.valueOf(timeMillis)};
            _database.delete(TABLE_NAME, where, whereArgs);
        } finally {
            _lock.writeLock().unlock();
        }
    }

    private List<MedusaEvent> loadFromDb() {
        _lock.writeLock().lock();
        String[] column = new String[]{Columns.EVENT.toString(), Columns.TYPE.toString()};
        Cursor cursor = _database.query(TABLE_NAME, column, null, null, null, null, null, null);
        List<MedusaEvent> events = new ArrayList<>();
        try {
            if (cursor != null) {
                while (cursor.moveToNext()) {
                    String event = cursor.getString(cursor.getColumnIndexOrThrow(Columns.EVENT.toString()));
                    @EventType int type = cursor.getInt(cursor.getColumnIndexOrThrow(Columns.TYPE.toString()));
                    MedusaEvent medusaEvent = fromJson(event, type);
                    if (medusaEvent != null) {
                        events.add(medusaEvent);
                    }
                }
            }
        } finally {
            if (cursor != null) {
                cursor.close();
            }
            _lock.writeLock().unlock();
        }
        return events;
    }

    private MedusaEvent fromJson(String jsonEvent, @EventType int eventType) {
        Type classType = getClassType(eventType);
        if (eventType != EVENT_NONE) {
            return (MedusaEvent) _gson.fromJson(jsonEvent, classType);
        } else {
            return null;
        }
    }

    private String toJson(MedusaEvent event) {
        Type classType = getClassType(getEventType(event));
        return _gson.toJson(event, classType);
    }

    @EventType
    private int getEventType(MedusaEvent event) {
        if (event instanceof AllApps) {
            return EVENT_ALL_APPS;
        } else if (event instanceof AppInstall) {
            return EVENT_APP_INSTALL;
        } else if (event instanceof AppUninstall) {
            return EVENT_APP_UNINSTALL;
        } else if (event instanceof SearchKeyword) {
            return EVENT_SEARCH_KEYWORD;
        } else if (event instanceof SearchCountEvent) {
            return EVENT_SEARCH_COUNT;
        } else {
            return EVENT_NONE;
        }
    }

    private Type getClassType(@EventType int eventType) {
        switch (eventType) {
            case EVENT_ALL_APPS:
                return AllApps.class;
            case EVENT_APP_INSTALL:
                return AppInstall.class;
            case EVENT_APP_UNINSTALL:
                return AppUninstall.class;
            case EVENT_SEARCH_KEYWORD:
                return SearchKeyword.class;
            case EVENT_SEARCH_COUNT:
                return SearchCountEvent.class;
            default:
                throw new ClassCastException("Unknown medusa event");
        }
    }

    private enum Columns {
        EVENT, TIMESTAMP, TYPE
    }

    @IntDef({EVENT_NONE, EVENT_ALL_APPS, EVENT_APP_INSTALL, EVENT_APP_UNINSTALL, EVENT_SEARCH_KEYWORD, EVENT_SEARCH_COUNT})
    @Retention(RetentionPolicy.SOURCE)
    private @interface EventType {
    }
}

Open in new window


package co.riva.vader.medusa;

import com.google.common.collect.Lists;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

import co.riva.vader.BuildConfig;
import co.riva.vader.medusa.event.MedusaEvent;
import co.riva.vader.medusa.store.MedusaEventStore;

public class MedusaService {
    private static final int BATCH_SIZE = 50;

    private final Executor _executor = Executors.newSingleThreadExecutor();
    private final MedusaEventStore _store;
    private final MedusaClient _medusaClient;

    public MedusaService(MedusaEventStore store, MedusaClient medusaClient) {
        _store = store;
        _medusaClient = medusaClient;
    }

    public void report(MedusaEvent event) {
        //noinspection PointlessBooleanExpression,ConstantConditions
        if (BuildConfig.REPORT_TO_MEDUSA && event.isEnabled()) {
            reportAll(Collections.singletonList(event));
        }
    }

    private void reportAll(final List<MedusaEvent> events) {
        _executor.execute(new Runnable() {
            @Override
            public void run() {
                synchronized (MedusaService.class) {
                    _store.addAll(events);
                    List<MedusaEvent> eventList = _store.getAll();
                    Collections.sort(eventList);
                    MedusaProperty property = new MedusaProperty();
                    List<List<MedusaEvent>> eventBatchList = Lists.partition(eventList, BATCH_SIZE);
                    for (List<MedusaEvent> eventBatch : eventBatchList) {
                        if (!processBatch(property, eventBatch)) break;
                    }
                }
            }
        });
    }

    private boolean processBatch(MedusaProperty property, List<MedusaEvent> eventBatch) {
        MedusaRequest medusaRequest = new MedusaRequest(eventBatch, property);
        boolean b = sendRequest(medusaRequest);
        if (b) {
            MedusaEvent medusaEvent = eventBatch.get(eventBatch.size() - 1);
            _store.removeAllBefore(medusaEvent.getEventTime());
        }
        return b;
    }

    private boolean sendRequest(MedusaRequest request) {
        return _medusaClient.send(request);
    }
}

Open in new window

gurpsbassiCommented:
Can we put some log statements after the lines:


String event = cursor.getString(cursor.getColumnIndexOrThrow(Columns.EVENT.toString()));
@EventType int type = cursor.getInt(cursor.getColumnIndexOrThrow(Columns.TYPE.toString()));

We just want to log out the event and type combination retrieved from the database.

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Java

From novice to tech pro — start learning today.