package open.dolphin.converter; import java.io.*; import java.lang.reflect.Method; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import javax.mail.MessagingException; import javax.mail.internet.MimeUtility; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; /** * * @author Kazushi Minagawa, Digital Globe, Inc. */ public final class PlistParser { private static final int TT_KEY = 0; private static final int TT_STRING = 1; private static final int TT_INTEGER = 2; private static final int TT_REAL = 3; private static final int TT_DATE = 4; private static final int TT_DATA = 5; private static final int TT_TRUE = 6; private static final int TT_FALSE = 7; private static final int TT_DICT = 8; private static final int TT_ARRAY = 9; private static final String XML_LT = "<"; private static final String XML_GT = ">"; private static final String XML_AND = "&"; private static final String XML_QUOT = """; private static final String XML_APOS = "'"; private static final String STRING_LT = "<"; private static final String STRING_GT = ">"; private static final String STRING_AND = "&"; private static final String STRING_QUOT = "\""; private static final String STRING_APOS = "'"; private static final String DICT = "dict"; private static final String ARRAY = "array"; private static final String[] ELEMENTS = new String[]{"key", "string", "integer", "real", "date", "data", "true", "false", DICT, ARRAY}; private static final String MODEL_PACKAGE = "open.dolphin.infomodel."; private static final String SET = "set"; //private static SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); private static final String BASE64 = "base64"; private static Boolean DEBUG = false; private List<Object> stack; private String currentKey; private StringBuilder characterBuffer; private int currentParsing; public PlistParser() { stack = new ArrayList<Object>(10); } /** * 引数のplistからInfoModel オブジェクトを生成する。 * @param plist XMLデータ * @return InfoModelオブジェクト */ public Object parse(String plist) { SAXParserFactory factory = SAXParserFactory.newInstance(); StringReader reader = new StringReader(plist); try { SAXParser saxParser = factory.newSAXParser(); DefaultHandler handler = new DefaultHandler() { @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if (qName.equals(DICT)) { // currentKey=xxxx で <dict> が来た時 // currentKeyはクラスの名前なのでそれからオブジェクトを生成する // stackの先頭オブジェクトに (クラス名=オブジェクト)をセットする // 生成したオブジェクトをstackに追加する // ただしplistのtop要素の<dict>の場合は何も行わない if (currentKey != null) { Object obj = createObject(currentKey); if (stack.size() > 0) { storeObject(currentKey, obj); } stack.add(0, obj); } } else if (qName.equals(ARRAY)) { // <array> が来た場合、Listを生成する // stackの先頭オブジェクトに (currentKey, List)をセットする // 生成したListをstackに追加する List list = new ArrayList(); if (stack.size() > 0) { storeList(currentKey, list); } stack.add(0, list); } } @Override public void endElement(String uri, String localName, String qName) throws SAXException { // パースした要素が何であるか調べる for (int i = 0; i < ELEMENTS.length; i++) { if (qName.equals(ELEMENTS[i])) { currentParsing = i; break; } } // 集積した charactor から文字列を取得 String value = builderToString(); // パースした要素で分岐する switch (currentParsing) { case TT_KEY: // <key>...</key> -> currentKey=... currentKey = value != null ? value : null; break; case TT_STRING: // <string>...</string> -> obj.set(currentKey, ...) if (value!=null && (!value.equals(""))) { value = value.replaceAll(XML_LT, STRING_LT); value = value.replaceAll(XML_GT, STRING_GT); value = value.replaceAll(XML_AND, STRING_AND); value = value.replaceAll(XML_QUOT, STRING_QUOT); value = value.replaceAll(XML_APOS, STRING_APOS); storeString(currentKey, value); } currentKey = null; break; case TT_INTEGER: // <integer>...</integer> -> obj.set(currentKey, ...) if (value != null) { storeInteger(currentKey, value); } currentKey = null; break; case TT_REAL: // <real>...</real> -> obj.set(currentKey, ...) if (value != null) { storeReal(currentKey, value); } currentKey = null; break; case TT_DATE: // <date>...</date> -> obj.set(currentKey, Date(...)) if (value != null) { Date date = parseDate(value); storeDate(currentKey, date); } currentKey = null; break; case TT_DATA: // <data>...</data> -> obj.set(currentKey, Base64Decord(...)) if (value != null) { try { byte[] bytes = base64Decode(value.getBytes()); storeByte(currentKey, bytes); } catch (Exception e) { System.err.println("TT_DATA Exception: " + e.getMessage()); } } currentKey = null; break; case TT_TRUE: // <true/> -> obj.set(currentKey, true) storeBoolean(currentKey, true); currentKey = null; break; case TT_FALSE: // <false/> -> obj.set(currentKey, false) storeBoolean(currentKey, false); currentKey = null; break; case TT_DICT: // </dict> -> stack の先頭オブジェクト(InfoModel)を取り除く if (stack.size() > 1) { stack.remove(0); //System.err.println("dict removed from stack, size=" + stack.size()); } currentKey = null; break; case TT_ARRAY: // </dict> -> stack の先頭オブジェクト(List)を取り除く if (stack.size() > 1) { stack.remove(0); //System.err.println("list removed from stack, size=" + stack.size()); } currentKey = null; break; } } @Override public void characters(char ch[], int start, int length) throws SAXException { // ゴミを除去する String parsedCharacterData = currentParsing == TT_DATA ? new String(ch, start, length).trim() : new String(ch, start, length); // 集積する if (characterBuffer == null) { characterBuffer = new StringBuilder(); characterBuffer.append(parsedCharacterData); } else { characterBuffer.append(parsedCharacterData); } } }; saxParser.parse(new InputSource(reader), handler); reader.close(); } catch (Exception e) { System.err.println("Exception at convert(): " + e.getMessage()); for (int i=0; i < stack.size(); i++) { stack.remove(0); } } return (stack.size() > 0) ? stack.remove(0) : null; } /** * 引数のクラス名からInfoModelオブジェクトを生成する。 * @param clsName クラス名 * @return InfoModelオブジェクト */ private Object createObject(String clsName) { Object ret; try { StringBuilder sb = new StringBuilder(); sb.append(MODEL_PACKAGE); sb.append(clsName.substring(0,1).toUpperCase()); sb.append(clsName.substring(1)); String fullName = sb.toString(); ret = Class.forName(fullName).newInstance(); } catch (Exception e) { debug(e.getMessage()); ret = null; } return ret; } private void storeBoolean(String name, Object value) { if (name != null && value != null) { setValue(name, value, Boolean.TYPE); } } private void storeByte(String name, byte[] bytes) { if (name != null && bytes != null) { setValue(name, bytes, bytes.getClass()); } } private void storeString(String name, Object value) { if (name != null && value != null) { if (currentTargetIsList()) { addToList(value); } else { setValue(name, value, String.class); } } } private void storeDate(String name, Object value) { if (name != null && value != null) { if (currentTargetIsList()) { addToList(value); } else { setValue(name, value, Date.class); } } } private void storeObject(String name, Object value) { if (name != null && value != null) { if (currentTargetIsList()) { addToList(value); } else { setValue(name, value, value.getClass()); } } } private void storeList(String name, Object list) { if (list != null) { if (currentTargetIsList()) { addToList(list); } else if (name!=null) { setValue(name, list, List.class); } } } /** * Stackの先頭要素オブジェクトにプロパティ値を設定する。 * @param name プロパティ名 * @param value プロパティ値 * @param cls プロパティクラス */ private void setValue(String name, Object value, Class cls) { try { Object target = stack.get(0); String setter = toSetter(name); Method mth = target.getClass().getMethod(setter, cls); mth.invoke(target, value); } catch (Exception e) { //System.err.println("Exception setValue: " + e.getMessage()); } } private void storeInteger(String name, String value) { Object target = stack.get(0); String setter = toSetter(name); try { Method mth = target.getClass().getMethod(setter, long.class); mth.invoke(target, Long.parseLong(value)); return; } catch (Exception e) { } try { Method mth = target.getClass().getMethod(setter, int.class); mth.invoke(target, Integer.parseInt(value)); } catch (Exception e) { } } private void storeReal(String name, String value) { Object target = stack.get(0); String setter = toSetter(name); try { Method mth = target.getClass().getMethod(setter, float.class); mth.invoke(target, Float.parseFloat(value)); return; } catch (Exception e) { } try { Method mth = target.getClass().getMethod(setter, double.class); mth.invoke(target, Double.parseDouble(value)); } catch (Exception e) { } } private void addToList(Object value) { try { ArrayList target = (ArrayList)stack.get(0); target.add(value); } catch (Exception e) { System.err.println("Exception addToList: " + e.getMessage()); } } private boolean currentTargetIsList() { Object target = stack.get(0); return (target instanceof ArrayList) ? true : false; } private String toSetter(String name) { StringBuilder sb = new StringBuilder(); sb.append(SET); sb.append(name.substring(0,1).toUpperCase()); sb.append(name.substring(1)); String setter = sb.toString(); return setter; } private String builderToString() { String ret = null; if (characterBuffer != null && characterBuffer.length() > 0) { ret = characterBuffer.toString(); characterBuffer = null; } return ret; } private static Date parseDate(String dateStr) { Date ret = null; try { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); ret = sdf.parse(dateStr); } catch (Exception e) { } return ret; } // public static byte[] base64Encode(byte[] value) throws IOException, MessagingException { // ByteArrayOutputStream baos = new ByteArrayOutputStream(); // OutputStream b64os = MimeUtility.encode(baos, BASE64); // b64os.write(value); // b64os.close(); // return baos.toByteArray(); // } // // public static byte[] base64Decode(byte[] value) throws IOException, MessagingException { // ByteArrayInputStream bais = new ByteArrayInputStream(value); // InputStream b64is = MimeUtility.decode(bais, BASE64); // byte[] tmp = new byte[value.length]; // int n = b64is.read(tmp); // byte[] res = new byte[n]; // System.arraycopy(tmp, 0, res, 0, n); // return res; // } public static byte[] base64Encode(byte[] value) throws IOException, MessagingException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); OutputStream b64os = MimeUtility.encode(baos, BASE64); b64os.write(value); b64os.close(); return baos.toByteArray(); } public static byte[] base64Decode(byte[] value) throws IOException, MessagingException { ByteArrayInputStream bais = new ByteArrayInputStream(value); InputStream b64is = MimeUtility.decode(bais, BASE64); byte[] tmp = new byte[value.length]; int n = b64is.read(tmp); byte[] res = new byte[n]; System.arraycopy(tmp, 0, res, 0, n); return res; } private void debug(String str) { if (DEBUG) { System.err.println(str); } } }