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}