TemplateMatcher.java :  » Code-Analyzer » Spoon » spoon » template » Java Open Source

Java Open Source » Code Analyzer » Spoon 
Spoon » spoon » template » TemplateMatcher.java
/*
 * Spoon - http://spoon.gforge.inria.fr/
 * Copyright (C) 2006 INRIA Futurs <renaud.pawlak@inria.fr>
 *
 * This software is governed by the CeCILL-C License under French law and
 * abiding by the rules of distribution of free software. You can use, modify
 * and/or redistribute the software under the terms of the CeCILL-C license as
 * circulated by CEA, CNRS and INRIA at http://www.cecill.info.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the CeCILL-C License for more details.
 *
 * The fact that you are presently reading this means that you have had
 * knowledge of the CeCILL-C license and that you accept its terms.
 */

package spoon.template;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import spoon.reflect.code.CtFieldAccess;
import spoon.reflect.code.CtInvocation;
import spoon.reflect.code.CtStatementList;
import spoon.reflect.declaration.CtClass;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtField;
import spoon.reflect.declaration.CtNamedElement;
import spoon.reflect.declaration.CtParameter;
import spoon.reflect.declaration.CtVariable;
import spoon.reflect.reference.CtExecutableReference;
import spoon.reflect.reference.CtFieldReference;
import spoon.reflect.reference.CtPackageReference;
import spoon.reflect.reference.CtReference;
import spoon.reflect.reference.CtTypeParameterReference;
import spoon.reflect.reference.CtTypeReference;
import spoon.reflect.visitor.CtScanner;
import spoon.reflect.visitor.Query;
import spoon.reflect.visitor.filter.InvocationFilter;
import spoon.support.template.DefaultParameterMatcher;
import spoon.support.template.ParameterMatcher;
import spoon.support.template.Parameters;
import spoon.support.util.RtHelper;

/**
 * This class defines an engine for matching a template to pieces of code.
 */
public class TemplateMatcher {

  private static List<CtInvocation<?>> getMethods(
      CtClass<? extends Template> root) {
    CtExecutableReference<?> methodRef = root.getFactory().Executable()
        .createReference(
            root.getFactory().Type().createReference(
                TemplateParameter.class),
            root.getFactory().Type().createTypeParameterReference(
                "T"), "S");
    List<CtInvocation<?>> meths = Query.getElements(root,
        new InvocationFilter(methodRef));

    return meths;
  }

  private static List<String> getTemplateNameParameters(
      CtClass<? extends Template> templateType) {
    final List<String> ts = new ArrayList<String>();
    final Collection<String> c = Parameters.getNames(templateType);
    ts.addAll(c);
    return ts;
  }

  private static List<CtTypeReference<?>> getTemplateTypeParameters(
      final CtClass<? extends Template> templateType) {

    final List<CtTypeReference<?>> ts = new ArrayList<CtTypeReference<?>>();
    final Collection<String> c = Parameters.getNames(templateType);
    new CtScanner() {
      @Override
      public void visitCtTypeParameterReference(
          CtTypeParameterReference reference) {
        if (c.contains(reference.getSimpleName())) {
          ts.add(reference);
        }
      };

      @Override
      public <T> void visitCtTypeReference(CtTypeReference<T> reference) {
        if (c.contains(reference.getSimpleName())) {
          ts.add(reference);
        }
      }

    }.scan(templateType);
    return ts;
  }

  @SuppressWarnings("unchecked")
  private static List<CtFieldReference<?>> getVarargs(
      CtClass<? extends Template> root, List<CtInvocation<?>> variables) {
    List<CtFieldReference<?>> fields = new ArrayList<CtFieldReference<?>>();
    for (CtFieldReference field : root.getReference().getAllFields()) {
      if (field.getType().getActualClass() == CtStatementList.class) {
        boolean alreadyAdded = false;
        for (CtInvocation<?> invocation : variables) {
          alreadyAdded |= ((CtFieldAccess) invocation.getTarget())
              .getVariable().getDeclaration().equals(field);
        }
        if (!alreadyAdded) {
          fields.add(field);
        }
      }
    }
    return fields;
  }

  private List<CtElement> finds = new ArrayList<CtElement>();

  private boolean found;

  private Map<Object, Object> matches = new HashMap<Object, Object>();

  private List<String> names;

  private CtClass<? extends Template> templateType;

  private List<CtTypeReference<?>> typeVariables;

  private List<CtFieldReference<?>> varArgs;

  private List<CtInvocation<?>> variables;

