net.voxton.mafiacraft.core.city.CityManager.java Source code

Java tutorial

Introduction

Here is the source code for net.voxton.mafiacraft.core.city.CityManager.java

Source

/*
 * This file is part of Mafiacraft.
 * 
 * Mafiacraft is released under the Voxton License version 1.
 *
 * Mafiacraft is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * In addition to this, you must also specify that this product includes 
 * software developed by Voxton.net and may not remove any code
 * referencing Voxton.net directly or indirectly.
 * 
 * Mafiacraft is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * and the Voxton license along with Mafiacraft. 
 * If not, see <http://voxton.net/voxton-license-v1.txt>.
 */
package net.voxton.mafiacraft.core.city;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
import java.util.concurrent.ExecutionException;
import java.util.logging.Logger;
import net.voxton.mafiacraft.core.util.logging.MLogger;
import net.voxton.mafiacraft.core.Mafiacraft;
import net.voxton.mafiacraft.core.MafiacraftCore;
import net.voxton.mafiacraft.core.player.MPlayer;
import net.voxton.mafiacraft.core.util.GeoUtils;
import gnu.trove.map.TIntObjectMap;
import gnu.trove.map.hash.TIntObjectHashMap;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import net.voxton.mafiacraft.core.geo.MWorld;
import net.voxton.mafiacraft.core.geo.Section;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.configuration.serialization.ConfigurationSerialization;

/**
 * Manager for handling district objects.
 */
public class CityManager {

    /**
     * Contains world names mapped to their respective cityworlds.
     */
    private Map<String, MWorld> worldMap = new HashMap<String, MWorld>();

    /**
     * Contains mappings of cities to their ids.
     */
    private TIntObjectMap<City> cities = new TIntObjectHashMap<City>();

    /**
     * Maps districts to worlds.
     */
    private Map<String, TIntObjectMap<District>> worlds = new HashMap<String, TIntObjectMap<District>>();

    /**
     * Holds all of the landowners.
     */
    private Map<String, LandOwner> landOwners = new HashMap<String, LandOwner>();

    /**
     * Contains mappings between cities and districts.
     */
    private Map<City, List<District>> cityDistrictMap = new HashMap<City, List<District>>();

    /**
     * Contains mappings between districts and cities.
     */
    private Map<District, City> districtCityMap = new HashMap<District, City>();

    /**
     * Map that stores section caches.
     */
    private Map<String, Cache<Long, Section>> sections = new HashMap<String, Cache<Long, Section>>();

    private final MafiacraftCore mc;

    /**
     * Constructor.
     *
     * @param mc The MafiacraftPlugin plugin.
     */
    public CityManager(MafiacraftCore mc) {
        this.mc = mc;

        registerSerializations();
    }

    /**
     * Registers all the serializable classes to serialize them.
     */
    private void registerSerializations() {
        ConfigurationSerialization.registerClass(City.class);
        ConfigurationSerialization.registerClass(District.class);
        ConfigurationSerialization.registerClass(MWorld.class);
    }

    /////////////////
    // WORLD
    /////////////////
    /**
     * Gets a city world from its name.
     *
     * @param worldString The name of the world.
     * @return The CityWorld corresponding with the name, or null if there is no
     * such world.
     */
    public MWorld getWorld(String worldString) {
        MWorld cworld = worldMap.get(worldString);
        if (cworld == null) {
            cworld = Mafiacraft.getImpl().getWorld(worldString);
            worldMap.put(worldString, cworld);
        }
        return cworld;
    }

    /**
     * Gets a list of city worlds currently loaded.
     *
     * @return The city world list.
     */
    public List<MWorld> getWorldList() {
        return new ArrayList<MWorld>(worldMap.values());
    }

    /////////////////
    // CITY
    /////////////////
    /**
     * Gets a city from its integer id.
     *
     * @param id
     * @return
     */
    public City getCity(int id) {
        return cities.get(id);
    }

