org.nees.rpi.vis.loaders.M3DVLoader.java Source code

Java tutorial

Introduction

Here is the source code for org.nees.rpi.vis.loaders.M3DVLoader.java

Source

/*
 * Copyright (c) 2004-2007 Rensselaer Polytechnic Institute
 * Copyright (c) 2010 Rensselaer Polytechnic Institute
 * Copyright (c) 2007 NEES Cyberinfrastructure Center
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 * For more information: http://nees.rpi.edu/3dviewer/
 */
package org.nees.rpi.vis.loaders;

import java.awt.*;
import java.io.File;
import java.net.URI;
import java.net.URL;
import java.util.*;
import java.util.List;

import javax.vecmath.Point3f;

import scala.util.Either;
import scala.util.Left;
import scala.util.Right;

import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.XMLConfiguration;

import org.nees.rpi.util.Pair;
import org.nees.rpi.vis.GeometryRenderer;
import org.nees.rpi.vis.*;
import org.nees.rpi.vis.model.*;

/**
 * Reads m3dv files loading the 3DDV model from the xml
 * descriptors as well as the data from the data files.
 */
public class M3DVLoader implements ModelLoader {
    private Either<URL, File> input;
    private XMLConfiguration config;
    private DVModel model;
    /** Counter used when creating names for nameless data shapes */
    private int shapeNameCounter = 1;

    /**
     * Creates a new loader.
     *
     * @param xmlInput
     *   The XML markup for the model.
     */
    public M3DVLoader(URL xmlInput) {
        this(xmlInput, null);
    }

    /**
     * Creates a new loader.
     *
     * @param fileInput
     *   The source file.
     */
    public M3DVLoader(File fileInput) {
        this(null, fileInput);
    }

    private M3DVLoader(URL xmlInput, File fileInput) {
        if (Pair.neither(xmlInput, fileInput)) {
            throw new IllegalArgumentException("Both inputs are null");
        }

        if (Pair.both(xmlInput, fileInput)) {
            throw new IllegalArgumentException("Neither inputs are null");
        }

        input = input != null ? new Left<URL, File>(xmlInput) : new Right<URL, File>(fileInput);
        config = null;
        model = null;
        shapeNameCounter = 1;
    }

    @Override
    public String getFileType() {
        return "m3dv";
    }

    @Override
    public DVModel load() throws LoaderException {
        try {
            config = input.isLeft() ? new XMLConfiguration(input.left().get())
                    : new XMLConfiguration(input.right().get());
            processModelInfo();
            processDataFile();
            Collection<DVGeometry> geometries = processGeometries();
            processGroups(geometries);
            assert model != null : "model is null";
            return model;
        } catch (ConfigurationException e) {
            throw new LoaderException(e);
        }
    }

    /**
     * Parses the input xml solely to get the names of the datafiles associated
     * with this model file
     *
     * @return
     *   a list of the file names for the model, returns an empty
     *   list if none found
     * @throws LoaderException
     *   If an error occurs while parsing the input for data files.
     */
    @Override
    public Iterable<String> getDataFiles() throws LoaderException {

        try {
            final List<String> datafiles = new ArrayList<String>();
            int i = 0;

            config = input.isLeft() ? new XMLConfiguration(input.left().get())
                    : new XMLConfiguration(input.right().get());

            do {
                String filename = config.getString("data-file(" + i + ").file-name");
                if (filename == null)
                    break;
                datafiles.add(filename);
                i++;
            } while (true);

            return new Iterable<String>() {
                public Iterator<String> iterator() {
                    return datafiles.iterator();
                }
            };
        } catch (ConfigurationException e) {
            throw new LoaderException(e);
        }
    }

    private void processModelInfo() {
        model = new DVModel(config.getString("title"));
        model.setPreparedBy(config.getString("prepared-by"));
    }

