/* * Copyright 2008 Google Inc. * * Licensed 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.google.gwt.dev.jjs.ast; import com.google.gwt.dev.jjs.InternalCompilerException; import com.google.gwt.dev.jjs.SourceInfo; import com.google.gwt.dev.jjs.SourceOrigin; import com.google.gwt.dev.util.StringInterner; import com.google.gwt.dev.util.collect.Lists; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; /** * A Java method implementation. */ public class JMethod extends JNode implements HasEnclosingType, HasName, HasType, CanBeAbstract, CanBeSetFinal, CanBeNative, CanBeStatic { private static class ExternalSerializedForm implements Serializable { private final JDeclaredType enclosingType; private final String signature; public ExternalSerializedForm(JMethod method) { enclosingType = method.getEnclosingType(); signature = method.getSignature(); } private Object readResolve() { return new JMethod(signature, enclosingType); } } private static class ExternalSerializedNullMethod implements Serializable { public static final ExternalSerializedNullMethod INSTANCE = new ExternalSerializedNullMethod(); private Object readResolve() { return NULL_METHOD; } } public static final JMethod NULL_METHOD = new JMethod(SourceOrigin.UNKNOWN, "nullMethod", null, JNullType.INSTANCE, false, false, true, AccessModifier.PUBLIC); private static final String TRACE_METHOD_WILDCARD = "*"; static { NULL_METHOD.setSynthetic(); NULL_METHOD.freezeParamTypes(); } static boolean replaces(List<? extends JMethod> newMethods, List<? extends JMethod> oldMethods) { if (newMethods.size() != oldMethods.size()) { return false; } for (int i = 0, c = newMethods.size(); i < c; ++i) { if (!newMethods.get(i).replaces(oldMethods.get(i))) { return false; } } return true; } private static void trace(String title, String code) { System.out.println("---------------------------"); System.out.println(title + ":"); System.out.println("---------------------------"); System.out.println(code); } protected transient String signature; /** * The access modifier; stored as an int to reduce memory / serialization * footprint. */ private int access; /** * Special serialization treatment. */ private transient JAbstractMethodBody body = null; private final JDeclaredType enclosingType; private boolean isAbstract; private boolean isFinal; private final boolean isStatic; private boolean isSynthetic = false; private final String name; private List<JType> originalParamTypes; private JType originalReturnType; /** * References to any methods which this method overrides. This should be an * EXHAUSTIVE list, that is, if C overrides B overrides A, then C's overrides * list will contain both A and B. */ private List<JMethod> overrides = Collections.emptyList(); private List<JParameter> params = Collections.emptyList(); private JType returnType; private List<JClassType> thrownExceptions = Collections.emptyList(); private boolean trace = false; private boolean traceFirst = true; /** * These are only supposed to be constructed by JProgram. */ public JMethod(SourceInfo info, String name, JDeclaredType enclosingType, JType returnType, boolean isAbstract, boolean isStatic, boolean isFinal, AccessModifier access) { super(info); this.name = StringInterner.get().intern(name); this.enclosingType = enclosingType; this.returnType = returnType; this.isAbstract = isAbstract; this.isStatic = isStatic; this.isFinal = isFinal; this.access = access.ordinal(); } /** * Construct a bare-bones deserialized external method. */ private JMethod(String signature, JDeclaredType enclosingType) { super(SourceOrigin.UNKNOWN); this.name = signature.substring(0, signature.indexOf('(')); this.enclosingType = enclosingType; this.signature = signature; this.isAbstract = false; this.isStatic = false; this.access = AccessModifier.PUBLIC.ordinal(); } /** * Add a method that this method overrides. */ public void addOverride(JMethod toAdd) { assert canBePolymorphic(); overrides = Lists.add(overrides, toAdd); } /** * Add methods that this method overrides. */ public void addOverrides(List<JMethod> toAdd) { assert canBePolymorphic(); overrides = Lists.addAll(overrides, toAdd); } /** * Adds a parameter to this method. */ public void addParam(JParameter x) { params = Lists.add(params, x); } public void addThrownException(JClassType exceptionType) { thrownExceptions = Lists.add(thrownExceptions, exceptionType); } public void addThrownExceptions(List<JClassType> exceptionTypes) { thrownExceptions = Lists.addAll(thrownExceptions, exceptionTypes); } /** * Returns true if this method can participate in virtual dispatch. Returns * true for non-private instance methods; false for static methods, private * instance methods, and constructors. */ public boolean canBePolymorphic() { return !isStatic() && !isPrivate(); } public void freezeParamTypes() { List<JType> paramTypes = new ArrayList<JType>(); for (JParameter param : params) { paramTypes.add(param.getType()); } setOriginalTypes(returnType, paramTypes); } public AccessModifier getAccess() { return AccessModifier.values()[access]; } public JAbstractMethodBody getBody() { assert !isExternal() : "External types do not have method bodies."; return body; } public JDeclaredType getEnclosingType() { return enclosingType; } public String getName() { return name; } public List<JType> getOriginalParamTypes() { return originalParamTypes; } public JType getOriginalReturnType() { return originalReturnType; } /** * Returns the transitive closure of all the methods this method overrides. */ public List<JMethod> getOverrides() { return overrides; } /** * Returns the parameters of this method. */ public List<JParameter> getParams() { return params; } public String getSignature() { if (signature == null) { StringBuilder sb = new StringBuilder(); sb.append(getName()); sb.append('('); for (JType type : getOriginalParamTypes()) { sb.append(type.getJsniSignatureName()); } sb.append(')'); sb.append(getOriginalReturnType().getJsniSignatureName()); signature = sb.toString(); } return signature; } public List<JClassType> getThrownExceptions() { return thrownExceptions; } public JType getType() { return returnType; } public boolean isAbstract() { return isAbstract; } public boolean isDefault() { return access == AccessModifier.DEFAULT.ordinal(); } public boolean isExternal() { return getEnclosingType() != null && getEnclosingType().isExternal(); } public boolean isFinal() { return isFinal; } public boolean isNative() { if (body == null) { return false; } else { return body.isNative(); } } public boolean isPrivate() { return access == AccessModifier.PRIVATE.ordinal(); } public boolean isStatic() { return isStatic; } public boolean isSynthetic() { return isSynthetic; } public boolean isTrace() { return trace; } /** * Returns <code>true</code> if this method can participate in instance * dispatch. */ public boolean needsVtable() { return !isStatic(); } /** * Removes the parameter at the specified index. */ public void removeParam(int index) { params = Lists.remove(params, index); } /** * Resolve an external references during AST stitching. */ public void resolve(JType originalReturnType, List<JType> originalParamTypes, JType returnType, List<JClassType> thrownExceptions) { if (getClass().desiredAssertionStatus()) { assert originalReturnType.replaces(this.originalReturnType); assert JType.replaces(originalParamTypes, this.originalParamTypes); assert returnType.replaces(this.returnType); assert JType.replaces(thrownExceptions, this.thrownExceptions); } this.originalReturnType = originalReturnType; this.originalParamTypes = Lists.normalize(originalParamTypes); this.returnType = returnType; this.thrownExceptions = Lists.normalize(thrownExceptions); } public void setAbstract(boolean isAbstract) { this.isAbstract = isAbstract; } public void setBody(JAbstractMethodBody body) { this.body = body; if (body != null) { body.setMethod(this); } } public void setFinal() { isFinal = true; } public void setOriginalTypes(JType returnType, List<JType> paramTypes) { if (originalParamTypes != null) { throw new InternalCompilerException("Param types already frozen"); } originalReturnType = returnType; originalParamTypes = Lists.normalize(paramTypes); // Determine if we should trace this method. if (enclosingType != null) { String jsniSig = JProgram.getJsniSig(this); Set<String> set = JProgram.traceMethods.get(enclosingType.getName()); if (set != null && (set.contains(name) || set.contains(jsniSig) || set.contains(TRACE_METHOD_WILDCARD))) { trace = true; } // Try the short name. if (!trace && enclosingType != null) { set = JProgram.traceMethods.get(enclosingType.getShortName()); if (set != null && (set.contains(name) || set.contains(jsniSig) || set.contains(TRACE_METHOD_WILDCARD))) { trace = true; } } } } public void setSynthetic() { isSynthetic = true; } public void setTrace() { this.trace = true; } public void setType(JType newType) { returnType = newType; } public void traverse(JVisitor visitor, Context ctx) { String before = null; before = traceBefore(visitor); if (visitor.visit(this, ctx)) { visitChildren(visitor); } visitor.endVisit(this, ctx); traceAfter(visitor, before); } protected void traceAfter(JVisitor visitor, String before) { if (trace && visitor instanceof JModVisitor) { String after = this.toSource(); if (!after.equals(before)) { String title = visitor.getClass().getSimpleName(); trace(title, after); } } } protected String traceBefore(JVisitor visitor) { if (trace && visitor instanceof JModVisitor) { String source = this.toSource(); if (traceFirst) { traceFirst = false; trace("JAVA INITIAL", source); } return source; } return null; } protected void visitChildren(JVisitor visitor) { params = visitor.acceptImmutable(params); if (body != null) { body = (JAbstractMethodBody) visitor.accept(body); } } protected Object writeReplace() { if (isExternal()) { return new ExternalSerializedForm(this); } else if (this == NULL_METHOD) { return ExternalSerializedNullMethod.INSTANCE; } else { return this; } } /** * See {@link #writeBody(ObjectOutputStream)}. * * @see #writeBody(ObjectOutputStream) */ void readBody(ObjectInputStream stream) throws IOException, ClassNotFoundException { body = (JAbstractMethodBody) stream.readObject(); } boolean replaces(JMethod originalMethod) { if (this == originalMethod) { return true; } return originalMethod.isExternal() && originalMethod.getSignature().equals(this.getSignature()) && this.getEnclosingType().replaces(originalMethod.getEnclosingType()); } /** * After all types, fields, and methods are written to the stream, this method * writes method bodies to the stream. * * @see JProgram#writeObject(ObjectOutputStream) */ void writeBody(ObjectOutputStream stream) throws IOException { stream.writeObject(body); } }