package com.isti.xmax.common; import java.awt.Color; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileWriter; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.util.Date; import java.util.List; import org.apache.log4j.Logger; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.jdom.Document; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.input.SAXBuilder; import org.jdom.output.XMLOutputter; import org.jdom.xpath.XPath; import org.xml.sax.Attributes; import org.xml.sax.Locator; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import org.xml.sax.helpers.DefaultHandler; import com.isti.traceview.common.AbstractEvent; import com.isti.traceview.common.IEvent; import com.isti.traceview.common.Station; import com.isti.traceview.common.TimeInterval; import com.isti.traceview.data.DataModule; import com.isti.traceview.data.PlotDataProvider; import com.isti.xmax.XMAX; import com.isti.xmax.XMAXException; import com.isti.xmax.XMAXconfiguration; /** * Pick mark, manually entered time label which can be shown on the trace and stored in special xml * database. * * @author Max Kokoulin */ public class Pick extends AbstractEvent implements IEvent { private static final Logger logger = Logger.getLogger(Pick.class); private static String thisSessionStart = TimeInterval.formatDate(new Date(), TimeInterval.DateFormatType.DATE_FORMAT_MIDDLE); private static Pick lastPick = null; private String sessionLabel = null; private transient PlotDataProvider channel = null; private Pick previousPick = null; private Pick nextPick = null; public enum PickType { P, S, PS, SP } private PickType pickType; /** * Default constructor */ public Pick(Date time) { this(time, null, null); } /** * Constructor to create pick from current session * * * @param time * pick time * @param channel * trace to attach pick */ public Pick(Date time, PlotDataProvider channel) { this(time, channel, thisSessionStart); addToXML(); } /** * Constructor to create old pick from closed session * * @param time * pick time * @param channel * trace to attach pick * @param sessionLabel * session label */ public Pick(Date time, PlotDataProvider channel, String sessionLabel) { super(time, 0); this.channel = channel; this.sessionLabel = sessionLabel; if (channel != null && sessionLabel != null) { this.previousPick = lastPick; if (lastPick != null) { lastPick.nextPick = this; } lastPick = this; logger.debug("Pick created: channel " + channel + ", session " + sessionLabel + ", time " + TimeInterval.formatDate(time, TimeInterval.DateFormatType.DATE_FORMAT_NORMAL) + "(" + time.getTime() + ")"); } } @Override public String getType() { return "PICK"; } public Color getColor() { return Color.BLUE; } /** * Getter of the property <tt>pickType</tt> * * @return type of pick. */ public PickType getPickType() { return pickType; } /** * Setter of the property <tt>pickType</tt> * * @param pickType * type of pick */ public void setPickType(PickType pickType) { this.pickType = pickType; } public PlotDataProvider getChannel() { return channel; } public void setChannel(PlotDataProvider channel) { this.channel = channel; } public String getSessionLabel() { return sessionLabel; } /** * @return name of picks database file for current session * @throws IOException if there is poblems creating a new file */ public File getSessionFile() throws IOException { String fName = sessionLabel.replace(",", "_").replace(":", ""); File ret = new File(XMAXconfiguration.getInstance().getPickPath() + File.separator + fName + ".xml"); RandomAccessFile raf = null; if (!ret.exists()) { // Create file here ret.createNewFile(); raf = new RandomAccessFile(ret, "rw"); raf.writeBytes("<?xml version=\"1.0\" ?>\n<Session time = \"" + sessionLabel + "\">\n</Session>"); raf.close(); } return ret; } /** * Removes this pick from trace */ public void detach() { if (channel != null) { channel.removeEvent(this); } channel = null; if (previousPick != null) { previousPick.nextPick = this.nextPick; } if (nextPick != null) { nextPick.previousPick = this.previousPick; } deleteFromXML(); } /** * Removes this pick from xml database */ private void deleteFromXML() { long sleep = 1; long maxsleep = 4096; boolean success = false; try { SAXBuilder builder = new SAXBuilder(); Document doc = builder.build(getSessionFile()); //Element session = doc.getRootElement(); String query = "//Pick[@time='" + TimeInterval.formatDate(getStartTime(), TimeInterval.DateFormatType.DATE_FORMAT_NORMAL) + "']"; XPath selectPickByTime = XPath.newInstance(query); List<?> picks = selectPickByTime.selectNodes(doc); for (Object o: picks) { Element pick = (Element) o; pick.detach(); } XMLOutputter outputter = new XMLOutputter(); FileWriter writer = null; // ugly hack to avoid Sun's error with unpredictable time of file's mapped buffer // releasing // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4724038 while (!success) { try { writer = new FileWriter(getSessionFile()); success = true; } catch (java.io.IOException e) { try { Thread.sleep(sleep); } catch (Exception ex) { logger.error("Exception:", ex); } sleep *= 2; if (sleep > maxsleep) { throw e; } System.gc(); System.runFinalization(); } finally{ //Close the writer before trying to instantiate another in next loop iteration. if(writer != null) writer.close(); } } outputter.output(doc, writer); writer.close(); } catch (JDOMException e) { // TODO Auto-generated catch block logger.error("JDOMException:", e); } catch (IOException e) { // TODO Auto-generated catch block logger.error("IOException:", e); } } /** * Adds this pick to xml database */ private void addToXML() { RandomAccessFile raf = null; try { File file = getSessionFile(); FileInputStream fis = new FileInputStream(file); FileChannel fch = fis.getChannel(); fis.close(); MappedByteBuffer mbf = fch.map(FileChannel.MapMode.READ_ONLY, 0, fch.size()); byte[] barray = new byte[(int) (fch.size())]; mbf.get(barray); String lines = new String(barray); // one big string int insertPlace = lines.lastIndexOf("\n</Session>"); String insertText = "\n <Pick network=\"" + getChannel().getNetworkName() + "\" station=\"" + getChannel().getStation().getName() + "\" location=\"" + getChannel().getLocationName() + "\" channel=\"" + getChannel().getChannelName() + "\" time=\"" + TimeInterval.formatDate(getStartTime(), TimeInterval.DateFormatType.DATE_FORMAT_NORMAL) + "\"/>"; raf = new RandomAccessFile(file, "rw"); raf.seek(insertPlace); raf.writeBytes(insertText + "\n</Session>"); raf.setLength(insertPlace + insertText.length() + 11); } catch (FileNotFoundException e) { // TODO Auto-generated catch block logger.error("FileNotFoundException:", e); } catch (IOException e) { // TODO Auto-generated catch block logger.error("IOException:", e); } finally { try { raf.close(); } catch (Exception e) { // do nothing logger.error("Exception:", e); } } } /** * Deletes last added pick from xml database */ public static void geleteLastPick() { if (lastPick != null) { Pick lp = lastPick.previousPick; lastPick.detach(); lastPick = lp; } } /** * Loads all picks from xml database * * @throws XMAXException if the Pick directory is not a directory */ public static void loadPicks() throws XMAXException { File[] dir; File f = new File(XMAXconfiguration.getInstance().getPickPath()); if (f.isDirectory()) { dir = f.listFiles(); if (dir.length > 0) { for (int i = 0; i < dir.length; i++) { if (!dir[i].isDirectory()) { try { SAXParserFactory spf = SAXParserFactory.newInstance(); SAXParser sp = spf.newSAXParser(); sp.parse(dir[i], new SAXHandler()); } catch (ParserConfigurationException e) { // TODO Auto-generated catch block logger.error("ParseConfigurationException:", e); } catch (SAXException e) { // TODO Auto-generated catch block logger.error("SAXException:", e); } catch (IOException e) { // TODO Auto-generated catch block logger.error("IOException:", e); } } } } } else { throw new XMAXException("Pick directory shouldn't be file"); } } } /** * SAX handler to parse xml picks database file */ class SAXHandler extends DefaultHandler { private static final Logger logger = Logger.getLogger(SAXHandler.class); private Locator locator = null; private String sessionLabel = ""; public SAXHandler() { super(); setDocumentLocator(locator); } public void startElement(String namespaceURI, String localName, String rawName, Attributes attrs) { String network = null; Station station = null; String location = null; String channel = null; Date time = null; if (rawName.equals("Pick")) { int len = attrs.getLength(); if (len != 5) error(new SAXParseException("Wrong Pick attributes count", locator)); for (int i = 0; i < len; i++) { String attrName = attrs.getQName(i); if (attrName.equals("time")) { time = TimeInterval.parseDate(attrs.getValue(i), TimeInterval.DateFormatType.DATE_FORMAT_NORMAL); } else if (attrName.equals("network")) { network = attrs.getValue(i); } else if (attrName.equals("station")) { station = DataModule.getStation(attrs.getValue(i)); } else if (attrName.equals("location")) { location = attrs.getValue(i); } else if (attrName.equals("channel")) { channel = attrs.getValue(i); } else { error(new SAXParseException("Wrong Pick attributes name: " + attrName, locator)); } } if (station != null) { PlotDataProvider ch = XMAX.getDataModule().getChannel(channel, station, network, location); if (ch != null && ch.getTimeRange().isContain(time)) { Pick pick = new Pick(time, ch, sessionLabel); ch.addEvent(pick); } } } else if (rawName.equals("Session")) { sessionLabel = attrs.getValue(0); } } public void endElement(String namespaceURI, String localName, String rawName) { } public void characters(char ch[], int start, int length) { } // // ErrorHandler methods // /** Warning. */ public void warning(SAXParseException ex) { logger.warn(getLocationString(ex) + ": ", ex); } /** Error. */ public void error(SAXParseException ex) { logger.error(getLocationString(ex) + ": ", ex); } /** Fatal error. */ public void fatalError(SAXParseException ex) { logger.error(getLocationString(ex) + ": ", ex); } /** Returns a string of the location. */ private String getLocationString(SAXParseException ex) { StringBuffer str = new StringBuffer(); String systemId = ex.getSystemId(); if (systemId != null) { int index = systemId.lastIndexOf('/'); if (index != -1) systemId = systemId.substring(index + 1); str.append(systemId); } str.append(':'); str.append(ex.getLineNumber()); str.append(':'); str.append(ex.getColumnNumber()); return str.toString(); } }