    private void processDataFile() throws LoaderException {
        boolean moreDataFiles = true;

        for (int i = 0; moreDataFiles; i++) {
            String filename = config.getString("data-file(" + i + ").file-name");
            String delimiter = config.getString("data-file(" + i + ").delimiter");
            String sSkipRows = config.getString("data-file(" + i + ").skip-rows");
            String timeChannelName = config.getString("data-file(" + i + ").time-channel");

            if (filename != null) {
                try {
                    File toRead = null;
                    if (filename.contains("/") || filename.contains("\\")) {
                        toRead = new File(filename);
                    } else {
                        String parentDir = null;
                        if (input.isLeft()) {
                            URL parentXMLURL = input.left().get();
                            parentDir = parentXMLURL.getPath().substring(0,
                                    parentXMLURL.getPath().lastIndexOf("/"));
                        } else {
                            parentDir = input.right().get().getParentFile().getAbsolutePath();
                        }
                        assert parentDir != null : "parentDir was not initialized correctly";
                        toRead = new File(new URI("file://" + parentDir + "/" + filename));
                    }

                    if (!toRead.exists()) {
                        throw new RuntimeException("could not find file at \"" + toRead.getAbsolutePath() + "\"");
                    }

                    DelimiterType delimiterType;

                    if (delimiter == null) {
                        delimiterType = DelimiterType.TAB_DELIMITED;
                    } else {
                        String delimiterTemp = delimiter.trim().toLowerCase();
                        if (delimiterTemp.equals("tab")) {
                            delimiterType = DelimiterType.TAB_DELIMITED;
                        } else if (delimiterTemp.equals("comma")) {
                            delimiterType = DelimiterType.COMMA_DELIMITED;
                        } else {
                            delimiterType = DelimiterType.OTHER;
                        }
                    }

                    int skipRows = 0;
                    try {
                        if (sSkipRows != null) {
                            skipRows = Integer.parseInt(sSkipRows);
                        }
                    } catch (Exception e) {
                        throw new LoaderException("cannot convert \"" + sSkipRows + "\" to a number", e);
                    }

                    DVDataFile datafile = DataFileLoader.loadFile(toRead.toURI().toURL(), skipRows, delimiterType,
                            delimiter, null);
                    if (timeChannelName != null) {
                        DVDataChannel timeChannel = datafile.getChannel(timeChannelName);
                        if (timeChannel == null) {
                            throw new LoaderException("cannot find the time-channel in the data-file");
                        } else {
                            datafile.setTimeChannel(timeChannel);
                        }
                    }

                    model.addDataFile(datafile);
                } catch (Exception e) {
                    throw new LoaderException(e);
                }
            } else {
                moreDataFiles = false;
            }
        }
    }

    private void processGroups(Collection<DVGeometry> geometries) throws LoaderException {
        boolean moreGroups = true;

        for (int i = 0; moreGroups; i++) {
            String tag = "group(" + i + ")";
            String name = config.getString(tag + ".name");

            DVAppearance appearance = processAppearance(tag);
            BorderInfo borderInfo = processBorderInfo(tag + ".border");

            if (name != null) {
                DVGroup group = new DVGroup(name, appearance);
                group.setUIOption(DVGroup.UIOptions.SINGLE);
                processShapes(group, tag, geometries, appearance, borderInfo);
                model.addGroup(group);
            } else {
                moreGroups = false;
            }
        }
    }

    private void processShapes(DVGroup group, String parentTag, Collection<DVGeometry> geometries,
            DVAppearance parentApp, BorderInfo parentBorderInfo) throws LoaderException {
        boolean moreShapes = true;
        for (int i = 0; moreShapes; i++) {
            String tag = parentTag + ".shape(" + i + ")";
            Point3f location = processShapeLocation(tag);
            Point3f endLocation = processShapeEndLocation(tag);
            DVShape dvShape = processShapePresetShape(tag, location);
            DVGeometry geom = processShapeGeometry(tag, geometries);

            if (dvShape == null && geom != null) {
                DVAppearance appearance = processAppearance(tag, parentApp);

                // Only cylinders can have end locations, the others have not been
                // implemented yet. 4/1/2008
                if (!(geom instanceof DVCylinder))
                    endLocation = null;

                if (!appearance.equals(parentApp))
                    dvShape = new DVShape(geom, location, endLocation, appearance);
                else
                    dvShape = new DVShape(geom, location, endLocation);
            }

            if (dvShape != null) {
                processShapeData(tag, dvShape);
                String name = config.getString(tag + ".name");
                if (name == null)
                    name = getShapeGeneratedName();
                dvShape.setName(name);

                BorderInfo borderInfo = processBorderInfo(tag + ".border", parentBorderInfo);
                if (borderInfo != null)
                    dvShape.setBorder(borderInfo.color);

                model.addShape(dvShape, group);
            } else {
                moreShapes = false;
            }
        }
    }

