Alvaro2112/Money_run

View on GitHub
app/src/main/java/sdp/moneyrun/database/game/GameDatabaseProxy.java

Summary

Maintainability
A
3 hrs
Test Coverage
A
90%
package sdp.moneyrun.database.game;

import android.location.Location;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.GenericTypeIndicator;
import com.google.firebase.database.ValueEventListener;

import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;

import sdp.moneyrun.database.DatabaseProxy;
import sdp.moneyrun.game.Game;
import sdp.moneyrun.map.Coin;
import sdp.moneyrun.player.Player;

@SuppressWarnings("FieldCanBeLocal")
public class GameDatabaseProxy extends DatabaseProxy {

    private final String TAG = GameDatabaseProxy.class.getSimpleName();
    private final String DATABASE_GAME = "games";
    private final String DATABASE_GAME_NAME = "name";
    private final String DATABASE_GAME_HOST = "host";
    private final String DATABASE_GAME_MAX_PLAYER_COUNT = "maxPlayerCount";
    private final String DATABASE_GAME_PLAYERS = "players";
    private final String DATABASE_GAME_START_LOCATION = "startLocation";
    private final String DATABASE_GAME_IS_VISIBLE = "isVisible";
    private final String DATABASE_LOCATION_LATITUDE = "latitude";
    private final String DATABASE_LOCATION_LONGITUDE = "longitude";
    private final String DATABASE_COIN = "coins";
    private final String DATABASE_GAME_RADIUS = "radius";
    private final String DATABASE_GAME_NUMCOINS = "numCoins";
    private final String DATABASE_GAME_DURATION = "duration";
    private final String DATABASE_GAME_STARTED = "started";
    private final String DATABASE_GAME_ENDED = "ended";
    private final String DATABASE_START_TIME = "startTime";

    @NonNull
    private final DatabaseReference gamesRef;

    public GameDatabaseProxy() {
        super();

        gamesRef = getReference().child(DATABASE_GAME);
    }

    /**
     * Adds the GameData to the database if not already present
     *
     * @return the Id of this game in the DB
     */
    @Nullable
    public String putGame(@Nullable Game game) {
        if (game == null) {
            throw new IllegalArgumentException("game should not be null.");
        }
        if (game.getHasBeenAdded()) {
            return game.getId();
        }

        String id = game.getId() != null ? game.getId() : gamesRef.push().getKey();
        if (id == null) {
            throw new IllegalArgumentException("Could not add game to database, id is null.");
        }
        game.setId(id);
        gamesRef.child(id).setValue(game.getGameDbData());
        linkPlayersToDB(game);
        linkCoinsToDB(game);
        game.setHasBeenAdded(true);
        game.setStarted(false, false);
        game.setEnded(false, false);
        game.setStartTime(System.currentTimeMillis() / 1000, false);
        return id;
    }

    /**
     * Remove a game to the database.
     *
     * @param game the game to be removed in the database
     */
    public void removeGame(@Nullable Game game) {
        if (game == null) {
            throw new IllegalArgumentException("game should not be null");
        }

        gamesRef.child(String.valueOf(game.getId())).removeValue();
    }

    /**
     * Put the game in the database, if already present, replaces it
     *
     * @param game
     * @param listener
     */
    public void updateGameInDatabase(@Nullable Game game, @Nullable OnCompleteListener listener) {
        if (game == null) throw new IllegalArgumentException();
        if (!game.getHasBeenAdded()) {
            putGame(game);
        }
        if (listener != null) {
            gamesRef.child(game.getId()).setValue(game.getGameDbData()).addOnCompleteListener(listener);
        } else {
            gamesRef.child(game.getId()).setValue(game.getGameDbData());
        }
    }

    /**
     * Links pertinent attributes to the DB instance corresponding to its ID.
     * For now the only pertinent attribute is the player List
     */
    private void linkPlayersToDB(@Nullable Game game) {
        if (game == null) {
            throw new IllegalArgumentException("game should not be null.");
        }

        gamesRef.child(game.getId())
                .child(DATABASE_GAME_PLAYERS)
                .addValueEventListener(new ValueEventListener() {
                    @Override
                    public void onDataChange(@NonNull DataSnapshot snapshot) {
                        List<Player> newData = snapshot.getValue(new GenericTypeIndicator<List<Player>>() {
                        });
                        if (newData != null) {
                            game.setPlayers(newData, true);
                        }
                    }

                    @Override
                    public void onCancelled(@NonNull DatabaseError error) {
                        Log.e(TAG, "failed " + error.getMessage());
                    }
                });
    }

    private void linkCoinsToDB(@NonNull Game game) {
        gamesRef.child(game.getId())
                .child(DATABASE_COIN)
                .addValueEventListener(new ValueEventListener() {
                    @Override
                    public void onDataChange(@NonNull DataSnapshot snapshot) {
                    }

                    @Override
                    public void onCancelled(@NonNull DatabaseError error) {
                        Log.e(TAG, "failed " + error.getMessage());
                    }
                });
    }


    /**
     * Gets a Serialized Game from the DB in an asynchronous manner
     *
     * @param id ID of the Game to retrieve
     * @return a Task containing the serialized Game or a null value if the game not present in DB
     */
    @NonNull
    public Task<DataSnapshot> getGameDataSnapshot(@Nullable String id) throws NoSuchElementException {
        if (id == null) {
            throw new IllegalArgumentException("id should not be null.");
        }

        return gamesRef.child(id).get();
    }

