/* * Copyright (C) 2009-2011 Mathias Doenitz * * 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.github.fge.grappa.transform; import com.github.fge.grappa.transform.base.ParserClassNode; import com.github.fge.grappa.transform.base.RuleMethod; import com.github.fge.grappa.transform.load.ReflectiveClassLoader; import com.github.fge.grappa.transform.generate.ActionClassGenerator; import com.github.fge.grappa.transform.generate.ClassNodeInitializer; import com.github.fge.grappa.transform.generate.ConstructorGenerator; import com.github.fge.grappa.transform.generate.VarInitClassGenerator; import com.github.fge.grappa.transform.process.BodyWithSuperCallReplacer; import com.github.fge.grappa.transform.process.CachingGenerator; import com.github.fge.grappa.transform.process.ImplicitActionsConverter; import com.github.fge.grappa.transform.process.InstructionGraphCreator; import com.github.fge.grappa.transform.process.InstructionGroupCreator; import com.github.fge.grappa.transform.process.InstructionGroupPreparer; import com.github.fge.grappa.transform.process.LabellingGenerator; import com.github.fge.grappa.transform.process.ReturnInstructionUnifier; import com.github.fge.grappa.transform.process.RuleMethodProcessor; import com.github.fge.grappa.transform.process.RuleMethodRewriter; import com.github.fge.grappa.transform.process.SuperCallRewriter; import com.github.fge.grappa.transform.process.UnusedLabelsRemover; import com.github.fge.grappa.transform.process.VarFramingGenerator; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import org.objectweb.asm.ClassWriter; import java.util.List; import java.util.Objects; import static com.github.fge.grappa.misc.AsmUtils.getExtendedParserClassName; public final class ParserTransformer { private ParserTransformer() { } // TODO: remove "synchronized" here // TODO: move elsewhere public static synchronized <T> Class<? extends T> transformParser( final Class<T> parserClass) throws Exception { Objects.requireNonNull(parserClass, "parserClass"); // first check whether we did not already create and load the extension // of the given parser class final String name = getExtendedParserClassName(parserClass.getName()); final Class<?> extendedClass; try ( final ReflectiveClassLoader loader = new ReflectiveClassLoader(parserClass.getClassLoader()); ) { extendedClass = loader.findClass(name); } final Class<?> ret = extendedClass != null ? extendedClass : extendParserClass(parserClass).getExtendedClass(); return (Class<? extends T>) ret; } /** * Dump the bytecode of a transformed parser class * * <p>This method will run all bytecode transformations on the parser class * then return a dump of the bytecode as a byte array.</p> * * @param parserClass the parser class * @return a bytecode dump * * @throws Exception FIXME * @see #extendParserClass(Class) */ // TODO: poor exception specification public static byte[] getByteCode(final Class<?> parserClass) throws Exception { final ParserClassNode node = extendParserClass(parserClass); return node.getClassCode(); } @VisibleForTesting public static ParserClassNode extendParserClass(final Class<?> parserClass) throws Exception { final ParserClassNode classNode = new ParserClassNode(parserClass); new ClassNodeInitializer().process(classNode); runMethodTransformers(classNode); new ConstructorGenerator().process(classNode); defineExtendedParserClass(classNode); return classNode; } // TODO: poor exception handling again private static void runMethodTransformers(final ParserClassNode classNode) throws Exception { final List<RuleMethodProcessor> methodProcessors = createRuleMethodProcessors(); // TODO: comment above may be right, but it's still dangerous // iterate through all rule methods // since the ruleMethods map on the classnode is a treemap we get the // methods sorted by name which puts all super methods first (since they // are prefixed with one or more '$') for (final RuleMethod ruleMethod: classNode.getRuleMethods().values()) { if (ruleMethod.hasDontExtend()) continue; for (final RuleMethodProcessor methodProcessor : methodProcessors) if (methodProcessor.appliesTo(classNode, ruleMethod)) methodProcessor.process(classNode, ruleMethod); } for (final RuleMethod ruleMethod: classNode.getRuleMethods().values()) if (!ruleMethod.isGenerationSkipped()) classNode.methods.add(ruleMethod); } private static List<RuleMethodProcessor> createRuleMethodProcessors() { return ImmutableList.of( new UnusedLabelsRemover(), new ReturnInstructionUnifier(), new InstructionGraphCreator(), new ImplicitActionsConverter(), new InstructionGroupCreator(), new InstructionGroupPreparer(), new ActionClassGenerator(false), new VarInitClassGenerator(false), new RuleMethodRewriter(), new SuperCallRewriter(), new BodyWithSuperCallReplacer(), new VarFramingGenerator(), new LabellingGenerator(), new CachingGenerator() ); } private static void defineExtendedParserClass(final ParserClassNode node) { final ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES); node.accept(writer); node.setClassCode(writer.toByteArray()); final String className = node.name.replace('/', '.'); final byte[] bytecode = node.getClassCode(); final ClassLoader classLoader = node.getParentClass().getClassLoader(); final Class<?> extendedClass; try ( final ReflectiveClassLoader loader = new ReflectiveClassLoader(classLoader); ) { extendedClass = loader.loadClass(className, bytecode); } node.setExtendedClass(extendedClass); } }