/* * Copyright 2011 Robert W. Vawter III <bob@vawter.org> * * 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 org.jsonddl.impl; import org.jsonddl.JsonDdlObject; import org.jsonddl.JsonDdlVisitor; import org.jsonddl.JsonDdlVisitor.Context; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * Provides runtime support for dynamic visitor methods. This type should not be referenced except * by generated code. */ public class VisitSupport { private static class Lookup { private final String name; private final Class<?> searchFor; private final Class<?> visitor; private final int hash; public Lookup(Class<?> visitor, String name, Class<?> searchFor) { this.name = name.intern(); this.searchFor = searchFor; this.visitor = visitor; hash = name.hashCode() * 13 + searchFor.hashCode() * 11 + searchFor.hashCode() * 7; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof Lookup)) { return false; } Lookup other = (Lookup) o; return name.equals(other.name) && searchFor.equals(other.searchFor) && visitor.equals(other.visitor); } @Override public int hashCode() { return hash; } } /** * Method lookups in findMethod() have been demonstrated to be expensive, so we'll cache them. */ private static final Map<Lookup, Method> lookup = new ConcurrentHashMap<Lookup, Method>(); private static final Method NULL_METHOD; static { try { NULL_METHOD = Object.class.getMethod("hashCode"); } catch (SecurityException e) { // Should never happen throw new RuntimeException("Cannot find Object.hashCode", e); } catch (NoSuchMethodException e) { // Should never happen throw new RuntimeException("Cannot find Object.hashCode", e); } } public static <J extends JsonDdlObject<J>> void endVisit(JsonDdlVisitor visitor, J obj, Context<J> ctx) { invoke(visitor, obj, ctx, "endVisit"); } public static <J extends JsonDdlObject<J>> boolean visit(JsonDdlVisitor visitor, J obj, Context<J> ctx) { Object o = invoke(visitor, obj, ctx, "visit"); return !Boolean.FALSE.equals(o); } private static Method findMethod(Class<?> visitor, String name, Class<?> searchFor) { Lookup l = new Lookup(visitor, name, searchFor); Method found = lookup.get(l); if (found != null) { return found == NULL_METHOD ? null : found; } while (searchFor != null) { try { Method m; try { m = visitor.getMethod(name, searchFor, Context.class); } catch (NoSuchMethodException e) { m = visitor.getMethod(name, searchFor); } m.setAccessible(true); lookup.put(l, m); return m; } catch (SecurityException e) { throw new RuntimeException(e); } catch (NoSuchMethodException e) { if (JsonDdlObject.class.equals(searchFor)) { break; } searchFor = searchFor.getSuperclass(); if (searchFor == null) { searchFor = JsonDdlObject.class; } } } lookup.put(l, NULL_METHOD); return null; } private static <J extends JsonDdlObject<J>> Object invoke(JsonDdlVisitor visitor, J obj, Context<J> ctx, String name) { Throwable ex; try { Method m = findMethod(visitor.getClass(), name, obj.getDdlObjectType()); if (m == null) { return null; } switch (m.getParameterTypes().length) { case 1: return m.invoke(visitor, obj); case 2: return m.invoke(visitor, obj, ctx); default: throw new RuntimeException("Should not have found method " + m.getName()); } } catch (IllegalAccessException e) { ex = e; } catch (InvocationTargetException e) { ex = e.getCause(); } throw new RuntimeException("Could not visit a " + visitor.getClass().getSimpleName() + " with a " + obj.getClass().getSimpleName(), ex); } }