de.nx42.maps4cim.header.HeaderEditor.java Source code

Java tutorial

Introduction

Here is the source code for de.nx42.maps4cim.header.HeaderEditor.java

Source

/**
 * maps4cim - a real world map generator for CiM 2
 * Copyright 2013 - 2014 Sebastian Straub
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package de.nx42.maps4cim.header;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;
import java.util.Date;

import javax.imageio.ImageIO;

import com.google.common.io.ByteSource;
import com.google.common.io.ByteStreams;
import com.google.common.io.Files;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.nx42.maps4cim.config.header.HeaderDef.BuildingSet;
import de.nx42.maps4cim.util.DateUtils;
import de.nx42.maps4cim.util.java2d.BitmapUtil;

/**
 * Provides functions to edit and replace the header of an existing map
 *
 * @author Sebastian Straub <sebastian-straub@gmx.net>
 */
public class HeaderEditor {

    private static Logger log = LoggerFactory.getLogger(HeaderEditor.class);
    protected static final int previewImageSize = 256;

    protected File mapFile;
    protected CustomHeader header;

    public HeaderEditor(String mapFilePath) throws IOException, ParseException {
        this(parseFileString(mapFilePath));
    }

    public HeaderEditor(File mapFile) throws ParseException, IOException {
        this.mapFile = mapFile;
        if (mapFile.isFile()) {
            if (mapFile.canRead()) {
                this.header = HeaderParser.parse(mapFile);
            } else {
                throw new IOException(mapFile + " can't be accessed!");
            }
        } else {
            throw new IOException(mapFile + " does not point to a file!");
        }
    }

    public void writeChanges(String dest) throws IOException {
        writeChanges(parseFileString(dest));
    }

    public void writeChanges(File dest) throws IOException {

        /*
         * - Find header end of old file
         * - write new header to byte array
         * - create new writer, append new header & main part of old file
         */

        // write to temp, rename if successful (useful if src == dest and errors occur...)
        File tmp = new File(dest.getParentFile(), dest.getName() + ".tmp");

        // create streams
        InputStream is = null;
        FileOutputStream fos = null;

        try {
            // open the source file and find the end of the header
            ByteSource source = Files.asByteSource(mapFile);
            int headerEnd = HeaderParser.findEndOfHeader(source);

            // open the destination file
            fos = new FileOutputStream(tmp);

            // write the header
            fos.write(header.generateHeader());

            // copy the contents (without header) from the source file to the destination
            is = source.openBufferedStream();
            long skipped = is.skip(headerEnd);
            if (skipped != headerEnd) {
                throw new IOException("Could not read from existing map file!");
            }
            ByteStreams.copy(is, fos);

            // move tmp file
            fos.close();
            if (dest.exists()) {
                dest.delete();
            }
            Files.move(tmp, dest);
        } finally {
            // close streams
            if (is != null) {
                is.close();
            }
            if (fos != null) {
                fos.close();
            }
        }
    }

    public void writeChanges() throws IOException {
        writeChanges(mapFile);
    }

    /**
     * Parses a String and returns it as File object, if the path is well-formed.
     * This function makes some more checks than new File(String), so empty file
     * Strings or invalid paths will be rejected right away.
     * @param filePath the file path String to parse
     * @return the File object represented by this path
     * @throws IOException if the file path is not well-formed or empty
     */
    protected static File parseFileString(String filePath) throws IOException {
        if (filePath == null || "".equals(filePath.trim())) {
            throw new FileNotFoundException("file path empty!");
        } else {
            File file = new File(filePath);
            // check if path is valid
            file.getCanonicalPath();
            return file;
        }
    }

    protected static final double ticksPerHour = DateUtils.ticksPerMs * 1000 * 60 * 60;

    protected static double ticksToHours(long ticks) {
        return ticks / ticksPerHour;
    }

    protected static long hoursToTicks(double hours) {
        return (long) (hours * ticksPerHour);
    }

