blob: 8d0d90472194ca1ef1b780142e53e44b617e1857 [file] [log] [blame]
/*
* 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 org.codehaus.groovy.transform.stc;
import groovy.lang.GroovyRuntimeException;
import org.apache.groovy.io.StringBuilderWriter;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.GenericsType;
import org.codehaus.groovy.ast.tools.WideningCategories;
import org.codehaus.groovy.classgen.asm.BytecodeHelper;
import org.codehaus.groovy.runtime.EncodingGroovyMethods;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.Writer;
import static org.codehaus.groovy.ast.ClassHelper.OBJECT_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.VOID_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.boolean_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.byte_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.char_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.double_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.float_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.int_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.long_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.short_TYPE;
/**
* First implementation of an inferred type signature codec.
*/
public class SignatureCodecVersion1 implements SignatureCodec {
private final ClassLoader classLoader;
public SignatureCodecVersion1(final ClassLoader classLoader) {
this.classLoader = classLoader;
}
private void doEncode(final ClassNode node, DataOutputStream dos) throws IOException {
dos.writeUTF(node.getClass().getSimpleName());
if (node instanceof UnionTypeClassNode) {
UnionTypeClassNode union = (UnionTypeClassNode) node;
ClassNode[] delegates = union.getDelegates();
dos.writeInt(delegates.length);
for (ClassNode delegate : delegates) {
doEncode(delegate, dos);
}
return;
} else if (node instanceof WideningCategories.LowestUpperBoundClassNode) {
WideningCategories.LowestUpperBoundClassNode lub = (WideningCategories.LowestUpperBoundClassNode) node;
dos.writeUTF(lub.getLubName());
doEncode(lub.getUnresolvedSuperClass(), dos);
ClassNode[] interfaces = lub.getInterfaces();
if (interfaces == null) {
dos.writeInt(-1);
} else {
dos.writeInt(interfaces.length);
for (ClassNode anInterface : interfaces) {
doEncode(anInterface, dos);
}
}
return;
}
if (node.isArray()) {
dos.writeBoolean(true);
doEncode(node.getComponentType(), dos);
} else {
dos.writeBoolean(false);
dos.writeUTF(BytecodeHelper.getTypeDescription(node));
dos.writeBoolean(node.isUsingGenerics());
GenericsType[] genericsTypes = node.getGenericsTypes();
if (genericsTypes == null) {
dos.writeInt(-1);
} else {
dos.writeInt(genericsTypes.length);
for (GenericsType type : genericsTypes) {
dos.writeBoolean(type.isPlaceholder());
dos.writeBoolean(type.isWildcard());
doEncode(type.getType(), dos);
ClassNode lb = type.getLowerBound();
if (lb == null) {
dos.writeBoolean(false);
} else {
dos.writeBoolean(true);
doEncode(lb, dos);
}
ClassNode[] upperBounds = type.getUpperBounds();
if (upperBounds == null) {
dos.writeInt(-1);
} else {
dos.writeInt(upperBounds.length);
for (ClassNode bound : upperBounds) {
doEncode(bound, dos);
}
}
}
}
}
}
@Override
public String encode(final ClassNode node) {
ByteArrayOutputStream baos = new ByteArrayOutputStream(128);
DataOutputStream dos = new DataOutputStream(baos);
Writer wrt = new StringBuilderWriter();
String encoded = null;
try {
doEncode(node, dos);
EncodingGroovyMethods.encodeBase64(baos.toByteArray()).writeTo(wrt);
encoded = wrt.toString();
} catch (IOException e) {
throw new GroovyRuntimeException("Unable to serialize type information", e);
}
return encoded;
}
private ClassNode doDecode(final DataInputStream dis) throws IOException {
String classNodeType = dis.readUTF();
if (UnionTypeClassNode.class.getSimpleName().equals(classNodeType)) {
int len = dis.readInt();
ClassNode[] delegates = new ClassNode[len];
for (int i = 0; i < len; i++) {
delegates[i] = doDecode(dis);
}
return new UnionTypeClassNode(delegates);
} else if (WideningCategories.LowestUpperBoundClassNode.class.getSimpleName().equals(classNodeType)) {
String name = dis.readUTF();
ClassNode upper = doDecode(dis);
int len = dis.readInt();
ClassNode[] interfaces = null;
if (len >= 0) {
interfaces = new ClassNode[len];
for (int i = 0; i < len; i++) {
interfaces[i] = doDecode(dis);
}
}
return new WideningCategories.LowestUpperBoundClassNode(name, upper, interfaces);
}
boolean makeArray = dis.readBoolean();
if (makeArray) {
return doDecode(dis).makeArray();
}
String typedesc = dis.readUTF();
char typeCode = typedesc.charAt(0);
ClassNode result = OBJECT_TYPE;
if (typeCode == 'L') {
// object type
String className = typedesc.replace('/', '.').substring(1, typedesc.length() - 1);
try {
result = ClassHelper.make(Class.forName(className, false, classLoader)).getPlainNodeReference();
} catch (ClassNotFoundException e) {
result = ClassHelper.make(className);
}
result.setUsingGenerics(dis.readBoolean());
int len = dis.readInt();
if (len >= 0) {
GenericsType[] gts = new GenericsType[len];
for (int i = 0; i < len; i++) {
boolean placeholder = dis.readBoolean();
boolean wildcard = dis.readBoolean();
ClassNode type = doDecode(dis);
boolean low = dis.readBoolean();
ClassNode lb = null;
if (low) {
lb = doDecode(dis);
}
int upc = dis.readInt();
ClassNode[] ups = null;
if (upc >= 0) {
ups = new ClassNode[upc];
for (int j = 0; j < upc; j++) {
ups[j] = doDecode(dis);
}
}
GenericsType gt = new GenericsType(
type, ups, lb
);
gt.setPlaceholder(placeholder);
gt.setWildcard(wildcard);
gts[i] = gt;
}
result.setGenericsTypes(gts);
}
} else {
// primitive type
switch (typeCode) {
case 'I': result = int_TYPE; break;
case 'Z': result = boolean_TYPE; break;
case 'B': result = byte_TYPE; break;
case 'C': result = char_TYPE; break;
case 'S': result = short_TYPE; break;
case 'D': result = double_TYPE; break;
case 'F': result = float_TYPE; break;
case 'J': result = long_TYPE; break;
case 'V': result = VOID_TYPE; break;
}
}
return result;
}
@Override
public ClassNode decode(final String signature) {
DataInputStream dis = new DataInputStream(
new ByteArrayInputStream(EncodingGroovyMethods.decodeBase64(signature)));
try {
return doDecode(dis);
} catch (IOException e) {
throw new GroovyRuntimeException("Unable to read type information", e);
}
}
}