/*
* This file is part of the X10 project (http://x10-lang.org).
*
* This file is licensed to You under the Eclipse Public License (EPL);
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.opensource.org/licenses/eclipse-1.0.php
*
* This file was originally derived from the Polyglot extensible compiler framework.
*
* (C) Copyright 2000-2007 Polyglot project group, Cornell University
* (C) Copyright IBM Corporation 2007-2012.
*/
package polyglot.util;
import java.io.*;
import java.util.*;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import polyglot.frontend.SchedulerException;
import polyglot.main.Reporter;
import polyglot.types.*;
import x10.util.CollectionFactory;
/**
* The <code>TypeEncoder</code> gives the ability to encode a polyglot
* <code>Type</code> as a Java string.
* <p>
* It uses a form of serialization to encode the <code>Type</code> into
* a byte stream and then converts the byte stream to a standard Java string.
* <p>
* The difference between the encoder and a normal serialization process is
* that in order to encode this type, we need to sever any links to other types
* in the current environment. So any <code>ClassType</code> other than the
* the type being encoded is replaced in the stream with a
* <code>PlaceHolder</code> that contains the name of the class. To aid
* in the decoding process, placeholders for member classes user their
* "mangled" name; non-member classes use their fully qualified name.
*/
public class TypeEncoder
{
protected TypeSystem ts;
protected Reporter reporter;
protected final boolean zip = true;
protected final boolean base64 = true;
protected final boolean test = false;
protected Map<Object, Object> placeHolderCache;
protected Map<Object, Object> dependencies;
protected int depth;
public TypeEncoder(TypeSystem ts) {
this.ts = ts;
this.reporter = ts.extensionInfo().getOptions().reporter;
this.placeHolderCache = null;
}
/**
* Serialize a type object into a string.
* @return String containing the encoded type object.
* @param t The TypeObject to encode.
* @throws IOException If the encoding fails.
*/
public String encode(TypeObject t) throws IOException {
ByteArrayOutputStream baos;
ObjectOutputStream oos;
if (reporter.should_report(Reporter.serialize, 1)) {
reporter.report(1, "Encoding type " + t);
}
baos = new ByteArrayOutputStream();
if (zip) {
oos = new TypeOutputStream(new GZIPOutputStream(baos), ts, t);
}
else {
oos = new TypeOutputStream(baos, ts, t);
}
oos.writeObject(t);
oos.flush();
oos.close();
byte[] b = baos.toByteArray();
if (reporter.should_report(Reporter.serialize, 2)) {
reporter.report(2, "Size of serialization (with"
+ (zip?"":"out") + " zipping) is " + b.length + " bytes");
}
String s;
if (base64) {
s = new String(Base64.encode(b));
}
else {
StringBuffer sb = new StringBuffer(b.length);
for (int i = 0; i < b.length; i++)
sb.append((char) b[i]);
s = sb.toString();
}
if (reporter.should_report(Reporter.serialize, 2)) {
reporter.report(2, "Size of serialization after conversion to string is "
+ s.length() + " characters");
}
if (test) {
// Test it.
try {
QName name = null;
if (t instanceof Named) {
name = ((Named) t).fullName();
}
decode(s, name);
}
catch (Exception e) {
e.printStackTrace();
throw new InternalCompilerError("Could not decode back to " + t +
": " + e.getMessage(), e);
}
}
return s;
}
/**
* Decode a serialized type object. If deserialization fails because
* a type could not be resolved, the method returns null. The calling
* pass should abort in that case.
* @param s String containing the encoded type object.
* @return The decoded TypeObject, or null if deserialization fails.
* @throws InvalidClassException If the string is malformed.
*/
public TypeObject decode(String s, QName name) throws InvalidClassException {
TypeInputStream ois;
byte[] b;
if (base64) {
b = Base64.decode(s.toCharArray());
}
else {
char[] source;
source = s.toCharArray();
b = new byte[source.length];
for (int i = 0; i < source.length; i++)
b[i] = (byte) source[i];
}
Map<Object, Object> oldCache = placeHolderCache;
placeHolderCache = CollectionFactory.newHashMap();
if (oldCache != null) {
placeHolderCache.putAll(oldCache);
}
Map<Object, Object> oldDeps = dependencies;
if (oldDeps == null) {
dependencies = CollectionFactory.newHashMap();
}
if (reporter.should_report(Reporter.serialize, 1))
reporter.report(1, "TypeEncoder depth " + depth + " at " + name);
depth++;
try {
if (zip && !base64) {
// The base64 decoder automatically unzips byte streams, so
// we only need an explicit GZIPInputStream if we are not
// using base64 encoding.
ois = new TypeInputStream(new GZIPInputStream(new ByteArrayInputStream(b)), ts, placeHolderCache);
}
else {
ois = new TypeInputStream(new ByteArrayInputStream(b), ts, placeHolderCache);
}
TypeObject o = (TypeObject) ois.readObject();
if (ois.deserializationFailed()) {
return null;
}
return o;
}
catch (InvalidClassException e) {
throw e;
}
catch (IOException e) {
throw new InternalCompilerError("IOException thrown while " +
"decoding serialized type info: " + e.getMessage(), e);
}
catch (ClassNotFoundException e) {
throw new InternalCompilerError("Unable to find one of the classes " +
"for the serialized type info: " + e.getMessage(), e);
}
catch (SchedulerException e) {
throw new InternalCompilerError("SchedulerException thrown while " +
"decoding serialized type info: " + e.getMessage(), e);
}
finally {
placeHolderCache = oldCache;
dependencies = oldDeps;
depth--;
}
}
}