docs: public Object invokeMissingProperty(Object instance, String propertyName, Object optionalValue, boolean isGetter) {
          Class theClass = instance instanceof Class ? (Class)instance : instance.getClass();
          CachedClass superClass = theCachedClass;
          while(superClass != null && superClass != ReflectionCache.OBJECT_CLASS) {
              final MetaBeanProperty property = findPropertyInClassHierarchy(propertyName, superClass);
              if(property != null) {
                  onSuperPropertyFoundInHierarchy(property);
                  if(!isGetter) {
                      property.setProperty(instance, optionalValue);
                      return null;
                  }
                  else {
                      return property.getProperty(instance);
                  }
              }
              superClass = superClass.getCachedSuperClass();
          }
          // got here to property not found, look for getProperty or setProperty overrides
          if(isGetter) {
              final Class[] getPropertyArgs = {String.class};
              final MetaMethod method = findMethodInClassHierarchy(instance.getClass(), GET_PROPERTY_METHOD, getPropertyArgs, this);
              if(method instanceof ClosureMetaMethod) {
                  onGetPropertyFoundInHierarchy(method);
                  return method.invoke(instance,new Object[]{propertyName});
              }
          }
          else {
              final Class[] setPropertyArgs = {String.class, Object.class};
              final MetaMethod method = findMethodInClassHierarchy(instance.getClass(), SET_PROPERTY_METHOD, setPropertyArgs, this);
              if(method instanceof ClosureMetaMethod) {
                  onSetPropertyFoundInHierarchy(method);
                  return method.invoke(instance, new Object[]{propertyName, optionalValue});
              }
          }
  
          try {
              if (!(instance instanceof Class)) {
                  if (isGetter) {
                      if (propertyMissingGet != null) {
                          return propertyMissingGet.invoke(instance, new Object[]{propertyName});
                      }
                  } else {
                      if (propertyMissingSet != null) {
                          return propertyMissingSet.invoke(instance, new Object[]{propertyName, optionalValue});
                      }
                  }
              }
          } catch (InvokerInvocationException iie) {
              boolean shouldHandle = isGetter && propertyMissingGet != null;
              if (!shouldHandle) shouldHandle = !isGetter && propertyMissingSet != null;
              if (shouldHandle &&  iie.getCause() instanceof MissingPropertyException) {
                  throw (MissingPropertyException) iie.getCause();
              }
              throw iie;
          }
  
          if (instance instanceof Class && theClass != Class.class) {
             final MetaProperty metaProperty = InvokerHelper.getMetaClass(Class.class).hasProperty(instance, propertyName);
             if (metaProperty != null)
               if (isGetter)
                 return metaProperty.getProperty(instance);
               else {
                 metaProperty.setProperty(instance, optionalValue);
                 return null;
               }
          }
          throw new MissingPropertyExceptionNoStack(propertyName, theClass);
      }