    private Point3f processShapeLocation(String tag) {
        float x = config.getFloat(tag + ".coordinates.x", -100);
        float y = config.getFloat(tag + ".coordinates.y", -100);
        float z = config.getFloat(tag + ".coordinates.z", -100);

        return new Point3f(x, y, z);
    }

    private Point3f processShapeEndLocation(String tag) {
        if (!config.containsKey(tag + ".coordinates-end.x"))
            return null;

        float x = config.getFloat(tag + ".coordinates-end.x", -100);
        float y = config.getFloat(tag + ".coordinates-end.y", -100);
        float z = config.getFloat(tag + ".coordinates-end.z", -100);

        return new Point3f(x, y, z);
    }

    private DVShape processShapePresetShape(String tag, Point3f location) {
        String presetShapeName = config.getString(tag + ".preset-shape");

        if (presetShapeName != null) {
            return DVShapeFactory.createPresetShape(presetShapeName, location);
        }

        return null;
    }

    private DVGeometry processShapeGeometry(String tag, Collection<DVGeometry> geometries) throws LoaderException {
        try {
            String geomId = config.getString(tag + ".geometryid");

            if (geomId != null) {
                DVGeometry geom = getGeometry(geometries, geomId);
                if (geom == null) {
                    throw new LoaderException("geometryid not recognized. Check the spelling or define the shape.");
                }
                return geom;
            } else if (config.getKeys(tag + ".cuboid").hasNext()) {
                return new CuboidParser().processParams(tag + ".cuboid", null);
            } else if (config.getKeys(tag + ".cube").hasNext()) {
                return new CubeParser().processParams(tag + ".cube", null);
            } else if (config.getKeys(tag + ".cylinder").hasNext()) {
                return new CylinderParser().processParams(tag + ".cylinder", null);
            } else if (config.getKeys(tag + ".sphere").hasNext()) {
                return new SphereParser().processParams(tag + ".sphere", null);
            } else {
                return null;
            }
        } catch (Exception e) {
            throw new LoaderException(e);
        }
    }

    private void processShapeData(String shapeTag, DVShape shape) throws LoaderException {
        boolean metaResult = processShapeDataMeta(shapeTag, shape);
        boolean seriesResult = processShapeDataSeries(shapeTag, shape);
        //shape has no metadata despite having series info, add it in
        if (!metaResult && seriesResult) {
            if (shape.isPresetShapeType()) {
                shape.addMetadata("Sensor Type:", shape.getPresetShapeType().displayName());
            }
            shape.addMetadata("Location", "X:" + shape.getX() + " Y:" + shape.getY() + " Z:" + shape.getZ());
        }

        if (metaResult || seriesResult) {
            shapeNameCounter++;
        }
    }

    private String getShapeGeneratedName() {
        return "Shape " + shapeNameCounter;
    }

    /**
     *
     * @param parentTag
     * @param shape
     * @return
     *   returns {@code true} if meta tags are defined for the processed shape
     */
    private boolean processShapeDataMeta(String parentTag, DVShape shape) {
        int metaCounter;
        boolean moreMeta = true;
        ArrayList<String> names = new ArrayList<String>();
        ArrayList<String> values = new ArrayList<String>();
        String metaname, metavalue, tag;

        for (metaCounter = 0; moreMeta; metaCounter++) {
            tag = parentTag + ".meta(" + metaCounter + ")";
            metaname = config.getString(tag + ".name");
            metavalue = config.getString(tag + ".value");
            if (metaname != null) {
                names.add(metaname);
                values.add(metavalue);
            } else {
                moreMeta = false;
            }
        }

        if (names.size() > 0) {
            shape.setMetadata(names.toArray(new String[names.size()]), values.toArray(new String[values.size()]));
            return true;
        } else {
            return false;
        }
    }

