001/* 002 * Gridarta MMORPG map editor for Crossfire, Daimonin and similar games. 003 * Copyright (C) 2000-2011 The Gridarta Developers. 004 * 005 * This program is free software; you can redistribute it and/or modify 006 * it under the terms of the GNU General Public License as published by 007 * the Free Software Foundation; either version 2 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU General Public License for more details. 014 * 015 * You should have received a copy of the GNU General Public License along 016 * with this program; if not, write to the Free Software Foundation, Inc., 017 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 018 */ 019 020package net.sf.gridarta.gui.mapfiles; 021 022import java.io.File; 023import java.io.IOException; 024import java.util.Collection; 025import java.util.regex.Pattern; 026import net.sf.gridarta.gui.map.mapview.MapView; 027import net.sf.gridarta.gui.map.mapview.MapViewsManager; 028import net.sf.gridarta.model.archetype.Archetype; 029import net.sf.gridarta.model.gameobject.GameObject; 030import net.sf.gridarta.model.io.MapReader; 031import net.sf.gridarta.model.io.MapReaderFactory; 032import net.sf.gridarta.model.maparchobject.MapArchObject; 033import net.sf.gridarta.model.mapcontrol.MapControl; 034import net.sf.gridarta.model.mapmanager.MapManager; 035import org.jetbrains.annotations.NotNull; 036import org.jetbrains.annotations.Nullable; 037 038/** 039 * Maintains the state of a pickmap file. A <code>Pickmap</code> is part of a 040 * {@link MapFolder} which is part of {@link MapFolderTree}. A pickmap has an 041 * underlying file from which a {@link MapControl} instance may be created. 042 * @author Andreas Kirschbaum 043 */ 044public class MapFile<G extends GameObject<G, A, R>, A extends MapArchObject<A>, R extends Archetype<G, A, R>> { 045 046 /** 047 * The {@link Pattern} that matches valid pickmap names. 048 */ 049 @NotNull 050 private static final Pattern PATTERN_VALID_PICKMAP_NAME = Pattern.compile("[-a-zA-Z_+ 0-9,]+"); 051 052 /** 053 * The folder this pickmap is part of. 054 */ 055 @NotNull 056 private final MapFolder<G, A, R> mapFolder; 057 058 /** 059 * The underlying map file name. 060 */ 061 @NotNull 062 private final String name; 063 064 /** 065 * The {@link MapManager} for creating new pickmaps. 066 */ 067 @NotNull 068 private final MapManager<G, A, R> pickmapManager; 069 070 /** 071 * The {@link MapReaderFactory} to use. 072 */ 073 @NotNull 074 private final MapReaderFactory<G, A> mapReaderFactory; 075 076 /** 077 * The {@link MapControl} instance representing the map file, or 078 * <code>null</code> if the map file is not loaded. 079 */ 080 @Nullable 081 private MapControl<G, A, R> pickmap; 082 083 /** 084 * The {@link MapView} or {@link #pickmap}, or <code>null</code> if 085 * <code>pickmap == null</code>. 086 */ 087 @Nullable 088 private MapView<G, A, R> pickmapView; 089 090 /** 091 * The {@link MapViewsManager}. 092 */ 093 @NotNull 094 private final MapViewsManager<G, A, R> mapViewsManager; 095 096 /** 097 * The synchronization object for accessed to {@link #pickmap} and {@link 098 * #pickmapView}. 099 */ 100 @NotNull 101 private final Object sync = new Object(); 102 103 /** 104 * Creates a new instance. 105 * @param mapFolder the folder ths pickmap is part of 106 * @param name the underlying map file name 107 * @param mapReaderFactory the map reader factory to use 108 * @param pickmapManager the map manager for creating pickmaps 109 * @param mapViewsManager the map views 110 * @throws InvalidNameException if <code>name</code> is not valid 111 */ 112 public MapFile(@NotNull final MapFolder<G, A, R> mapFolder, @NotNull final String name, @NotNull final MapReaderFactory<G, A> mapReaderFactory, @NotNull final MapManager<G, A, R> pickmapManager, @NotNull final MapViewsManager<G, A, R> mapViewsManager) throws InvalidNameException { 113 if (!isValidPickmapName(name)) { 114 throw new InvalidNameException(name); 115 } 116 117 this.mapFolder = mapFolder; 118 this.name = name; 119 this.pickmapManager = pickmapManager; 120 this.mapReaderFactory = mapReaderFactory; 121 this.mapViewsManager = mapViewsManager; 122 } 123 124 /** 125 * Returns whether a pickmap name is valid. 126 * @param name the name to check 127 * @return whether the name is valid 128 */ 129 public static boolean isValidPickmapName(@NotNull final CharSequence name) { 130 return PATTERN_VALID_PICKMAP_NAME.matcher(name).matches(); 131 } 132 133 /** 134 * Returns the underlying map file. 135 * @return the map file 136 */ 137 @NotNull 138 public File getFile() { 139 return new File(mapFolder.getDir(), name); 140 } 141 142 /** 143 * Returns the {@link MapControl} representing this pickmap. Returns 144 * <code>null</code> unless {@link #loadPickmap()} was successfully called. 145 * @return the <code>MapControl</code> instance, or <code>null if the map 146 * file has not been loaded 147 */ 148 @Nullable 149 public MapControl<G, A, R> getPickmap() { 150 synchronized (sync) { 151 return pickmap; 152 } 153 } 154 155 /** 156 * Returns the {@link MapView} instance for this pickmap. Returns 157 * <code>null</code> unless {@link #loadPickmap()} was successfully called. 158 * @return the <code>MapView</code> instance, or <code>null if the map file 159 * has not been loaded 160 */ 161 @Nullable 162 public MapView<G, A, R> getMapView() { 163 synchronized (sync) { 164 return pickmapView; 165 } 166 } 167 168 /** 169 * Loads the pickmap from the underlying map file. 170 * @throws IOException if the map file cannot be loaded 171 */ 172 public void loadPickmap() throws IOException { 173 synchronized (sync) { 174 if (pickmap != null) { 175 return; 176 } 177 178 final File file = getFile(); 179 final MapReader<G, A> decoder = mapReaderFactory.newMapReader(file); 180 pickmap = pickmapManager.newMap(decoder.getGameObjects(), decoder.getMapArchObject(), file, true); 181 pickmapView = mapViewsManager.newMapView(pickmap, null, null); 182 } 183 } 184 185 /** 186 * Unloads the map file. Undoes the effect of {@link #loadPickmap()}. 187 */ 188 public void freePickmap() { 189 synchronized (sync) { 190 if (pickmap != null) { 191 assert pickmapView != null; 192 mapViewsManager.closeView(pickmapView); 193 assert pickmap != null; 194 pickmapManager.release(pickmap); 195 pickmap = null; 196 } 197 } 198 } 199 200 /** 201 * Returns this pickmap if it has been loaded and is modified. 202 * @param unsavedPickmaps the collection to add <code>this</code> to if 203 * modified 204 */ 205 public void getUnsavedPickmaps(@NotNull final Collection<MapControl<G, A, R>> unsavedPickmaps) { 206 synchronized (sync) { 207 if (pickmap != null && pickmap.getMapModel().isModified()) { 208 unsavedPickmaps.add(pickmap); 209 } 210 } 211 } 212 213 /** 214 * Removes this pickmap from its folder. 215 * @param deleteFile if set, delete the map file as well 216 */ 217 public void remove(final boolean deleteFile) { 218 mapFolder.removePickmap(this, deleteFile); 219 } 220 221 /** 222 * Saves this pickmap. Does nothing if the pickmap is not loaded or not 223 * modified. 224 * @throws IOException if saving fails 225 */ 226 public void save() throws IOException { 227 final MapControl<G, A, R> oldPickmap = getPickmap(); 228 if (oldPickmap != null && oldPickmap.getMapModel().isModified()) { 229 oldPickmap.save(); 230 } 231 } 232 233 /** 234 * Reverts this pickmap to its underlying map file. Does nothing if the 235 * pickmap is not loaded. 236 * @throws IOException if the pickmap cannot be loaded 237 */ 238 public void revert() throws IOException { 239 final MapControl<G, A, R> oldPickmap = getPickmap(); 240 if (oldPickmap != null) { 241 freePickmap(); 242 loadPickmap(); 243 mapFolder.firePickmapReverted(this, oldPickmap); 244 } 245 } 246 247}