    /**
     * Gets a city from its name.
     *
     * @param name
     * @return
     */
    public City getCity(String name) {
        for (City city : getCityList()) {
            if (city.getName().equalsIgnoreCase(name)) {
                return city;
            }
        }
        return null;
    }

    /**
     * Gets a list of all cities on the server.
     *
     * @return
     */
    public List<City> getCityList() {
        return new ArrayList<City>(cities.valueCollection());
    }

    /**
     * Returns true if a city already exists with the given name.
     *
     * @param name
     * @return
     */
    public boolean cityExists(String name) {
        return getCity(name) != null;
    }

    /**
     * Founds a city.
     *
     * @param player
     * @param name
     * @param center
     * @return
     */
    public City foundCity(MPlayer player, String name, District center) {
        //Make city
        City city = new City(getNextCityId());
        city.setName(name);
        city.setMayor(player.getName());
        city.attachNewDistrict(center);
        center.setType(DistrictType.GOVERNMENT);
        insertCity(city);
        center.getWorld().setCapital(city);
        return city;
    }

    /**
     * Gets the next available city ID.
     *
     * @return
     */
    public int getNextCityId() {
        int id = 0;
        for (int i = 1; getCity(i) != null; ++i) {
            id = i;
        }
        return id;
    }

    /////////////////
    // DISTRICT METHODS
    /////////////////
    /**
     * Gets the district in the specified world. This will create a district if
     * it needs to.
     *
     * @param world
     * @param x
     * @param z
     * @return
     */
    public District getDistrict(MWorld world, int x, int z) {
        int id = GeoUtils.coordsToDistrictId(x, z);
        District d = getDistrictMap(world).get(id);
        if (d == null) {
            d = createDistrict(world, x, z);
        }
        return d;
    }

    public District getDistrict(MWorld world, int id) {
        int x = GeoUtils.xFromDistrictId(id);
        int z = GeoUtils.zFromDistrictId(id);
        return getDistrict(world, x, z);
    }

    /**
     * Gets the city that corresponds with the given district.
     *
     * @param district The district
     * @return The city of the district.
     */
    public City getCityOf(District district) {
        return districtCityMap.get(district);
    }

    /**
     * Gets a district from their uid.
     *
     * @param uid The uid of the district as defined in District.getUid().
     * @return The District corresponding with the UID.
     */
    public District getDistrictFromUid(String uid) {
        String[] split = uid.split(";");
        if (split.length < 2) {
            MLogger.log(Level.SEVERE,
                    "Invalid District UID encountered: not enough semicolon delimited parts! UID in question: '"
                            + uid + "'.");
        }

        MWorld world = getWorld(split[0]);
        if (world == null) {
            MLogger.log(Level.SEVERE, "Invalid District UID encountered for world: '" + uid + "'!");
        }

        int id = 0;
        try {
            id = Integer.parseInt(split[1]);
        } catch (NumberFormatException ex) {
            MLogger.log(Level.SEVERE, "Invalid District UID encoutered for world: '" + uid + "'!");
        }

        return getDistrictMap(world).get(id);
    }

    /**
     * Inserts a city.
     *
     * @param city The city to insert.
     * @return This CityManager.
     */
    public CityManager insertCity(City city) {
        cities.put(city.getId(), city);
        registerLandOwner(city);
        return this;
    }

    /**
     * Inserts the given district.
     *
     * @param district The district to insert.
     * @return This CityManager.
     */
    public CityManager insertDistrict(District district) {
        MWorld world = district.getWorld();
        getDistrictMap(world).put(district.getId(), district);
        registerLandOwner(district);
        return this;
    }

    /**
     * Attaches the district to a city.
     *
     * @param district
     * @param city
     * @return
     */
    public CityManager attachDistrict(District district, City city) {
        districtCityMap.put(district, city);
        getActualCityDistricts(city).add(district);
        return this;
    }

