// 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.swing; import java.beans.IntrospectionException; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Collection; import java.util.Collections; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.Lists; import com.palantir.ptoss.cinch.core.BindableModel; import com.palantir.ptoss.cinch.core.Binding; import com.palantir.ptoss.cinch.core.BindingContext; import com.palantir.ptoss.cinch.core.BindingException; import com.palantir.ptoss.cinch.core.BindingWiring; import com.palantir.ptoss.cinch.core.ModelUpdate; import com.palantir.ptoss.cinch.core.ObjectFieldMethod; /** * A binding that will set the Visible state of the annotated component to the state of a model * boolean. The component must have a "setVisible" method that takes a boolean. */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD}) public @interface VisibleIf { /** * Enum to specify if normal or inverted comparisons should be used. */ public enum Type { NORMAL, INVERTED }; /** * The model boolean property to bind to. */ String to(); /** * Whether or not to invert the boolean. */ Type type() default Type.NORMAL; /** * Inner utility class that performs the runtime wiring of all {@link VisibleIf} bindings. */ public static class Wiring implements BindingWiring { private static final Logger logger = LoggerFactory.getLogger(VisibleIf.class); public Collection<Binding> wire(final BindingContext context) { final List<Field> actions = context.getAnnotatedFields(VisibleIf.class); final List<Binding> bindings = Lists.newArrayList(); for (final Field field : actions) { final VisibleIf action = field.getAnnotation(VisibleIf.class); final String to = action.to(); final boolean invert = (action.type() == Type.INVERTED); try { bindings.addAll(wire(to, field, context, invert)); } catch (final Exception e) { throw new BindingException("could not wire up @VisibleIf on " + field.getName(), e); } } return bindings; } private static Collection<Binding> wire(final String to, final Field field, final BindingContext context, final boolean invert) throws SecurityException, NoSuchMethodException, IllegalArgumentException, IntrospectionException { final Method setVisibleMethod = field.getType().getMethod("setVisible", boolean.class); if (setVisibleMethod == null) { throw new BindingException("no setVisible call on VisibleIf field: " + field); } final Object setVisibleObject = context.getFieldObject(field, Object.class); final ObjectFieldMethod getter = context.findGetter(to); if (getter == null) { throw new BindingException("could not find bindable property: " + to); } if (getter.getMethod().getReturnType() != boolean.class) { throw new BindingException("VisibleIf binding must return boolean: " + to); } final Binding binding = new Binding() { public <T extends Enum<?> & ModelUpdate> void update(final T... changed) { try { getter.getMethod().setAccessible(true); boolean visible = (Boolean)getter.getMethod().invoke(getter.getObject()); if (invert) { visible = !visible; } setVisibleMethod.invoke(setVisibleObject, visible); } catch (final Exception e) { Wiring.logger.error("exception during VisibleIf binding", e); } } }; ((BindableModel)getter.getObject()).bind(binding); return Collections.singleton(binding); } } }