Package org.gbcpainter.env

Source Code of org.gbcpainter.env.GameSettings

package org.gbcpainter.env;

import net.jcip.annotations.GuardedBy;
import org.gbcpainter.loaders.textures.ResolutionTextureLoader;
import org.ini4j.IniPreferences;
import org.ini4j.Wini;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.awt.event.KeyEvent;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.*;
import java.util.prefs.Preferences;

/**
* Keeps the game settings like controls, level and texture folders, etc.
* <p/>
* In order to retrieve those settings {@link #getInstance()} must be used to get the singleton
* <p/>
* The default config file is "config.ini" and it's loaded/saved from/in the current working path.
*
* @author Lorenzo Pellegrini
*/
public class GameSettings {

  @NotNull
  @NonNls
  private static final String DEFAULT_LEVELS_PATH = "levels";
  @NotNull
  @NonNls
  private static final String DEFAULT_TEXTURES_PATH = "textures";
  @NotNull
  @NonNls
  private static final String DEFAULT_CONFIG_FILE = "config.ini";
  @NotNull
  @NonNls
  private static final String GRAPHICS_SETTINGS_SECTION = "Graphics";
  @NotNull
  @NonNls
  private static final String CONTROLS_SETTINGS_SECTION = "Controls";
  @NotNull
  @NonNls
  private static final String RESOURCES_SETTINGS_SECTION = "Resources";
  private static final int DEFAULT_UP_KEY = KeyEvent.VK_W;
  private static final int DEFAULT_DOWN_KEY = KeyEvent.VK_S;
  private static final int DEFAULT_LEFT_KEY = KeyEvent.VK_A;
  private static final int DEFAULT_RIGHT_KEY = KeyEvent.VK_D;
  private static final int DEFAULT_PAUSE_KEY = KeyEvent.VK_ESCAPE;
  private static final int DEFAULT_MOVE_MILLIS_TIMEOUT = 150;
  private static final int DEFAULT_FPS = 45;
  private static final int DEFAULT_ANTIALIAS = 0;
  private static final int DEFAULT_OPENGL = 0;
  @NotNull
  private static final List<STRING_SETTINGS_TYPE> immutableStringSettings = Collections
      .emptyList();
  @NotNull
  private static final List<INTEGER_SETTINGS_TYPE> immutableIntegerSettings = Arrays
      .asList( INTEGER_SETTINGS_TYPE.ANTIALIAS, INTEGER_SETTINGS_TYPE.OPENGL );
  @Nullable
  @GuardedBy ( "org.gbcpainter.env.GameSettings.class" )
  private static GameSettings ourInstance;
  @NotNull
  private final Map<STRING_SETTINGS_TYPE, String> stringSettingsMap = new EnumMap<>(
      STRING_SETTINGS_TYPE.class );

  @NotNull
  private final Map<INTEGER_SETTINGS_TYPE, Integer> integerSettingsMap = new EnumMap<>(
      INTEGER_SETTINGS_TYPE.class );

  @NotNull
  private final Map<STRING_SETTINGS_TYPE, String> nextRunStringSettingsMap = new HashMap<>(
      immutableStringSettings.size() );

  @NotNull
  private final Map<INTEGER_SETTINGS_TYPE, Integer> nextRunIntegerSettingsMap = new HashMap<>(
      immutableIntegerSettings.size() );

