All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.googlecode.openbeans.XMLEncoder Maven / Gradle / Ivy

The newest version!
/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package com.googlecode.openbeans;

import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;

import org.apache.harmony.beans.BeansUtils;
import org.apache.harmony.beans.internal.nls.Messages;

/**
 * XMLEncoder extends Encoder to write out the encoded statements and expressions in XML format. The XML can be read by
 * XMLDecoder later to restore objects and their states.
 * 

* The API is similar to ObjectOutputStream. *

* */ public class XMLEncoder extends Encoder { private static final String DEFAULT_ENCODING = "UTF-8"; //$NON-NLS-1$ private static int DEADLOCK_THRESHOLD = 7; /* * Every object written by the encoder has a record. */ private static class Record { // The expression by which the object is created or obtained. Expression exp = null; // Id of the object, if it is referenced more than once. String id = null; // Count of the references of the object. int refCount = 0; // A list of statements that execute on the object. ArrayList stats = new ArrayList(); } private static final int INDENT_UNIT = 1; private static final boolean isStaticConstantsSupported = true; // the main record of all root objects private ArrayList flushPending = new ArrayList(); // the record of root objects with a void tag private ArrayList flushPendingStat = new ArrayList(); // keep the pre-required objects for each root object private ArrayList flushPrePending = new ArrayList(); private boolean hasXmlHeader = false; /* * if any expression or statement references owner, it is set true in method recordStatement() or recordExpressions(), and, at the first time flushObject() * meets an owner object, it calls the flushOwner() method and then set needOwner to false, so that all succeeding flushing of owner will call * flushExpression() or flushStatement() normally, which will get a reference of the owner property. */ private boolean needOwner = false; private PrintWriter out; private Object owner = null; private IdentityHashMap objRecordMap = new IdentityHashMap(); private IdentityHashMap, Integer> clazzCounterMap = new IdentityHashMap, Integer>(); private IdentityHashMap> objPrePendingCache = new IdentityHashMap>(); private boolean writingObject = false; /** * Construct a XMLEncoder. * * @param out * the output stream where XML is written to */ public XMLEncoder(OutputStream out) { if (null != out) { try { this.out = new PrintWriter(new OutputStreamWriter(out, DEFAULT_ENCODING), true); } catch (UnsupportedEncodingException e) { // should never occur } } } /** * Call flush() first, then write out XML footer and close the underlying output stream. */ public void close() { flush(); out.println(" "); //$NON-NLS-1$ out.close(); } private StringBuffer decapitalize(String s) { StringBuffer buf = new StringBuffer(s); buf.setCharAt(0, Character.toLowerCase(buf.charAt(0))); return buf; } /** * Writes out all objects since last flush to the output stream. *

* The implementation write the XML header first if it has not been written. Then all pending objects since last flush are written. *

*/ @SuppressWarnings("nls") public void flush() { synchronized (this) { // write xml header if (!hasXmlHeader) { out.println(" "); out.println(" "); hasXmlHeader = true; } // preprocess pending objects for (Iterator iter = flushPending.iterator(); iter.hasNext();) { Object o = iter.next(); Record rec = objRecordMap.get(o); if (rec != null) { preprocess(o, rec); } } // flush pending objects for (Iterator iter = flushPending.iterator(); iter.hasNext();) { Object o = iter.next(); flushObject(o, INDENT_UNIT); // remove flushed obj iter.remove(); } // clear statement records objRecordMap.clear(); flushPendingStat.clear(); objPrePendingCache.clear(); clazzCounterMap.clear(); // remove all old->new mappings super.clear(); } } @SuppressWarnings("nls") private void flushBasicObject(Object obj, int indent) { if (obj instanceof Proxy) { return; } flushIndent(indent); if (obj == null) { out.println(" "); } else if (obj instanceof String) { Record rec = objRecordMap.get(obj); if (null != rec) { flushExpression(obj, rec, indent - 3, flushPendingStat.contains(obj)); return; } out.print(""); flushString((String) obj); out.println(" "); } else if (obj instanceof Class) { out.println("" + ((Class) obj).getName() + " "); } else if (obj instanceof Boolean) { out.println("" + obj + " "); } else if (obj instanceof Byte) { out.println("" + obj + " "); } else if (obj instanceof Character) { char objChar = ((Character) obj).charValue(); if (invalidCharacter(objChar)) { out.println(""); } else { out.println("" + objChar + " "); } } else if (obj instanceof Double) { out.println("" + obj + " "); } else if (obj instanceof Float) { out.println("" + obj + " "); } else if (obj instanceof Integer) { out.println("" + obj + " "); } else if (obj instanceof Long) { out.println("" + obj + " "); } else if (obj instanceof Short) { out.println("" + obj + " "); } else { getExceptionListener().exceptionThrown(new Exception(Messages.getString("beans.73", obj))); } } private boolean invalidCharacter(char c) { return ((0x0000 <= c && c < 0x0009) || (0x000a < c && c < 0x000d) || (0x000d < c && c < 0x0020) || (0xd7ff < c && c < 0xe000) || c == 0xfffe); } @SuppressWarnings("nls") private void flushExpression(Object obj, Record rec, int indent, boolean asStatement) { // flush Statement stat = asStatement ? new Statement(rec.exp.getTarget(), rec.exp.getMethodName(), rec.exp.getArguments()) : rec.exp; if (isStaticConstantsSupported && "getField".equals(stat.getMethodName())) { flushStatField(stat, indent); return; } // not first time, use idref if (rec.id != null) { flushIndent(indent); out.print(" "); return; } // generate id, if necessary if (rec.refCount > 1 && rec.id == null) { rec.id = idSerialNoOfObject(obj); } // flush flushStatement(stat, rec.id, rec.stats, indent); } private void flushIndent(int indent) { for (int i = 0; i < indent; i++) { out.print(' '); } } private void flushObject(Object obj, int indent) { Record rec = objRecordMap.get(obj); if (rec == null && !isBasicType(obj)) { return; } if (obj == owner && this.needOwner) { flushOwner(obj, rec, indent); this.needOwner = false; return; } if (isBasicType(obj)) { flushBasicObject(obj, indent); } else { flushExpression(obj, rec, indent, flushPendingStat.contains(obj)); } } @SuppressWarnings("nls") private void flushOwner(Object obj, Record rec, int indent) { if (rec.refCount > 1 && rec.id == null) { rec.id = idSerialNoOfObject(obj); } flushIndent(indent); String tagName = "void"; out.print("<"); out.print(tagName); // id attribute if (rec.id != null) { out.print(" id=\""); out.print(rec.id); out.print("\""); } out.print(" property=\"owner\""); // open tag, end if (rec.exp.getArguments().length == 0 && rec.stats.isEmpty()) { out.println("/> "); return; } out.println("> "); // arguments for (int i = 0; i < rec.exp.getArguments().length; i++) { flushObject(rec.exp.getArguments()[i], indent + INDENT_UNIT); } // sub statements flushSubStatements(rec.stats, indent); // close tag flushIndent(indent); out.print(" "); } @SuppressWarnings("nls") private void flushStatArray(Statement stat, String id, List subStats, int indent) { // open tag, begin flushIndent(indent); out.print(") stat.getArguments()[0]).getName()); out.print("\" length=\""); out.print(stat.getArguments()[1]); out.print("\""); // open tag, end if (subStats.isEmpty()) { out.println("/> "); return; } out.println("> "); // sub statements flushSubStatements(subStats, indent); // close tag flushIndent(indent); out.println(" "); } @SuppressWarnings("nls") private void flushStatCommon(Statement stat, String id, List subStats, int indent) { // open tag, begin flushIndent(indent); String tagName = stat instanceof Expression ? "object" : "void"; out.print("<"); out.print(tagName); // id attribute if (id != null) { out.print(" id=\""); out.print(id); out.print("\""); } // special class attribute if (stat.getTarget() instanceof Class) { out.print(" class=\""); out.print(((Class) stat.getTarget()).getName()); out.print("\""); } // method attribute if (!"new".equals(stat.getMethodName())) { out.print(" method=\""); out.print(stat.getMethodName()); out.print("\""); } // open tag, end if (stat.getArguments().length == 0 && subStats.isEmpty()) { out.println("/> "); return; } out.println("> "); // arguments for (int i = 0; i < stat.getArguments().length; i++) { flushObject(stat.getArguments()[i], indent + INDENT_UNIT); } // sub statements flushSubStatements(subStats, indent); // close tag flushIndent(indent); out.print(" "); } @SuppressWarnings("nls") private void flushStatement(Statement stat, String id, List subStats, int indent) { Object target = stat.getTarget(); String method = stat.getMethodName(); Object args[] = stat.getArguments(); // special case for array if (Array.class == target && BeansUtils.NEWINSTANCE.equals(method)) { flushStatArray(stat, id, subStats, indent); return; } // special case for get(int) and set(int, Object) if (isGetArrayStat(target, method, args) || isSetArrayStat(target, method, args)) { flushStatIndexed(stat, id, subStats, indent); return; } // special case for getProperty() and setProperty(Object) if (isGetPropertyStat(method, args) || isSetPropertyStat(method, args)) { flushStatGetterSetter(stat, id, subStats, indent); return; } if (isStaticConstantsSupported && "getField".equals(stat.getMethodName())) { flushStatField(stat, indent); return; } // common case flushStatCommon(stat, id, subStats, indent); } @SuppressWarnings("nls") private void flushStatField(Statement stat, int indent) { // open tag, begin flushIndent(indent); out.print(") { out.print(" class=\""); out.print(((Class) target).getName()); out.print("\""); } Field field = null; if (target instanceof Class && stat.getArguments().length == 1 && stat.getArguments()[0] instanceof String) { try { field = ((Class) target).getField((String) stat.getArguments()[0]); } catch (Exception e) { // ignored } } if (field != null && Modifier.isStatic(field.getModifiers())) { out.print(" field=\""); out.print(stat.getArguments()[0]); out.print("\""); out.println("/> "); } else { out.print(" method=\""); out.print(stat.getMethodName()); out.print("\""); out.println("> "); flushObject(stat.getArguments()[0], indent + INDENT_UNIT); flushIndent(indent); out.println(" "); } } @SuppressWarnings("nls") private void flushStatGetterSetter(Statement stat, String id, List subStats, int indent) { // open tag, begin flushIndent(indent); String tagName = "void"; out.print("<"); out.print(tagName); // id attribute if (id != null) { out.print(" id=\""); out.print(id); out.print("\""); } // special class attribute if (stat.getTarget() instanceof Class) { out.print(" class=\""); out.print(((Class) stat.getTarget()).getName()); out.print("\""); } // property attribute out.print(" property=\""); out.print(decapitalize(stat.getMethodName().substring(3))); out.print("\""); // open tag, end if (stat.getArguments().length == 0 && subStats.isEmpty()) { out.println("/> "); return; } out.println("> "); // arguments for (int i = 0; i < stat.getArguments().length; i++) { flushObject(stat.getArguments()[i], indent + INDENT_UNIT); } // sub statements flushSubStatements(subStats, indent); // close tag flushIndent(indent); out.print(" "); } @SuppressWarnings("nls") private void flushStatIndexed(Statement stat, String id, List subStats, int indent) { // open tag, begin flushIndent(indent); String tagName = stat instanceof Expression ? "object" : "void"; out.print("<"); out.print(tagName); // id attribute if (id != null) { out.print(" id=\""); out.print(id); out.print("\""); } // special class attribute if (stat.getTarget() instanceof Class) { out.print(" class=\""); out.print(((Class) stat.getTarget()).getName()); out.print("\""); } // index attribute out.print(" index=\""); out.print(stat.getArguments()[0]); out.print("\""); // open tag, end if (stat.getArguments().length == 1 && subStats.isEmpty()) { out.println("/> "); return; } out.println("> "); // arguments for (int i = 1; i < stat.getArguments().length; i++) { flushObject(stat.getArguments()[i], indent + INDENT_UNIT); } // sub statements flushSubStatements(subStats, indent); // close tag flushIndent(indent); out.print(" "); } @SuppressWarnings("nls") private void flushString(String s) { char c; for (int i = 0; i < s.length(); i++) { c = s.charAt(i); if (c == '<') { out.print("<"); } else if (c == '>') { out.print(">"); } else if (c == '&') { out.print("&"); } else if (c == '\'') { out.print("'"); } else if (c == '"') { out.print("""); } else { if (invalidCharacter(c)) { out.print(""); } else { out.print(c); } } } } private void flushSubStatements(List subStats, int indent) { for (int i = 0; i < subStats.size(); i++) { Statement subStat = (Statement) subStats.get(i); try { if (subStat.getClass() == Expression.class) { Expression subExp = (Expression) subStat; Object obj = subExp.getValue(); Record rec = objRecordMap.get(obj); flushExpression(obj, rec, indent + INDENT_UNIT, true); } else { flushStatement(subStat, null, Collections.EMPTY_LIST, indent + INDENT_UNIT); } } catch (Exception e) { // should not happen getExceptionListener().exceptionThrown(e); } } } /** * Returns the owner of this encoder. * * @return the owner of this encoder */ public Object getOwner() { return owner; } private boolean isBasicType(Object value) { return value == null || value instanceof Boolean || value instanceof Byte || value instanceof Character || value instanceof Class || value instanceof Double || value instanceof Float || value instanceof Integer || value instanceof Long || value instanceof Short || value instanceof String || value instanceof Proxy; } private boolean isGetArrayStat(Object target, String method, Object[] args) { return (BeansUtils.GET.equals(method) && args.length == 1 && args[0] instanceof Integer && target.getClass().isArray()); } private boolean isGetPropertyStat(String method, Object[] args) { return (method.startsWith(BeansUtils.GET) && method.length() > 3 && args.length == 0); } private boolean isSetArrayStat(Object target, String method, Object[] args) { return (BeansUtils.SET.equals(method) && args.length == 2 && args[0] instanceof Integer && target.getClass().isArray()); } private boolean isSetPropertyStat(String method, Object[] args) { return (method.startsWith(BeansUtils.SET) && method.length() > 3 && args.length == 1); } private String idSerialNoOfObject(Object obj) { Class clazz = obj.getClass(); Integer serialNo = (Integer) clazzCounterMap.get(clazz); serialNo = serialNo == null ? 0 : serialNo; String id = BeansUtils.idOfClass(obj.getClass()) + serialNo; clazzCounterMap.put(clazz, ++serialNo); return id; } /* * The preprocess removes unused statements and counts references of every object */ private void preprocess(Object obj, Record rec) { if (writingObject && isBasicType(obj)) { return; } if (obj instanceof Class) { return; } // count reference rec.refCount++; // do things only one time for each record if (rec.refCount > 1) { return; } // do it recursively if (null != rec.exp) { // deal with 'field' property Record targetRec = objRecordMap.get(rec.exp.getTarget()); if (targetRec != null && targetRec.exp != null && "getField".equals(targetRec.exp.getMethodName())) { objRecordMap.remove(obj); } Object args[] = rec.exp.getArguments(); for (int i = 0; i < args.length; i++) { Record argRec = objRecordMap.get(args[i]); if (argRec != null) { preprocess(args[i], argRec); } } } for (Iterator iter = rec.stats.iterator(); iter.hasNext();) { Statement subStat = (Statement) iter.next(); if (subStat.getClass() == Expression.class) { try { Expression subExp = (Expression) subStat; Record subRec = objRecordMap.get(subExp.getValue()); if (subRec == null || subRec.exp == null || subRec.exp != subExp) { iter.remove(); continue; } preprocess(subExp.getValue(), subRec); if (subRec.stats.isEmpty()) { if (isGetArrayStat(subExp.getTarget(), subExp.getMethodName(), subExp.getArguments()) || isGetPropertyStat(subExp.getMethodName(), subExp.getArguments())) { iter.remove(); continue; } } } catch (Exception e) { getExceptionListener().exceptionThrown(e); iter.remove(); } continue; } Object subStatArgs[] = subStat.getArguments(); for (int i = 0; i < subStatArgs.length; i++) { Record argRec = objRecordMap.get(subStatArgs[i]); if (argRec != null) { preprocess(subStatArgs[i], argRec); } } } } private void recordExpression(Object value, Expression exp) { // record how a new object is created or obtained Record rec = objRecordMap.get(value); if (rec == null) { rec = new Record(); objRecordMap.put(value, rec); } if (rec.exp == null) { // it is generated by its sub statements for (Statement statement : rec.stats) { if (statement.getClass() == Expression.class) { flushPrePending.add(value); } } } rec.exp = exp; // deal with 'owner' property if (value == owner && owner != null) { needOwner = true; } // also record as a statement recordStatement(exp); } private void recordStatement(Statement stat) { if (null == stat) { return; } // deal with 'owner' property Object target = stat.getTarget(); if (target == owner && owner != null) { needOwner = true; } // record how a statement affects the target object Record rec = objRecordMap.get(target); if (rec == null) { rec = new Record(); objRecordMap.put(target, rec); } boolean hasRecord = false; String methodName = stat.getMethodName(); Object[] args = stat.getArguments(); if (isSetPropertyStat(methodName, args) || isSetArrayStat(target, methodName, args)) { for (Statement subStat : rec.stats) { if (target == subStat.getTarget() && methodName.equals(subStat.getMethodName())) { Object[] subArgs = subStat.getArguments(); if (args.length == subArgs.length) { boolean equals = true; for (int index = 0; index < args.length; index++) { if (getPersistenceDelegate(args[index].getClass()).mutatesTo(args[index], subArgs[index])) { continue; } equals = false; break; } if (equals) { hasRecord = true; break; } } } } } if (!hasRecord) { rec.stats.add(stat); } } /** * Imperfect attempt to detect a dead loop. This works with specific patterns that can be found in our AWT implementation. See HARMONY-5707 for details. * * @param value * the object to check dupes for * @return true if a dead loop detected; false otherwise FIXME */ private boolean checkDeadLoop(Object value) { int n = 0; Object obj = value; while (obj != null) { Record rec = objRecordMap.get(obj); if (rec != null && rec.exp != null) { obj = rec.exp.getTarget(); } else { break; } if (obj != null && (obj.getClass().isAssignableFrom(value.getClass())) && obj.equals(value)) { n++; if (n >= DEADLOCK_THRESHOLD) { // System.out.println("Dead loop hit!"); return true; } } } return false; } /** * Sets the owner of this encoder. * * @param owner * the owner to set */ public void setOwner(Object owner) { this.owner = owner; } /** * Records the expression so that it can be written out later, then calls super implementation. */ @Override public void writeExpression(Expression oldExp) { if (null == oldExp) { throw new NullPointerException(); } boolean oldWritingObject = writingObject; writingObject = true; // get expression value Object oldValue = expressionValue(oldExp); // check existence if (oldValue == null || get(oldValue) != null && (oldWritingObject || oldValue.getClass() != String.class)) { return; } // record how the object is obtained if (!isBasicType(oldValue) || (!oldWritingObject && oldValue.getClass() == String.class)) { recordExpression(oldValue, oldExp); } // try to detect if we run into a dead loop if (checkDeadLoop(oldValue)) { return; } super.writeExpression(oldExp); writingObject = oldWritingObject; } /** * Records the object so that it can be written out later, then calls super implementation. */ @Override public void writeObject(Object o) { synchronized (this) { ArrayList prePending = objPrePendingCache.get(o); if (prePending == null) { boolean oldWritingObject = writingObject; writingObject = true; try { super.writeObject(o); } finally { writingObject = oldWritingObject; } } else { flushPrePending.clear(); flushPrePending.addAll(prePending); } // root object if (!writingObject) { boolean isNotCached = prePending == null; // is not cached, add to cache if (isNotCached && o != null) { prePending = new ArrayList(); prePending.addAll(flushPrePending); objPrePendingCache.put(o, prePending); } // add to pending flushPending.addAll(flushPrePending); flushPendingStat.addAll(flushPrePending); flushPrePending.clear(); if (isNotCached && flushPending.contains(o)) { flushPendingStat.remove(o); } else { flushPending.add(o); } if (needOwner) { this.flushPending.remove(owner); this.flushPending.add(0, owner); } } } } /** * Records the statement so that it can be written out later, then calls super implementation. */ @Override public void writeStatement(Statement oldStat) { if (null == oldStat) { System.err.println("java.lang.Exception: XMLEncoder: discarding statement null"); System.err.println("Continuing..."); return; } // record how the object is changed recordStatement(oldStat); super.writeStatement(oldStat); } }