// Copyright 2011 Palantir Technologies // // 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.palantir.ptoss.cinch.core; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Collection; import java.util.List; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; /** * An annotation to mark that a method should be called when the bound model updates * in a specific way. The method can have any access modifiers, i.e. it can be private. */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface CallOnUpdate { /** * The model field name to bind to. If this is left blank and the {@link BindingContext} has * only a single {@link BindableModel} then it will bind to that. */ String model() default ""; /** * What model update type to trigger on, or blank (default) for all updates. */ String[] on() default ""; //$NON-NLS-1$ /** * Utility class that performs the wiring for {@link CallOnUpdate} annotations. */ static class Wiring implements BindingWiring { private static final Logger logger = LoggerFactory.getLogger(CallOnUpdate.class); public Collection<Binding> wire(final BindingContext context) { final List<ObjectFieldMethod> methods = context.getAnnotatedParameterlessMethods(CallOnUpdate.class); final List<Binding> bindings = Lists.newArrayList(); for (final ObjectFieldMethod method : methods) { final CallOnUpdate callOnUpdate = method.getMethod().getAnnotation(CallOnUpdate.class); bindings.addAll(wire(callOnUpdate, context, method)); } return bindings; } private static Collection<Binding> wire(final CallOnUpdate callOnUpdate, final BindingContext context, final ObjectFieldMethod method) { final String to = callOnUpdate.model(); final BindableModel model; if (Strings.isNullOrEmpty(to)) { Set<BindableModel> models = context.getBindableModels(); if (models.size() != 1) { throw new BindingException("more than one bindable model for empty 'to'"); //$NON-NLS-1$ } model = models.iterator().next(); } else { model = context.getBindableModel(to); if (model == null) { throw new BindingException("can't find method to bind to: " + to); //$NON-NLS-1$ } } final String[] ons = callOnUpdate.on(); List<Object> onObjects = BindingContext.getOnObjects(ons, model); Binding binding = makeBinding(method, onObjects); model.bind(binding); return ImmutableList.of(binding); } private static Binding makeBinding(final ObjectFieldMethod method, final List<Object> onObjects) { final Method actualMethod = method.getMethod(); actualMethod.setAccessible(true); final Binding binding = new Binding() { public <T extends Enum<?> & ModelUpdate> void update(final T... changed) { if (!BindingContext.isOn(onObjects, changed)) { return; } try { actualMethod.invoke(method.getObject()); } catch (final InvocationTargetException itex) { logger.error("exception during CallOnUpdate firing", itex.getCause()); //$NON-NLS-1$ } catch (final Exception e) { logger.error("exception in method binding", e); //$NON-NLS-1$ } } }; return binding; } } }