/* * Copyright (c) 2005, 2008 Sven Efftinge and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Sven Efftinge - Initial API and implementation * Artem Tikhomirov (Borland) - Migration to OCL expressions */ package org.eclipse.gmf.internal.xpand.codeassist; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Set; import java.util.Stack; import java.util.StringTokenizer; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.emf.ecore.EClassifier; import org.eclipse.gmf.internal.xpand.BuiltinMetaModel; import org.eclipse.gmf.internal.xpand.ast.Advice; import org.eclipse.gmf.internal.xpand.editor.Activator; import org.eclipse.gmf.internal.xpand.model.AnalysationIssue; import org.eclipse.gmf.internal.xpand.model.ExecutionContext; import org.eclipse.gmf.internal.xpand.model.Variable; import org.eclipse.gmf.internal.xpand.model.XpandAdvice; import org.eclipse.gmf.internal.xpand.model.XpandDefinition; import org.eclipse.gmf.internal.xpand.model.XpandResource; import org.eclipse.gmf.internal.xpand.util.TypeNameUtil; import org.eclipse.ocl.ecore.CollectionType; import org.eclipse.ocl.expressions.CollectionKind; public class FastAnalyzer { private final Pattern PARAM_PATTERN = Pattern.compile("([\\[\\]:\\w]+)\\s+([\\w]+)"); private final Pattern IMPORT_PATTERN = Pattern.compile(XpandTokens.LT + "\\s*IMPORT\\s+'([^']+)'\\s*" + XpandTokens.RT); private final Pattern EXTENSION_PATTERN = Pattern.compile(XpandTokens.LT + "\\s*EXTENSION\\s+([\\w\\:]+)\\s*" + XpandTokens.RT); private final Pattern INCOMPLETE_IMPORT_PATTERN = Pattern.compile(XpandTokens.LT + "\\s*IMPORT\\s+[\\w\\:]*\\z"); private final Pattern INCOMPLETE_EXTENSION_PATTERN = Pattern.compile(XpandTokens.LT + "\\s*EXTENSION\\s+[\\w\\:]*\\z"); private final Pattern DEFINE_PATTERN = Pattern.compile("(DEFINE|AROUND)\\s*(([\\w\\*:]+)\\s*(\\(([\\[\\]:\\w\\s\\,]*)\\*?\\s*\\))?\\s*FOR\\s*([\\[\\]:\\w\\s]+))"); private final Pattern BLOCK_PATTERN = Pattern.compile(getBlockPattern()); private final Pattern FOREACH_PATTERN = Pattern.compile("FOREACH\\s+(.+)\\s+AS\\s+(\\w+)(\\s+ITERATOR\\s+(\\w+))?"); private final Pattern EXPAND_PATTERN = Pattern.compile("EXPAND\\s+([\\w:]*)\\z"); private final Pattern LET_PATTERN = Pattern.compile("LET\\s+(.+)\\s+AS\\s+(\\w+)"); private final Pattern TYPEDECL_DEFINE_PATTERN1 = Pattern.compile("(DEFINE|AROUND)\\s*[\\w\\*:]+\\s*\\(([^\\)]*)\\z"); private final Pattern TYPEDECL_DEFINE_PATTERN2 = Pattern.compile("(DEFINE|AROUND)\\s*[\\w\\*:]+\\s*(\\([\\[\\]:\\w\\s\\,]*\\*?\\s*\\))?\\s*FOR\\s+[^" + XpandTokens.RT + "\\s]*\\z"); private final Pattern TYPEDECL_PARAM_PATTERN = Pattern.compile("(,|\\(|\\A)\\s*[\\[\\]:\\w]*\\z"); private final Pattern IN_TAG_PATTERN = Pattern.compile(XpandTokens.LT + "([^" + XpandTokens.RT + "]*)\\z"); private FastAnalyzer() { } private static FastAnalyzer get() { FastAnalyzer fa = Activator.findState(FastAnalyzer.class); if (fa == null) { fa = new FastAnalyzer(); Activator.putState(FastAnalyzer.class, fa); } return fa; } public static boolean isInExpand(final String str) { return get().EXPAND_PATTERN.matcher(str).find(); } public static boolean isInComment(final String str) { final int index = str.lastIndexOf(XpandTokens.LT + "REM" + XpandTokens.RT); if (index >= 0) { final int index2 = str.lastIndexOf(XpandTokens.LT + "ENDREM" + XpandTokens.RT); if (index2 == -1) { return true; } return index2 < index; } return false; } public static boolean isInDefine(final String str) { return !computeStack(str).isEmpty(); } public static boolean isInTypeDecl(final String str) { Matcher m = get().IN_TAG_PATTERN.matcher(str); if (!m.find()) { return false; } final String tag = m.group(1); m = get().TYPEDECL_DEFINE_PATTERN1.matcher(tag); if (m.find()) { m = get().TYPEDECL_PARAM_PATTERN.matcher(m.group(2)); return m.find(); } else { m = get().TYPEDECL_DEFINE_PATTERN2.matcher(tag); return m.find(); } } private static String getBlockPattern() { final String[] parts = new String[] { XpandTokens.DEFINE, XpandTokens.AROUND, XpandTokens.FOREACH, XpandTokens.LET, XpandTokens.IF, XpandTokens.FILE, XpandTokens.PROTECT }; final StringBuilder buff = new StringBuilder(); for (int i = 0; i < parts.length; i++) { final String part = parts[i]; buff.append(XpandTokens.LT).append("\\s*").append(part); buff.append("|"); buff.append(XpandTokens.LT).append("\\s*").append("END").append(part); if (i < parts.length - 1) { buff.append("|"); } } return buff.toString(); } public final static List<String> findImports(final String template) { final Matcher m = get().IMPORT_PATTERN.matcher(template); final ArrayList<String> result = new ArrayList<String>(5); while (m.find()) { result.add(m.group(1)); } return result; } public final static List<String> findExtensions(final String template) { final Matcher m = get().EXTENSION_PATTERN.matcher(template); final List<String> result = new ArrayList<String>(5); while (m.find()) { result.add(m.group(1)); } return result; } public final static Stack<StackElement> computeStack(String templatePart) { int start = templatePart.lastIndexOf(XpandTokens.ENDDEFINE); final int start1 = templatePart.lastIndexOf(XpandTokens.ENDAROUND); if (start1 > start) { start = start1; } if (start > 0) { templatePart = templatePart.substring(start); } final Stack<StackElement> stack = new Stack<StackElement>(); final Matcher matcher = get().BLOCK_PATTERN.matcher(templatePart); while (matcher.find()) { final String txt = matcher.group(); // handle variable scope if (txt.endsWith(XpandTokens.ENDFOREACH)) { stack.pop(); } else if (txt.endsWith(XpandTokens.ENDLET)) { stack.pop(); } else if (txt.endsWith(XpandTokens.ENDIF)) { stack.pop(); } else if (txt.endsWith(XpandTokens.ENDPROTECT)) { stack.pop(); } else if (txt.endsWith(XpandTokens.ENDFILE)) { stack.pop(); } else if (txt.endsWith(XpandTokens.ENDDEFINE)) { stack.pop(); } else if (txt.endsWith(XpandTokens.ENDAROUND)) { stack.pop(); } else if (txt.endsWith(XpandTokens.DEFINE) || txt.endsWith(XpandTokens.AROUND)) { final StackElement se = new StackElement(); if (txt.endsWith(XpandTokens.AROUND)) { se.block = XpandTokens.AROUND; } else { se.block = XpandTokens.DEFINE; } Matcher m = get().DEFINE_PATTERN.matcher(templatePart.substring(matcher.start())); if (m.find()) { LazyVar ctx = new LazyVar(); ctx.typeName = m.group(6).trim(); ctx.name = ExecutionContext.IMPLICIT_VARIABLE; se.variables.put(ctx.name, ctx); final String params = m.group(5); if ((params != null) && !"".equals(params.trim())) { final StringTokenizer st = new StringTokenizer(params, ","); while (st.hasMoreTokens()) { final String param = st.nextToken(); m = get().PARAM_PATTERN.matcher(param); m.find(); ctx = new LazyVar(); ctx.typeName = m.group(1).trim(); ctx.name = m.group(2).trim(); se.variables.put(ctx.name, ctx); } } if (se.block.equals(XpandTokens.AROUND)) { ctx = new LazyVar(); ctx.typeName = BuiltinMetaModel.DEFINITION_TYPE.getName(); ctx.name = Advice.DEF_VAR_NAME; se.variables.put(ctx.name, ctx); } stack.push(se); } } else if (txt.endsWith(XpandTokens.FOREACH)) { final StackElement se = new StackElement(); se.block = XpandTokens.FOREACH; final Matcher m = get().FOREACH_PATTERN.matcher(templatePart.substring(matcher.start())); if (m.find()) { LazyVar ctx = new LazyVar(); ctx.expression = m.group(1); ctx.name = m.group(2); ctx.forEach = true; se.variables.put(ctx.name, ctx); stack.push(se); if (m.group(3) != null) { ctx = new LazyVar(); ctx.typeName = BuiltinMetaModel.ITERATOR_TYPE.getName(); ctx.name = m.group(4); se.variables.put(ctx.name, ctx); } } } else if (txt.endsWith(XpandTokens.LET)) { final StackElement se = new StackElement(); se.block = XpandTokens.LET; final Matcher m = get().LET_PATTERN.matcher(templatePart.substring(matcher.start())); if (m.find()) { final LazyVar ctx = new LazyVar(); ctx.expression = m.group(1); ctx.name = m.group(2); se.variables.put(ctx.name, ctx); stack.push(se); } } else if (txt.endsWith(XpandTokens.IF)) { final StackElement se = new StackElement(); se.block = XpandTokens.IF; stack.push(se); } else if (txt.endsWith(XpandTokens.PROTECT)) { final StackElement se = new StackElement(); se.block = XpandTokens.PROTECT; stack.push(se); } else if (txt.endsWith(XpandTokens.FILE)) { final StackElement se = new StackElement(); se.block = XpandTokens.FILE; stack.push(se); } } return stack; } /** * is public only for testing purposes */ public static boolean isInExtensionImport(final String s) { final Matcher m = get().INCOMPLETE_EXTENSION_PATTERN.matcher(s); return m.find(); } /** * is public only for testing purposes */ public static boolean isInImport(final String s) { final Matcher m = get().INCOMPLETE_IMPORT_PATTERN.matcher(s); return m.find(); } public final static XpandPartition computePartition(final String str) { if (!isInTag(str)) { return XpandPartition.DEFAULT; } if (isInComment(str)) { return XpandPartition.COMMENT; } if (isInExpand(str)) { return XpandPartition.EXPAND_STATEMENT; } if (isInImport(str)) { return XpandPartition.NAMESPACE_IMPORT; } if (isInExtensionImport(str)) { return XpandPartition.EXTENSION_IMPORT; } if (isInTypeDecl(str)) { return XpandPartition.TYPE_DECLARATION; } return XpandPartition.EXPRESSION; } public final static ExecutionContext computeExecutionContext(final String str, ExecutionContext ctx) { final XpandPartition p = computePartition(str); if (p != XpandPartition.TYPE_DECLARATION && p != XpandPartition.EXPRESSION) { return ctx; } final List<String> imports = findImports(str); final List<String> extensions = findExtensions(str); // XXX what's the goal having this fake resource??? final XpandResource tpl = new XpandResource() { public XpandDefinition[] getDefinitions() { Exception e = new Exception("This code was refactored to return no definitions every time invoked, instead of definitions obtained from last saved state from resource subsystem"); e.fillInStackTrace(); e.printStackTrace(); // if we get to this point, we'd better start refactoring back. // at this moment, looks like nobody ever invokes this method, so no reason to // do all file parsing just to provide definitions here nobody uses. // Moreover, what's the point of having definitions here from filesystem state rather than from // document string? I'd say str should be parsed for definitions, not resource. return new XpandDefinition[0]; } public String getFullyQualifiedName() { throw new UnsupportedOperationException(); } public String[] getImportedNamespaces() { return imports.toArray(new String[imports.size()]); } public String[] getImportedExtensions() { return extensions.toArray(new String[extensions.size()]); } public void analyze(ExecutionContext ctx, Set<AnalysationIssue> issues) { Exception ex = new Exception("ANALYZE!!!"); ex.fillInStackTrace(); ex.printStackTrace(); } public XpandAdvice[] getAdvices() { return new XpandAdvice[0]; } }; ctx = ctx.cloneWithResource(tpl); final Stack<StackElement> s = computeStack(str); for (StackElement element : s) { final Collection<LazyVar> vars = element.variables.values(); for (LazyVar v : vars) { EClassifier vType = null; if (v.typeName != null) { vType = typeCS(v.typeName, ctx); } else { vType = oclExpressionCS(v.expression, ctx); if (v.forEach) { if (vType instanceof CollectionType) { vType = ((CollectionType) vType).getElementType(); } else { vType = ctx.getOCLEnvironment().getOCLStandardLibrary().getOclAny(); } } } ctx = ctx.cloneWithVariable(new Variable(v.name, vType, null)); } } return ctx; } public static boolean isInTag(final String str) { return get().IN_TAG_PATTERN.matcher(str).find(); } // FIXME use parser and error tokens instead of this rudimentary support private static EClassifier typeCS(String typeName, ExecutionContext ctx) { EClassifier oclAny = ctx.getOCLEnvironment().getOCLStandardLibrary().getOclAny(); if (null != oclAny.getEPackage().getEClassifier(typeName)) { return oclAny.getEPackage().getEClassifier(typeName); } for (CollectionKind ck : CollectionKind.VALUES) { String literal = ck.getLiteral(); if (typeName.startsWith(literal)) { String suffix = typeName.substring(literal.length()).trim(); if (suffix.length() > 1 && suffix.charAt(0) == '(' && suffix.charAt(suffix.length()-1) == ')') { if (suffix.length() > 2) { suffix.substring(1, suffix.length() - 2).trim(); } else { suffix = ""; } EClassifier nested = suffix.length() == 0 ? oclAny : typeCS(suffix, ctx); return (EClassifier) ctx.getOCLEnvironment().getTypeResolver().resolveCollectionType(ck, nested); } } } return ctx.getOCLEnvironment().lookupClassifier(Arrays.asList(typeName.split(TypeNameUtil.NS_DELIM))); } private static EClassifier oclExpressionCS(String expression, ExecutionContext ctx) { // FIXME implement correctly, for now, just don't care about LET expressions return ctx.getOCLEnvironment().getOCLStandardLibrary().getOclAny(); } }