  /**
   * Constructs a matcher for a given template.
   *
   * @param templateType
   *            the type of the template
   */
  public TemplateMatcher(CtClass<? extends Template> templateType) {
    if (templateType == null) {
      throw new TemplateException(
          "Template type is null. Use the template factory to access a template CtClass and check your template source path.");
    }
    variables = TemplateMatcher.getMethods(templateType);
    typeVariables = TemplateMatcher.getTemplateTypeParameters(templateType);
    names = TemplateMatcher.getTemplateNameParameters(templateType);
    varArgs = TemplateMatcher.getVarargs(templateType, variables);
    this.templateType = templateType;
  }

  private boolean addMatch(Object template, Object target) {
    Object inv = matches.get(template);
    Object o = matches.put(template, target);
    return (null == inv) || inv.equals(o);
  }

  private CtElement checkListStatements(List<?> teList) {
    for (Object tem : teList) {
      if (variables.contains(tem) && (tem instanceof CtInvocation)) {
        CtInvocation<?> listCand = (CtInvocation<?>) tem;
        boolean ok = listCand.getFactory().Type().createReference(
            TemplateParameterList.class).isAssignableFrom(
            listCand.getTarget().getType());
        return ok ? listCand : null;
      }
      if (tem instanceof CtVariable) {
        CtVariable<?> var = (CtVariable<?>) tem;
        String name = var.getSimpleName();
        for (CtFieldReference<?> f : varArgs) {
          if (f.getSimpleName().equals(name)) {
            return f.getDeclaration();
          }
        }
      }
    }

    return null;
  }

  /**
   * Finds all target program sub-trees that correspond to a template. Once
   * this method has been called, {@link #getFinds()} will give the mathing
   * CtElements if any.
   *
   * @param targetRoot
   *            the target to be tested for match
   * @param templateRoot
   *            the template to match against
   * @return true if there is one or more matches
   *
   * @see #getFinds()
   */
  public boolean find(CtElement targetRoot, final CtElement templateRoot) {
    found = false;
    new CtScanner() {
      @Override
      public void scan(CtElement element) {
        if (match(element, templateRoot)) {
          finds.add(element);
          found = true;
          // matches.clear();
        }
        super.scan(element);
      }
    }.scan(targetRoot);

    return found;
  }

  private ParameterMatcher findParameterMatcher(CtElement declaration,
      String name) throws InstantiationException, IllegalAccessException {
    if (declaration == null) {
      return new DefaultParameterMatcher();
    }
    CtClass<?> clazz = declaration.getParent(CtClass.class);
    if (clazz == null) {
      return new DefaultParameterMatcher();
    }

    Collection<CtFieldReference<?>> fields = clazz.getReference()
        .getAllFields();

    CtFieldReference<?> param = null;
    for (CtFieldReference<?> field : fields) {
      Parameter p = field.getAnnotation(Parameter.class);
      if (p == null) {
        continue; // not a parameter.
      }
      String proxy = p.value();
      if (proxy != "") {
        if (name.contains(proxy)) {
          param = field;
          break;
        }
      }

      if (name.contains(field.getSimpleName())) {
        param = field;
        break;
      }
      // todo: check for field hack.
    }

    if (param == null) {
      throw new IllegalStateException("Parameter not defined " + name
          + "at " + declaration.getPosition());
    }
    return getParameterInstance(param);
  }

  @SuppressWarnings("unused")
  private String getBindedParameter(String pname) {
    final String[] x = new String[1]; // HACK! jeje
    x[0] = pname;
    new CtScanner() {
      @Override
      public <T> void visitCtField(CtField<T> f) {
        Parameter p = f.getAnnotation(Parameter.class);
        if ((p != null) && p.value().equals(x[0])) {
          x[0] = f.getSimpleName();
          return;
        }
        super.visitCtField(f);
      }
    }.scan(templateType);

    return x[0];
  }

  /**
   * Returns all the elements that correspond to a given template. The
   * {@link #find(CtElement, CtElement)} method must have been called before
   */
  public List<CtElement> getFinds() {
    return finds;
  }

  /**
   * Returns all the matches in a map where the keys are the corresponding
   * template parameters. The {@link #match(CtElement, CtElement)} method must
   * have been called before.
   */
  public Map<Object, Object> getMatches() {
    return matches;
  }

  private ParameterMatcher getParameterInstance(CtFieldReference<?> param)
      throws InstantiationException, IllegalAccessException {
    Parameter anParam = param.getAnnotation(Parameter.class);
    if (anParam == null) {
      // Parameter not annotated. Probably is a TemplateParameter. Just
      // return a default impl
      return new DefaultParameterMatcher();
    }
    Class<? extends ParameterMatcher> pm = anParam.match();
    ParameterMatcher instance = pm.newInstance();
    return instance;
  }

