001/*
002 * Gridarta MMORPG map editor for Crossfire, Daimonin and similar games.
003 * Copyright (C) 2000-2010 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.model.mappathnormalizer;
021
022import java.io.File;
023import java.io.IOException;
024import net.sf.gridarta.model.mapmodel.MapModel;
025import net.sf.gridarta.model.settings.ProjectSettings;
026import org.jetbrains.annotations.NotNull;
027import org.jetbrains.annotations.Nullable;
028
029/**
030 * Normalizes map path specifications into {@link File Files}.
031 * @author Andreas Kirschbaum
032 */
033public class MapPathNormalizer {
034
035    /**
036     * The project settings instance.
037     */
038    @NotNull
039    private final ProjectSettings projectSettings;
040
041    /**
042     * Creates a new instance.
043     * @param projectSettings the project settings instance
044     */
045    public MapPathNormalizer(@NotNull final ProjectSettings projectSettings) {
046        this.projectSettings = projectSettings;
047    }
048
049    /**
050     * Normalizes a map path relative to a {@link MapModel}.
051     * @param mapModel the map model to start from
052     * @param path the destination path
053     * @return the normalized destination file
054     * @throws InvalidPathException if the destination file is invalid
055     * @throws IOErrorException if an I/O error occurs
056     * @throws RelativePathOnUnsavedMapException if the path is relative and the
057     * map has not yet been saved
058     * @throws SameMapException if the destination path points to the source
059     * map
060     */
061    @NotNull
062    public File normalizeMapPath(@NotNull final MapModel<?, ?, ?> mapModel, @NotNull final String path) throws InvalidPathException, IOErrorException, RelativePathOnUnsavedMapException, SameMapException {
063        return normalizeMapPath(mapModel.getMapFile(), path);
064    }
065
066    /**
067     * Normalizes a map path relative to a {@link File}.
068     * @param mapFile the file to start from
069     * @param path the destination path
070     * @return the normalized destination file
071     * @throws InvalidPathException if the destination file is invalid
072     * @throws IOErrorException if an I/O error occurs
073     * @throws RelativePathOnUnsavedMapException if the path is relative and the
074     * map has not yet been saved
075     * @throws SameMapException if the destination path points to the source
076     * map
077     */
078    @NotNull
079    private File normalizeMapPath(@Nullable final File mapFile, @NotNull final String path) throws InvalidPathException, IOErrorException, RelativePathOnUnsavedMapException, SameMapException {
080        @NotNull final File newFile;
081        if (path.startsWith(File.pathSeparator) || path.startsWith("/")) {
082            // we have an absolute path:
083            newFile = new File(projectSettings.getMapsDirectory().getAbsolutePath(), path.substring(1));
084        } else {
085            // we have a relative path:
086            if (mapFile == null) {
087                throw new RelativePathOnUnsavedMapException(path);
088            }
089            newFile = new File(mapFile.getParent(), path);
090        }
091        if (path.isEmpty() || (mapFile != null && newFile.equals(mapFile))) {
092            throw new SameMapException();
093        }
094
095        if (!newFile.exists() || newFile.isDirectory()) {
096            // The path is wrong
097            // TODO: Differ between non-existing paths, wrongly formatted paths and directories - give more info to the user.
098            throw new InvalidPathException(newFile);
099        }
100
101        // its important to force the canonical file here or the
102        // file path is added every time we use a ../ or a ./ .
103        // This results in giant file names like "xx/../yy/../xx/../yy/.."
104        // and after some times in buffer overflows.
105        final File canonicalNewFile;
106        try {
107            canonicalNewFile = newFile.getCanonicalFile();
108        } catch (final IOException ex) {
109            throw new IOErrorException(newFile, ex);
110        }
111
112        return canonicalNewFile;
113    }
114
115}