    /**
     *
     * @param parentTag
     * @param shape
     * @return
     *   returns {@code true} if a series is defined for the processed shape
     */
    private boolean processShapeDataSeries(String parentTag, DVShape shape) throws LoaderException {
        String seriesTag = parentTag + ".series";
        String timeSeriesTag = parentTag + ".time-series";

        String seriesX = config.getString(seriesTag + ".x");
        String seriesY = config.getString(seriesTag + ".y");
        String timeSeriesName = config.getString(timeSeriesTag);

        if (seriesX == null && seriesY == null && timeSeriesName == null) {
            return false;
        } else if (timeSeriesName != null) {
            DVDataChannel data = model.getDataChannel(timeSeriesName);
            if (data == null) {
                throw new LoaderException("no data found for \"" + timeSeriesName + "\"");
            } else {
                shape.setTimeSeries(data);
                return true;
            }
        } else if (seriesX != null && seriesY != null) {
            DVDataChannel x = model.getDataChannel(seriesX);
            DVDataChannel y = model.getDataChannel(seriesY);

            if (x == null) {
                throw new LoaderException("no data found for \"" + seriesX + "\"");
            } else if (y == null) {
                throw new LoaderException("no data found for \"" + seriesY + "\"");
            } else {
                shape.setSeries(x, y);
                return true;
            }
        } else {
            throw new LoaderException("must define both X and Y series");
        }
    }

    private static abstract class GeometryParser {
        abstract DVGeometry processParams(String parentTag, String id);
    }

    private class CubeParser extends GeometryParser {
        DVCube processParams(String parentTag, String id) {
            float size = config.getFloat(parentTag + ".getGroupCount", -1);
            if (size == -1) {
                return null;
            } else {
                return new DVCube(id, size);
            }
        }
    }

    private class CuboidParser extends GeometryParser {
        DVCuboid processParams(String parentTag, String id) {
            //switch y and z
            float sizex = config.getFloat(parentTag + ".sizex", -1);
            float sizez = config.getFloat(parentTag + ".sizey", -1);
            float sizey = config.getFloat(parentTag + ".sizez", -1);

            if (sizex == -1 || sizey == -1 || sizez == -1) {
                return null;
            } else {
                return new DVCuboid(id, sizex, sizey, sizez);
            }
        }
    }

    private class CylinderParser extends GeometryParser {
        DVCylinder processParams(String parentTag, String id) {
            float radius = config.getFloat(parentTag + ".radius", -1);

            if (radius == -1) {
                return null;
            } else {
                return new DVCylinder(id, radius);
            }
        }
    }

    private class SphereParser extends GeometryParser {
        DVSphere processParams(String parentTag, String id) {
            float radius = config.getFloat(parentTag + ".radius", -1);

            if (radius == -1) {
                return null;
            } else {
                return new DVSphere(id, radius);
            }
        }
    }

    private Collection<DVGeometry> processGeometries() throws LoaderException {
        ArrayList<DVGeometry> geometries = new ArrayList<DVGeometry>();

        //process cuboids
        processGeometriesHelper(geometries, "geometries.cuboid", new CuboidParser());
        //process cubes
        processGeometriesHelper(geometries, "geometries.cube", new CubeParser());
        //process cubes
        processGeometriesHelper(geometries, "geometries.cylinder", new CylinderParser());
        //process spheres
        processGeometriesHelper(geometries, "geometries.sphere", new SphereParser());

        return geometries;
    }

    private void processGeometriesHelper(ArrayList<DVGeometry> geometries, String gptag, GeometryParser gpType)
            throws LoaderException {
        boolean moreGeometries = true;
        String tag = "";

        try {
            for (int i = 0; moreGeometries; i++) {
                tag = gptag + "(" + i + ")";
                String id = config.getString(tag + ".id");
                if (id != null) {
                    DVGeometry geom = gpType.processParams(tag, id);

                    if (geom != null) {
                        geometries.add(geom);
                    } else {
                        throw new LoaderException("Invalid geometry type or invalid properties");
                    }
                } else {
                    moreGeometries = false;
                }
            }
        } catch (Exception e) {
            throw new LoaderException("Badly formatted geometry", e);
        }
    }

