/* * Copyright 2010 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.impl; import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.dev.jjs.SourceInfo; import com.google.gwt.dev.jjs.ast.Context; import com.google.gwt.dev.jjs.ast.JArrayType; import com.google.gwt.dev.jjs.ast.JCastOperation; import com.google.gwt.dev.jjs.ast.JClassLiteral; import com.google.gwt.dev.jjs.ast.JClassType; import com.google.gwt.dev.jjs.ast.JDeclarationStatement; import com.google.gwt.dev.jjs.ast.JEnumField; import com.google.gwt.dev.jjs.ast.JEnumType; import com.google.gwt.dev.jjs.ast.JExpression; import com.google.gwt.dev.jjs.ast.JField; import com.google.gwt.dev.jjs.ast.JFieldRef; import com.google.gwt.dev.jjs.ast.JInstanceOf; import com.google.gwt.dev.jjs.ast.JMethod; import com.google.gwt.dev.jjs.ast.JMethodCall; import com.google.gwt.dev.jjs.ast.JNewArray; import com.google.gwt.dev.jjs.ast.JNode; import com.google.gwt.dev.jjs.ast.JParameter; import com.google.gwt.dev.jjs.ast.JPrimitiveType; import com.google.gwt.dev.jjs.ast.JProgram; import com.google.gwt.dev.jjs.ast.JType; import com.google.gwt.dev.jjs.ast.RuntimeConstants; import com.google.gwt.dev.jjs.ast.js.JsniClassLiteral; import com.google.gwt.dev.jjs.ast.js.JsniFieldRef; import com.google.gwt.dev.jjs.ast.js.JsniMethodRef; import com.google.gwt.dev.util.log.speedtracer.CompilerEventType; import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger; import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event; import com.google.gwt.thirdparty.guava.common.collect.Lists; import com.google.gwt.thirdparty.guava.common.collect.Multimap; import com.google.gwt.thirdparty.guava.common.collect.Ordering; import com.google.gwt.thirdparty.guava.common.collect.Sets; import com.google.gwt.thirdparty.guava.common.collect.TreeMultimap; import java.util.Comparator; import java.util.List; import java.util.Set; import java.util.TreeSet; /** * This optimizer replaces enum constants with their ordinal value (a simple * int) when possible. We call this process "ordinalization". * * Generally, this can be done for enums that are only ever referred to by * reference, or by their ordinal value. For the specific set of conditions * under which ordinalization can proceed, see the notes for the nested class * {@link EnumOrdinalizer.CannotBeOrdinalAnalyzer} below. * * This optimizer modifies enum classes to change their field constants to ints, * and to remove initialization of those constants in the clinit method. An * ordinalized enum class will not be removed from the AST by this optimizer, * but as long as all references to it are replaced, then the enum class itself * will be pruned by subsequent optimizer passes. Some enum classes may not be * completely removed however. Ordinalization can proceed in cases where there * are added static fields or methods in the enum class. In such cases a reduced * version of the original enum class can remain in the AST, containing only * static fields and methods which aren't part of the enum infrastructure (in * which case it will no longer behave as an enum class at all). * * Regardless of whether an ordinalized enum class ends up being completely * pruned away, the AST is expected to be in a coherent and usable state after * any pass of this optimizer. Thus, this optimizer should be considered to be * stateless. * * The process is broken up into 2 separate passes over the AST, each * implemented as a separate visitor class. The first visitor, * {@link EnumOrdinalizer.CannotBeOrdinalAnalyzer} compiles information about * each enum class in the AST, and looks for reasons not to ordinalize each * enum. Thus, it prepares a "black-list" of enum classes that cannot be * ordinalized (and it follows that all enums that don't get entered in the * black-list, will be allowed to be ordinalized. The set of conditions which * cause an enum to be black-listed are outlined below. * * If there are enum classes that didn't get black-listed remaining, the * subsequent passes of the optimizer will be invoked. The first, * {@link ReplaceOrdinalizedEnumTypes}, replaces the type info * for each field or expression involved with a target enum constant with an * integer. Also replaces access to the field {@code ordinal} and to the method * {@code ordinal} with the appropriate ordinal value. */ public class EnumOrdinalizer { /** * A simple Tracker class for compiling lists of enum classes processed by * this optimizer. If enabled, the results can be logged as debug output, and * the results can be tested after running with a given input. */ public static class Tracker { private static final Comparator<SourceInfo> SOURCE_INFO_COMPARATOR = new Comparator<SourceInfo>() { @Override public int compare(SourceInfo s1, SourceInfo s2) { int fileNameComp = s1.getFileName().compareTo(s2.getFileName()); return fileNameComp != 0 ? fileNameComp : s1.getStartLine() - s2.getStartLine(); } }; private final Set<String> allEnumsOrdinalized = Sets.newTreeSet(); private final Set<String> allEnumsVisited = Sets.newTreeSet(); private final Multimap<String, SourceInfo> enumInfoMap = TreeMultimap.create(Ordering.natural(), SOURCE_INFO_COMPARATOR); private final List<Set<String>> enumsOrdinalizedPerPass = Lists.newArrayList(); private final List<Set<String>> enumsVisitedPerPass = Lists.newArrayList(); private int runCount = -1; // use TreeSets, for nice sorted iteration for output public Tracker() { // add entry for initial pass enumsVisitedPerPass.add(new TreeSet<String>()); enumsOrdinalizedPerPass.add(new TreeSet<String>()); } public void addEnumNotOrdinalizedInfo(String enumName, SourceInfo info) { enumInfoMap.put(enumName, info); } public void addOrdinalized(String ordinalized) { enumsOrdinalizedPerPass.get(runCount).add(ordinalized); allEnumsOrdinalized.add(ordinalized); } public void addVisited(String visited) { enumsVisitedPerPass.get(runCount).add(visited); allEnumsVisited.add(visited); } public String getInfoString(SourceInfo info) { if (info == null) { return null; } return info.getFileName() + ": Line " + info.getStartLine(); } public Set<String> getOrdinalizedNames() { return allEnumsOrdinalized; } public int getNumOrdinalized() { return allEnumsOrdinalized.size(); } public int getNumVisited() { return allEnumsVisited.size(); } public void incrementRunCount() { runCount++; enumsVisitedPerPass.add(new TreeSet<String>()); enumsOrdinalizedPerPass.add(new TreeSet<String>()); } public boolean isOrdinalized(String className) { return allEnumsOrdinalized.contains(className); } public boolean isVisited(String className) { return allEnumsVisited.contains(className); } public void logEnumsNotOrdinalized(TreeLogger logger, TreeLogger.Type logType) { if (logger == null) { return; } boolean initialMessageLogged = false; for (String enumVisited : allEnumsVisited) { if (isOrdinalized(enumVisited)) { continue; } if (!initialMessageLogged) { logger = logger.branch(logType, "Enums Not Ordinalized:"); initialMessageLogged = true; } TreeLogger subLogger = logger.branch(logType, enumVisited); for (SourceInfo info : enumInfoMap.get(enumVisited)) { subLogger.branch(logType, getInfoString(info)); } } } public void logEnumsOrdinalizedPerPass(TreeLogger logger, TreeLogger.Type logType) { if (logger == null) { return; } if (allEnumsOrdinalized.size() == 0) { return; } int pass = 0; for (Set<String> enumsOrdinalized : enumsOrdinalizedPerPass) { pass++; if (enumsOrdinalized.size() > 0) { TreeLogger subLogger = logger.branch(logType, "Pass " + pass + ": " + enumsOrdinalized.size() + " ordinalized"); for (String enumOrdinalized : enumsOrdinalized) { subLogger.branch(logType, enumOrdinalized); } } } } public void logResultsDetailed(TreeLogger logger, TreeLogger.Type logType) { logger = logResultsSummary(logger, logType); logEnumsOrdinalizedPerPass(logger, logType); logEnumsNotOrdinalized(logger, logType); } public TreeLogger logResultsSummary(TreeLogger logger, TreeLogger.Type logType) { if (logger == null) { return null; } logger = logger.branch(logType, "EnumOrdinalizer Results:"); logger.branch(logType, (runCount + 1) + " ordinalization passes completed"); logger.branch(logType, allEnumsOrdinalized.size() + " of " + allEnumsVisited.size() + " ordinalized"); return logger; } public void maybeDumpAST(JProgram program, int stage) { AstDumper.maybeDumpAST(program, NAME + "_" + (runCount + 1) + "_" + stage); } } /** * A visitor which keeps track of the enums which cannot be ordinalized. It * does this by keeping track of a "black-list" for ordinals which violate the * conditions for ordinalization, below. * * An enum cannot be ordinalized, if it: * <ul> * <li>is implicitly upcast.</li> * <li>is implicitly cast to from a nullType.</li> * <li>is implicitly cast to or from a javaScriptObject type.</li> * <li>is explicitly cast to another type (or vice-versa).</li> * <li>is tested in an instanceof expression.</li> * <li>it's class literal is used explicitly.</li> * <li>has any field referenced, except for:</li> * <ul> * <li>static fields.</li> * <li>Enum.ordinal.</li> * </ul> * <li>has any method called, except for:</li> * <ul> * <li>ordinal().</li> * <li>Enum.ordinal().</li> * <li>Enum() super constructor.</li> * <li>Enum.createValueOfMap().</li> * <li>static methods other than valueOf().</li> * </ul> * </ul> * * This visitor extends the ImplicitUpcastAnalyzer, which encapsulates all the * conditions where implicit upcasting can occur in an AST. The rest of the * logic for checking ordinalizability is encapsulated in this sub-class. * * It also keeps track of all enums encountered, so we can know if we need to * continue with the other visitors of the optimizer after this visitor runs. * * We make special allowances not to check any code statements that appear * within the ClassLiteralHolder class, which can contain a reference to all * enum class literals in the program, even after ordinalization occurs. * * Also, we ignore visiting the getClass() method for any enum subclass, since * it will also cause a visit to the enum's class literal, and we don't * necessarily want to prevent ordinalization in that case. * * Special checking is needed to detect a class literal reference that occurs * within a JSNI method body. We don't get a visit to JClassLiteral in that * case, so we need to inspect visits to JsniFieldRef for the possibility it * might be a reference to a class literal. * * We also skip any checking in a method call to Enum.createValueOfMap(), * since this is generated for any enum class initially within the extra * enumClass$Map class, and this call contains an implicit upcast in the * method call args (as well as a reference to the static enumClass$VALUES * field), which we want to ignore. The enumClass$Map class will not get * pruned as long as the enumClass is not ordinalized, and so we need to * ignore it's presence in the consideration for whether an enum class is * ordinalizable. */ private class CannotBeOrdinalAnalyzer extends ImplicitUpcastAnalyzer { public CannotBeOrdinalAnalyzer(JProgram program) { super(program); } @Override public void endVisit(JCastOperation x, Context ctx) { // check for explicit cast (check both directions) blackListIfEnumCast(x.getExpr().getType(), x.getCastType(), x.getSourceInfo()); blackListIfEnumCast(x.getCastType(), x.getExpr().getType(), x.getSourceInfo()); } @Override public void endVisit(JClassLiteral x, Context ctx) { /* * Check for references to an enum's class literal. We need to black-list * classes in this case, since there could be a call to * Enum.valueOf(someEnum.class,"name"), etc. * * Note: we won't get here for class literals that occur in the * ClassLiteralHolder class, or within the getClass method of an enum * class (see comments above). */ blackListIfEnum(x.getRefType(), x.getSourceInfo()); } @Override public void endVisit(JClassType x, Context ctx) { // keep track of all enum classes visited JEnumType maybeEnum = x.isEnumOrSubclass(); if (maybeEnum == null) { return; } enumsVisited.add(maybeEnum); // don't need to re-ordinalize a previously ordinalized enum if (maybeEnum.isOrdinalized()) { addToBlackList(maybeEnum, x.getSourceInfo()); } } @Override public void endVisit(JFieldRef x, Context ctx) { // don't need to check Enum.ordinal if (x.getField() == enumOrdinalField) { return; } if (x.getInstance() != null) { // check any instance field reference other than ordinal blackListIfEnumExpression(x.getInstance()); } } @Override public void endVisit(JInstanceOf x, Context ctx) { // If any instanceof tests haven't been optimized out, black list. blackListIfEnum(x.getExpr().getType(), x.getSourceInfo()); // TODO (jbrosenberg): Investigate further whether ordinalization can be // allowed in this case. blackListIfEnum(x.getTestType(), x.getSourceInfo()); } @Override public void endVisit(JMethodCall x, Context ctx) { // exempt calls to certain methods on the Enum super class if (x.getTarget() == enumCreateValueOfMapMethod || x.getTarget() == enumSuperConstructor || x.getTarget() == enumOrdinalMethod) { return; } // any other method on an enum class should cause it to be black-listed if (x.getInstance() != null) { blackListIfEnumExpression(x.getInstance()); } else if (x.getTarget().isStatic()) { // black-list static method calls on an enum class only for valueOf() // and values() JMethod target = x.getTarget(); maybeBlackListDueToStaticCall(x.getSourceInfo(), target); } if (x.getTarget().isJsniMethod()) { // Black list enum types declared in parameters of native functions. for (JParameter parameter :x.getTarget().getParams()) { blackListIfEnum(parameter.getType(), x.getSourceInfo()); } } // defer to ImplicitUpcastAnalyzer to check method call args & params super.endVisit(x, ctx); } @Override public void endVisit(JsniClassLiteral x, Context ctx) { /* * Check for references to an enum's class literal. We need to black-list * classes in this case, since there could be a call to * Enum.valueOf(someEnum.class,"name"), etc. * * Note: we won't get here for class literals that occur in the * ClassLiteralHolder class, or within the getClass method of an enum * class (see comments above). */ blackListIfEnum(x.getRefType(), x.getSourceInfo()); } @Override public void endVisit(JsniFieldRef x, Context ctx) { /* * Can't do the same thing as for JFieldRef, all JsniFieldRefs are cast to * JavaScriptObjects. Need to check both the field type and the type of * the instance or enclosing class referencing the field. */ // check the field type blackListIfEnum(x.getField().getType(), x.getSourceInfo()); // check the referrer if (x.getInstance() != null) { blackListIfEnumExpression(x.getInstance()); } else { blackListIfEnum(x.getField().getEnclosingType(), x.getSourceInfo()); } } @Override public void endVisit(JsniMethodRef x, Context ctx) { // no enum methods are exempted if occur within a JsniMethodRef if (x.getInstance() != null) { blackListIfEnumExpression(x.getInstance()); } else if (x.getTarget().isStatic()) { maybeBlackListDueToStaticCall(x.getSourceInfo(), x.getTarget()); } // Black list enums returned to JSNI. blackListIfEnum(x.getTarget().getType(), x.getSourceInfo()); // defer to ImplicitUpcastAnalyzer to check method call args & params super.endVisit(x, ctx); } @Override public boolean visit(JNewArray x, Context ctx) { // Do not visit the implicit Class literal. if (x.getDimensionExpressions() != null) { accept(x.getDimensionExpressions()); } if (x.getInitializers() != null) { accept(x.getInitializers()); } return false; } @Override public boolean visit(JClassType x, Context ctx) { /* * Don't want to visit the large ClassLiteralHolder class, it doesn't * contain references to actual usage of enum class literals. It's also a * time savings to not traverse this class. */ if (x == classLiteralHolderType) { return false; } return true; } @Override public boolean visit(JMethod x, Context ctx) { /* * Don't want to visit the generated getClass() method on an enum, since * it contains a reference to an enum's class literal that we don't want * to consider. Make sure this is not a user overloaded version (i.e. * check that it has no params). */ if (x.getEnclosingType().isEnumOrSubclass() != null && x.getName().equals("getClass") && (x.getOriginalParamTypes() == null || x.getOriginalParamTypes().size() == 0)) { return false; } // defer to parent method on ImplicitCastAnalyzer return super.visit(x, ctx); } @Override public boolean visit(JMethodCall x, Context ctx) { /* * Skip all calls to Enum.createValueOfMap, since they'd get falsely * flagged for implicitly upcasting an array of an enum class, in the * arg passing. This method is only used by the enumClass$Map class to * support Enum.valueOf(Class,String), which if reachable already prevents * the enum from being ordinalized. Once ordinalization proceeds, * this $Map class should be pruned. */ if (x.getTarget() == enumCreateValueOfMapMethod) { return false; } // ok to visit return true; } /* * Override for the method called from ImplicitUpcastAnalyzer, which will be * called for any implicit upcast. */ @Override protected void processImplicitUpcast(JType fromType, JType destType, SourceInfo info) { if (fromType.isNullType()) { // handle case where a nullType is cast to an enum blackListIfEnum(destType, info); } else if (fromType == javaScriptObjectType) { // handle case where a javaScriptObject is cast to an enum blackListIfEnum(destType, info); } else { blackListIfEnumCast(fromType, destType, info); } } private void addToBlackList(JEnumType enumType, SourceInfo info) { ordinalizationBlackList.add(enumType); if (tracker != null) { tracker.addEnumNotOrdinalizedInfo(enumType.getName(), info); } } private void blackListIfEnum(JType maybeEnum, SourceInfo info) { JEnumType actualEnum = maybeEnum.isEnumOrSubclass(); if (actualEnum != null) { addToBlackList(actualEnum, info); } } private void blackListIfEnumCast(JType maybeEnum, JType destType, SourceInfo info) { JEnumType actualEnum = maybeEnum.isEnumOrSubclass(); JEnumType actualDestType = destType.isEnumOrSubclass(); if (actualEnum != null) { if (actualDestType != actualEnum) { addToBlackList(actualEnum, info); } return; } // check JArrayTypes of enums actualEnum = getEnumTypeFromArrayLeafType(maybeEnum); actualDestType = getEnumTypeFromArrayLeafType(destType); if (actualEnum != null) { if (actualDestType != actualEnum) { addToBlackList(actualEnum, info); } } } private void blackListIfEnumExpression(JExpression instance) { if (instance != null) { blackListIfEnum(instance.getType(), instance.getSourceInfo()); } } /** * Blacklist the enum if there is a call to MyEnum.valueOf(). */ private void maybeBlackListDueToStaticCall(SourceInfo info, JMethod target) { if (target.getEnclosingType().isEnumOrSubclass() != null && (target.getName().equals("valueOf"))) { blackListIfEnum(target.getEnclosingType(), info); } } } /** * A visitor which replaces enum types with an integer. * * It sub-classes TypeRemapper, which encapsulates all the locations for a * settable type. The overridden remap() method will be called in each * instance, and it will test whether the type is a candidate for replacement, * and if so, return the new type to set (JPrimitiveType.INT). * <p> * Any reference to an enum field constant in an expression is replaced with * integer. * <p> * This will also explicitly replace an enum's field constants with its * ordinal int values; also replaces access to the ordinal field and class to * the ordinal() method with the corresponding int value. */ private class ReplaceOrdinalizedEnumTypes extends TypeRemapper { public ReplaceOrdinalizedEnumTypes(OptimizerContext optimizerCtx) { super(optimizerCtx); } @Override public boolean visit(JClassType x, Context ctx) { if (canBeOrdinal(x)) { /* * Remove any static impl mappings for any methods in an ordinal enum * class. An ordinalized enum will no longer have an instance passed as * the first argument for a static impl (it will just be an int). This * is needed to preserve proper assumptions about static impls by other * optimizers (e.g. we might need to insert a clinit, when it wouldn't * be needed if a method call still had a static impl target). */ for (JMethod method : x.getMethods()) { program.removeStaticImplMapping(method); } } return true; } @Override public void endVisit(JFieldRef x, Context ctx) { super.endVisit(x, ctx); JField field = x.getField(); if (field instanceof JEnumField && canBeOrdinal(field.getEnclosingType()) && !ctx.isLvalue()) { int ordinal = ((JEnumField) field).ordinal(); ctx.replaceMe(program.getLiteralInt(ordinal)); return; } maybeReplaceOrdinalAccess(x.getInstance(), field, ctx); } @Override public void endVisit(JMethodCall x, Context ctx) { super.endVisit(x, ctx); maybeReplaceOrdinalAccess(x.getInstance(),x.getTarget(), ctx); } @Override public void endVisit(JDeclarationStatement x, Context ctx) { super.endVisit(x, ctx); if (!(x.getVariableRef().getTarget() instanceof JEnumField)) { return; } JEnumField enumField = (JEnumField) x.getVariableRef().getTarget(); if (!(canBeOrdinal(enumField.getEnclosingType()))) { return; } // Also replace the declaration statement so that the enum instances are not constructed; // eventually the field will be pruned but the declaration statement will only completely // disappear if there are no side effects (constructing a new instance counts as side // effects). ctx.replaceMe(new JDeclarationStatement(x.getSourceInfo(), x.getVariableRef(), program.getLiteralInt(enumField.ordinal()))); } /** * Remap enum types with JPrimitiveType.INT. Also handle case for arrays, * replace enum leaftype with JPrimitive.INT. This is an override * implementation called from TypeRemapper. */ @Override protected JType remap(JType type) { JType remappedType = getOrdinalizedType(type); if (remappedType == null) { return type; } return remappedType; } private boolean canBeOrdinal(JType type) { JType uType = type.getUnderlyingType(); return uType instanceof JEnumType && !ordinalizationBlackList.contains(uType); } private JType getOrdinalizedType(JType type) { if (canBeOrdinal(type)) { return JPrimitiveType.INT; } JType uType = type.getUnderlyingType(); if (!(uType instanceof JArrayType)) { return null; } JArrayType aType = (JArrayType) uType; JType leafType = aType.getLeafType(); if (canBeOrdinal(leafType)) { JArrayType newAType = program.getOrCreateArrayType(JPrimitiveType.INT, aType.getDims()); return !type.canBeNull() ? newAType.strengthenToNonNull() : newAType; } return null; } private void maybeReplaceOrdinalAccess(JExpression instance, JNode member, Context ctx) { if (member != enumOrdinalField && member != enumOrdinalMethod) { return; } JType instanceType = instance.getType(); // Access to x.ordinal and x.ordinal() need to be replaced by x if the type was ordinalized. // Ordinalized types in this stage can also appear as primitive ints. if (instanceType == JPrimitiveType.INT || canBeOrdinal(instanceType)) { ctx.replaceMe(instance); } } } private static final String NAME = EnumOrdinalizer.class.getSimpleName(); private static Tracker tracker = null; private static boolean trackerEnabled = (System.getProperty("gwt.enableEnumOrdinalizerTracking") != null); public static void enableTracker() { trackerEnabled = true; } public static OptimizerStats exec(JProgram program, OptimizerContext optimizerCtx) { Event optimizeEvent = SpeedTracerLogger.start(CompilerEventType.OPTIMIZE, "optimizer", NAME); startTracker(); OptimizerStats stats = new EnumOrdinalizer(program).execImpl(optimizerCtx); optimizerCtx.incOptimizationStep(); optimizeEvent.end("didChange", "" + stats.didChange()); return stats; } public static Tracker getTracker() { return tracker; } public static void resetTracker() { if (tracker != null) { tracker = null; startTracker(); } } private static void startTracker() { if (trackerEnabled && tracker == null) { tracker = new Tracker(); } } private final JType classLiteralHolderType; private final JMethod enumCreateValueOfMapMethod; private final JField enumOrdinalField; private final JMethod enumOrdinalMethod; private final JMethod enumSuperConstructor; private final Set<JEnumType> enumsVisited = Sets.newHashSet(); private final JType javaScriptObjectType; private final Set<JEnumType> ordinalizationBlackList = Sets.newHashSet(); private final JProgram program; public EnumOrdinalizer(JProgram program) { this.program = program; this.classLiteralHolderType = program.getTypeClassLiteralHolder(); this.javaScriptObjectType = program.getJavaScriptObject(); this.enumOrdinalField = program.getIndexedField(RuntimeConstants.ENUM_ORDINAL); this.enumCreateValueOfMapMethod = program.getIndexedMethod(RuntimeConstants.ENUM_CREATE_VALUE_OF_MAP); this.enumOrdinalMethod = program.getIndexedMethod(RuntimeConstants.ENUM_ORDINAL); this.enumSuperConstructor = program.getIndexedMethod(RuntimeConstants.ENUM_ENUM); } private OptimizerStats execImpl(OptimizerContext optimizerCtx) { OptimizerStats stats = new OptimizerStats(NAME); if (tracker != null) { tracker.incrementRunCount(); tracker.maybeDumpAST(program, 0); } // Create black list of enum refs which can't be converted to an ordinal ref CannotBeOrdinalAnalyzer ordinalAnalyzer = new CannotBeOrdinalAnalyzer(program); ordinalAnalyzer.accept(program); // Bail if we don't need to do any ordinalization if (enumsVisited.size() == ordinalizationBlackList.size()) { // Update tracker stats if (tracker != null) { for (JEnumType type : enumsVisited) { tracker.addVisited(type.getName()); } } return stats; } // Replace enum type refs ReplaceOrdinalizedEnumTypes replaceEnums = new ReplaceOrdinalizedEnumTypes(optimizerCtx); replaceEnums.accept(program); stats.recordModified(replaceEnums.getNumMods()); if (tracker != null) { tracker.maybeDumpAST(program, 1); } // Update enums ordinalized, and tracker stats for (JEnumType type : enumsVisited) { if (tracker != null) { tracker.addVisited(type.getName()); } if (!ordinalizationBlackList.contains(type)) { if (tracker != null) { tracker.addOrdinalized(type.getName()); } type.setOrdinalized(); } } JavaAstVerifier.assertProgramIsConsistent(program); return stats; } private JEnumType getEnumTypeFromArrayLeafType(JType type) { type = type.getUnderlyingType(); if (type instanceof JArrayType) { type = ((JArrayType) type).getLeafType(); return type.isEnumOrSubclass(); } return null; } }