    /**
     * Detaches the district from its city.
     *
     * @param district The district to detach from
     * @return This CityManager.
     */
    public CityManager detachDistrict(District district) {
        City city = district.getCity();
        districtCityMap.remove(district);
        if (city != null) {
            getActualCityDistricts(city).remove(district);
        }
        return this;
    }

    /**
     * Creates a district based on a sample chunk within the potential district.
     *
     * @param sample
     * @return
     */
    private District createDistrict(Section sample) {
        return createDistrict(sample.getWorld(), ((sample.getX()) >> 4), ((sample.getZ() >> 4)));
    }

    /**
     * Creates a district for the specified city.
     *
     * @param x
     * @param z
     * @param city
     * @return The created district, or the existing district
     */
    private District createDistrict(MWorld world, int x, int z) {
        District d = new District(world, x, z);
        insertDistrict(d);
        MLogger.logVerbose(
                "A district was created in the world '" + world.getName() + "' at (" + x + ", " + z + ").");
        return d;
    }

    /**
     * Gets the map of districts to coordinates in a world.
     *
     * @param world
     * @return
     */
    private TIntObjectMap<District> getDistrictMap(MWorld world) {
        TIntObjectMap<District> worldDistricts = worlds.get(world.getName());
        if (worldDistricts == null) {
            worldDistricts = new TIntObjectHashMap<District>();
            worlds.put(world.getName(), worldDistricts);
        }
        return worldDistricts;
    }

    /**
     * Gets a list of all districts in a world.
     *
     * @param world
     * @return
     */
    public List<District> getDistrictList(MWorld world) {
        return new ArrayList<District>(getDistrictMap(world).valueCollection());
    }

    /**
     * Gets a list of all Districts loaded.
     *
     * @return The list of districts.
     */
    public List<District> getDistrictList() {
        return new ArrayList<District>(districtCityMap.keySet());
    }

    /**
     * Gets the actual city district mapping for the given city. INTERNAL USE
     * ONLY!
     *
     * @param city
     * @return
     */
    private List<District> getActualCityDistricts(City city) {
        List<District> districts = cityDistrictMap.get(city);
        if (districts == null) {
            districts = new ArrayList<District>();
            cityDistrictMap.put(city, districts);
        }
        return districts;
    }

    /**
     * Gets a list of all districts in a city.
     *
     * @param city
     * @return
     */
    public List<District> getCityDistricts(City city) {
        return new ArrayList(getActualCityDistricts(city));
    }

    /**
     * Disbands a city and wipes it off of the map. Forever.
     *
     * @param city
     * @return
     */
    public CityManager disbandCity(City city) {
        for (District district : city.getDistricts()) {
            district.detachFromCity();
        }

        MWorld cw = city.getCityWorld();
        City capital = cw.getCapital();
        if (capital.equals(city)) {
            cw.removeCapital();
        }
        return this;
    }

    /////////////////
    // SECTION METHODS
    /////////////////
    /**
     * Gets a section from its coordinates.
     *
     * @param world The world.
     * @param x The x coordinate.
     * @param y The y coordinate.
     * @param z The z coordinate.
     * @return The section.
     */
    public Section getSection(MWorld world, int x, int y, int z) {
        try {
            return getSectionCache(world).get(getSectionKey(x, y, z));
        } catch (ExecutionException ex) {
            MLogger.log(Level.SEVERE,
                    "Could not retrieve section (" + world + ", " + x + ", " + y + ", " + z + ") from cache.", ex);
        }
        return null;
    }

    /**
     * Gets the section cache of a world.
     *
     * @param world The world of the cache.
     * @return The section cache.
     */
    private Cache<Long, Section> getSectionCache(final MWorld world) {
        Cache<Long, Section> cache = sections.get(world.getName());
        if (cache == null) {
            CacheBuilder builder = CacheBuilder.newBuilder();

            builder.maximumSize(10000).expireAfterWrite(10, TimeUnit.MINUTES);

            cache = builder.build(new CacheLoader<Long, Section>() {

                @Override
                public Section load(Long key) throws Exception {
                    int x = getXFromKey(key);
                    int y = getYFromKey(key);
                    int z = getZFromKey(key);
                    return createSection(world, x, y, z);
                }

            });
        }
        sections.put(world.getName(), cache);
        return cache;
    }

