Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.googlecode.openbeans.XMLEncoder Maven / Gradle / Ivy
/*
* 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);
}
}