// // @(#)MapMetadata.java 10/2002 // // Copyright 2002,2003 Zachary DelProposto. All rights reserved. // Use is subject to license terms. // // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program 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 General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. // Or from http://www.gnu.org/ // package dip.gui.map; import java.awt.geom.Point2D; import java.io.Serializable; import java.util.HashMap; import java.util.Map; import java.util.StringTokenizer; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.svg.SVGDocument; import dip.misc.Log; import dip.world.Coast; import dip.world.Power; import dip.world.Province; import dip.world.World; import dip.world.variant.data.Symbol; import dip.world.variant.data.SymbolPack; /** * Extracts map information, and SVG Elements, from the SVG file. * Metadata (jDip-specific, in its own XML namespace) is also extracted. * <p> * * * * */ public class MapMetadata implements Serializable { /** * */ private static final long serialVersionUID = 9061880502629595108L; /** jDip namespace constant */ public static final String JDIP_NAMESPACE = "http://jdip.sourceforge.org/jdipNS"; /** jDip namespace element: for display control */ public static final String EL_DISPLAY = "DISPLAY"; /** jDip namespace element: for display control (zoom) */ public static final String EL_ZOOM = "ZOOM"; /** jDip namespace element: for display control (labels) */ public static final String EL_LABELS = "LABELS"; /** jDip namespace attributes: display: zoom */ public static final String ATT_ZOOM_MIN = "min"; /** jDip namespace attributes: display: zoom */ public static final String ATT_ZOOM_MAX = "max"; /** jDip namespace attributes: display: zoom */ public static final String ATT_ZOOM_FACTOR = "factor"; /** jDip namespace attributes: display: labels */ public static final String ATT_LABELS_BRIEF = "brief"; /** jDip namespace attributes: display: labels */ public static final String ATT_LABELS_FULL = "full"; /** jDip namespace element: for province metadata (main grouping) */ public static final String EL_PROVINCE_DATA = "PROVINCE_DATA"; /** jDip namespace element: for province metadata: province */ public static final String EL_PROVINCE = "PROVINCE"; /** jDip namespace element: for province metadata: units */ public static final String EL_UNIT = "UNIT"; /** jDip namespace element: for province metadata: dislodged units */ public static final String EL_DISLODGED_UNIT = "DISLODGED_UNIT"; /** jDip namespace element: for province metadata: supply centers */ public static final String EL_SC = "SUPPLY_CENTER"; /** jDip namespace attributes: province */ public static final String ATT_X = "x"; /** jDip namespace attributes: province */ public static final String ATT_Y = "y"; /** jDip namespace attributes: province, symbolsize */ public static final String ATT_NAME = "name"; /** jDip namespace for Order metadata */ public static final String EL_ORDERDRAWING = "ORDERDRAWING"; /** Hold order drawing parameter element */ public static final String EL_HOLD = "HOLD"; /** Disband order drawing parameter element */ public static final String EL_DISBAND = "DISBAND"; /** Remove order drawing parameter element */ public static final String EL_REMOVE = "REMOVE"; /** Build order drawing parameter element */ public static final String EL_BUILD = "BUILD"; /** Move order drawing parameter element */ public static final String EL_MOVE = "MOVE"; /** Retreat order drawing parameter element */ public static final String EL_RETREAT = "RETREAT"; /** Support order drawing parameter element */ public static final String EL_SUPPORT = "SUPPORT"; /** Convoy order drawing parameter element */ public static final String EL_CONVOY = "CONVOY"; /** Waive order drawing parameter element */ public static final String EL_WAIVE = "WAIVE"; /** Power Colors group */ public static final String EL_POWERCOLORS = "POWERCOLORS"; /** Power Color item */ public static final String EL_POWERCOLOR = "POWERCOLOR"; /** Symbol Size element */ public static final String EL_SYMBOLSIZE = "SYMBOLSIZE"; /** jDip namespace attribute: (optional) province data: dislodgedUnitOffset */ private static final String ATT_DISLODGED_OFFSET = "dislodgedUnitOffset"; /** jDip namespace attribute: order drawing: deltaRadius */ public static final String ATT_DELTA_RADIUS = "deltaRadius"; /** jDip namespace attribute: order drawing: stroke CSS style */ public static final String ATT_STROKESTYLE = "strokeCSSStyle"; /** jDip namespace attribute: order drawing: filter ID */ public static final String ATT_FILTERID = "filterID"; /** jDip namespace attribute: order drawing: marker ID */ public static final String ATT_MARKERID = "markerID"; /** jDip namespace attribute: power colors: power name */ public static final String ATT_POWER = "power"; /** jDip namespace attribute: power colors: color name/value */ public static final String ATT_COLOR = "color"; /** jDip namespace attribute: order drawing: highlight offset (float) */ public static final String ATT_HILIGHT_OFFSET = "hilightOffset"; /** jDip namespace attribute: order drawing: highlight class */ public static final String ATT_HILIGHT_CLASS = "hilightCSSClass"; /** jDip namespace attribute: order drawing: order line widths */ public static final String ATT_WIDTHS = "widths"; /** jDip namespace attribute: order drawing: order shadow line widths */ public static final String ATT_SHADOW_WIDTHS = "shadowWidths"; /** jDip namespace attribute: symbol size: symbol width */ public static final String ATT_WIDTH = "width"; /** jDip namespace attribute: symbol size: symbol height */ public static final String ATT_HEIGHT = "height"; /** Internal constant for a coordinate at (0.0, 0.0) */ private static final Point2D.Float POINT_ZERO = new Point2D.Float(0.0f, 0.0f); // instance variables private Map infoMap; // placement info private HashMap displayProps; // display info //private final MapPanel mp; private Point2D.Float dislodgedUnitOffset = null; private boolean supressPlacementErrors = false; private SymbolPack sp = null; private World w; /* * Display Props is also used for order info. Except that * there are 2 keys (element, attribute), and no default value, since * all values are required. * * */ /** * Create a MapMetadata Object, by parsing the * SVG placement id group metadata * <p> * If supressPlacementErrors are suppressed, using the * appropriate boolean flag, only valid parsed placement * data will be used. Data which is missing, or invalid, * will be replaced with 0 values. Display and Order Drawing * metadata parsing is unaffected by this flag. * */ public MapMetadata(SVGDocument doc,World w, SymbolPack sp, boolean supressPlacementErrors) throws MapException { //this.mp = mp; this.w = w; this.sp = sp; this.supressPlacementErrors = supressPlacementErrors; infoMap = new HashMap(113); displayProps = new HashMap(47); Element root = doc.getRootElement(); parseDisplayMetadata(root); parsePlacements(root); parseOrderDrawingData(root); }// MapMetadata() /** Clean up any resources used by this object */ public void close() { //mp.getClientFrame().fireMMDReady(null); // VERY important infoMap.clear(); displayProps.clear(); }// close() /** Get an InfoEntry */ public InfoEntry getInfoEntry(Province key) { return (InfoEntry) infoMap.get(key); }// getInfoEntry() /** * Set an InfoEntry * <p> * This generally should NOT be used. It is intended for map editors * and what not. */ public void setInfoEntry(Province key, InfoEntry value) { infoMap.put(key, value); }// setInfoEntry() /** Convenience method: get Unit placement point for this Province */ public Point2D.Float getUnitPt(Province key, Coast coast) { InfoEntry entry = getInfoEntry(key); if (entry == null){ System.out.println(key.hashCode()); System.out.println(key); System.out.println("Available:"); for (Object k: infoMap.keySet()){ System.out.println(k + ":"+k.hashCode()); } return null; } return entry.getUnitPt(coast); } /** Convenience method: get Dislodged Unit placement point for this Province */ public Point2D.Float getDislodgedUnitPt(Province key, Coast coast) { return getInfoEntry(key).getDislodgedUnitPt(coast); } /** Convenience method: get Supply Center placement point for this Province */ public Point2D.Float getSCPt(Province key) { return getInfoEntry(key).getSCPt(); } /** * Stores coordinate information for Symbol placement within a province. * <p> * Rectangles are used; while the x,y position is most important, the width * and height information can be used to scale. */ public static class InfoEntry implements Serializable { /** * */ private static final long serialVersionUID = 8615206459571077626L; private final Point2D.Float unit; private final Point2D.Float dislodgedUnit; private final Point2D.Float sc; private Map unitCoasts; private Map dislodgedUnitCoasts; /** Create an InfoEntry object; if directional coasts, use setCoastMapings as well. */ public InfoEntry(Point2D.Float unit, Point2D.Float dislodgedUnit, Point2D.Float sc) { // safety-check if(unit == null || dislodgedUnit == null || sc == null) { throw new IllegalArgumentException(); } this.unit = makePt(unit); this.dislodgedUnit = makePt(dislodgedUnit); this.sc = makePt(sc); }// InfoEntry() /** Sets coast data maps for multi-coastal provinces; if not set, default placement data is used. */ public void setCoastMappings(Map unitCoasts, Map dislodgedUnitCoasts) { this.unitCoasts = unitCoasts; this.dislodgedUnitCoasts = dislodgedUnitCoasts; }// setCoastData() /** Adds data to coast mapping */ public void addCoastMapping(Coast coast, Point2D.Float unitPt, Point2D.Float dislodgedPt) { if(unitPt == null || dislodgedPt == null) { throw new IllegalArgumentException(); } if(unitCoasts == null) { unitCoasts = new HashMap(3); } if(dislodgedUnitCoasts == null) { dislodgedUnitCoasts = new HashMap(3); } unitCoasts.put(coast, unitPt); dislodgedUnitCoasts.put(coast, dislodgedPt); }// addCoastMapping() /** Location where units are placed */ public Point2D.Float getUnitPt(Coast coast) { if(unitCoasts == null) { return makePt(unit); } Point2D.Float pt = (Point2D.Float) unitCoasts.get(coast); return (pt == null) ? makePt(unit) : makePt(pt); }// getUnitPt() /** Location where dislodged units are placed */ public Point2D.Float getDislodgedUnitPt(Coast coast) { if(dislodgedUnitCoasts == null) { return makePt(dislodgedUnit); } Point2D.Float pt = (Point2D.Float) dislodgedUnitCoasts.get(coast); return (pt == null) ? makePt(dislodgedUnit) : makePt(pt); }// getDislodgedUnitPt() /** Location where supply centers are placed */ public Point2D.Float getSCPt() { return makePt(sc); } /** Makes a new point from an existing point, since Point2D objects are mutable. */ private final Point2D.Float makePt(Point2D.Float p) { return new Point2D.Float(p.x, p.y); }// makePt() }// nested class InfoEntry /** * Gets the SymbolSize for a symbol; null if symbol * is not recognized. Case sensitive. */ public SymbolSize getSymbolSize(String symbolName) { StringBuffer sbKey = new StringBuffer(64); sbKey.append( EL_SYMBOLSIZE ); sbKey.append( symbolName ); return (SymbolSize) displayProps.get(sbKey.toString()); }// getSymbolSize() /** Gets a float metadata value */ public float getDisplayParamFloat(String key, float defaultValue) { String value = (String) displayProps.get(key); if(value != null) { try { return Float.parseFloat(value.trim()); } catch(NumberFormatException e) { } } return defaultValue; }// getDisplayParamFloat() /** Gets an int metadata value */ public int getDisplayParamInt(String key, int defaultValue) { String value = (String) displayProps.get(key); if(value != null) { try { return Integer.parseInt(value.trim()); } catch(NumberFormatException e) { } } return defaultValue; }// getDisplayParamInt() /** Gets a boolean display metadata value */ public boolean getDisplayParamBoolean(String key, boolean defaultValue) { String value = (String) displayProps.get(key); if(value != null) { value = value.trim(); if("false".equalsIgnoreCase(value)) { return false; } else if("true".equalsIgnoreCase(value)) { return true; } } return defaultValue; }// getDisplayParamBoolean() /** * Gets the String version of a parameter. * Throws an IllegalArgumentException if parameter is not found; * will never return null. * <p> * Example usage: * String id = getOrderParamString(EL_BUILD, ATT_FILTERID); */ public String getOrderParamString(String orderElement, String attribute) { return (String) getOrderParam(orderElement, attribute); }// getOrderParamString() /** * Gets the float version of a parameter. * Throws an IllegalArgumentException if parameter is not found. */ public float getOrderParamFloat(String orderElement, String attribute) { return ((Float) getOrderParam(orderElement, attribute)).floatValue(); }// getOrderParamFloat() /** * Gets the adjusted radius for an order. This is a convenience method. * It is equivalent to calling <code>getOrderParamFloat(<order>, ATT_DELTA_RADIUS)</code> and * passing that value into <code>SymbolSize.getRadius()</code>. * <p> * symbolName is typically a Unit symbol name (e.g., "Wing", "Army", "DislodgedFleet", etc.).<br> * orderElement is a order element constant (e.g., EL_HOLD, EL_MOVE).<br> */ public float getOrderRadius(String orderElement, String symbolName) { final float deltaRadius = ((Float) getOrderParam(orderElement, ATT_DELTA_RADIUS)).floatValue(); return getSymbolSize(symbolName).getRadius(deltaRadius); }// getOrderRadius() /** * Gets the float array version of a parameter. * Throws an IllegalArgumentException if parameter is not found. */ public float[] getOrderParamFloatArray(String orderElement, String attribute) { return ((float[]) getOrderParam(orderElement, attribute)); }// getOrderParamFloat() /** * Helper method for getting order drawing parameters. NEVER RETURNS NULL. * <p> * For filter parameter, if no filter is supplied, returns an empty string. */ private Object getOrderParam(String el, String att) { StringBuffer sb = new StringBuffer(64); sb.append(el); sb.append(att); Object value = displayProps.get(sb.toString()); if(value == null) { throw new IllegalArgumentException("order parameters: key/attribute: "+el+","+att+" not found!"); } return value; }// getOrderParam() /** Get power color (the color of orders associated w/a power */ public String getPowerColor(Power power) { if(power == null) { throw new IllegalArgumentException("null power"); } return (String) displayProps.get(power); }// getPowerColor() /** * Gets a single element from a root-level element; returns null if no tag exists; returns last tag if * multiple tags exist. This will go multiple levels deep!!!! Note that. * <p> * the root element's namespace is used automatically. */ private Element getElement(Element root, String elementName) { NodeList nl = root.getElementsByTagNameNS(root.getNamespaceURI(), elementName); if(nl.getLength() == 0) { return null; } return (Element) nl.item(nl.getLength() - 1); }// getElement() /** * Parses an element that has 2 attributes, (x and y) and returns a Point2D. * This only looks ONE level deep. If element is not found, (0,0) is returned. * parent namespace is assumed. An Exception is thrown if bad coordinate values * are passed. */ private Point2D.Float parseCoordElement(Element root, String elementName) throws MapException { Node child = root.getFirstChild(); while(child != null) { if(elementName.equals(child.getLocalName()) && child.getNodeType() == Node.ELEMENT_NODE) { try { Element el = (Element) child; float x = Float.parseFloat(el.getAttribute(ATT_X).trim()); float y = Float.parseFloat(el.getAttribute(ATT_Y).trim()); return new Point2D.Float(x, y); } catch(NumberFormatException e) { throw new MapException("Bad coordinate value for element "+elementName+"; must be a decimal value. "+e.getMessage()); } } child = child.getNextSibling(); } return POINT_ZERO; }// parseCoordElement() /** Converts PROVINCE element & sub-element data to parsed placement information. */ private void parsePlacements(Element root) throws MapException { // get the PROVINCE_DATA element, to see if a ATT_DISLODGED_OFFSET // was specified. If so, we will use that offset. NodeList nl = root.getElementsByTagNameNS(JDIP_NAMESPACE, EL_PROVINCE_DATA); if(nl.getLength() != 1) { throw new MapException("Missing "+EL_PROVINCE_DATA+" element."); } Element elProvData = (Element) nl.item(0); if(!"".equals(elProvData.getAttribute(ATT_DISLODGED_OFFSET))) { dislodgedUnitOffset = parseCoord( EL_PROVINCE_DATA, ATT_DISLODGED_OFFSET, elProvData.getAttribute(ATT_DISLODGED_OFFSET) ); } // now process province elements // nl = root.getElementsByTagNameNS(JDIP_NAMESPACE, EL_PROVINCE); for(int i=0; i<nl.getLength(); i++) { try { Element elProvince = (Element) nl.item(i); String provinceName = elProvince.getAttribute(ATT_NAME); // Strip of coast text, and lookup Province Province province = w.getMap().getProvince(Coast.getProvinceName(provinceName)); if(province == null) { throw new MapException("SVG error in PROVINCE tag: Province name=\""+provinceName+"\" not recognized."); } // parse coast; if no directional coast is present, the coast will be // UNDEFINED and isCoastSpecified == false Coast coast = Coast.parse(provinceName); boolean isCoastSpecified = coast.isDirectional(); // parse coordinate data elements Point2D.Float unit = parseCoordElement(elProvince, EL_UNIT); Point2D.Float dislodged = parseCoordElement(elProvince, EL_DISLODGED_UNIT); Point2D.Float sc = parseCoordElement(elProvince, EL_SC); // fix unset dislodged units to use dislodgedUnitOffset, if present. if(dislodgedUnitOffset != null && POINT_ZERO.equals(dislodged)) { dislodged.x = unit.x + dislodgedUnitOffset.x; dislodged.y = unit.y + dislodgedUnitOffset.y; } // create InfoMap if(!isCoastSpecified) { InfoEntry ie = new InfoEntry(unit, dislodged, sc); infoMap.put(province, ie); } else { InfoEntry ie = (InfoEntry) infoMap.get(province); if(ie == null) { throw new MapException("Error in PROVINCE: "+provinceName+"; province metadata with coast must succeed those without; e.g., stp-sc must come AFTER stp"); } ie.addCoastMapping(coast, unit, dislodged); } } catch(MapException me) { // do not throw an exception if we are suppressing errors. if(!supressPlacementErrors) { throw me; } } } // verify: make sure each province has at least one InfoEntry. // if we are supressing errors, fill in with empty data. Province[] provinces = w.getMap().getProvinces(); for(int i=0; i<provinces.length; i++) { if(infoMap.get(provinces[i]) == null) { if(supressPlacementErrors) { InfoEntry ie = new InfoEntry( new Point2D.Float(0,0), new Point2D.Float(0,0), new Point2D.Float(0,0) ); infoMap.put(provinces[i], ie); Log.println("MMD: added empty entry for province ", provinces[i]); } else { throw new MapException("Missing PROVINCE placement information for province: "+provinces[i]); } } } }// parsePlacements() /** * Parses the Display metadata XML elements. * * root: the top-level tag from which to start parsing. We put these in a * HashMap. the HashMap is indexed by ATTRIBUTE (ATT_*). */ private void parseDisplayMetadata(Element root) throws MapException { NodeList nl = root.getElementsByTagNameNS(JDIP_NAMESPACE, EL_DISPLAY); if(nl.getLength() != 1) { throw new MapException("There are "+nl.getLength()+" DISPLAY elements in the SVG file; a single DISPLAY group is required."); } Element displayRoot = (Element) nl.item(0); Element el = getElement(displayRoot, EL_ZOOM); if(el != null) { displayProps.put(ATT_ZOOM_MIN, el.getAttribute(ATT_ZOOM_MIN).trim()); displayProps.put(ATT_ZOOM_MAX, el.getAttribute(ATT_ZOOM_MAX).trim()); displayProps.put(ATT_ZOOM_FACTOR, el.getAttribute(ATT_ZOOM_FACTOR).trim()); } el = getElement(displayRoot, EL_LABELS); if(el != null) { displayProps.put(ATT_LABELS_BRIEF, el.getAttribute(ATT_LABELS_BRIEF).trim()); displayProps.put(ATT_LABELS_FULL, el.getAttribute(ATT_LABELS_FULL).trim()); } }// parseDisplayMetadata() /** * Parses the ORDERDRAWING metadata XML subelements. * * root: the top-level tag from which to start parsing. We put these in a * HashMap. the HashMap is indexed by ELEMENT+ATTRIBUTE */ private void parseOrderDrawingData(Element root) throws MapException { // get ORDERDRAWING element NodeList nl = root.getElementsByTagNameNS(JDIP_NAMESPACE, EL_ORDERDRAWING); if(nl.getLength() != 1) { throw new MapException("There are "+nl.getLength()+" "+EL_ORDERDRAWING+" elements in the SVG file; a single element is required."); } Element orderRoot = (Element) nl.item(0); // parse SYMBOLSIZE info NodeList ssnl = orderRoot.getElementsByTagNameNS(JDIP_NAMESPACE, EL_SYMBOLSIZE); for(int i=0; i<ssnl.getLength(); i++) { parseAndAddSymbolSize( (Element) ssnl.item(i) ); } // HOLD Element el = getElement(orderRoot, EL_HOLD); checkElement(EL_HOLD, el); putOrderParam(EL_HOLD, ATT_DELTA_RADIUS, parseFloat(EL_HOLD, ATT_DELTA_RADIUS, el.getAttribute(ATT_DELTA_RADIUS))); putOrderParam(EL_HOLD, ATT_STROKESTYLE, el.getAttribute(ATT_STROKESTYLE).trim()); putOrderParam(EL_HOLD, ATT_HILIGHT_OFFSET, parseFloat(EL_HOLD, ATT_HILIGHT_OFFSET, el.getAttribute(ATT_HILIGHT_OFFSET))); putOrderParam(EL_HOLD, ATT_HILIGHT_CLASS, el.getAttribute(ATT_HILIGHT_CLASS).trim()); putOrderParam(EL_HOLD, ATT_WIDTHS, parseFloatArray(EL_HOLD, ATT_WIDTHS, el.getAttribute(ATT_WIDTHS))); putOrderParam(EL_HOLD, ATT_SHADOW_WIDTHS, parseFloatArray(EL_HOLD, ATT_SHADOW_WIDTHS, el.getAttribute(ATT_SHADOW_WIDTHS))); putOptionalOrderParam(EL_HOLD, ATT_FILTERID, el.getAttribute(ATT_FILTERID).trim()); // DISBAND el = getElement(orderRoot, EL_DISBAND); checkElement(EL_DISBAND, el); putOrderParam(EL_DISBAND, ATT_DELTA_RADIUS, parseFloat(EL_DISBAND, ATT_DELTA_RADIUS, el.getAttribute(ATT_DELTA_RADIUS))); // REMOVE el = getElement(orderRoot, EL_REMOVE); checkElement(EL_REMOVE, el); putOrderParam(EL_REMOVE, ATT_DELTA_RADIUS, parseFloat(EL_REMOVE, ATT_DELTA_RADIUS, el.getAttribute(ATT_DELTA_RADIUS))); // BUILD el = getElement(orderRoot, EL_BUILD); checkElement(EL_BUILD, el); putOrderParam(EL_BUILD, ATT_DELTA_RADIUS, parseFloat(EL_BUILD, ATT_DELTA_RADIUS, el.getAttribute(ATT_DELTA_RADIUS))); // WAIVE el = getElement(orderRoot, EL_WAIVE); checkElement(EL_WAIVE, el); putOrderParam(EL_WAIVE, ATT_DELTA_RADIUS, parseFloat(EL_WAIVE, ATT_DELTA_RADIUS, el.getAttribute(ATT_DELTA_RADIUS))); // MOVE el = getElement(orderRoot, EL_MOVE); checkElement(EL_MOVE, el); putOrderParam(EL_MOVE, ATT_DELTA_RADIUS, parseFloat(EL_MOVE, ATT_DELTA_RADIUS, el.getAttribute(ATT_DELTA_RADIUS))); putOrderParam(EL_MOVE, ATT_STROKESTYLE, el.getAttribute(ATT_STROKESTYLE).trim()); putOrderParam(EL_MOVE, ATT_MARKERID, el.getAttribute(ATT_MARKERID).trim()); putOrderParam(EL_MOVE, ATT_HILIGHT_OFFSET, parseFloat(EL_MOVE, ATT_HILIGHT_OFFSET, el.getAttribute(ATT_HILIGHT_OFFSET))); putOrderParam(EL_MOVE, ATT_HILIGHT_CLASS, el.getAttribute(ATT_HILIGHT_CLASS).trim()); putOrderParam(EL_MOVE, ATT_WIDTHS, parseFloatArray(EL_MOVE, ATT_WIDTHS, el.getAttribute(ATT_WIDTHS))); putOrderParam(EL_MOVE, ATT_SHADOW_WIDTHS, parseFloatArray(EL_MOVE, ATT_SHADOW_WIDTHS, el.getAttribute(ATT_SHADOW_WIDTHS))); putOptionalOrderParam(EL_MOVE, ATT_FILTERID, el.getAttribute(ATT_FILTERID).trim()); // RETREAT el = getElement(orderRoot, EL_RETREAT); checkElement(EL_RETREAT, el); putOrderParam(EL_RETREAT, ATT_DELTA_RADIUS, parseFloat(EL_RETREAT, ATT_DELTA_RADIUS, el.getAttribute(ATT_DELTA_RADIUS))); putOrderParam(EL_RETREAT, ATT_STROKESTYLE, el.getAttribute(ATT_STROKESTYLE).trim()); putOrderParam(EL_RETREAT, ATT_MARKERID, el.getAttribute(ATT_MARKERID).trim()); putOrderParam(EL_RETREAT, ATT_HILIGHT_OFFSET, parseFloat(EL_RETREAT, ATT_HILIGHT_OFFSET, el.getAttribute(ATT_HILIGHT_OFFSET))); putOrderParam(EL_RETREAT, ATT_HILIGHT_CLASS, el.getAttribute(ATT_HILIGHT_CLASS).trim()); putOptionalOrderParam(EL_RETREAT, ATT_FILTERID, el.getAttribute(ATT_FILTERID).trim()); // SUPPORT el = getElement(orderRoot, EL_SUPPORT); checkElement(EL_SUPPORT, el); putOrderParam(EL_SUPPORT, ATT_DELTA_RADIUS, parseFloat(EL_SUPPORT, ATT_DELTA_RADIUS, el.getAttribute(ATT_DELTA_RADIUS))); putOrderParam(EL_SUPPORT, ATT_STROKESTYLE, el.getAttribute(ATT_STROKESTYLE).trim()); putOrderParam(EL_SUPPORT, ATT_MARKERID, el.getAttribute(ATT_MARKERID).trim()); putOrderParam(EL_SUPPORT, ATT_HILIGHT_OFFSET, parseFloat(EL_SUPPORT, ATT_HILIGHT_OFFSET, el.getAttribute(ATT_HILIGHT_OFFSET))); putOrderParam(EL_SUPPORT, ATT_HILIGHT_CLASS, el.getAttribute(ATT_HILIGHT_CLASS).trim()); putOptionalOrderParam(EL_SUPPORT, ATT_FILTERID, el.getAttribute(ATT_FILTERID).trim()); // CONVOY el = getElement(orderRoot, EL_CONVOY); checkElement(EL_CONVOY, el); putOrderParam(EL_CONVOY, ATT_DELTA_RADIUS, parseFloat(EL_CONVOY, ATT_DELTA_RADIUS, el.getAttribute(ATT_DELTA_RADIUS))); putOrderParam(EL_CONVOY, ATT_STROKESTYLE, el.getAttribute(ATT_STROKESTYLE).trim()); putOrderParam(EL_CONVOY, ATT_MARKERID, el.getAttribute(ATT_MARKERID).trim()); putOrderParam(EL_CONVOY, ATT_HILIGHT_OFFSET, parseFloat(EL_CONVOY, ATT_HILIGHT_OFFSET, el.getAttribute(ATT_HILIGHT_OFFSET))); putOrderParam(EL_CONVOY, ATT_HILIGHT_CLASS, el.getAttribute(ATT_HILIGHT_CLASS).trim()); putOptionalOrderParam(EL_CONVOY, ATT_FILTERID, el.getAttribute(ATT_FILTERID).trim()); // POWERCOLOR(S) el = getElement(orderRoot, EL_POWERCOLORS); checkElement(EL_POWERCOLORS, el); dip.world.Map map = w.getMap(); nl = el.getChildNodes(); for(int i=0; i<nl.getLength(); i++) { Node node = nl.item(i); if(node.getNodeType() == Node.ELEMENT_NODE) { el = (Element) nl.item(i); Power power = map.getPower( el.getAttribute(ATT_POWER).trim() ); if(power != null) { String color = el.getAttribute(ATT_COLOR).trim(); if(color == null) { throw new MapException(EL_POWERCOLOR+" color \""+el.getAttribute(ATT_COLOR)+"\" not specified."); } displayProps.put(power, color); } } } // verify all powers have a color Power[] powers = map.getPowers(); for(int i=0; i<powers.length; i++) { if(displayProps.get(powers[i]) == null) { throw new MapException(EL_POWERCOLORS+": no color defined for power "+powers[i]); } } }// parseOrderDrawingData() /** Check element method; checks if element is null. */ private void checkElement(String name, Element el) throws MapException { if(el == null) { throw new MapException("Missing required element: "+name); } }// checkElement() /** Helper method: set an order parameter */ private void putOrderParam(String el, String att, Object value) throws MapException { if(el == null || att == null) { throw new IllegalArgumentException(); } if(value == null || "".equals(value)) { throw new MapException(el+" attribute "+att+" is missing!"); } StringBuffer sb = new StringBuffer(64); sb.append(el); sb.append(att); displayProps.put(sb.toString(), value); }// putOrderParam() /** Helper method: set an order parameter, but if it doesn't exist, don't complain. */ private void putOptionalOrderParam(String el, String att, Object value) throws MapException { if(el == null || att == null) { throw new IllegalArgumentException(); } StringBuffer sb = new StringBuffer(64); sb.append(el); sb.append(att); displayProps.put(sb.toString(), value); }// putOptionalOrderParam() /** * Parses a SYMBOLSIZE element and adds it to the display properties * HashMap, with a prefix of SYMBOLSIZE. Symbolsizes are not allowed * to contain "%" as a unit type. The Value inserted is a SymbolSize * object (contains two strings: width, height). * <p> * Furthermore, this method also ensures that the given symbol is * in fact defined in the SymbolPack, and scales it appropriately. */ private void parseAndAddSymbolSize(Element el) throws MapException { String name = el.getAttribute(ATT_NAME).trim(); String w = el.getAttribute(ATT_WIDTH).trim(); String h = el.getAttribute(ATT_HEIGHT).trim(); Symbol symbol = sp.getSymbol(name); if(symbol == null) { throw new MapException("Element "+el.getTagName()+" symbol named \""+name+"\" not found in symbol pack! Case sensitive."); } StringBuffer sbKey = new StringBuffer(64); sbKey.append( EL_SYMBOLSIZE ); sbKey.append( name ); displayProps.put(sbKey.toString(), new SymbolSize(w,h, symbol.getScale(), el)); }// parseAndAddSymbolSize() /** * Textual width/height of a symbol, as valid SVG dimensions. */ public static class SymbolSize implements Serializable { /** * */ private static final long serialVersionUID = -8550000985756317593L; private final String w; private final String h; private final String r; private final float rFloat; private final String units; /** * Create a SymbolSize element * <p> * Throws an exception if the value cannot be parsed, contains * a "%" sign (cannot be relative), or, the units (if present) * are different between the width and height attributes (e.g., * "px" and "cm"). And numbers cannot be negative, either. */ public SymbolSize(String w, String h, float scale, Element element) throws MapException { Object[] tmp = makeValues(w, h, scale, element); this.w = (String) tmp[0]; this.h = (String) tmp[1]; this.r = (String) tmp[2]; this.rFloat = ((Float) tmp[3]).floatValue(); this.units = (String) tmp[4]; }// SymbolSize() /** Get the Width */ public String getWidth() { return w; } /** Get the Height */ public String getHeight() { return h; } /** * Get the Radius of a circle centered over the width/height that * will circumscribe width/height. */ public String getRadius() { return r; } /** Gets the Units (never null, but may be empty). */ public String getUnits() { return units; } /** * Computes a Radius given the known radius and a * delta (smaller/larger). Delta and Radius are * assumed to have the same units. */ public float getRadius(float delta) { return (rFloat + delta); }// getRadius() /** * Returns the Radius as a String (formatted float + units) */ public String getRadiusString(float delta) { return (SVGUtils.floatToString(rFloat + delta) + getUnits()); }// getRadiusString() /** * Makes all values. Returns an array of length 3; * 0:width, 1:height, 2:radius, 3:radius (as a float), * 4: units ("" if none) * */ private Object[] makeValues(String w, String h, float scale, Element el) throws MapException { Object[] obj = parseDim(w, el, ATT_WIDTH); float width = ((Float) obj[0]).floatValue(); String widthUnits = (String) obj[1]; obj = parseDim(h, el, ATT_HEIGHT); float height = ((Float) obj[0]).floatValue(); String heightUnits = (String) obj[1]; // check that units are identical if(!widthUnits.equals(heightUnits)) { throw new MapException("Element "+el.getTagName()+ " width and height attributes must both have the same (or no) unit specifiers."); } // scale width *= scale; height *= scale; // get radius (1/2 of diagonal) float radius = (float) (Math.sqrt((width*width) + (height*height)) / 2.0); // return strings Object[] values = new Object[5]; values[0] = (SVGUtils.floatToString(width) + widthUnits); values[1] = (SVGUtils.floatToString(height) + widthUnits); values[2] = (SVGUtils.floatToString(radius) + widthUnits); values[3] = new Float(radius); values[4] = widthUnits; return values; }// makeValues() /** * Parse a Dimension into a Float and Unit specifier (empty if not present); * Object[0] : Float, Object[1] : String. Neither value will be null. */ private Object[] parseDim(String in, Element el, String attributeName) throws MapException { if(in.length() == 0 || in.indexOf('%') >= 0 || in.indexOf('-') >= 0) { throw new MapException("Element "+el.getTagName()+" attribute "+attributeName+" cannot have a % (relative size) in width or height attributes, or be zero or negative."); } // otherwise, first extract the numeric part (digits, decimal); // then extract (if any) the unit part (save for later) // int idx = 0; while(idx < in.length()) { char c = in.charAt(idx); if(!Character.isDigit(c) && c != '.') { break; } idx++; } String num = in.substring(0, idx); String units = in.substring(idx); Object[] obj = new Object[2]; obj[0] = parseFloat(el.getTagName(), attributeName, num); obj[1] = units; return obj; }// parseDim() }// nested class SymbolSize /** Parse a float; if fails, returns a MapException */ private static Float parseFloat(String el, String att, String value) throws MapException { try { return new Float(value.trim()); } catch(NumberFormatException e) { throw new MapException(el+" attribute "+att+" value \""+value+"\" is not specified or not a valid floating point value."); } }// parseFloat() /** Parse a coordinate: e.g. n n or n,n format. */ private Point2D.Float parseCoord(String el, String att, String in) throws MapException { try { Point2D.Float val = new Point2D.Float(); StringTokenizer st = new StringTokenizer(in, ", )(;"); if(st.hasMoreTokens()) { val.x = ( Float.parseFloat(st.nextToken().trim()) ); } else { throw new NumberFormatException(); } if(st.hasMoreTokens()) { val.y = ( Float.parseFloat(st.nextToken().trim()) ); } else { throw new NumberFormatException(); } return val; } catch(NumberFormatException e) { } throw new MapException(el+" attribute "+att+" value \""+in+"\" is not specified or not a valid floating point value pair. (e.g., \"3.0, 2.01\")"); }// parseCoord() /** Parse a float array; if fails, returns a MapException */ private float[] parseFloatArray(String el, String att, String value) throws MapException { StringTokenizer st = new StringTokenizer(value,",; \n\r\t"); final float[] arr = new float[st.countTokens()]; if(arr.length == 0) { throw new MapException(el+" attribute "+att+" value \""+value+"\" must contain at least one positive floating point value."); } int count = 0; while(st.hasMoreTokens()) { try { float fVal = Float.parseFloat(st.nextToken().trim()); if(fVal <= 0.0f) { throw new MapException(el+" attribute "+att+" value \""+value+"\" only values > 0 are valid."); } arr[count] = fVal; count++; } catch(NumberFormatException e) { throw new MapException(el+" attribute "+att+" value \""+value+"\" is not a valid postive floating point value."); } } return arr; }// parseFloatArray() }// class MapMetadata