  private GameSettings() {

    Preferences preferences;
    try {
      preferences = this.loadPreferences();
    } catch ( IOException e ) {
      preferences = null;
    }

    if ( preferences == null ) {
      this.stringSettingsMap.put( STRING_SETTINGS_TYPE.LEVELS_PATH, DEFAULT_LEVELS_PATH );
      this.stringSettingsMap.put( STRING_SETTINGS_TYPE.TEXTURES_PATH, DEFAULT_TEXTURES_PATH );
      this.integerSettingsMap.put( INTEGER_SETTINGS_TYPE.KEY_UP, DEFAULT_UP_KEY );
      this.integerSettingsMap.put( INTEGER_SETTINGS_TYPE.KEY_DOWN, DEFAULT_DOWN_KEY );
      this.integerSettingsMap.put( INTEGER_SETTINGS_TYPE.KEY_LEFT, DEFAULT_LEFT_KEY );
      this.integerSettingsMap.put( INTEGER_SETTINGS_TYPE.KEY_RIGHT, DEFAULT_RIGHT_KEY );
      this.integerSettingsMap.put( INTEGER_SETTINGS_TYPE.KEY_PAUSE, DEFAULT_PAUSE_KEY );
      this.integerSettingsMap
          .put( INTEGER_SETTINGS_TYPE.MOVE_MILLIS_TIMEOUT, DEFAULT_MOVE_MILLIS_TIMEOUT );
      this.integerSettingsMap.put( INTEGER_SETTINGS_TYPE.REDRAW_FPS, DEFAULT_FPS );
      this.integerSettingsMap.put( INTEGER_SETTINGS_TYPE.ANTIALIAS, DEFAULT_ANTIALIAS );
      this.integerSettingsMap.put( INTEGER_SETTINGS_TYPE.OPENGL, DEFAULT_OPENGL );
    } else {

      {
        Preferences resourcesSection = preferences.node( RESOURCES_SETTINGS_SECTION );
        this.stringSettingsMap.put( STRING_SETTINGS_TYPE.LEVELS_PATH,
                               resourcesSection
                                   .get( STRING_SETTINGS_TYPE.LEVELS_PATH.toString(),
                                         DEFAULT_LEVELS_PATH ) );
        this.stringSettingsMap.put( STRING_SETTINGS_TYPE.TEXTURES_PATH,
                               resourcesSection
                                   .get( STRING_SETTINGS_TYPE.TEXTURES_PATH.toString(),
                                         DEFAULT_TEXTURES_PATH ) );
      }

      {
        Preferences controlsSection = preferences.node( CONTROLS_SETTINGS_SECTION );
        this.integerSettingsMap.put( INTEGER_SETTINGS_TYPE.KEY_UP,
                                getInteger( controlsSection,
                                            INTEGER_SETTINGS_TYPE.KEY_UP,
                                            DEFAULT_UP_KEY ) );
        this.integerSettingsMap.put( INTEGER_SETTINGS_TYPE.KEY_DOWN,
                                getInteger( controlsSection,
                                            INTEGER_SETTINGS_TYPE.KEY_DOWN,
                                            DEFAULT_DOWN_KEY ) );
        this.integerSettingsMap.put( INTEGER_SETTINGS_TYPE.KEY_LEFT,
                                getInteger( controlsSection,
                                            INTEGER_SETTINGS_TYPE.KEY_LEFT,
                                            DEFAULT_LEFT_KEY ) );
        this.integerSettingsMap.put( INTEGER_SETTINGS_TYPE.KEY_RIGHT,
                                getInteger( controlsSection,
                                            INTEGER_SETTINGS_TYPE.KEY_RIGHT,
                                            DEFAULT_RIGHT_KEY ) );
        this.integerSettingsMap.put( INTEGER_SETTINGS_TYPE.MOVE_MILLIS_TIMEOUT,
                                getInteger( controlsSection,
                                            INTEGER_SETTINGS_TYPE.MOVE_MILLIS_TIMEOUT,
                                            DEFAULT_MOVE_MILLIS_TIMEOUT ) );
        this.integerSettingsMap.put( INTEGER_SETTINGS_TYPE.KEY_PAUSE,
                                getInteger( controlsSection,
                                            INTEGER_SETTINGS_TYPE.KEY_PAUSE,
                                            DEFAULT_PAUSE_KEY ) );

      }

      {
        Preferences graphicsSection = preferences.node( GRAPHICS_SETTINGS_SECTION );
        this.integerSettingsMap.put( INTEGER_SETTINGS_TYPE.ANTIALIAS,
                                getInteger( graphicsSection,
                                            INTEGER_SETTINGS_TYPE.ANTIALIAS,
                                            DEFAULT_ANTIALIAS ) );
        this.integerSettingsMap.put( INTEGER_SETTINGS_TYPE.OPENGL,
                                getInteger( graphicsSection,
                                            INTEGER_SETTINGS_TYPE.OPENGL,
                                            DEFAULT_OPENGL ) );
        this.integerSettingsMap.put( INTEGER_SETTINGS_TYPE.REDRAW_FPS,
                                getInteger( graphicsSection,
                                            INTEGER_SETTINGS_TYPE.REDRAW_FPS,
                                            DEFAULT_FPS ) );
      }

    }

  }