    private Section createSection(MWorld world, int x, int y, int z) {
        return new Section(world, x, y, z);
    }

    /////////////
    // MISC
    /////////////
    /**
     * Gets a LandOwner based on their id.
     *
     * @param id The id of the landowner
     * @return The LandOwner corresponding with the id
     */
    public LandOwner getLandOwner(String id) {
        if (id == null) {
            return null;
        }

        if (id.length() < 1) {
            return null;
        }

        LandOwner owner = landOwners.get(id);
        if (owner == null) {
            char type = id.charAt(0);

            if (type == 'P') {
                String playerName = id.substring(1);
                return Mafiacraft.getPlayer(playerName);
            }
        }

        return owner;
    }

    /**
     * Registers a landowner with the manager.
     *
     * @param owner
     * @return
     */
    public CityManager registerLandOwner(LandOwner owner) {
        landOwners.put(owner.getOwnerId(), owner);
        return this;
    }

    ///////////
    // LOADING
    ///////////
    /**
     * Loads this CityManager from memory.
     *
     * @return This newly loaded CityManager.
     */
    public CityManager load() {
        return loadCityWorlds().loadCities().loadDistricts().loadCityDistrictMappings();
    }

    /**
     * Loads all CityWorlds from files into memory.
     *
     * @return This newly loaded CityManager.
     */
    public CityManager loadCityWorlds() {
        File cityFile = Mafiacraft.getOrCreateSubFile("geo", "cityworlds.yml");
        YamlConfiguration conf = YamlConfiguration.loadConfiguration(cityFile);

        worldMap = new HashMap<String, MWorld>();

        for (String key : conf.getKeys(false)) {
            MWorld cityWorld = (MWorld) conf.get(key);
            worldMap.put(cityWorld.getName(), cityWorld);
        }

        return this;
    }

    /**
     * Loads all cities from files into memory.
     *
     * @return This newly loaded CityManager.
     */
    public CityManager loadCities() {
        File cityFile = Mafiacraft.getOrCreateSubFile("geo", "cities.yml");
        YamlConfiguration conf = YamlConfiguration.loadConfiguration(cityFile);

        cities = new TIntObjectHashMap<City>();

        for (String key : conf.getKeys(false)) {
            City city = (City) conf.get(key);

            insertCity(city);
        }

        return this;
    }

    /**
     * Loads all districts from files into memory.
     *
     * @return This newly loaded CityManager.
     */
    public CityManager loadDistricts() {
        File districtFile = Mafiacraft.getOrCreateSubFile("geo", "districts.yml");
        YamlConfiguration conf = YamlConfiguration.loadConfiguration(districtFile);

        cityDistrictMap = new HashMap<City, List<District>>();

        for (String key : conf.getKeys(false)) {
            District district = (District) conf.get(key);

            insertDistrict(district);
        }

        return this;
    }

    /**
     * Loads all city district mappings into memory.
     *
     * @return The city/district mappings.
     */
    public CityManager loadCityDistrictMappings() {
        File mappingFile = Mafiacraft.getOrCreateSubFile("geo", "city_district_mappings.yml");
        YamlConfiguration conf = YamlConfiguration.loadConfiguration(mappingFile);

        cityDistrictMap = new HashMap<City, List<District>>();
        districtCityMap = new HashMap<District, City>();

        for (String key : conf.getKeys(false)) {
            City city = getCity(key);

            List<String> dists = conf.getStringList(key);
            for (String distUid : dists) {
                District dist = getDistrictFromUid(distUid);
                attachDistrict(dist, city);
            }
        }

        return this;
    }

