package open.dolphin.converter; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.StringWriter; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import javax.mail.MessagingException; import javax.mail.internet.MimeUtility; import open.dolphin.infomodel.IInfoModel; import open.dolphin.infomodel.KarteBean; import open.dolphin.infomodel.UserModel; /** * * @author Kazushi Minagawa. Digital Globe, Inc. */ public final class PlistConverter { private static final String XML_START = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"; private static final String PLIST_START = "<plist version=\"1.0\">"; private static final String PLIST_END = "</plist>"; private static final String KEY_START = "<key>"; private static final String KEY_END = "</key>"; private static final String DICT_START = "<dict>"; private static final String DICT_END = "</dict>"; private static final String ARRAY_START = "<array>"; private static final String ARRAY_END = "</array>"; private static final String STRING_START = "<string>"; private static final String STRING_END = "</string>"; private static final String DATE_START = "<date>"; private static final String DATE_END = "</date>"; private static final String INTEGER_START = "<integer>"; private static final String INTEGER_END = "</integer>"; private static final String REAL_START = "<real>"; private static final String REAL_END = "</real>"; private static final String BOOLEAN_TRUE = "<true/>"; private static final String BOOLEAN_FALSE = "<false/>"; private static final String DATA_START = "<data>"; private static final String DATA_END = "</data>"; private static final String BASE64 = "base64"; 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 SimpleDateFormat ISO_DF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); private static final String ARRAY_BYTE = "[B"; private static final String GET = "get"; private static final String CONVERTER_PACKAGE = "open.dolphin.converter."; private static final String CONVERTER_EXT = "Converter"; /** * 引数のメソッドがgetterかどうかを返す。 * @param method テストする Method * @return getterの時 true */ private static boolean isGetter(Method method) { if (!method.getName().startsWith(GET)) { return false; } if (method.getParameterTypes().length != 0) { return false; } if (void.class.equals(method.getReturnType())) { return false; } return true; } /** * 引数のメソッドからプロパティ名を生成する。 * @param method Method * @return プロパティ名 */ private static String methodToProperty(Method method) { // ex. getName,setName // n + ame String name = method.getName(); String first = name.substring(3,4).toLowerCase(); String rest = name.substring(4); return first+rest; } /** * 引数のwriterへ <dict>を書き込む。 * @param writer 書き込む writer * @throws IOException */ private static void dictStart(StringWriter writer) throws IOException { writer.write(DICT_START); } /** * 引数のwriterへ </dict>を書き込む。 * @param writer 書き込む writer * @throws IOException */ private static void dictEnd(StringWriter writer) throws IOException { writer.write(DICT_END); } /** * 引数のwriterへ <key>keyName</key><dict>を書き込む。 * @param key keyの名前 * @param writer 書き込む writer * @throws IOException */ private static void keyDict(String keyName, StringWriter writer) throws IOException { writer.write(KEY_START); writer.write(keyName); writer.write(KEY_END); writer.write(DICT_START); } /** * 引数のwriterへ <array>を書き込む。 * @param writer 書き込む writer * @throws IOException */ private static void arrayStart(StringWriter writer) throws IOException { writer.write(ARRAY_START); } /** * 引数のwriterへ </array>を書き込む。 * @param writer 書き込む writer * @throws IOException */ private static void arrayEnd(StringWriter writer) throws IOException { writer.write(ARRAY_END); } /** * 引数のwriterへ <key>keyName</key><array>を書き込む。 * @param key keyの名前 * @param writer 書き込む writer * @throws IOException */ private static void keyArray(String key, StringWriter writer) throws IOException { writer.write(KEY_START); writer.write(key); writer.write(KEY_END); writer.write(ARRAY_START); } /** * writerへ <key>keyName</key><integer>value</integer>を書き込む。 * @param key 名前 * @param value int の値 * @param writer 書き込む writer * @throws IOException */ private static void keyInteger(String keyName, int value, StringWriter writer) throws IOException { writer.write(KEY_START); writer.write(keyName); writer.write(KEY_END); writer.write(INTEGER_START); writer.write(String.valueOf(value)); writer.write(INTEGER_END); } /** * writerへ <key>keyName</key><integer>value</integer>を書き込む。 * @param key 名前 * @param value long の値 * @param writer 書き込む writer * @throws IOException */ private static void keyLong(String key, long value, StringWriter writer) throws IOException { writer.write(KEY_START); writer.write(key); writer.write(KEY_END); writer.write(INTEGER_START); writer.write(String.valueOf(value)); writer.write(INTEGER_END); } /** * writerへ <key>keyName</key><real>value</real>を書き込む。 * @param key 名前 * @param value long の値 * @param writer 書き込む writer * @throws IOException */ private static void keyFloat(String key, float value, StringWriter writer) throws IOException { writer.write(KEY_START); writer.write(key); writer.write(KEY_END); writer.write(REAL_START); writer.write(String.valueOf(value)); writer.write(REAL_END); } /** * writerへ <key>keyName</key><real>value</real>を書き込む。 * @param key 名前 * @param value double の値 * @param writer 書き込む writer * @throws IOException */ private static void keyDouble(String key, double value, StringWriter writer) throws IOException { writer.write(KEY_START); writer.write(key); writer.write(KEY_END); writer.write(REAL_START); writer.write(String.valueOf(value)); writer.write(REAL_END); } /** * writerへ <key>keyName</key><string>value</string>を書き込む。 * @param key 名前 * @param value string の値 * @param writer 書き込む writer * @throws IOException */ private static void keyString(String key, String value, StringWriter writer) throws IOException { if (value != null) { value = value.replaceAll(STRING_LT, XML_LT); value = value.replaceAll(STRING_GT, XML_GT); value = value.replaceAll(STRING_AND, XML_AND); value = value.replaceAll(STRING_QUOT, XML_QUOT); value = value.replaceAll(STRING_APOS, XML_APOS); writer.write(KEY_START); writer.write(key); writer.write(KEY_END); writer.write(STRING_START); writer.write(value); writer.write(STRING_END); } } /** * writerへ <string>value</string>を書き込む。 * @param value string の値 * @param writer 書き込む writer * @throws IOException */ protected static void string(String value, StringWriter writer) throws IOException { if (value != null) { value = value.replaceAll(STRING_LT, XML_LT); value = value.replaceAll(STRING_GT, XML_GT); value = value.replaceAll(STRING_AND, XML_AND); value = value.replaceAll(STRING_QUOT, XML_QUOT); value = value.replaceAll(STRING_APOS, XML_APOS); writer.write(STRING_START); writer.write(value); writer.write(STRING_END); } } /** * writerへ <key>keyName</key><true/>または<false/>を書き込む。 * @param key key 名前 * @param value 真偽値 * @param writer 書き込む writer * @throws IOException */ private static void keyBoolean(String key, boolean value, StringWriter writer) throws IOException { writer.write(KEY_START); writer.write(key); writer.write(KEY_END); if (value) { writer.write(BOOLEAN_TRUE); } else { writer.write(BOOLEAN_FALSE); } } /** * writerへ <key>keyName</key><date>Date</date>を書き込む。 * @param key key 名前 * @param value Date値 * @param writer 書き込む writer * @throws IOException */ private static void keyDate(String key, Date value, StringWriter writer) throws IOException { if (value != null) { writer.write(KEY_START); writer.write(key); writer.write(KEY_END); writer.write(DATE_START); writer.write(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(value)); writer.write(DATE_END); } } /** * writerへ <key>keyName</key><data>byte[]</data>を書き込む。 * @param key key 名前 * @param value byte[]の値 * @param writer 書き込む writer * @throws IOException */ private static void keyData(String key, byte[] value, StringWriter writer) throws IOException, MessagingException { if (value != null) { String base64Str = new String(base64Encode(value)); writer.write(KEY_START); writer.write(key); writer.write(KEY_END); writer.write(DATA_START); writer.write(base64Str); writer.write(DATA_END); } } /** * バイトデータを Base64にエンコードする。 * @param value byteデータ * @return base64 byte[] * @throws IOException * @throws MessagingException */ private 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(); } protected static KarteBean createDuumyKarteBean(long pk) { KarteBean ret = new KarteBean(); ret.setId(pk); return ret; } protected static UserModel createDummyUserModel(long pk) { UserModel ret = new UserModel(); ret.setId(pk); return ret; } private static void closeWriter(StringWriter sw) { try { if (sw != null) { sw.close(); } } catch (IOException e) { } } /** * 引数のオブジェクトをplistへ変換する。 * @param target 変換するオブジェクト * @param writer 書き込むwriter * @throws IOException * @throws IllegalAccessException * @throws IllegalArgumentException * @throws InvocationTargetException * @throws MessagingException * @throws ClassNotFoundException * @throws InstantiationException */ private static void reflectConvert(IInfoModelConverter target, StringWriter writer) throws IOException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, MessagingException, ClassNotFoundException, InstantiationException { // class名から key を生成する // open.dolphin.infomodel.PatientModelConverter -> PatientModel -> patientModel String clsName = target.getClass().getName(); int from = clsName.lastIndexOf(".") + 1; int to = clsName.indexOf(CONVERTER_EXT); clsName = clsName.substring(from, to); StringBuilder sb = new StringBuilder(); sb.append(clsName.substring(0,1).toLowerCase()); sb.append(clsName.substring(1)); //------------------------------------------ // <key>patientModel</key> // <dict>...........</dict> //------------------------------------------ keyDict(sb.toString(), writer); // Methodを調べ、plistのxml要素へ変換する Method[] methods = target.getClass().getMethods(); for (Method method : methods) { // getterでなければ continue if (!isGetter(method)) { continue; } // getterの値を取得する Object value = method.invoke(target, (Object[])null); // nullなら continue if (value == null) { continue; } // getterからプロパティ名を得る String prop = methodToProperty(method); // getterのreturn Typeに応じて xml要素を生成する Class retType = method.getReturnType(); if (retType.equals(long.class)) { // <key>prop</key><integer>val</integer> keyLong(prop, ((Long)value).longValue(), writer); } else if (retType.equals(String.class)) { // <key>prop</key><string>val</string> keyString(prop, (String)value, writer); } else if (retType.equals(Date.class)) { // <key>prop</key><date>val</date> keyDate(prop, (Date)value, writer); } else if (retType.getName().equals(ARRAY_BYTE)) { // byte[] // <key>prop</key><data>(byte[])val</data> keyData(prop, (byte[])value, writer); } else if (retType.equals(List.class)) { // <key>prop</key><array> keyArray(prop, writer); // Listをiterate List list = (List)value; for (Object obj : list) { //---------------------------------------------------- // Listの要素はStringまたはInfoModelの制限 if (obj instanceof String) { string((String)obj, writer); } else { IInfoModelConverter converter = createConverter((IInfoModel)obj); reflectConvert(converter, writer); } //---------------------------------------------------- } // </array> arrayEnd(writer); } else if (value instanceof IInfoModel) { // 再起する IInfoModelConverter ac = createConverter((IInfoModel)value); reflectConvert(ac, writer); } else if (retType.equals(int.class)) { // <key>prop</key><integer>val</integer> keyInteger(prop, ((Integer)value).intValue(), writer); } else if (retType.equals(float.class)) { // <key>prop</key><real>val</real> keyFloat(prop, ((Float)value).floatValue(), writer); } else if (retType.equals(double.class)) { // <key>prop</key><real>val</real> keyDouble(prop, ((Double)value).doubleValue(), writer); } else if (retType.equals(Boolean.TYPE)) { // <key>prop</key><true/> or <false/> keyBoolean(prop, (Boolean)value, writer); } } // </dict> dictEnd(writer); } /** * InfoModelのConverterを生成する。 * @param model InfoModel * @return 対応するConverter * @throws ClassNotFoundException * @throws InstantiationException * @throws IllegalAccessException */ private static IInfoModelConverter createConverter(IInfoModel model) throws ClassNotFoundException, InstantiationException, IllegalAccessException { // open.dolphin.infomodel.PatientModel -> // open.dolphin.converter.PatientModelConverter String className = model.getClass().getName(); int index = className.lastIndexOf("."); className = className.substring(index+1); StringBuilder sb = new StringBuilder(); sb.append(CONVERTER_PACKAGE); sb.append(className); sb.append(CONVERTER_EXT); String converterName = sb.toString(); IInfoModelConverter ret = (IInfoModelConverter)Class.forName(converterName).newInstance(); // ConverterへModelをセットする ret.setModel(model); return ret; } /** * plistのトップ要素が<array>の場合のコンバート。 * @param list コンバートする list * @return <array>......</array> * @throws IOException * @throws ClassNotFoundException * @throws InstantiationException * @throws IllegalAccessException * @throws IllegalArgumentException * @throws InvocationTargetException * @throws MessagingException */ private static String convertAsList(List list) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, MessagingException { StringWriter writer = new StringWriter(); // <array> arrayStart(writer); for (Object o : list) { if (o instanceof IInfoModel) { dictStart(writer); IInfoModelConverter converter = createConverter((IInfoModel)o); reflectConvert(converter, writer); dictEnd(writer); } else if (o instanceof List) { arrayStart(writer); List l = (List)o; for (Object obj : l) { dictStart(writer); IInfoModelConverter converter = createConverter((IInfoModel)obj); reflectConvert(converter, writer); dictEnd(writer); } arrayEnd(writer); } } // </array> arrayEnd(writer); String ret = writer.toString(); closeWriter(writer); return ret; } /** * plistのトップ要素が<dict>の場合のコンバート。 * @param target コンバートする InfoModel * @return <dict>......</dict> * @throws IOException * @throws ClassNotFoundException * @throws InstantiationException * @throws IllegalAccessException * @throws IllegalArgumentException * @throws InvocationTargetException * @throws MessagingException */ private static String convertAsRoot(IInfoModel target) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, MessagingException { StringWriter writer = new StringWriter(); dictStart(writer); IInfoModelConverter converter = createConverter(target); reflectConvert(converter, writer); dictEnd(writer); String ret = writer.toString(); closeWriter(writer); return ret; } /** * 引数のオブジェクトをplistへコンバートする。 * @param obj コンバートするオブジェクト * @return plist * @throws ConverterException */ public String convert(Object obj) throws ConverterException { try { // <?xml version="1.0" encoding="UTF-8"?> // <plist version="1.0">...........</plist> StringWriter writer = new StringWriter(); writer.write(XML_START); writer.write(PLIST_START); if (obj instanceof IInfoModel) { writer.write(convertAsRoot((IInfoModel)obj)); } else if (obj instanceof List) { writer.write(convertAsList((List)obj)); } writer.write(PLIST_END); String result = writer.toString(); closeWriter(writer); return result; } catch (Exception e) { e.printStackTrace(System.err); throw new ConverterException(e); } } }