  /*
   * Made private to hide the Objects.
   */
  @SuppressWarnings("unchecked")
  private boolean helperMatch(Object target, Object template) {
    if ((target == null) && (template == null)) {
      return true;
    }
    if ((target == null) || (template == null)) {
      return false;
    }
    if (variables.contains(template) || typeVariables.contains(template)) {
      // TODO: upcall the parameter matcher if defined
      boolean add = invokeCallBack(target, template);
      if (add) {
        return addMatch(template, target);
      }
      return false;
    }
    if (target.getClass() != template.getClass()) {
      return false;
    }
    if ((template instanceof CtTypeReference)
        && template.equals(templateType.getReference())) {
      return true;
    }
    if ((template instanceof CtPackageReference)
        && template.equals(templateType.getPackage())) {
      return true;
    }
    if (template instanceof CtReference) {
      CtReference tRef = (CtReference) template;
      boolean ok = matchNames(tRef.getSimpleName(),
          ((CtReference) target).getSimpleName());
      if (ok && !template.equals(target)) {
        boolean remove = !invokeCallBack(target, template);
        if (remove) {
          matches.remove(tRef.getSimpleName());
          return false;
        }
        return true;
      }
    }

    if (template instanceof CtNamedElement) {
      CtNamedElement named = (CtNamedElement) template;
      boolean ok = matchNames(named.getSimpleName(),
          ((CtNamedElement) target).getSimpleName());
      if (ok && !template.equals(target)) {
        boolean remove = !invokeCallBack(target, template);
        if (remove) {
          matches.remove(named.getSimpleName());
          return false;
        }
      }
    }

    if (template instanceof Collection) {
      return matchCollections((Collection) target, (Collection) template);
    }

    if (template instanceof Map) {
      if (template.equals(target)) {
        return true;
      }

      Map temMap = (Map) template;
      Map tarMap = (Map) target;

      if (!temMap.keySet().equals(tarMap.keySet())) {
        return false;
      }

      return matchCollections(tarMap.values(), temMap.values());

    }

    if ((target instanceof CtElement) || (target instanceof CtReference)) {
      for (Field f : RtHelper.getAllFields(target.getClass())) {
        f.setAccessible(true);
        if (Modifier.isStatic(f.getModifiers())) {
          continue;
        }
        if (f.getName().equals("parent")) {
          continue;
        }
        if (f.getName().equals("position")) {
          continue;
        }
        if (f.getName().equals("docComment")) {
          continue;
        }
        if (f.getName().equals("factory")) {
          continue;
        }
        try {
          if (!helperMatch(f.get(target), f.get(template))) {
            return false;
          }
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
      return true;
    } else if (target instanceof String) {
      return matchNames((String) template, (String) target);
    } else {
      return target.equals(template);
    }
  }

  @SuppressWarnings("unchecked")
  private boolean invokeCallBack(Object target, Object template) {
    try {
      if (template instanceof CtInvocation) {
        CtFieldAccess<?> param = (CtFieldAccess) ((CtInvocation<?>) template)
            .getTarget();
        ParameterMatcher instance = getParameterInstance(param
            .getVariable());
        return instance.match(this, (CtInvocation) template,
            (CtElement) target);
      } else if (template instanceof CtReference) {
        // Get parameter
        CtReference ref = (CtReference) template;
        Parameter param = ref.getAnnotation(Parameter.class);
        ParameterMatcher instance;
        if (param == null) {
          instance = new DefaultParameterMatcher();
        } else {
          instance = param.match().newInstance();
        }
        return instance.match(this, (CtReference) template,
            (CtReference) target);
      } else if (template instanceof CtNamedElement) {
        CtNamedElement named = (CtNamedElement) template;
        ParameterMatcher instance = findParameterMatcher(named, named
            .getSimpleName());
        return instance.match(this, (CtElement) template,
            (CtElement) target);
      }

      else {
        // Should not happen
        throw new RuntimeException();
      }
    } catch (InstantiationException e) {
      e.printStackTrace();
      return true;
    } catch (IllegalAccessException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
      return true;
    }
  }

  private boolean isCurrentTemplate(Object object, CtElement inMulti) {
    if (object instanceof CtInvocation<?>) {
      return object.equals(inMulti);
    }
    if (object instanceof CtParameter) {
      CtParameter<?> param = (CtParameter<?>) object;
      for (CtFieldReference<?> varArg : varArgs) {
        if (param.getSimpleName().equals(varArg.getSimpleName())) {
          return varArg.equals(inMulti);
        }
      }
    }
    return false;
  }

  /**
   * Matches a target program sub-tree against a template. Once this method
   * has been called, {@link #getMatches()} will give the mathing parts if
   * any.
   *
   * @param targetRoot
   *            the target to be tested for match
   * @param templateRoot
   *            the template to match against
   * @return true if matches
   *
   * @see #getMatches()
   */
  public boolean match(CtElement targetRoot, CtElement templateRoot) {
    return helperMatch(targetRoot, templateRoot);
  }

  @SuppressWarnings("unchecked")
  private boolean matchCollections(Collection target, Collection template) {
    List teList = new ArrayList(template);
    List taList = new ArrayList(target);

    int numOfNonParamsinTeList = teList.size();

    // inMulti keeps the multiElement templateVariable we are at
    CtElement inMulti = nextListStatement(teList, null);

    // multi keeps the values to assign to inMulti
    List<CtElement> multi = new ArrayList();

    if (null == inMulti) {
      // If we are not looking at template with multiElements
      // the sizes should then be the same
      if (teList.size() != taList.size()) {
        return false;
      }

      for (int te = 0, ta = 0; (te < teList.size())
          && (ta < taList.size()); te++, ta++) {
        if (!helperMatch(taList.get(ta), teList.get(te))) {
          return false;
        }
      }
      return true;
    }
    for (int te = 0, ta = 0; (te < teList.size()) && (ta < taList.size()); te++, ta++) {

      if (isCurrentTemplate(teList.get(te), inMulti)) {
        numOfNonParamsinTeList--;
        if (te + 1 >= teList.size()) {
          multi.addAll(taList.subList(te, taList.size()));
          CtStatementList tpl = templateType.getFactory().Core()
              .createStatementList();
          tpl.setStatements(multi);
          if (!invokeCallBack(tpl, inMulti)) {
            return false;
          }
          boolean ret = addMatch(inMulti, multi);
          return ret;
        }
        te++;
        while ((te < teList.size()) && (ta < taList.size())
            && !helperMatch(taList.get(ta), teList.get(te))) {
          multi.add((CtElement) taList.get(ta));
          ta++;
        }
        CtStatementList tpl = templateType.getFactory().Core()
            .createStatementList();
        tpl.setStatements(multi);
        if (!invokeCallBack(tpl, inMulti)) {
          return false;
        }
        addMatch(inMulti, tpl);
        // update inMulti
        inMulti = nextListStatement(teList, inMulti);
        multi = new ArrayList();
        numOfNonParamsinTeList--;
      } else {
        if (!helperMatch(taList.get(ta), teList.get(te))) {
          return false;
        }
        if (!(ta + 1 < taList.size()) && (inMulti != null)) {
          CtStatementList tpl = templateType.getFactory().Core()
              .createStatementList();
          tpl.setStatements(multi);
          if (!invokeCallBack(tpl, inMulti)) {
            return false;
          }
          addMatch(inMulti, tpl);
          // update inMulti
          inMulti = nextListStatement(teList, inMulti);
          multi = new ArrayList();
          numOfNonParamsinTeList--;
        }
      }
    }
    return true;
  }

  private boolean matchNames(String name, String tname) {

    try {
      for (String pname : names) {
        // pname = pname.replace("_FIELD_", "");
        if (name.contains(pname)) {
          String newName = name.replace(pname, "(.*)");
          Pattern p = Pattern.compile(newName);
          Matcher m = p.matcher(tname);
          if (!m.matches()) {
            return false;
          }
          // TODO: fix with parameter from @Parameter
          // boolean ok = addMatch(getBindedParameter(pname),
          // m.group(1));
          boolean ok = addMatch(pname, m.group(1));
          if (!ok) {
            System.out.println("incongruent match");
            return false;
          }
          return true;
        }
      }
    } catch (RuntimeException e) {
      // //fall back on dumb way to do it.
      // if
    }
    return name.equals(tname);
  }

  private CtElement nextListStatement(List<?> teList, CtElement inMulti) {
    if (inMulti == null) {
      return checkListStatements(teList);
    }
    List<?> teList2 = new ArrayList<Object>(teList);
    if (inMulti instanceof CtInvocation) {
      teList2.remove(inMulti);
    } else if (inMulti instanceof CtVariable) {
      CtVariable<?> var = (CtVariable<?>) inMulti;
      for (Iterator<?> iter = teList2.iterator(); iter.hasNext();) {
        CtVariable<?> teVar = (CtVariable<?>) iter.next();
        if (teVar.getSimpleName().equals(var.getSimpleName())) {
          iter.remove();
        }
      }
    }
    return checkListStatements(teList2);
  }

}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.