/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mozilla.javascript.serialize; import java.util.Map; import java.util.HashMap; import java.util.StringTokenizer; import java.io.*; import org.mozilla.javascript.*; /** * Class ScriptableOutputStream is an ObjectOutputStream used * to serialize JavaScript objects and functions. Note that * compiled functions currently cannot be serialized, only * interpreted functions. The top-level scope containing the * object is not written out, but is instead replaced with * another top-level object when the ScriptableInputStream * reads in this object. Also, object corresponding to names * added to the exclude list are not written out but instead * are looked up during deserialization. This approach avoids * the creation of duplicate copies of standard objects * during deserialization. * * @author Norris Boyd */ // API class public class ScriptableOutputStream extends ObjectOutputStream { /** * ScriptableOutputStream constructor. * Creates a ScriptableOutputStream for use in serializing * JavaScript objects. Calls excludeStandardObjectNames. * * @param out the OutputStream to write to. * @param scope the scope containing the object. */ public ScriptableOutputStream(OutputStream out, Scriptable scope) throws IOException { super(out); this.scope = scope; table = new HashMap<Object,String>(); table.put(scope, ""); enableReplaceObject(true); excludeStandardObjectNames(); // XXX } public void excludeAllIds(Object[] ids) { for (Object id: ids) { if (id instanceof String && (scope.get((String) id, scope) instanceof Scriptable)) { this.addExcludedName((String)id); } } } /** * Adds a qualified name to the list of object to be excluded from * serialization. Names excluded from serialization are looked up * in the new scope and replaced upon deserialization. * @param name a fully qualified name (of the form "a.b.c", where * "a" must be a property of the top-level object). The object * need not exist, in which case the name is ignored. * @throws IllegalArgumentException if the object is not a * {@link Scriptable}. */ public void addOptionalExcludedName(String name) { Object obj = lookupQualifiedName(scope, name); if(obj != null && obj != UniqueTag.NOT_FOUND) { if (!(obj instanceof Scriptable)) { throw new IllegalArgumentException( "Object for excluded name " + name + " is not a Scriptable, it is " + obj.getClass().getName()); } table.put(obj, name); } } /** * Adds a qualified name to the list of objects to be excluded from * serialization. Names excluded from serialization are looked up * in the new scope and replaced upon deserialization. * @param name a fully qualified name (of the form "a.b.c", where * "a" must be a property of the top-level object) * @throws IllegalArgumentException if the object is not found or is not * a {@link Scriptable}. */ public void addExcludedName(String name) { Object obj = lookupQualifiedName(scope, name); if (!(obj instanceof Scriptable)) { throw new IllegalArgumentException("Object for excluded name " + name + " not found."); } table.put(obj, name); } /** * Returns true if the name is excluded from serialization. */ public boolean hasExcludedName(String name) { return table.get(name) != null; } /** * Removes a name from the list of names to exclude. */ public void removeExcludedName(String name) { table.remove(name); } /** * Adds the names of the standard objects and their * prototypes to the list of excluded names. */ public void excludeStandardObjectNames() { String[] names = { "Object", "Object.prototype", "Function", "Function.prototype", "String", "String.prototype", "Math", // no Math.prototype "Array", "Array.prototype", "Error", "Error.prototype", "Number", "Number.prototype", "Date", "Date.prototype", "RegExp", "RegExp.prototype", "Script", "Script.prototype", "Continuation", "Continuation.prototype", }; for (int i=0; i < names.length; i++) { addExcludedName(names[i]); } String[] optionalNames = { "XML", "XML.prototype", "XMLList", "XMLList.prototype", }; for (int i=0; i < optionalNames.length; i++) { addOptionalExcludedName(optionalNames[i]); } } static Object lookupQualifiedName(Scriptable scope, String qualifiedName) { StringTokenizer st = new StringTokenizer(qualifiedName, "."); Object result = scope; while (st.hasMoreTokens()) { String s = st.nextToken(); result = ScriptableObject.getProperty((Scriptable)result, s); if (result == null || !(result instanceof Scriptable)) break; } return result; } static class PendingLookup implements Serializable { static final long serialVersionUID = -2692990309789917727L; PendingLookup(String name) { this.name = name; } String getName() { return name; } private String name; } @Override protected Object replaceObject(Object obj) throws IOException { if (false) throw new IOException(); // suppress warning String name = table.get(obj); if (name == null) return obj; return new PendingLookup(name); } private Scriptable scope; private Map<Object,String> table; }