  /**
   * Loads the singleton instance from the default config file.
   *
   * @throws java.lang.IllegalStateException If settings were already loaded.
   * @throws java.lang.Exception             If an error occours while loading the configuration.
   */
  public static void loadInstance() throws Exception {
    synchronized (org.gbcpainter.env.GameSettings.class) {
      if ( ourInstance == null ) {
        ourInstance = new GameSettings();
      } else {
        throw new IllegalStateException( "Settings already loaded" );
      }
    }
  }


  /**
   * Retrieves the singleton.
   *
   * @return The settings singleton.
   */
  @NotNull
  public static GameSettings getInstance() {
    final GameSettings localInstance;
    synchronized (org.gbcpainter.env.GameSettings.class) {
      localInstance = ourInstance;
    }

    if ( localInstance == null ) {
      throw new IllegalStateException( "Singleton not initialized" );
    }
    return localInstance;
  }

  @NotNull
  private static Integer getInteger( @NotNull Preferences prefs,
                                     @NotNull INTEGER_SETTINGS_TYPE key,
                                     @NotNull Integer defaultValue ) {
    String strValue = prefs.get( key.toString(), "" + defaultValue );
    Integer result;
    try {
      result = Integer.parseInt( strValue );
    } catch ( NumberFormatException e ) {
      result = defaultValue;
    }

    return result;
  }

  /**
   * Utility method used to retrieve a resource directory location in the actual file system.
   * <p/>
   * This method must be used to retrieve the location of a resource folder. First the location of
   * the jar/{@link org.gbcpainter.env.GameSettings} class file is checked.
   *
   * @param directory The directory to be resolved
   *
   * @return The resolved directory. It is never null. The described file may not exist
   */
  @NotNull
  public static File getRelativeDirectory( @NotNull @NonNls String directory ) {
    URL root = ResolutionTextureLoader.class.getProtectionDomain().getCodeSource()
                                            .getLocation();
    final File candidate;

    File workingDirectory = new File( directory );
    if ( workingDirectory.exists() && workingDirectory.isDirectory() ) {
      return workingDirectory;
    }

    File location = new File( "." );
    try {
      location = new File( root.toURI() );

    } catch ( URISyntaxException ignored ) {
    }

    if ( location.isFile() ) {
      candidate = new File( location.getParent(), directory );
    } else {
      candidate = new File( location, directory );
    }

    if ( ! candidate.exists() ) {
      /* Fail... */
      return workingDirectory;
    } else {
      return candidate;
    }
  }

  /**
   * Updates a setting.
   *
   * @param type  The setting to modify. It cannot be an immutable setting.
   * @param value The new value of the setting
   *
   * @throws java.lang.IllegalArgumentException If the specified setting is immutable or the value
   *                                            is not compatible
   */
  public synchronized void updateData( @NotNull STRING_SETTINGS_TYPE type,
                                       @NotNull @NonNls String value ) {
    if ( immutableStringSettings.contains( type ) ) {
      throw new IllegalArgumentException( "Immutable setting " + type );
    }

    this.getStringSettingsMap().put( type, value );
  }

