| /* |
| * Copyright 2000-2009 JetBrains s.r.o. |
| * |
| * 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.intellij.patterns.compiler; |
| |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.util.Condition; |
| import com.intellij.openapi.util.Ref; |
| import com.intellij.openapi.util.text.StringHash; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.patterns.ElementPattern; |
| import com.intellij.patterns.ElementPatternCondition; |
| import com.intellij.patterns.InitialPatternCondition; |
| import com.intellij.util.ArrayUtil; |
| import com.intellij.util.Function; |
| import com.intellij.util.ProcessingContext; |
| import com.intellij.util.ReflectionCache; |
| import com.intellij.util.containers.ContainerUtil; |
| import com.intellij.util.containers.Stack; |
| import com.intellij.util.containers.StringInterner; |
| import gnu.trove.THashMap; |
| import gnu.trove.THashSet; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.lang.reflect.*; |
| import java.util.*; |
| |
| /** |
| * @author Gregory.Shrago |
| */ |
| public class PatternCompilerImpl<T> implements PatternCompiler<T> { |
| |
| private static final Logger LOG = Logger.getInstance(PatternCompilerImpl.class.getName()); |
| |
| private final Set<Method> myStaticMethods; |
| private final StringInterner myStringInterner = new StringInterner(); |
| |
| public PatternCompilerImpl(final List<Class> patternClasses) { |
| myStaticMethods = getStaticMethods(patternClasses); |
| } |
| |
| private static final Node ERROR_NODE = new Node(null, null, null); |
| @Override |
| public ElementPattern<T> createElementPattern(final String text, final String displayName) { |
| try { |
| return compileElementPattern(text); |
| } |
| catch (Exception ex) { |
| final Throwable cause = ex.getCause() != null ? ex.getCause() : ex; |
| LOG.warn("error processing place: " + displayName + " [" + text + "]", cause); |
| return new LazyPresentablePattern<T>(new Node(ERROR_NODE, text, null)); |
| } |
| } |
| |
| //@Override |
| //public ElementPattern<T> compileElementPattern(final String text) { |
| // return processElementPatternText(text, new Function<Frame, Object>() { |
| // public Object fun(final Frame frame) { |
| // try { |
| // final Object[] args = frame.params.toArray(); |
| // preInvoke(frame.target, frame.methodName, args); |
| // return invokeMethod(frame.target, frame.methodName, args, myStaticMethods); |
| // } |
| // catch (Throwable throwable) { |
| // throw new IllegalArgumentException(text, throwable); |
| // } |
| // } |
| // }); |
| //} |
| |
| @Override |
| public synchronized ElementPattern<T> compileElementPattern(final String text) { |
| Node node = processElementPatternText(text, new Function<Frame, Object>() { |
| public Node fun(final Frame frame) { |
| final Object[] args = frame.params.toArray(); |
| for (int i = 0, argsLength = args.length; i < argsLength; i++) { |
| args[i] = args[i] instanceof String ? myStringInterner.intern((String)args[i]) : args[i]; |
| } |
| return new Node((Node)frame.target, myStringInterner.intern(frame.methodName), args.length == 0 ? ArrayUtil.EMPTY_OBJECT_ARRAY : args); |
| } |
| }); |
| if (node == null) node = new Node(ERROR_NODE, text, null); |
| return new LazyPresentablePattern<T>(node); |
| } |
| |
| private static Set<Method> getStaticMethods(List<Class> patternClasses) { |
| return new THashSet<Method>(ContainerUtil.concat(patternClasses, new Function<Class, Collection<? extends Method>>() { |
| public Collection<Method> fun(final Class aClass) { |
| return ContainerUtil.findAll(ReflectionCache.getMethods(aClass), new Condition<Method>() { |
| public boolean value(final Method method) { |
| return Modifier.isStatic(method.getModifiers()) |
| && Modifier.isPublic(method.getModifiers()) |
| && !Modifier.isAbstract(method.getModifiers()) |
| && ElementPattern.class.isAssignableFrom(method.getReturnType()); |
| } |
| }); |
| } |
| })); |
| } |
| |
| private enum State { |
| init, name, name_end, |
| param_start, param_end, literal, escape, |
| invoke, invoke_end |
| } |
| |
| private static class Frame { |
| State state = State.init; |
| Object target; |
| String methodName; |
| ArrayList<Object> params = new ArrayList<Object>(); |
| } |
| |
| @Nullable |
| private static <T> T processElementPatternText(final String text, final Function<Frame, Object> executor) { |
| final Stack<Frame> stack = new Stack<Frame>(); |
| int curPos = 0; |
| Frame curFrame = new Frame(); |
| Object curResult = null; |
| final StringBuilder curString = new StringBuilder(); |
| while (true) { |
| if (curPos > text.length()) break; |
| final char ch = curPos++ < text.length()? text.charAt(curPos-1) : 0; |
| switch (curFrame.state) { |
| case init: |
| if (Character.isWhitespace(ch)) { |
| } |
| else if (Character.isJavaIdentifierStart(ch)) { |
| curString.append(ch); |
| curFrame.state = State.name; |
| } |
| else { |
| throwError(curPos, ch, "method call expected"); |
| } |
| break; |
| case name: |
| if (Character.isJavaIdentifierPart(ch)) { |
| curString.append(ch); |
| } |
| else if (ch == '(' || Character.isWhitespace(ch)) { |
| curFrame.methodName = curString.toString(); |
| curString.setLength(0); |
| curFrame.state = ch == '('? State.param_start : State.name_end; |
| } |
| else { |
| throwError(curPos, ch, "'"+curString+ch+"' method name start is invalid, '(' expected"); |
| } |
| break; |
| case name_end: |
| if (ch == '(') { |
| curFrame.state = State.param_start; |
| } |
| else if (!Character.isWhitespace(ch)) { |
| throwError(curPos, ch, "'(' expected after '"+curFrame.methodName+"'"); |
| } |
| break; |
| case param_start: |
| if (Character.isWhitespace(ch)) { |
| } |
| else if (Character.isDigit(ch) || ch == '\"') { |
| curFrame.state = State.literal; |
| curString.append(ch); |
| } |
| else if (ch == ')') { |
| curFrame.state = State.invoke; |
| } |
| else if (Character.isJavaIdentifierStart(ch)) { |
| curString.append(ch); |
| stack.push(curFrame); |
| curFrame = new Frame(); |
| curFrame.state = State.name; |
| } |
| else { |
| throwError(curPos, ch, "expression expected in '" + curFrame.methodName + "' call"); |
| } |
| break; |
| case param_end: |
| if (ch == ')') { |
| curFrame.state = State.invoke; |
| } |
| else if (ch == ',') { |
| curFrame.state = State.param_start; |
| } |
| else if (!Character.isWhitespace(ch)) { |
| throwError(curPos, ch, "')' or ',' expected in '" + curFrame.methodName + "' call"); |
| } |
| break; |
| case literal: |
| if (curString.charAt(0) == '\"') { |
| curString.append(ch); |
| if (ch == '\\') { |
| curFrame.state = State.escape; |
| } |
| else { |
| if (ch == '\"') { |
| curFrame.params.add(makeParam(curString.toString())); |
| curString.setLength(0); |
| curFrame.state = State.param_end; |
| } |
| } |
| } |
| else if (Character.isWhitespace(ch) || ch == ',' || ch == ')') { |
| curFrame.params.add(makeParam(curString.toString())); |
| curString.setLength(0); |
| curFrame.state = ch == ')' ? State.invoke : |
| ch == ',' ? State.param_start : State.param_end; |
| } |
| else { |
| curString.append(ch); |
| } |
| break; |
| case escape: |
| if (ch != 0) { |
| curString.append(ch); |
| curFrame.state = State.literal; |
| } |
| else { |
| throwError(curPos, ch, "unclosed escape sequence"); |
| } |
| break; |
| case invoke: |
| curResult = executor.fun(curFrame); |
| if (ch == 0 && stack.isEmpty()) { |
| return (T)curResult; |
| } |
| else if (ch == '.') { |
| curFrame = new Frame(); |
| curFrame.target = curResult; |
| curFrame.state = State.init; |
| curResult = null; |
| } |
| else if (ch == ',' || ch == ')') { |
| curFrame = stack.pop(); |
| curFrame.params.add(curResult); |
| curResult = null; |
| curFrame.state = ch == ')' ? State.invoke : State.param_start; |
| } |
| else if (Character.isWhitespace(ch)) { |
| curFrame.state = State.invoke_end; |
| } |
| else { |
| throwError(curPos, ch, (stack.isEmpty()? "'.' or <eof>" : "'.' or ')'") |
| + "expected after '" + curFrame.methodName + "' call"); |
| } |
| break; |
| case invoke_end: |
| if (ch == 0 && stack.isEmpty()) { |
| return (T)curResult; |
| } |
| else if (ch == ')') { |
| curFrame.state = State.invoke; |
| } |
| else if (ch == ',') { |
| curFrame.state = State.param_start; |
| } |
| else if (ch == '.') { |
| curFrame = new Frame(); |
| curFrame.target = curResult; |
| curFrame.state = State.init; |
| curResult = null; |
| } |
| else if (!Character.isWhitespace(ch)) { |
| throwError(curPos, ch, (stack.isEmpty()? "'.' or <eof>" : "'.' or ')'") |
| + "expected after '" + curFrame.methodName + "' call"); |
| } |
| break; |
| } |
| } |
| return null; |
| } |
| |
| private static void throwError(int offset, char ch, String message) { |
| throw new IllegalStateException(offset+"("+ch+"): "+message); |
| } |
| |
| private static Object makeParam(final String s) { |
| if (s.length() >= 2 && s.startsWith("\"") && s.endsWith("\"")) { |
| return StringUtil.unescapeStringCharacters(s.substring(1, s.length() - 1)); |
| } |
| try { |
| return Integer.valueOf(s); |
| } |
| catch (NumberFormatException e) {} |
| return s; |
| } |
| |
| private static Class<?> getNonPrimitiveType(final Class<?> type) { |
| if (!type.isPrimitive()) return type; |
| if (type == boolean.class) return Boolean.class; |
| if (type == byte.class) return Byte.class; |
| if (type == short.class) return Short.class; |
| if (type == int.class) return Integer.class; |
| if (type == long.class) return Long.class; |
| if (type == float.class) return Float.class; |
| if (type == double.class) return Double.class; |
| if (type == char.class) return Character.class; |
| return type; |
| } |
| |
| private static Object invokeMethod(@Nullable final Object target, final String methodName, final Object[] arguments, final Collection<Method> staticMethods) throws Throwable { |
| final Ref<Boolean> convertVarArgs = Ref.create(Boolean.FALSE); |
| final Collection<Method> methods = target == null ? staticMethods : Arrays.asList(target.getClass().getMethods()); |
| final Method method = findMethod(methodName, arguments, methods, convertVarArgs); |
| if (method != null) { |
| try { |
| final Object[] newArgs; |
| if (!convertVarArgs.get()) newArgs = arguments; |
| else { |
| final Class<?>[] parameterTypes = method.getParameterTypes(); |
| newArgs = new Object[parameterTypes.length]; |
| System.arraycopy(arguments, 0, newArgs, 0, parameterTypes.length - 1); |
| final Object[] varArgs = (Object[])Array |
| .newInstance(parameterTypes[parameterTypes.length - 1].getComponentType(), arguments.length - parameterTypes.length + 1); |
| System.arraycopy(arguments, parameterTypes.length - 1, varArgs, 0, varArgs.length); |
| newArgs[parameterTypes.length - 1] = varArgs; |
| } |
| return method.invoke(target, newArgs); |
| } |
| catch (InvocationTargetException e) { |
| throw e.getTargetException(); |
| } |
| } |
| throw new NoSuchMethodException("unknown symbol: "+methodName + "(" + StringUtil.join(arguments, new Function<Object, String>() { |
| public String fun(Object o) { |
| return String.valueOf(o); |
| } |
| }, ", ")+")"); |
| } |
| |
| @Nullable |
| private static Method findMethod(final String methodName, final Object[] arguments, final Collection<Method> methods, Ref<Boolean> convertVarArgs) { |
| main: for (Method method : methods) { |
| if (!methodName.equals(method.getName())) continue; |
| final Class<?>[] parameterTypes = method.getParameterTypes(); |
| if (!method.isVarArgs() && parameterTypes.length != arguments.length) continue; |
| convertVarArgs.set(false); |
| for (int i = 0, parameterTypesLength = parameterTypes.length; i < arguments.length; i++) { |
| final Class<?> type = getNonPrimitiveType(i < parameterTypesLength ? parameterTypes[i] : parameterTypes[parameterTypesLength - 1]); |
| final Object argument = arguments[i]; |
| final Class<?> componentType = |
| method.isVarArgs() && i < parameterTypesLength - 1 ? null : parameterTypes[parameterTypesLength - 1].getComponentType(); |
| if (argument != null) { |
| if (!type.isInstance(argument)) { |
| if ((componentType == null || !componentType.isInstance(argument))) continue main; |
| else convertVarArgs.set(true); |
| } |
| } |
| } |
| if (parameterTypes.length > arguments.length) { |
| convertVarArgs.set(true); |
| } |
| return method; |
| } |
| return null; |
| } |
| |
| @Override |
| public String dumpContextDeclarations() { |
| final StringBuilder sb = new StringBuilder(); |
| final THashMap<Class, Collection<Class>> classes = new THashMap<Class, Collection<Class>>(); |
| final THashSet<Class> missingClasses = new THashSet<Class>(); |
| classes.put(Object.class, missingClasses); |
| for (Method method : myStaticMethods) { |
| for (Class<?> type = method.getReturnType(); type != null && ElementPattern.class.isAssignableFrom(type); type = type.getSuperclass()) { |
| final Class<?> enclosingClass = type.getEnclosingClass(); |
| if (enclosingClass != null) { |
| Collection<Class> list = classes.get(enclosingClass); |
| if (list == null) { |
| list = new THashSet<Class>(); |
| classes.put(enclosingClass, list); |
| } |
| list.add(type); |
| } |
| else if (!classes.containsKey(type)) { |
| classes.put(type, null); |
| } |
| } |
| } |
| for (Class aClass : classes.keySet()) { |
| if (aClass == Object.class) continue; |
| printClass(aClass, classes, sb); |
| } |
| for (Method method : myStaticMethods) { |
| printMethodDeclaration(method, sb, classes); |
| } |
| for (Class aClass : missingClasses) { |
| sb.append("class ").append(aClass.getSimpleName()); |
| final Class superclass = aClass.getSuperclass(); |
| if (missingClasses.contains(superclass)) { |
| sb.append(" extends ").append(superclass.getSimpleName()); |
| } |
| sb.append("{}\n"); |
| } |
| //System.out.println(sb); |
| return sb.toString(); |
| } |
| |
| private static void printClass(Class aClass, Map<Class, Collection<Class>> classes, StringBuilder sb) { |
| final boolean isInterface = aClass.isInterface(); |
| sb.append(isInterface ? "interface ": "class "); |
| dumpType(aClass, aClass, sb, classes); |
| final Type superClass = aClass.getGenericSuperclass(); |
| final Class rawSuperClass = (Class)(superClass instanceof ParameterizedType ? ((ParameterizedType)superClass).getRawType() : superClass); |
| if (superClass != null && classes.containsKey(rawSuperClass)) { |
| sb.append(" extends "); |
| dumpType(null, superClass, sb, classes); |
| } |
| int implementsIdx = 1; |
| for (Type superInterface : aClass.getGenericInterfaces()) { |
| final Class rawSuperInterface = (Class)(superInterface instanceof ParameterizedType ? ((ParameterizedType)superInterface).getRawType() : superClass); |
| if (classes.containsKey(rawSuperInterface)) { |
| if (implementsIdx++ == 1) sb.append(isInterface? " extends " : " implements "); |
| else sb.append(", "); |
| dumpType(null, superInterface, sb, classes); |
| } |
| } |
| sb.append(" {\n"); |
| for (Method method : aClass.getDeclaredMethods()) { |
| if (Modifier.isStatic(method.getModifiers()) || |
| !Modifier.isPublic(method.getModifiers()) || |
| Modifier.isVolatile(method.getModifiers())) continue; |
| printMethodDeclaration(method, sb.append(" "), classes); |
| } |
| final Collection<Class> innerClasses = classes.get(aClass); |
| sb.append("}\n"); |
| if (innerClasses != null) { |
| for (Class innerClass : innerClasses) { |
| printClass(innerClass, classes, sb); |
| } |
| } |
| } |
| |
| private static void dumpType(GenericDeclaration owner, Type type, StringBuilder sb, Map<Class, Collection<Class>> classes) { |
| if (type instanceof Class) { |
| final Class aClass = (Class)type; |
| final Class enclosingClass = aClass.getEnclosingClass(); |
| if (enclosingClass != null) { |
| sb.append(enclosingClass.getSimpleName()).append("_"); |
| } |
| else if (!aClass.isArray() && !aClass.isPrimitive() && !aClass.getName().startsWith("java.") && !classes.containsKey(aClass)) { |
| classes.get(Object.class).add(aClass); |
| } |
| sb.append(aClass.getSimpleName()); |
| if (owner == aClass) { |
| dumpTypeParametersArray(owner, aClass.getTypeParameters(), sb, "<", ">", classes); |
| } |
| } |
| else if (type instanceof TypeVariable) { |
| TypeVariable typeVariable = (TypeVariable)type; |
| sb.append(typeVariable.getName()); |
| if (typeVariable.getGenericDeclaration() == owner) { |
| dumpTypeParametersArray(null, typeVariable.getBounds(), sb, " extends ", "", classes); |
| } |
| } |
| else if (type instanceof WildcardType) { |
| final WildcardType wildcardType = (WildcardType)type; |
| sb.append("?"); |
| dumpTypeParametersArray(owner, wildcardType.getUpperBounds(), sb, " extends ", "", classes); |
| dumpTypeParametersArray(owner, wildcardType.getLowerBounds(), sb, " super ", "", classes); |
| } |
| else if (type instanceof ParameterizedType) { |
| final ParameterizedType parameterizedType = (ParameterizedType)type; |
| final Type raw = parameterizedType.getRawType(); |
| dumpType(null, raw, sb, classes); |
| dumpTypeParametersArray(owner, parameterizedType.getActualTypeArguments(), sb, "<", ">", classes); |
| } |
| else if (type instanceof GenericArrayType) { |
| dumpType(owner, ((GenericArrayType)type).getGenericComponentType(), sb, classes); |
| sb.append("[]"); |
| } |
| } |
| |
| private static void dumpTypeParametersArray(GenericDeclaration owner, final Type[] typeVariables, |
| final StringBuilder sb, |
| final String prefix, final String suffix, Map<Class, Collection<Class>> classes) { |
| int typeVarIdx = 1; |
| for (Type typeVariable : typeVariables) { |
| if (typeVariable == Object.class) continue; |
| if (typeVarIdx++ == 1) sb.append(prefix); |
| else sb.append(", "); |
| dumpType(owner, typeVariable, sb, classes); |
| } |
| if (typeVarIdx > 1) sb.append(suffix); |
| } |
| |
| private static void printMethodDeclaration(Method method, StringBuilder sb, Map<Class, Collection<Class>> classes) { |
| if (Modifier.isStatic(method.getModifiers())) { |
| sb.append("static "); |
| } |
| dumpTypeParametersArray(method, method.getTypeParameters(), sb, "<", "> ", classes); |
| dumpType(null, method.getGenericReturnType(), sb, classes); |
| sb.append(" ").append(method.getName()).append("("); |
| int paramIdx = 1; |
| for (Type parameter : method.getGenericParameterTypes()) { |
| if (paramIdx != 1) sb.append(", "); |
| dumpType(null, parameter, sb, classes); |
| sb.append(" ").append("p").append(paramIdx++); |
| } |
| sb.append(")"); |
| if (!method.getDeclaringClass().isInterface()) sb.append("{}"); |
| sb.append("\n"); |
| } |
| |
| //@Nullable |
| //public static ElementPattern<PsiElement> createElementPatternGroovy(final String text, final String displayName, final String supportId) { |
| // final Binding binding = new Binding(); |
| // final ArrayList<MetaMethod> metaMethods = new ArrayList<MetaMethod>(); |
| // for (Class aClass : getPatternClasses(supportId)) { |
| // // walk super classes as well? |
| // for (CachedMethod method : ReflectionCache.getCachedClass(aClass).getMethods()) { |
| // if (!Modifier.isStatic(method.getModifiers()) || !Modifier.isPublic(method.getModifiers()) || Modifier.isAbstract(method.getModifiers())) continue; |
| // metaMethods.add(method); |
| // } |
| // } |
| // |
| // final ExpandoMetaClass metaClass = new ExpandoMetaClass(Object.class, false, metaMethods.toArray(new MetaMethod[metaMethods.size()])); |
| // final GroovyShell shell = new GroovyShell(binding); |
| // try { |
| // final Script script = shell.parse("return " + text); |
| // metaClass.initialize(); |
| // script.setMetaClass(metaClass); |
| // final Object value = script.run(); |
| // return value instanceof ElementPattern ? (ElementPattern<PsiElement>)value : null; |
| // } |
| // catch (GroovyRuntimeException ex) { |
| // Configuration.LOG.warn("error processing place: "+displayName+" ["+text+"]", ex); |
| // } |
| // return null; |
| //} |
| |
| private static final ElementPattern<?> ALWAYS_FALSE = new FalsePattern(); |
| |
| private static class Node { |
| final Node target; |
| final String method; |
| final Object[] args; |
| |
| private Node(final Node target, final String method, final Object[] args) { |
| this.target = target; |
| this.method = method; |
| this.args = args; |
| } |
| } |
| |
| private static class FalsePattern extends InitialPatternCondition<Object> implements ElementPattern<Object> { |
| private final ElementPatternCondition<Object> myCondition = new ElementPatternCondition<Object>(this); |
| |
| protected FalsePattern() { |
| super(Object.class); |
| } |
| |
| @Override |
| public boolean accepts(@Nullable final Object o) { |
| return false; |
| } |
| |
| @Override |
| public boolean accepts(@Nullable final Object o, final ProcessingContext context) { |
| return false; |
| } |
| |
| @Override |
| public ElementPatternCondition<Object> getCondition() { |
| return myCondition; |
| } |
| } |
| |
| |
| public class LazyPresentablePattern<T> implements ElementPattern<T> { |
| |
| private ElementPattern<T> myCompiledPattern; |
| private final Node myNode; |
| private final long myHashCode; |
| |
| public LazyPresentablePattern(@NotNull Node node) { |
| myNode = node; |
| myHashCode = StringHash.calc(toString()); |
| } |
| |
| @Override |
| public boolean accepts(@Nullable final Object o) { |
| return getCompiledPattern().accepts(o, new ProcessingContext()); |
| } |
| |
| @Override |
| public boolean accepts(@Nullable final Object o, final ProcessingContext context) { |
| return getCompiledPattern().accepts(o, context); |
| } |
| |
| @Override |
| public ElementPatternCondition<T> getCondition() { |
| return getCompiledPattern().getCondition(); |
| } |
| |
| public ElementPattern<T> getCompiledPattern() { |
| if (myCompiledPattern == null) { |
| Object result; |
| try { |
| result = compile(); |
| } |
| catch (Throwable throwable) { |
| LOG.error(toString(), throwable); |
| result = ALWAYS_FALSE; |
| } |
| myCompiledPattern = (ElementPattern<T>)result; |
| } |
| return myCompiledPattern; |
| } |
| |
| public ElementPattern<?> compile() throws Throwable { |
| return myNode.target == ERROR_NODE? ALWAYS_FALSE : (ElementPattern<?>)execute(myNode); |
| } |
| |
| @Override |
| public String toString() { |
| return toString(myNode, new StringBuilder()).toString(); |
| } |
| |
| private StringBuilder toString(final Node node, final StringBuilder sb) { |
| if (node.target == ERROR_NODE) { |
| return sb.append(node.method); |
| } |
| if (node.target != null) { |
| toString(node.target, sb); |
| sb.append('.'); |
| } |
| sb.append(node.method).append('('); |
| boolean first = true; |
| for (Object arg : node.args) { |
| if (first) first = false; |
| else sb.append(',').append(' '); |
| if (arg instanceof Node) { |
| toString((Node)arg, sb); |
| } |
| else if (arg instanceof String) { |
| sb.append('\"').append(StringUtil.escapeStringCharacters((String)arg)).append('\"'); |
| } |
| else if (arg instanceof Number) { |
| sb.append(arg); |
| } |
| } |
| sb.append(')'); |
| return sb; |
| } |
| |
| private Object execute(final Node node) throws Throwable { |
| final Object target = node.target != null? execute(node.target) : null; |
| final String methodName = node.method; |
| final Object[] args; |
| if (node.args.length == 0) { |
| args = node.args; |
| } |
| else { |
| args = new Object[node.args.length]; |
| for (int i = 0, len = node.args.length; i < len; i++) { |
| args[i] = node.args[i] instanceof Node? execute((Node)node.args[i]) : node.args[i]; |
| } |
| } |
| return invokeMethod(target, methodName, args, myStaticMethods); |
| } |
| |
| @Override |
| public int hashCode() { |
| return (int)myHashCode; |
| } |
| |
| @Override |
| public boolean equals(final Object obj) { |
| return obj instanceof LazyPresentablePattern && |
| ((LazyPresentablePattern)obj).myHashCode == myHashCode; |
| } |
| } |
| } |