    /**
     * Draws a preview image with the specified message
     * @return the bufferedimage with the message
     */
    protected static BufferedImage drawErrorImage(String message) {
        int width = 256, height = 256;
        BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        Graphics2D ig2 = bi.createGraphics();

        Font font = new Font("Tahoma", Font.BOLD, 20);
        ig2.setFont(font);
        FontMetrics fontMetrics = ig2.getFontMetrics();
        int stringWidth = fontMetrics.stringWidth(message);
        int stringHeight = fontMetrics.getAscent();
        ig2.setPaint(Color.red);
        ig2.drawString(message, (width - stringWidth) / 2, height / 2 + stringHeight / 4);

        return bi;
    }

    /**
     * @return the header
     */
    public CustomHeader getHeader() {
        return header;
    }

    /**
     * @param header the header to set
     */
    public void setHeader(CustomHeader header) {
        this.header = header;
    }

    /**
     * @return the unusedDate1
     */
    public Date getUnusedDate1() {
        return header.unusedDate1;
    }

    /**
     * @param unusedDate1 the unusedDate1 to set
     */
    public void setUnusedDate1(Date unusedDate1) {
        header.unusedDate1 = unusedDate1;
    }

    /**
     * @return the unusedDate2
     */
    public Date getUnusedDate2() {
        return header.unusedDate2;
    }

    /**
     * @param unusedDate2 the unusedDate2 to set
     */
    public void setUnusedDate2(Date unusedDate2) {
        header.unusedDate2 = unusedDate2;
    }

    /**
     * @return the lastSaved
     */
    public Date getLastSaved() {
        return header.lastSaved;
    }

    /**
     * @param lastSaved the lastSaved to set
     */
    public void setLastSaved(Date lastSaved) {
        header.lastSaved = lastSaved;
    }

    /**
     * @return the mapCreated
     */
    public Date getMapCreated() {
        return header.mapCreated;
    }

    /**
     * @param mapCreated the mapCreated to set
     */
    public void setMapCreated(Date mapCreated) {
        header.mapCreated = mapCreated;
    }

    /**
     * @return the workTime1
     */
    public double getWorkHours1() {
        return ticksToHours(header.workTime1);
    }

    /**
     * @param workTime1 the workTime1 to set
     */
    public void setWorkHours1(double workHours1) {
        header.workTime1 = hoursToTicks(workHours1);
    }

    /**
     * @return the workTime2
     */
    public double getWorkHours2() {
        return ticksToHours(header.workTime2);
    }

    /**
     * @param workTime2 the workTime2 to set
     */
    public void setWorkHours2(double workHours2) {
        header.workTime2 = hoursToTicks(workHours2);
    }

    /**
     * @return the mapName
     */
    public String getMapName() {
        return header.mapName;
    }

    /**
     * @param mapName the mapName to set
     */
    public void setMapName(String mapName) {
        header.mapName = mapName;
    }

    /**
     * @return the buildingSet
     */
    public BuildingSet getBuildingSet() {
        return header.buildingSet;
    }

    /**
     * @param buildingSet the buildingSet to set
     */
    public void setBuildingSet(BuildingSet buildingSet) {
        header.setBuildingSet(buildingSet);
    }

    /**
     * @return the png
     */
    public BufferedImage getPreviewImage() {
        try {
            return ImageIO.read(new ByteArrayInputStream(header.png));
        } catch (IOException e) {
            log.error("Unable to convert PNG from header as BufferedImage", e);
            return drawErrorImage("Could not load preview image!");
        }
    }

    public void setPreviewImage(BufferedImage image) {
        setPreviewImage(image, previewImageSize, true);
    }

    public void setPreviewImage(BufferedImage image, int customEdgeLen, boolean forceResize) {
        // resize if desired or necessary (image too large or not squarish)
        BufferedImage render = image;
        if (forceResize || image.getWidth() != image.getHeight() || image.getWidth() > customEdgeLen) {
            render = BitmapUtil.resizeAndRectify(image, customEdgeLen);
        }

        // write png
        try {
            header.png = BitmapUtil.writePng(render);
            header.pngLength = CustomHeader.int24write(header.png.length);
        } catch (IOException e) {
            log.error("Could not convert buffered image to PNG byte[]", e);
        }
    }

}