  /**
   * Updates a setting.
   *
   * @param type  The setting to modify. It cannot be an immutable setting
   * @param value The new value of the setting
   *
   * @throws java.lang.IllegalArgumentException If the specified setting is immutable or the value
   *                                            is not compatible
   */
  public synchronized void updateData( @NotNull INTEGER_SETTINGS_TYPE type, int value ) {
    if ( immutableIntegerSettings.contains( type ) ) {
      throw new IllegalArgumentException( "Immutable setting " + type );
    }

    this.getIntegerSettingsMap().put( type, value );
  }

  /**
   * Updates a setting for future application instances.
   * <p/>
   * The updated value can be queried via {@link #getNextRunValue(org.gbcpainter.env.GameSettings.STRING_SETTINGS_TYPE)}
   * but doesn't affect the current value of the setting. The new value will be written to the
   * config file via {@link #storeSettings()} and will be used in future application instances
   *
   * @param type  The setting to modify. It can be an immutable setting.
   * @param value The new value of the setting
   *
   * @throws java.lang.IllegalArgumentException If the specified value is not compatible
   */
  public synchronized void updateNextRunData( @NotNull STRING_SETTINGS_TYPE type,
                                              @NotNull @NonNls String value ) {
    this.nextRunStringSettingsMap.put( type, value );
  }

  /**
   * Updates a setting for future application instances.
   * <p/>
   * The updated value can be queried via {@link #getNextRunValue(org.gbcpainter.env.GameSettings.INTEGER_SETTINGS_TYPE)}
   * but doesn't affect the current value of the setting. The new value will be written to the
   * config file via {@link #storeSettings()} and will be used in future application instances
   *
   * @param type  The setting to modify. It can be an immutable setting.
   * @param value The new value of the setting
   *
   * @throws java.lang.IllegalArgumentException If the specified value is not compatible
   */
  public synchronized void updateNextRunData( @NotNull INTEGER_SETTINGS_TYPE type, int value ) {
    this.nextRunIntegerSettingsMap.put( type, value );
  }

  /**
   * Returns the current value of a setting.
   * <p/>
   * If the setting is immutable {@link #getInstanceValue(org.gbcpainter.env.GameSettings.STRING_SETTINGS_TYPE)}
   * should be used to obtain a better performance.
   *
   * @param type The setting
   *
   * @return The value of the setting
   */
  @NotNull
  @NonNls
  public synchronized String getValue( @NotNull STRING_SETTINGS_TYPE type ) {
    final String value = this.getStringSettingsMap().get( type );
    if ( value == null ) {
      throw new IllegalArgumentException( "Unknown enum constant " + type );
    }

    return value;
  }

  /**
   * Returns the current value of a setting.
   * <p/>
   * If the setting is immutable {@link #getInstanceValue(org.gbcpainter.env.GameSettings.INTEGER_SETTINGS_TYPE)}
   * should be used to obtain a better performance.
   *
   * @param type The setting
   *
   * @return The value of the setting
   */
  public synchronized int getValue( @NotNull INTEGER_SETTINGS_TYPE type ) {

    final Integer value = this.getIntegerSettingsMap().get( type );
    if ( value == null ) {
      throw new IllegalArgumentException( "Unknown enum constant " + type );
    }

    return value;
  }

  /**
   * Returns the current value of an immutable setting
   * <p/>
   * If a setting is immutable this method should be used instead of {@link
   * #getValue(org.gbcpainter.env.GameSettings.STRING_SETTINGS_TYPE)} to obtain a better
   * performance.
   *
   * @param type The immutable setting
   *
   * @return The value of the setting
   *
   * @throws java.lang.IllegalArgumentException If the setting is not immutable.
   */
  @NotNull
  public String getInstanceValue( @NotNull STRING_SETTINGS_TYPE type ) {
    if ( ! immutableStringSettings.contains( type ) ) {
      throw new IllegalArgumentException( "Non immutable setting " + type );
    }

    final String value = this.getStringSettingsMap().get( type );
    if ( value == null ) {
      throw new IllegalArgumentException( "Unknown enum constant " + type );
    }

    return value;
  }

