| /* |
| * 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.SourceInfo; |
| import com.google.gwt.dev.util.Preconditions; |
| import com.google.gwt.dev.util.collect.Lists; |
| |
| import java.io.IOException; |
| import java.io.ObjectInputStream; |
| import java.io.ObjectOutputStream; |
| import java.util.Arrays; |
| import java.util.Comparator; |
| import java.util.List; |
| |
| /** |
| * Base class for any declared type. |
| * |
| * Declared types have fields and methods. Two of the methods are treated specially: the class |
| * initializer method (named <code>$clinit</code>) and the instance initializer method |
| * (named <code>$init</code>). |
| * |
| * The class initializer method is responsible for initializing all class variables as well as |
| * those of the superclasses (by calling the superclass class initializer method). |
| * |
| * The instance initializer is responsible for initializing all instance variables as well as those |
| * of the superclasses (by calling the superclass instance initializer method). |
| * |
| * Optimizations may eliminate class initializers (<code>$clinit</code>) if no static variables need |
| * initialization, and use the private variable <code>clinitTarget</code>to keep track which |
| * initializer in the superclass chain needs to be called. |
| * |
| */ |
| public abstract class JDeclaredType extends JReferenceType { |
| |
| /** |
| * The other nodes that this node should implicitly rescue. Special |
| * serialization treatment. |
| */ |
| protected transient List<JNode> artificialRescues = Lists.create(); |
| |
| /** |
| * This type's fields. Special serialization treatment. |
| */ |
| protected transient List<JField> fields = Lists.create(); |
| |
| /** |
| * This type's methods. Special serialization treatment. |
| */ |
| protected transient List<JMethod> methods = Lists.create(); |
| |
| /** |
| * Tracks the target static initialization for this class. Default to self (if it has a non |
| * empty initializer) or point to a superclass or be null. |
| */ |
| private JDeclaredType clinitTarget = this; |
| |
| /** |
| * The type which originally enclosed this type. Null if this class was a |
| * top-level type. Note that all classes are converted to top-level types in |
| * {@link com.google.gwt.dev.jjs.impl.GenerateJavaAST}; this information is |
| * for tracking purposes. |
| */ |
| private JDeclaredType enclosingType; |
| |
| /** |
| * True if this class is provided externally to the program by the program's |
| * host execution environment. For example, while compiling for the JVM, JRE |
| * types are external types. External types definitions are provided by class |
| * files which are considered opaque by the GWT compiler. |
| */ |
| private boolean isExternal; |
| |
| /** |
| * This type's implemented interfaces. |
| */ |
| private List<JInterfaceType> superInterfaces = Lists.create(); |
| |
| /** |
| * Determines whether a subclass of this type is in the collection <code>types</code>. |
| * |
| * @param types a collections of types. |
| * @return the first subtype found in the collection if the collection <code>types</code> |
| * contains a subtype of this type; null otherwise. |
| */ |
| public JDeclaredType findSubtype(Iterable<JDeclaredType> types) { |
| for (JDeclaredType type : types) { |
| JDeclaredType tp = type; |
| while (tp != null) { |
| if (this == tp) { |
| return type; |
| } |
| tp = tp.getSuperClass(); |
| } |
| } |
| return null; |
| } |
| |
| public JDeclaredType(SourceInfo info, String name) { |
| super(info, name); |
| } |
| |
| public void addArtificialRescue(JNode node) { |
| artificialRescues = Lists.add(artificialRescues, node); |
| } |
| |
| /** |
| * Adds a field to this type. |
| */ |
| public void addField(JField field) { |
| assert field.getEnclosingType() == this; |
| fields = Lists.add(fields, field); |
| } |
| |
| /** |
| * Adds an implemented interface to this type. |
| */ |
| public void addImplements(JInterfaceType superInterface) { |
| superInterfaces = Lists.add(superInterfaces, superInterface); |
| } |
| |
| /** |
| * Adds a method to this type. |
| */ |
| public final void addMethod(JMethod method) { |
| assert method.getEnclosingType() == this; |
| assert !method.getName().equals("$clinit") || getMethods().size() == 0 : "Attempted adding " |
| + "$clinit method with index != 0"; |
| assert !method.getName().equals("$init") || getMethods().size() == 1 : "Attempted adding $init " |
| + "method with index != 1"; |
| methods = Lists.add(methods, method); |
| } |
| |
| /** |
| * Returns <code>true</code> if a static field access of |
| * <code>targetType</code> from within this type should generate a clinit |
| * call. This will be true in cases where <code>targetType</code> has a live |
| * clinit method which we cannot statically know has already run. We can |
| * statically know the clinit method has already run when: |
| * <ol> |
| * <li><code>this == targetType</code></li> |
| * <li><code>this</code> is a subclass of <code>targetType</code>, because my |
| * clinit would have already run this <code>targetType</code>'s clinit; see |
| * JLS 12.4</li> |
| * </ol> |
| */ |
| public boolean checkClinitTo(JDeclaredType targetType) { |
| if (this == targetType) { |
| // Call to self (very common case). |
| return false; |
| } |
| if (targetType == null || !targetType.hasClinit()) { |
| // Target has no clinit (common case). |
| return false; |
| } |
| /* |
| * The clinit for the source of the reference must already have run, so if |
| * it's the same as this one, there it must have already run. One example is |
| * a reference from a subclass to something in a superclass. |
| */ |
| return this.getClinitTarget() != targetType.getClinitTarget(); |
| } |
| |
| public List<JNode> getArtificialRescues() { |
| return artificialRescues; |
| } |
| |
| /** |
| * Returns the instance initializer ($init) method. |
| * Can only be called after making sure the class has an instance initializer method. |
| * |
| * @return The instance initializer method. |
| */ |
| public final JMethod getInitMethod() { |
| assert getMethods().size() > 1; |
| JMethod init = this.getMethods().get(1); |
| |
| assert init != null; |
| assert init.getName().equals("$init"); |
| return init; |
| } |
| |
| /** |
| * Returns the class initializer method. |
| * Can only be called after making sure the class has a class initializer method. |
| * |
| * @return The class initializer method. |
| */ |
| public final JMethod getClinitMethod() { |
| assert getMethods().size() != 0; |
| JMethod clinit = this.getMethods().get(0); |
| |
| assert clinit != null; |
| assert clinit.getName().equals("$clinit"); |
| return clinit; |
| } |
| |
| /** |
| * Returns the class that must be initialized to use this class. May be a |
| * superclass, or <code>null</code> if this class has no static initializer. |
| */ |
| public final JDeclaredType getClinitTarget() { |
| return clinitTarget; |
| } |
| |
| /** |
| * Returns the type which encloses this type. |
| * |
| * @return The enclosing type. May be {@code null}. |
| */ |
| public JDeclaredType getEnclosingType() { |
| return enclosingType; |
| } |
| |
| /** |
| * Returns this type's fields;does not include fields defined in a super type |
| * unless they are overridden by this type. |
| */ |
| public List<JField> getFields() { |
| return fields; |
| } |
| |
| /** |
| * Returns this type's implemented interfaces. Returns an empty list if this |
| * type implements no interfaces. |
| */ |
| public List<JInterfaceType> getImplements() { |
| return superInterfaces; |
| } |
| |
| @Override |
| public String getJavahSignatureName() { |
| return "L" + name.replaceAll("_", "_1").replace('.', '_') + "_2"; |
| } |
| |
| @Override |
| public String getJsniSignatureName() { |
| return "L" + name.replace('.', '/') + ';'; |
| } |
| |
| /** |
| * Returns this type's declared methods; does not include methods defined in a |
| * super type unless they are overridden by this type. |
| */ |
| public final List<JMethod> getMethods() { |
| return methods; |
| } |
| |
| @Override |
| public String getShortName() { |
| int dotpos = name.lastIndexOf('.'); |
| return name.substring(dotpos + 1); |
| } |
| |
| /** |
| * Returns this type's super class, or <code>null</code> if this type is |
| * {@link Object} or an interface. |
| */ |
| public abstract JClassType getSuperClass(); |
| |
| /** |
| * Returns <code>true</code> when this class's clinit must be run dynamically. |
| */ |
| public boolean hasClinit() { |
| return clinitTarget != null; |
| } |
| |
| @Override |
| public boolean isExternal() { |
| return isExternal; |
| } |
| |
| /** |
| * Removes the field at the specified index. |
| */ |
| public void removeField(int i) { |
| assert !isExternal() : "External types can not be modified."; |
| fields = Lists.remove(fields, i); |
| } |
| |
| /** |
| * Removes the method at the specified index. |
| */ |
| public void removeMethod(int i) { |
| assert !isExternal() : "External types can not be modified."; |
| methods = Lists.remove(methods, i); |
| } |
| |
| /** |
| * Resets the clinitTarget to the current class. Used by optimizations that move initializers from |
| * superclasses down. |
| * |
| * Prerequisite: the $clinit method must exist and be non empty. |
| */ |
| public void resetClinitTarget() { |
| assert !((JMethodBody) getClinitMethod().getBody()).getStatements().isEmpty() : "Attempted to " |
| + "reset the clinitTarget to an empty $clinit"; |
| this.clinitTarget = this; |
| } |
| |
| /** |
| * Resolves external references during AST stitching. |
| */ |
| public void resolve(List<JInterfaceType> resolvedInterfaces, List<JNode> resolvedRescues) { |
| assert JType.replaces(resolvedInterfaces, superInterfaces); |
| superInterfaces = Lists.normalize(resolvedInterfaces); |
| assert JNameOf.replacesNamedElements(resolvedRescues, artificialRescues); |
| artificialRescues = Lists.normalize(resolvedRescues); |
| } |
| |
| /** |
| * Sets the type which encloses this types. |
| * |
| * @param enclosingType May be {@code null}. |
| */ |
| public void setEnclosingType(JDeclaredType enclosingType) { |
| this.enclosingType = enclosingType; |
| } |
| |
| public void setExternal(boolean isExternal) { |
| this.isExternal = isExternal; |
| } |
| |
| /** |
| * Sorts this type's fields according to the specified sort. |
| */ |
| public void sortFields(Comparator<? super JField> sort) { |
| fields = Lists.sort(fields, sort); |
| } |
| |
| /** |
| * Sorts this type's methods according to the specified sort. |
| */ |
| public void sortMethods(Comparator<? super JMethod> sort) { |
| // Sort the methods manually to avoid sorting clinit out of place! |
| JMethod a[] = methods.toArray(new JMethod[methods.size()]); |
| Arrays.sort(a, 1, a.length, sort); |
| methods = Lists.create(a); |
| } |
| |
| /** |
| * Subclasses must replace themselves with a shallow reference when |
| * {@link #isExternal()} is <code>true</code>. |
| */ |
| protected abstract Object writeReplace(); |
| |
| /** |
| * Clears all existing implemented interfaces. |
| */ |
| void clearImplements() { |
| superInterfaces = Lists.create(); |
| } |
| |
| /** |
| * See {@link #writeMembers(ObjectOutputStream)}. |
| * |
| * @see #writeMembers(ObjectOutputStream) |
| */ |
| @SuppressWarnings("unchecked") |
| void readMembers(ObjectInputStream stream) throws IOException, ClassNotFoundException { |
| fields = (List<JField>) stream.readObject(); |
| methods = (List<JMethod>) stream.readObject(); |
| artificialRescues = (List<JNode>) stream.readObject(); |
| } |
| |
| /** |
| * See {@link #writeMethodBodies(ObjectOutputStream)}. |
| * |
| * @see #writeMethodBodies(ObjectOutputStream) |
| */ |
| void readMethodBodies(ObjectInputStream stream) throws IOException, ClassNotFoundException { |
| for (JMethod method : methods) { |
| method.readBody(stream); |
| } |
| } |
| |
| /** |
| * Called to set this class's trivial initializer to point to a superclass. |
| */ |
| void setClinitTarget(JDeclaredType newClinitTarget) { |
| if (clinitTarget == newClinitTarget) { |
| return; |
| } |
| if (newClinitTarget != null && getClass().desiredAssertionStatus()) { |
| // Make sure this is a pure upgrade to a superclass or null. |
| for (JClassType current = (JClassType) clinitTarget; current != newClinitTarget; current = |
| current.getSuperClass()) { |
| Preconditions.checkNotNull(current.getSuperClass(), |
| "Null super class for: %s (currentTarget: %s; newTarget: %s) in %s", current, |
| clinitTarget, newClinitTarget, this); |
| } |
| } |
| clinitTarget = newClinitTarget; |
| } |
| |
| /** |
| * After all types are written to the stream without transient members, this |
| * method actually writes fields and methods to the stream, which establishes |
| * type identity for them. |
| * |
| * @see JProgram#writeObject(ObjectOutputStream) |
| */ |
| void writeMembers(ObjectOutputStream stream) throws IOException { |
| stream.writeObject(fields); |
| stream.writeObject(methods); |
| stream.writeObject(artificialRescues); |
| } |
| |
| /** |
| * After all types, fields, and methods are written to the stream, this method |
| * writes method bodies to the stream. |
| * |
| * @see JProgram#writeObject(ObjectOutputStream) |
| */ |
| void writeMethodBodies(ObjectOutputStream stream) throws IOException { |
| for (JMethod method : methods) { |
| method.writeBody(stream); |
| } |
| } |
| } |