    ///////////
    // SAVING
    ///////////
    /**
     * Saves everything in this CityManager.
     *
     * @return This CityManager.
     */
    public CityManager save() {
        return saveCityWorlds().saveCities().saveDistricts().saveCityDistrictMappings();
    }

    /**
     * Saves all CityWorlds to the config.
     *
     * @return This City Manager.
     */
    public CityManager saveCityWorlds() {
        File cityFile = Mafiacraft.getOrCreateSubFile("geo", "cityworlds.yml");
        YamlConfiguration conf = new YamlConfiguration();

        for (MWorld cityWorld : getWorldList()) {
            conf.set(cityWorld.getName(), cityWorld);
        }

        try {
            conf.save(cityFile);
        } catch (IOException ex) {
            MLogger.log(Level.SEVERE, "The city world file could not be written for some odd reason!", ex);
        }

        return this;
    }

    /**
     * Saves all cities currently loaded to files.
     *
     * @return This CityManager.
     */
    public CityManager saveCities() {
        File cityFile = Mafiacraft.getOrCreateSubFile("geo", "cities.yml");
        YamlConfiguration conf = new YamlConfiguration();

        for (City city : getCityList()) {
            conf.set(Integer.toString(city.getId()), city);
        }

        try {
            conf.save(cityFile);
        } catch (IOException ex) {
            MLogger.log(Level.SEVERE, "The city file could not be written for some odd reason!", ex);
        }

        return this;
    }

    /**
     * Saves all districts currently loaded to files.
     *
     * @return This CityManager.
     */
    public CityManager saveDistricts() {
        File districtFile = Mafiacraft.getOrCreateSubFile("geo", "districts.yml");
        YamlConfiguration conf = new YamlConfiguration();

        for (District district : getDistrictList()) {
            conf.set(district.getUid(), district);
        }

        try {
            conf.save(districtFile);
        } catch (IOException ex) {
            MLogger.log(Level.SEVERE, "The district file could not be written for some odd reason!", ex);
        }

        return this;
    }

    /**
     * Saves all city/district mappings.
     *
     * @return This CityManager.
     */
    public CityManager saveCityDistrictMappings() {
        File mappingFile = Mafiacraft.getOrCreateSubFile("geo", "city_district_mappings.yml");
        YamlConfiguration conf = new YamlConfiguration();

        for (Entry<City, List<District>> mapping : cityDistrictMap.entrySet()) {
            City city = mapping.getKey();
            List<District> dists = mapping.getValue();
            List<String> distStrs = new ArrayList<String>();
            for (District district : dists) {
                distStrs.add(district.getUid());
            }
            conf.set(Integer.toString(city.getId()), dists);
        }

        try {
            conf.save(mappingFile);
        } catch (IOException ex) {
            MLogger.log(Level.SEVERE, "The city/district mapping file could not be written for some odd reason!",
                    ex);
        }

        return this;
    }

    /**
     * Gets a section key from an x, y, and z.
     *
     * @param x The x.
     * @param y The y.
     * @param z The z.
     * @return The section key.
     */
    static long getSectionKey(int x, int y, int z) {
        x += 0x100000;
        y += 0x100000;
        z += 0x100000;
        return
        //X -- 63rd bit & 40 - 60
        (((x & 0x1fffffL) << 42))

                //Y -- 62nd bit & 20-39
                | (((y & 0x1fffffL) << 21))

                //Z -- 61st bit & 0-19
                | ((z & 0x1fffffL));
    }

    static int getXFromKey(long key) {
        return (int) (((key & 0x7ffff60000000000L) >>> 42) - 0x100000);
    }

    static int getYFromKey(long key) {
        return (int) (((key & 0x3ffffe00000L) >>> 21) - 0x100000);
    }

    static int getZFromKey(long key) {
        return (int) ((key & 0x1fffffL) - 0x100000);
    }

}