001: /*
002: * Spoon - http://spoon.gforge.inria.fr/
003: * Copyright (C) 2006 INRIA Futurs <renaud.pawlak@inria.fr>
004: *
005: * This software is governed by the CeCILL-C License under French law and
006: * abiding by the rules of distribution of free software. You can use, modify
007: * and/or redistribute the software under the terms of the CeCILL-C license as
008: * circulated by CEA, CNRS and INRIA at http://www.cecill.info.
009: *
010: * This program is distributed in the hope that it will be useful, but WITHOUT
011: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
012: * FITNESS FOR A PARTICULAR PURPOSE. See the CeCILL-C License for more details.
013: *
014: * The fact that you are presently reading this means that you have had
015: * knowledge of the CeCILL-C license and that you accept its terms.
016: */
017:
018: package spoon.template;
019:
020: import java.lang.reflect.Field;
021: import java.lang.reflect.Modifier;
022: import java.util.ArrayList;
023: import java.util.Collection;
024: import java.util.HashMap;
025: import java.util.Iterator;
026: import java.util.List;
027: import java.util.Map;
028: import java.util.regex.Matcher;
029: import java.util.regex.Pattern;
030:
031: import spoon.reflect.code.CtFieldAccess;
032: import spoon.reflect.code.CtInvocation;
033: import spoon.reflect.code.CtStatementList;
034: import spoon.reflect.declaration.CtClass;
035: import spoon.reflect.declaration.CtElement;
036: import spoon.reflect.declaration.CtField;
037: import spoon.reflect.declaration.CtNamedElement;
038: import spoon.reflect.declaration.CtParameter;
039: import spoon.reflect.declaration.CtVariable;
040: import spoon.reflect.reference.CtExecutableReference;
041: import spoon.reflect.reference.CtFieldReference;
042: import spoon.reflect.reference.CtPackageReference;
043: import spoon.reflect.reference.CtReference;
044: import spoon.reflect.reference.CtTypeParameterReference;
045: import spoon.reflect.reference.CtTypeReference;
046: import spoon.reflect.visitor.CtScanner;
047: import spoon.reflect.visitor.Query;
048: import spoon.reflect.visitor.filter.InvocationFilter;
049: import spoon.support.template.DefaultParameterMatcher;
050: import spoon.support.template.ParameterMatcher;
051: import spoon.support.template.Parameters;
052: import spoon.support.util.RtHelper;
053:
054: /**
055: * This class defines an engine for matching a template to pieces of code.
056: */
057: public class TemplateMatcher {
058:
059: private static List<CtInvocation<?>> getMethods(
060: CtClass<? extends Template> root) {
061: CtExecutableReference<?> methodRef = root
062: .getFactory()
063: .Executable()
064: .createReference(
065: root.getFactory().Type().createReference(
066: TemplateParameter.class),
067: root.getFactory().Type()
068: .createTypeParameterReference("T"), "S");
069: List<CtInvocation<?>> meths = Query.getElements(root,
070: new InvocationFilter(methodRef));
071:
072: return meths;
073: }
074:
075: private static List<String> getTemplateNameParameters(
076: CtClass<? extends Template> templateType) {
077: final List<String> ts = new ArrayList<String>();
078: final Collection<String> c = Parameters.getNames(templateType);
079: ts.addAll(c);
080: return ts;
081: }
082:
083: private static List<CtTypeReference<?>> getTemplateTypeParameters(
084: final CtClass<? extends Template> templateType) {
085:
086: final List<CtTypeReference<?>> ts = new ArrayList<CtTypeReference<?>>();
087: final Collection<String> c = Parameters.getNames(templateType);
088: new CtScanner() {
089: @Override
090: public void visitCtTypeParameterReference(
091: CtTypeParameterReference reference) {
092: if (c.contains(reference.getSimpleName())) {
093: ts.add(reference);
094: }
095: };
096:
097: @Override
098: public <T> void visitCtTypeReference(
099: CtTypeReference<T> reference) {
100: if (c.contains(reference.getSimpleName())) {
101: ts.add(reference);
102: }
103: }
104:
105: }.scan(templateType);
106: return ts;
107: }
108:
109: @SuppressWarnings("unchecked")
110: private static List<CtFieldReference<?>> getVarargs(
111: CtClass<? extends Template> root,
112: List<CtInvocation<?>> variables) {
113: List<CtFieldReference<?>> fields = new ArrayList<CtFieldReference<?>>();
114: for (CtFieldReference field : root.getReference()
115: .getAllFields()) {
116: if (field.getType().getActualClass() == CtStatementList.class) {
117: boolean alreadyAdded = false;
118: for (CtInvocation<?> invocation : variables) {
119: alreadyAdded |= ((CtFieldAccess) invocation
120: .getTarget()).getVariable()
121: .getDeclaration().equals(field);
122: }
123: if (!alreadyAdded) {
124: fields.add(field);
125: }
126: }
127: }
128: return fields;
129: }
130:
131: private List<CtElement> finds = new ArrayList<CtElement>();
132:
133: private boolean found;
134:
135: private Map<Object, Object> matches = new HashMap<Object, Object>();
136:
137: private List<String> names;
138:
139: private CtClass<? extends Template> templateType;
140:
141: private List<CtTypeReference<?>> typeVariables;
142:
143: private List<CtFieldReference<?>> varArgs;
144:
145: private List<CtInvocation<?>> variables;
146:
147: /**
148: * Constructs a matcher for a given template.
149: *
150: * @param templateType
151: * the type of the template
152: */
153: public TemplateMatcher(CtClass<? extends Template> templateType) {
154: if (templateType == null) {
155: throw new TemplateException(
156: "Template type is null. Use the template factory to access a template CtClass and check your template source path.");
157: }
158: variables = TemplateMatcher.getMethods(templateType);
159: typeVariables = TemplateMatcher
160: .getTemplateTypeParameters(templateType);
161: names = TemplateMatcher.getTemplateNameParameters(templateType);
162: varArgs = TemplateMatcher.getVarargs(templateType, variables);
163: this .templateType = templateType;
164: }
165:
166: private boolean addMatch(Object template, Object target) {
167: Object inv = matches.get(template);
168: Object o = matches.put(template, target);
169: return (null == inv) || inv.equals(o);
170: }
171:
172: private CtElement checkListStatements(List<?> teList) {
173: for (Object tem : teList) {
174: if (variables.contains(tem)
175: && (tem instanceof CtInvocation)) {
176: CtInvocation<?> listCand = (CtInvocation<?>) tem;
177: boolean ok = listCand.getFactory().Type()
178: .createReference(TemplateParameterList.class)
179: .isAssignableFrom(
180: listCand.getTarget().getType());
181: return ok ? listCand : null;
182: }
183: if (tem instanceof CtVariable) {
184: CtVariable<?> var = (CtVariable<?>) tem;
185: String name = var.getSimpleName();
186: for (CtFieldReference<?> f : varArgs) {
187: if (f.getSimpleName().equals(name)) {
188: return f.getDeclaration();
189: }
190: }
191: }
192: }
193:
194: return null;
195: }
196:
197: /**
198: * Finds all target program sub-trees that correspond to a template. Once
199: * this method has been called, {@link #getFinds()} will give the mathing
200: * CtElements if any.
201: *
202: * @param targetRoot
203: * the target to be tested for match
204: * @param templateRoot
205: * the template to match against
206: * @return true if there is one or more matches
207: *
208: * @see #getFinds()
209: */
210: public boolean find(CtElement targetRoot,
211: final CtElement templateRoot) {
212: found = false;
213: new CtScanner() {
214: @Override
215: public void scan(CtElement element) {
216: if (match(element, templateRoot)) {
217: finds.add(element);
218: found = true;
219: // matches.clear();
220: }
221: super .scan(element);
222: }
223: }.scan(targetRoot);
224:
225: return found;
226: }
227:
228: private ParameterMatcher findParameterMatcher(
229: CtElement declaration, String name)
230: throws InstantiationException, IllegalAccessException {
231: if (declaration == null) {
232: return new DefaultParameterMatcher();
233: }
234: CtClass<?> clazz = declaration.getParent(CtClass.class);
235: if (clazz == null) {
236: return new DefaultParameterMatcher();
237: }
238:
239: Collection<CtFieldReference<?>> fields = clazz.getReference()
240: .getAllFields();
241:
242: CtFieldReference<?> param = null;
243: for (CtFieldReference<?> field : fields) {
244: Parameter p = field.getAnnotation(Parameter.class);
245: if (p == null) {
246: continue; // not a parameter.
247: }
248: String proxy = p.value();
249: if (proxy != "") {
250: if (name.contains(proxy)) {
251: param = field;
252: break;
253: }
254: }
255:
256: if (name.contains(field.getSimpleName())) {
257: param = field;
258: break;
259: }
260: // todo: check for field hack.
261: }
262:
263: if (param == null) {
264: throw new IllegalStateException("Parameter not defined "
265: + name + "at " + declaration.getPosition());
266: }
267: return getParameterInstance(param);
268: }
269:
270: @SuppressWarnings("unused")
271: private String getBindedParameter(String pname) {
272: final String[] x = new String[1]; // HACK! jeje
273: x[0] = pname;
274: new CtScanner() {
275: @Override
276: public <T> void visitCtField(CtField<T> f) {
277: Parameter p = f.getAnnotation(Parameter.class);
278: if ((p != null) && p.value().equals(x[0])) {
279: x[0] = f.getSimpleName();
280: return;
281: }
282: super .visitCtField(f);
283: }
284: }.scan(templateType);
285:
286: return x[0];
287: }
288:
289: /**
290: * Returns all the elements that correspond to a given template. The
291: * {@link #find(CtElement, CtElement)} method must have been called before
292: */
293: public List<CtElement> getFinds() {
294: return finds;
295: }
296:
297: /**
298: * Returns all the matches in a map where the keys are the corresponding
299: * template parameters. The {@link #match(CtElement, CtElement)} method must
300: * have been called before.
301: */
302: public Map<Object, Object> getMatches() {
303: return matches;
304: }
305:
306: private ParameterMatcher getParameterInstance(
307: CtFieldReference<?> param) throws InstantiationException,
308: IllegalAccessException {
309: Parameter anParam = param.getAnnotation(Parameter.class);
310: if (anParam == null) {
311: // Parameter not annotated. Probably is a TemplateParameter. Just
312: // return a default impl
313: return new DefaultParameterMatcher();
314: }
315: Class<? extends ParameterMatcher> pm = anParam.match();
316: ParameterMatcher instance = pm.newInstance();
317: return instance;
318: }
319:
320: /*
321: * Made private to hide the Objects.
322: */
323: @SuppressWarnings("unchecked")
324: private boolean helperMatch(Object target, Object template) {
325: if ((target == null) && (template == null)) {
326: return true;
327: }
328: if ((target == null) || (template == null)) {
329: return false;
330: }
331: if (variables.contains(template)
332: || typeVariables.contains(template)) {
333: // TODO: upcall the parameter matcher if defined
334: boolean add = invokeCallBack(target, template);
335: if (add) {
336: return addMatch(template, target);
337: }
338: return false;
339: }
340: if (target.getClass() != template.getClass()) {
341: return false;
342: }
343: if ((template instanceof CtTypeReference)
344: && template.equals(templateType.getReference())) {
345: return true;
346: }
347: if ((template instanceof CtPackageReference)
348: && template.equals(templateType.getPackage())) {
349: return true;
350: }
351: if (template instanceof CtReference) {
352: CtReference tRef = (CtReference) template;
353: boolean ok = matchNames(tRef.getSimpleName(),
354: ((CtReference) target).getSimpleName());
355: if (ok && !template.equals(target)) {
356: boolean remove = !invokeCallBack(target, template);
357: if (remove) {
358: matches.remove(tRef.getSimpleName());
359: return false;
360: }
361: return true;
362: }
363: }
364:
365: if (template instanceof CtNamedElement) {
366: CtNamedElement named = (CtNamedElement) template;
367: boolean ok = matchNames(named.getSimpleName(),
368: ((CtNamedElement) target).getSimpleName());
369: if (ok && !template.equals(target)) {
370: boolean remove = !invokeCallBack(target, template);
371: if (remove) {
372: matches.remove(named.getSimpleName());
373: return false;
374: }
375: }
376: }
377:
378: if (template instanceof Collection) {
379: return matchCollections((Collection) target,
380: (Collection) template);
381: }
382:
383: if (template instanceof Map) {
384: if (template.equals(target)) {
385: return true;
386: }
387:
388: Map temMap = (Map) template;
389: Map tarMap = (Map) target;
390:
391: if (!temMap.keySet().equals(tarMap.keySet())) {
392: return false;
393: }
394:
395: return matchCollections(tarMap.values(), temMap.values());
396:
397: }
398:
399: if ((target instanceof CtElement)
400: || (target instanceof CtReference)) {
401: for (Field f : RtHelper.getAllFields(target.getClass())) {
402: f.setAccessible(true);
403: if (Modifier.isStatic(f.getModifiers())) {
404: continue;
405: }
406: if (f.getName().equals("parent")) {
407: continue;
408: }
409: if (f.getName().equals("position")) {
410: continue;
411: }
412: if (f.getName().equals("docComment")) {
413: continue;
414: }
415: if (f.getName().equals("factory")) {
416: continue;
417: }
418: try {
419: if (!helperMatch(f.get(target), f.get(template))) {
420: return false;
421: }
422: } catch (Exception e) {
423: e.printStackTrace();
424: }
425: }
426: return true;
427: } else if (target instanceof String) {
428: return matchNames((String) template, (String) target);
429: } else {
430: return target.equals(template);
431: }
432: }
433:
434: @SuppressWarnings("unchecked")
435: private boolean invokeCallBack(Object target, Object template) {
436: try {
437: if (template instanceof CtInvocation) {
438: CtFieldAccess<?> param = (CtFieldAccess) ((CtInvocation<?>) template)
439: .getTarget();
440: ParameterMatcher instance = getParameterInstance(param
441: .getVariable());
442: return instance.match(this , (CtInvocation) template,
443: (CtElement) target);
444: } else if (template instanceof CtReference) {
445: // Get parameter
446: CtReference ref = (CtReference) template;
447: Parameter param = ref.getAnnotation(Parameter.class);
448: ParameterMatcher instance;
449: if (param == null) {
450: instance = new DefaultParameterMatcher();
451: } else {
452: instance = param.match().newInstance();
453: }
454: return instance.match(this , (CtReference) template,
455: (CtReference) target);
456: } else if (template instanceof CtNamedElement) {
457: CtNamedElement named = (CtNamedElement) template;
458: ParameterMatcher instance = findParameterMatcher(named,
459: named.getSimpleName());
460: return instance.match(this , (CtElement) template,
461: (CtElement) target);
462: }
463:
464: else {
465: // Should not happen
466: throw new RuntimeException();
467: }
468: } catch (InstantiationException e) {
469: e.printStackTrace();
470: return true;
471: } catch (IllegalAccessException e) {
472: // TODO Auto-generated catch block
473: e.printStackTrace();
474: return true;
475: }
476: }
477:
478: private boolean isCurrentTemplate(Object object, CtElement inMulti) {
479: if (object instanceof CtInvocation<?>) {
480: return object.equals(inMulti);
481: }
482: if (object instanceof CtParameter) {
483: CtParameter<?> param = (CtParameter<?>) object;
484: for (CtFieldReference<?> varArg : varArgs) {
485: if (param.getSimpleName()
486: .equals(varArg.getSimpleName())) {
487: return varArg.equals(inMulti);
488: }
489: }
490: }
491: return false;
492: }
493:
494: /**
495: * Matches a target program sub-tree against a template. Once this method
496: * has been called, {@link #getMatches()} will give the mathing parts if
497: * any.
498: *
499: * @param targetRoot
500: * the target to be tested for match
501: * @param templateRoot
502: * the template to match against
503: * @return true if matches
504: *
505: * @see #getMatches()
506: */
507: public boolean match(CtElement targetRoot, CtElement templateRoot) {
508: return helperMatch(targetRoot, templateRoot);
509: }
510:
511: @SuppressWarnings("unchecked")
512: private boolean matchCollections(Collection target,
513: Collection template) {
514: List teList = new ArrayList(template);
515: List taList = new ArrayList(target);
516:
517: int numOfNonParamsinTeList = teList.size();
518:
519: // inMulti keeps the multiElement templateVariable we are at
520: CtElement inMulti = nextListStatement(teList, null);
521:
522: // multi keeps the values to assign to inMulti
523: List<CtElement> multi = new ArrayList();
524:
525: if (null == inMulti) {
526: // If we are not looking at template with multiElements
527: // the sizes should then be the same
528: if (teList.size() != taList.size()) {
529: return false;
530: }
531:
532: for (int te = 0, ta = 0; (te < teList.size())
533: && (ta < taList.size()); te++, ta++) {
534: if (!helperMatch(taList.get(ta), teList.get(te))) {
535: return false;
536: }
537: }
538: return true;
539: }
540: for (int te = 0, ta = 0; (te < teList.size())
541: && (ta < taList.size()); te++, ta++) {
542:
543: if (isCurrentTemplate(teList.get(te), inMulti)) {
544: numOfNonParamsinTeList--;
545: if (te + 1 >= teList.size()) {
546: multi.addAll(taList.subList(te, taList.size()));
547: CtStatementList tpl = templateType.getFactory()
548: .Core().createStatementList();
549: tpl.setStatements(multi);
550: if (!invokeCallBack(tpl, inMulti)) {
551: return false;
552: }
553: boolean ret = addMatch(inMulti, multi);
554: return ret;
555: }
556: te++;
557: while ((te < teList.size()) && (ta < taList.size())
558: && !helperMatch(taList.get(ta), teList.get(te))) {
559: multi.add((CtElement) taList.get(ta));
560: ta++;
561: }
562: CtStatementList tpl = templateType.getFactory().Core()
563: .createStatementList();
564: tpl.setStatements(multi);
565: if (!invokeCallBack(tpl, inMulti)) {
566: return false;
567: }
568: addMatch(inMulti, tpl);
569: // update inMulti
570: inMulti = nextListStatement(teList, inMulti);
571: multi = new ArrayList();
572: numOfNonParamsinTeList--;
573: } else {
574: if (!helperMatch(taList.get(ta), teList.get(te))) {
575: return false;
576: }
577: if (!(ta + 1 < taList.size()) && (inMulti != null)) {
578: CtStatementList tpl = templateType.getFactory()
579: .Core().createStatementList();
580: tpl.setStatements(multi);
581: if (!invokeCallBack(tpl, inMulti)) {
582: return false;
583: }
584: addMatch(inMulti, tpl);
585: // update inMulti
586: inMulti = nextListStatement(teList, inMulti);
587: multi = new ArrayList();
588: numOfNonParamsinTeList--;
589: }
590: }
591: }
592: return true;
593: }
594:
595: private boolean matchNames(String name, String tname) {
596:
597: try {
598: for (String pname : names) {
599: // pname = pname.replace("_FIELD_", "");
600: if (name.contains(pname)) {
601: String newName = name.replace(pname, "(.*)");
602: Pattern p = Pattern.compile(newName);
603: Matcher m = p.matcher(tname);
604: if (!m.matches()) {
605: return false;
606: }
607: // TODO: fix with parameter from @Parameter
608: // boolean ok = addMatch(getBindedParameter(pname),
609: // m.group(1));
610: boolean ok = addMatch(pname, m.group(1));
611: if (!ok) {
612: System.out.println("incongruent match");
613: return false;
614: }
615: return true;
616: }
617: }
618: } catch (RuntimeException e) {
619: // //fall back on dumb way to do it.
620: // if
621: }
622: return name.equals(tname);
623: }
624:
625: private CtElement nextListStatement(List<?> teList,
626: CtElement inMulti) {
627: if (inMulti == null) {
628: return checkListStatements(teList);
629: }
630: List<?> teList2 = new ArrayList<Object>(teList);
631: if (inMulti instanceof CtInvocation) {
632: teList2.remove(inMulti);
633: } else if (inMulti instanceof CtVariable) {
634: CtVariable<?> var = (CtVariable<?>) inMulti;
635: for (Iterator<?> iter = teList2.iterator(); iter.hasNext();) {
636: CtVariable<?> teVar = (CtVariable<?>) iter.next();
637: if (teVar.getSimpleName().equals(var.getSimpleName())) {
638: iter.remove();
639: }
640: }
641: }
642: return checkListStatements(teList2);
643: }
644:
645: }
|