    private DVGeometry getGeometry(Collection<DVGeometry> geometries, String id) {
        for (final DVGeometry geom : geometries) {
            if (geom.id().equals(id)) {
                return geom;
            }
        }
        return null;
    }

    private static class BorderInfo {
        Color color;
    }

    private BorderInfo processBorderInfo(String tag) throws LoaderException {
        return processBorderInfo(tag, null);
    }

    private BorderInfo processBorderInfo(String tag, BorderInfo parent) throws LoaderException {
        try {
            String show = config.getString(tag + ".show");
            if (show != null) {
                show = show.trim().toLowerCase();
                if (show.equals("yes")) {
                    BorderInfo bi = new BorderInfo();
                    Color defaultColor;
                    if (parent == null) {
                        defaultColor = AppSettings.getInstance().getDefaultShapeBorderColor();
                    } else {
                        defaultColor = parent.color;
                    }
                    bi.color = processColor(tag + ".color", defaultColor);
                    return bi;
                } else if (show.equals("no")) {
                    return null;
                } else {
                    throw new LoaderException("Invalid show attribute for border");
                }
            } else if (parent != null) {
                return parent;
            } else {
                return null;
            }
        } catch (Exception e) {
            throw new LoaderException("Badly formatted border setting", e);
        }
    }

    private DVAppearance processAppearance(String tag) throws LoaderException {
        AppSettings AS = AppSettings.getInstance();
        DVAppearance dvApp = new DVAppearance(AS.getDefaultShapeFillColor(), AS.getDefaultShapeTransparency());
        return processAppearance(tag, dvApp);
    }

    private DVAppearance processAppearance(String tag, DVAppearance source) throws LoaderException {
        Color color = processColor(tag + ".color", source.getColor());
        float transparency = processTransparency(tag + ".transparency", source.getTransparency());
        DVAppearance.AppearanceStatus starterApp = processStarterAppearance(tag + ".starter-appearance",
                source.getAppearanceStatus());
        if (source.getColor().equals(color) && source.getAppearanceStatus() == starterApp
                && source.getTransparency() == transparency) {
            return source;
        } else {
            DVAppearance dvApp = new DVAppearance(color, transparency);
            dvApp.setAppearanceStatus(starterApp);
            return dvApp;
        }
    }

    private Color processColor(String tag, Color defaultColor) throws LoaderException {
        List colorList = config.getList(tag);
        String hexColor = config.getString(tag);
        try {
            if (colorList.size() == 3) {
                int red = Integer.parseInt(colorList.get(0).toString());
                int green = Integer.parseInt(colorList.get(1).toString());
                int blue = Integer.parseInt(colorList.get(2).toString());
                return new Color(red, green, blue);
            } else if (hexColor != null) {
                return Color.decode(hexColor);
            }
        } catch (Exception e) {
            throw new LoaderException("Badly formatted color setting", e);
        }

        return defaultColor;
    }

    private DVAppearance.AppearanceStatus processStarterAppearance(String tag,
            DVAppearance.AppearanceStatus sourceStarterApp) throws LoaderException {
        try {
            String starter = config.getString(tag);
            if (starter != null) {
                starter = starter.trim().toLowerCase();
                if (starter.compareTo("solid") == 0) {
                    return DVAppearance.AppearanceStatus.SOLID;
                } else if (starter.compareTo("line") == 0) {
                    return DVAppearance.AppearanceStatus.LINE;
                } else if (starter.compareTo("transparent") == 0) {
                    return DVAppearance.AppearanceStatus.TRANSPARENT;
                } else {
                    throw new LoaderException("Invalid fill-option");
                }
            } else {
                return sourceStarterApp;
            }
        } catch (Exception e) {
            throw new LoaderException("Badly formatted transparency setting", e);
        }
    }

    private float processTransparency(String tag, float defaultTransp) throws LoaderException {
        try {
            float transparency = config.getFloat(tag, defaultTransp * 100f);
            if (transparency > 100) {
                throw new LoaderException("Transparency cannot be higher than 100");
            }

            if (transparency < 0) {
                throw new LoaderException("Transparency cannot be a negative number");
            }

            return transparency / 100f;
        } catch (Exception e) {
            throw new LoaderException("Badly formatted transparency setting", e);
        }
    }
}