/* * Part of the CCNx Java Library. * * Copyright (C) 2008, 2009, 2011 Palo Alto Research Center, Inc. * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 2.1 * as published by the Free Software Foundation. * 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., 51 Franklin Street, * Fifth Floor, Boston, MA 02110-1301 USA. */ package org.ccnx.ccn.impl.encoding; import java.io.IOException; import java.nio.ByteBuffer; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.TimeZone; import java.util.logging.Level; import org.ccnx.ccn.impl.support.DataUtils; import org.ccnx.ccn.impl.support.Log; import org.ccnx.ccn.protocol.CCNTime; /** * A text-based XML codec. * * Close to standard text XML, though with limited support for things like namespaces. * This class contains utility functions used by TextXMLEncoder and TextXMLDecoder * as well as setup to use this codec with XMLCodecFactory. */ public class TextXMLCodec implements XMLCodec { public static final String CCN_NAMESPACE = "http://www.parc.com/ccn"; public static final String CCN_PREFIX = "ccn"; public static final String CODEC_NAME = "Text"; public static final String BINARY_ATTRIBUTE = "ccnbencoding"; public static final String BINARY_ATTRIBUTE_VALUE = "base64Binary"; /** * The name of this codec. Used to generate XMLEncoder and XMLDecoder instances with XMLCodecFactory. * @return the codec name. */ public static String codecName() { return CODEC_NAME; } protected static DateFormat canonicalWriteDateFormat = null; protected static DateFormat canonicalReadDateFormat = null; protected static final String PAD_STRING = "000000000"; protected static final int NANO_LENGTH = 9; static { canonicalWriteDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); /* new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.S'Z'"); // writing ns doesn't format leading 0's correctly */ canonicalWriteDateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); canonicalReadDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); // new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.S'Z'"); canonicalReadDateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); } /** * Encodes a binary element as base 64. * @param element the element data to encode. Needs to handle null and 0-length elements * @return the binary data base64 encoded into a String */ public static String encodeBinaryElement(byte [] element) { if ((null == element) || (0 == element.length)) return new String(""); return new String(DataUtils.base64Encode(element)); } /** * Encodes a binary element as base 64. * @param element the element data to encode. Needs to handle null and 0-length elements * @param offset the offset into element at which to start encoding * @param length how many bytes of element to encode * @return the binary data base64 encoded into a String */ public static String encodeBinaryElement(byte [] element, int offset, int length) { if ((null == element) || (0 == element.length)) return new String(""); ByteBuffer bbuf = ByteBuffer.wrap(element, offset, length); return new String(DataUtils.base64Encode(bbuf.array())); } /** * Decodes a base64-encoded binary element back into a byte array. * @param element base64-encoded element content * @return the decoded byte array * @throws IOException if element is not valid base64 */ public static byte [] decodeBinaryElement(String element) throws IOException { if ((null == element) || (0 == element.length())) return new byte[0]; return DataUtils.base64Decode(element.getBytes()); } /** * Encapsulate our timestamp formatting/parsing for consistency. Use a simple * standard format for outputing a quantized CCNTime. * @param dateTime the timestamp to encode * @return the formatted timestamp */ public static String formatDateTime(CCNTime dateTime) { // Handles nanoseconds String date = ((SimpleDateFormat)canonicalWriteDateFormat.clone()).format(dateTime); if (dateTime.getNanos() > 0) { String ns = String.format(".%09d", dateTime.getNanos()); // now need to truncate trailing 0's if (ns.endsWith("0")) { // we know this has at least 1 non-0 character before the last 0's int trailerEnd = ns.length()-2; while ((ns.charAt(trailerEnd) == '0') && (trailerEnd > 0)) { --trailerEnd; } ns = ns.substring(0,trailerEnd+1); } date = date.replace("Z", ns) + "Z"; } //Library.finest("Timestamp: " + dateTime + " formatted timestamp: " + date); return date; } /** * Encapsulate our timestamp formatting/parsing for consistency. Use a simple * standard format for outputing a quantized CCNTime. * @param strDateTime the string-encoded timestamp * @return the parsed timestamp as a CCNTime */ public static CCNTime parseDateTime(String strDateTime) throws ParseException { // no . but has the Z if (strDateTime.indexOf('.') < 0) { Date noNsDate = ((SimpleDateFormat)canonicalWriteDateFormat.clone()).parse(strDateTime); return new CCNTime(noNsDate); } // Split on the . String [] dateParts = strDateTime.split("\\."); // Not sure whether we really need the clone here, but we're running into some // odd parsing behavior... Date thisDate = ((SimpleDateFormat)canonicalReadDateFormat.clone()).parse(dateParts[0]); CCNTime ts = new CCNTime(thisDate); // Deal with nanos. Parser ignores them, so don't have to pull them out. int index = strDateTime.indexOf('.'); int nanos = 0; if (index >= 0) { try { String nanostr = dateParts[1].substring(0, dateParts[1].length()-1); // remove trailing Z nanostr = (nanostr.length() < NANO_LENGTH) ? (nanostr + PAD_STRING.substring(0, (NANO_LENGTH - nanostr.length()))) : (nanostr); nanos = Integer.valueOf(nanostr.toString()); ts.setNanos(nanos); if (Log.isLoggable(Log.FAC_ENCODING, Level.FINEST)) Log.finest(Log.FAC_ENCODING, "Nanostr: " + nanostr + " originally: " + dateParts[1] + " nanos: " + nanos + " pre-nano ts parse: " + ts); } catch (NumberFormatException nfe) { Log.info("Exception in parsing nanoseconds from time: " + strDateTime); } } //Library.finest("Parsed timestamp: " + ts + " from string: " + strDateTime); return ts; } }