    /**
     * Deserializes DB Game data into a Game instance if the Task is Successfully, returns Null otherwise
     * This function is to be used in combination with getGameDataSnapshot()
     * the former retrieves a Task with all the game data in the DB, while this function
     * extracts the Data and gives you a Game class once the task is complete
     * <p>
     * For this it is recommended to add a success listener on the task and call this function once it
     * it is ready
     * <p>
     * Due to the asynchronous code it is better for the caller to call each function independently
     *
     * @param task Task that you get from getGameDataSnapshot()
     * @return A Game instance
     */
    @Nullable
    public Game getGameFromTaskSnapshot(@Nullable Task<DataSnapshot> task) {
        if (task == null)
            throw new IllegalArgumentException("task should not be null.");

        Game toReturn = null;

        if (task.isSuccessful()) {

            DataSnapshot ds = task.getResult();

            String retName = getDatabaseValue(ds, DATABASE_GAME_NAME, String.class);
            Player retHost = getDatabaseValue(ds, DATABASE_GAME_HOST, Player.class);
            Integer retMaxPlayerCount = getDatabaseValue(ds, DATABASE_GAME_MAX_PLAYER_COUNT, Integer.class);

            List<Player> retPlayers = ds.child(DATABASE_GAME_PLAYERS).getValue(new GenericTypeIndicator<List<Player>>() {
            });
            List<Coin> retCoin = ds.child(DATABASE_COIN).getValue(new GenericTypeIndicator<List<Coin>>() {
            });

            Double retLatitude = ds.child(DATABASE_GAME_START_LOCATION)
                    .child(DATABASE_LOCATION_LATITUDE)
                    .getValue(Double.class);
            Double retLongitude = ds.child(DATABASE_GAME_START_LOCATION)
                    .child(DATABASE_LOCATION_LONGITUDE)
                    .getValue(Double.class);

            Boolean retIsVisible = getDatabaseValue(ds, DATABASE_GAME_IS_VISIBLE, Boolean.class);
            Boolean started = getDatabaseValue(ds, DATABASE_GAME_STARTED, Boolean.class);
            Boolean ended = getDatabaseValue(ds, DATABASE_GAME_ENDED, Boolean.class);
            Long start_time = getDatabaseValue(ds, DATABASE_START_TIME, Long.class);
            Integer numCoins = getDatabaseValue(ds, DATABASE_GAME_NUMCOINS, Integer.class);
            Double radius = getDatabaseValue(ds, DATABASE_GAME_RADIUS, Double.class);
            Double duration = getDatabaseValue(ds, DATABASE_GAME_DURATION, Double.class);

            if (retCoin == null)
                retCoin = new ArrayList<>();

            if (retPlayers == null)
                throw new IllegalArgumentException("players should not be null.");

            if (retMaxPlayerCount == null)
                throw new IllegalArgumentException("max player count should not be null.");

            if (retLatitude == null)
                throw new IllegalArgumentException("latitude should not be null.");

            if (retLongitude == null)
                throw new IllegalArgumentException("longitude should not be null.");

            //Cant deserialize Location properly so we do it manually
            Location retLocation = new Location("");
            retLocation.setLatitude(retLatitude);
            retLocation.setLongitude(retLongitude);


            Game retGame = new Game(retName, retHost, retPlayers, retMaxPlayerCount, retLocation, retIsVisible, retCoin, numCoins, radius, duration);
            retGame.setId(ds.getKey());
            retGame.setHasBeenAdded(true);
            retGame.setStarted(started, true);
            retGame.setEnded(ended, true);
            retGame.setStartTime(start_time, true);

            toReturn = retGame;
        }

        return toReturn;
    }

    /**
     * Add listener to game entry in database
     *
     * @param game
     * @param l
     */
    public void addGameListener(@Nullable Game game, @Nullable ValueEventListener l) {
        checkGameListenerMethodsArguments(game, l);
        if (game.getHasBeenAdded()) {
            gamesRef.child(game.getId()).addValueEventListener(l);
        }
    }

    /**
     * removes listener to entry in database
     *
     * @param game
     * @param listener
     */
    public void removeGameListener(@NonNull Game game, @NonNull ValueEventListener listener) {
        checkGameListenerMethodsArguments(game, listener);
        if (game.getHasBeenAdded()) {
            gamesRef.child(game.getId()).removeEventListener(listener);
        }
    }

    /**
     * Checks that those argument aren't null
     *
     * @param game
     * @param listener
     */
    private void checkGameListenerMethodsArguments(@Nullable Game game, @Nullable ValueEventListener listener) {
        if (game == null) {
            throw new IllegalArgumentException("game should not be null.");
        }
        if (listener == null) {
            throw new IllegalArgumentException("event listener should not be null.");
        }
    }


    /**
     * Add listener to game entry in database
     *
     * @param game
     * @param listener
     */
    public void addCoinListener(@Nullable Game game, @Nullable ValueEventListener listener) {
        if (listener == null || game == null) throw new IllegalArgumentException();
        getDatabaseChildOfGame(game, DATABASE_COIN).addValueEventListener(listener);
    }

    /**
     * Removes listener to game entry in database
     *
     * @param game
     * @param listener
     */
    public void removeCoinListener(@Nullable Game game, @Nullable ValueEventListener listener) {
        if (listener == null || game == null) throw new IllegalArgumentException();
        getDatabaseChildOfGame(game, DATABASE_COIN).removeEventListener(listener);
    }


    @NonNull
    public DatabaseReference getDatabaseChildOfGame(@NonNull Game game, @NonNull String variable) {
        return gamesRef.child(game.getId())
                .child(variable);
    }

    @Nullable
    public <T> T getDatabaseValue(@NonNull DataSnapshot ds, @NonNull String variable, @NonNull Class<T> type) {
        T value = ds.child(variable).getValue(type);
        if (value == null) {
            throw new IllegalArgumentException(variable + " should not be null.");
        }

        return value;
    }
}