  /**
   * Returns the current value of an immutable setting
   * <p/>
   * If a setting is immutable this method should be used instead of {@link
   * #getValue(org.gbcpainter.env.GameSettings.INTEGER_SETTINGS_TYPE)} to obtain a better
   * performance.
   *
   * @param type The immutable setting
   *
   * @return The value of the setting
   *
   * @throws java.lang.IllegalArgumentException If the setting is not immutable.
   */
  public int getInstanceValue( @NotNull INTEGER_SETTINGS_TYPE type ) {
    if ( ! immutableIntegerSettings.contains( type ) ) {
      throw new IllegalArgumentException( "Non immutable setting " + type );
    }

    final Integer value = this.getIntegerSettingsMap().get( type );
    if ( value == null ) {
      throw new IllegalArgumentException( "Unknown enum constant " + type );
    }

    return value;
  }

  /**
   * Returns the current value of an immutable setting
   * <p/>
   * If a setting is immutable this method should be used instead of {@link
   * #getValue(org.gbcpainter.env.GameSettings.STRING_SETTINGS_TYPE)} to obtain a better
   * performance.
   *
   * @param type The immutable setting
   *
   * @return The value of the setting
   *
   * @throws java.lang.IllegalArgumentException If the setting is not immutable.
   */
  @NotNull
  public synchronized String getNextRunValue( @NotNull STRING_SETTINGS_TYPE type ) {

    String value = this.nextRunStringSettingsMap.get( type );
    if ( value == null ) {
      value = this.getValue( type );
    }

    return value;
  }

  /**
   * Returns the setting's value that will be saved in the config file.
   *
   * @param type The setting
   *
   * @return The value that will be saved in the config file
   *
   * @see #storeSettings()
   */
  public synchronized int getNextRunValue( @NotNull INTEGER_SETTINGS_TYPE type ) {

    Integer value = this.nextRunIntegerSettingsMap.get( type );
    if ( value == null ) {
      value = this.getValue( type );
    }

    return value;
  }

  /**
   * Stores all the settings in the config file.
   *
   * @throws IOException If an exception occours.
   */
  public synchronized void storeSettings() throws IOException {

    Wini settingsFile = new Wini();

    settingsFile.add( GRAPHICS_SETTINGS_SECTION,
                      INTEGER_SETTINGS_TYPE.ANTIALIAS.toString(),
                      "" + this.getNextRunValue( INTEGER_SETTINGS_TYPE.ANTIALIAS ) );
    settingsFile.add( GRAPHICS_SETTINGS_SECTION,
                      INTEGER_SETTINGS_TYPE.OPENGL.toString(),
                      "" + this.getNextRunValue( INTEGER_SETTINGS_TYPE.OPENGL ) );
    settingsFile.add( GRAPHICS_SETTINGS_SECTION,
                      INTEGER_SETTINGS_TYPE.REDRAW_FPS.toString(),
                      "" + this.getNextRunValue( INTEGER_SETTINGS_TYPE.REDRAW_FPS ) );

    settingsFile.add( CONTROLS_SETTINGS_SECTION,
                      INTEGER_SETTINGS_TYPE.KEY_LEFT.toString(),
                      "" + this.getNextRunValue( INTEGER_SETTINGS_TYPE.KEY_LEFT ) );
    settingsFile.add( CONTROLS_SETTINGS_SECTION,
                      INTEGER_SETTINGS_TYPE.KEY_RIGHT.toString(),
                      "" + this.getNextRunValue( INTEGER_SETTINGS_TYPE.KEY_RIGHT ) );
    settingsFile.add( CONTROLS_SETTINGS_SECTION,
                      INTEGER_SETTINGS_TYPE.KEY_UP.toString(),
                      "" + this.getNextRunValue( INTEGER_SETTINGS_TYPE.KEY_UP ) );
    settingsFile.add( CONTROLS_SETTINGS_SECTION,
                      INTEGER_SETTINGS_TYPE.KEY_DOWN.toString(),
                      "" + this.getNextRunValue( INTEGER_SETTINGS_TYPE.KEY_DOWN ) );
    settingsFile.add( CONTROLS_SETTINGS_SECTION,
                      INTEGER_SETTINGS_TYPE.KEY_PAUSE.toString(),
                      "" + this.getNextRunValue( INTEGER_SETTINGS_TYPE.KEY_PAUSE ) );
    settingsFile.add( CONTROLS_SETTINGS_SECTION,
                      INTEGER_SETTINGS_TYPE.MOVE_MILLIS_TIMEOUT.toString(),
                      "" + this.getNextRunValue( INTEGER_SETTINGS_TYPE.MOVE_MILLIS_TIMEOUT ) );

    settingsFile.add( RESOURCES_SETTINGS_SECTION,
                      STRING_SETTINGS_TYPE.LEVELS_PATH.toString(),
                      this. getNextRunValue( STRING_SETTINGS_TYPE.LEVELS_PATH ) );
    settingsFile.add( RESOURCES_SETTINGS_SECTION,
                      STRING_SETTINGS_TYPE.TEXTURES_PATH.toString(),
                      this.getNextRunValue( STRING_SETTINGS_TYPE.TEXTURES_PATH ) );

    File preferencesDirectory = getRelativeDirectory( "." );
    settingsFile.store( new File( preferencesDirectory, DEFAULT_CONFIG_FILE ) );
  }

  @NotNull
  private Map<STRING_SETTINGS_TYPE, String> getStringSettingsMap() {
    return this.stringSettingsMap;
  }

  @NotNull
  private Map<INTEGER_SETTINGS_TYPE, Integer> getIntegerSettingsMap() {
    return this.integerSettingsMap;
  }

  @NotNull
  private Preferences loadPreferences() throws IOException {
    File preferencesDirectory = getRelativeDirectory( "." );
    Wini ini = new Wini( new File( preferencesDirectory, DEFAULT_CONFIG_FILE ) );
    return new IniPreferences( ini );
  }

  /**
   * Returns a string rappresentation of the stored settings
   * <p/>
   * It shouldn't be used to display in a user view.
   *
   * @return A string representing the actual settings mapping
   */
  @Override
  @NonNls
  public synchronized String toString() {
    return "GameSettings{" +
           "stringSettingsMap=" +this. getStringSettingsMap() +
           "integerSettingsMap=" + this.getIntegerSettingsMap() +
           '}';
  }

  /**
   * An enum defining all the available settings that have a {@link java.lang.String} value
   */
  public static enum STRING_SETTINGS_TYPE {
    LEVELS_PATH, TEXTURES_PATH;

    @NonNls
    public String toString() {
      switch ( this ) {

        case LEVELS_PATH:
          return "LevelsPath";
        case TEXTURES_PATH:
          return "TexturesPath";
      }

      throw new AssertionError( "Unknown enum constant " + this );
    }
  }

  /**
   * An enum defining all the available settings that have an {@link java.lang.Integer} value
   */
  public static enum INTEGER_SETTINGS_TYPE {
    KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, KEY_PAUSE, MOVE_MILLIS_TIMEOUT, REDRAW_FPS,
    ANTIALIAS, OPENGL;

    @NonNls
    public String toString() {
      switch ( this ) {

        case KEY_UP:
          return "KeyUp";
        case KEY_DOWN:
          return "KeyDown";
        case KEY_LEFT:
          return "KeyLeft";
        case KEY_RIGHT:
          return "KeyRight";
        case KEY_PAUSE:
          return "KeyPause";
        case MOVE_MILLIS_TIMEOUT:
          return "MoveTimeout";
        case REDRAW_FPS:
          return "Fps";
        case ANTIALIAS:
          return "Antialias";
        case OPENGL:
          return "Opengl";
      }

      throw new AssertionError( "Unknown enum constant " + this );
    }
  }
}
TOP

Related Classes of org.gbcpainter.env.GameSettings

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.