├── settings.gradle ├── .circleci ├── template.sh └── .gitattributes ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── versions.props ├── .changelog.yml ├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── gradle.properties ├── .bulldozer.yml ├── .excavator.yml ├── src ├── test │ └── java │ │ └── com │ │ └── palantir │ │ └── ptoss │ │ └── cinch │ │ ├── example │ │ ├── dynamic │ │ │ └── BooleanModel.java │ │ ├── demo │ │ │ ├── package-info.java │ │ │ ├── DemoController.java │ │ │ ├── DrawingCanvasModel.java │ │ │ └── DrawingCanvas.java │ │ ├── superdemo │ │ │ ├── package-info.java │ │ │ ├── SuperDemoModel.java │ │ │ ├── SubDemo.java │ │ │ └── SuperDemo.java │ │ ├── simple │ │ │ ├── package-info.java │ │ │ ├── CallOnUpdateExample.java │ │ │ ├── BoundJCheckBoxExample.java │ │ │ └── BoundJListExample.java │ │ ├── extension │ │ │ ├── package-info.java │ │ │ ├── ExtendedBindings.java │ │ │ ├── LoggedModel.java │ │ │ └── ExtendedExample.java │ │ ├── Examples.java │ │ └── package-info.java │ │ ├── swing │ │ └── BoundTest.java │ │ ├── negative │ │ ├── NegativeController.java │ │ ├── NegativeModel.java │ │ ├── NegativeActionTest.java │ │ ├── WrongTypeTest.java │ │ ├── CantFindGetterSetterTest.java │ │ └── InaccessibleControllerMethodTest.java │ │ ├── BooleanModel.java │ │ ├── SerializableBindableModel.java │ │ ├── EnumTypesTest.java │ │ ├── SimpleModel.java │ │ ├── BoundJCheckBoxTest.java │ │ ├── BoundJCheckBoxMenuItemTest.java │ │ ├── AbstractBoundMethodTest.java │ │ ├── NotBindableTest.java │ │ ├── BoundJSliderTest.java │ │ ├── BoundJLabelTest.java │ │ ├── SerializableModelTest.java │ │ ├── AllCinchTests.java │ │ ├── ActionTest.java │ │ ├── ViewSubclassModelNameCollisionTest.java │ │ ├── BoundJComboBoxTest.java │ │ ├── BindingContextIndexTest.java │ │ ├── EnabledIfTest.java │ │ ├── BoundJTextComponentTest.java │ │ ├── BoundJToggleButtonTest.java │ │ ├── BindingContextTest.java │ │ ├── BoundJProgressBarTest.java │ │ └── SubclassEdgeCasesTest.java └── main │ └── java │ └── com │ └── palantir │ └── ptoss │ ├── util │ ├── package-info.java │ ├── Visitor.java │ ├── Throwables.java │ └── Mutator.java │ └── cinch │ ├── core │ ├── package-info.java │ ├── ModelUpdate.java │ ├── Binding.java │ ├── BindingException.java │ ├── BindingWiring.java │ ├── NopBindableModel.java │ ├── BindableModel.java │ ├── Bindable.java │ ├── NotBindable.java │ ├── WiringHarness.java │ ├── ModelUpdates.java │ ├── DefaultBindableModel.java │ ├── SimpleBinding.java │ ├── ObjectFieldMethod.java │ ├── WeakBindableModelSupport.java │ ├── WeakBindableModel.java │ ├── Bindings.java │ └── CallOnUpdate.java │ └── swing │ ├── package-info.java │ ├── JLabelWiringHarness.java │ ├── AbstractButtonWiringHarness.java │ ├── JSliderWiringHarness.java │ ├── JProgressBarWiringHarness.java │ ├── JComboBoxWiringHarness.java │ ├── JTextComponentWiringHarness.java │ ├── JPasswordFieldWiringHarness.java │ ├── OnClick.java │ ├── VisibleIf.java │ ├── JToggleButtonWiringHarness.java │ └── EnabledIf.java ├── LICENSE └── gradlew.bat /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'cinch' 2 | 3 | -------------------------------------------------------------------------------- /.circleci/template.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | export CIRCLECI_TEMPLATE=java-library-oss 3 | export JDK=8 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/palantir/Cinch/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /.circleci/.gitattributes: -------------------------------------------------------------------------------- 1 | # Mark .circleci/config.yml as generated for github reviews 2 | config.yml linguist-generated=true 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle/ 2 | target/ 3 | .*.swp 4 | *~ 5 | .settings 6 | build/ 7 | out/ 8 | .classpath 9 | .project 10 | *.iml 11 | *.ipr 12 | *.iws 13 | -------------------------------------------------------------------------------- /versions.props: -------------------------------------------------------------------------------- 1 | com.google.guava:guava = 33.5.0-jre 2 | de.ruedigermoeller:fst = 2.55 3 | junit:junit = 4.13.2 4 | org.apache.commons:commons-lang3 = 3.19.0 5 | org.slf4j:* = 1.7.30 6 | -------------------------------------------------------------------------------- /.changelog.yml: -------------------------------------------------------------------------------- 1 | # Excavator auto-updates this file. Please contribute improvements to the central template. 2 | 3 | # This file is intentionally empty. The file's existence enables changelog-app and is empty to use the default configuration. 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.4-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## What happened? 2 | 3 | 7 | 8 | ## What did you want to happen? 9 | 10 | 13 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.parallel=true 2 | org.gradle.jvmargs = --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED 3 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Before this PR 2 | 3 | 4 | ## After this PR 5 | 6 | ==COMMIT_MSG== 7 | ==COMMIT_MSG== 8 | 9 | ## Possible downsides? 10 | 11 | 12 | -------------------------------------------------------------------------------- /.bulldozer.yml: -------------------------------------------------------------------------------- 1 | # Excavator auto-updates this file. Please contribute improvements to the central template. 2 | 3 | version: 1 4 | merge: 5 | trigger: 6 | labels: ["merge when ready"] 7 | ignore: 8 | labels: ["do not merge"] 9 | method: squash 10 | options: 11 | squash: 12 | body: pull_request_body 13 | message_delimiter: ==COMMIT_MSG== 14 | delete_after_merge: true 15 | update: 16 | trigger: 17 | labels: ["update me"] 18 | -------------------------------------------------------------------------------- /.excavator.yml: -------------------------------------------------------------------------------- 1 | # Excavator auto-updates this file. Please contribute improvements to the central template. 2 | 3 | auto-label: 4 | names: 5 | versions-props/upgrade-all: [ "merge when ready" ] 6 | circleci/manage-circleci: [ "merge when ready" ] 7 | tags: 8 | donotmerge: [ "do not merge" ] 9 | roomba: [ "merge when ready", "🤖 fix nits" ] 10 | automerge: [ "merge when ready", "🤖 fix nits" ] 11 | standards: [ "merge when ready", "🤖 fix nits" ] 12 | autorelease: [ "autorelease" ] 13 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/example/dynamic/BooleanModel.java: -------------------------------------------------------------------------------- 1 | package com.palantir.ptoss.cinch.example.dynamic; 2 | 3 | import com.palantir.ptoss.cinch.core.DefaultBindableModel; 4 | 5 | /** 6 | * Simple model for a boolean field. 7 | */ 8 | public class BooleanModel extends DefaultBindableModel { 9 | boolean state = false; 10 | 11 | public boolean isState() { 12 | return state; 13 | } 14 | 15 | public void setState(boolean state) { 16 | this.state = state; 17 | update(); 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/swing/BoundTest.java: -------------------------------------------------------------------------------- 1 | package com.palantir.ptoss.cinch.swing; 2 | 3 | import junit.framework.TestCase; 4 | 5 | public class BoundTest extends TestCase { 6 | 7 | public void testIsNullOrBlank() { 8 | assertTrue(Bound.Utilities.isNullOrBlank(null)); 9 | assertTrue(Bound.Utilities.isNullOrBlank("")); 10 | assertTrue(Bound.Utilities.isNullOrBlank(" ")); 11 | assertFalse(Bound.Utilities.isNullOrBlank("a")); 12 | assertFalse(Bound.Utilities.isNullOrBlank(" a b c ")); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2011 Palantir Technologies 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/example/demo/package-info.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | /** 15 | * Fully-featured demo of the Cinch library. 16 | */ 17 | package com.palantir.ptoss.cinch.example.demo; 18 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/example/superdemo/package-info.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | /** 15 | * An example showing how inheritance interacts with Cinch. 16 | */ 17 | package com.palantir.ptoss.cinch.example.superdemo; 18 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/example/simple/package-info.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | /** 15 | * A couple of simple demo programs that show single features of Cinch. 16 | */ 17 | package com.palantir.ptoss.cinch.example.simple; 18 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/negative/NegativeController.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.negative; 15 | 16 | public class NegativeController { 17 | 18 | private void privateMethod() { 19 | // no-op 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/ptoss/util/package-info.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | /** 15 | * A collection of generic utility code that this package depends upon. 16 | * Pulled from a larger commons package internal to Palantir. 17 | */ 18 | package com.palantir.ptoss.util; 19 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/example/extension/package-info.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | /** 15 | * An example of extending the Cinch framework with custom {@link com.palantir.ptoss.cinch.core.Binding} annotations. 16 | */ 17 | package com.palantir.ptoss.cinch.example.extension; 18 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/ptoss/util/Visitor.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.util; 15 | 16 | /** 17 | * Interface for a visitor class. 18 | * 19 | * @param the type to be visited. 20 | * 21 | */ 22 | public interface Visitor { 23 | public void visit(T obj); 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/ptoss/cinch/core/package-info.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | /** 15 | * The core classes that implement the cinch binding framework - 16 | * see the Cinch Wiki 17 | * for full documentation. 18 | */ 19 | package com.palantir.ptoss.cinch.core; 20 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/ptoss/cinch/core/ModelUpdate.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.core; 15 | 16 | /** 17 | * A marker interface to designate that an {@link Enum} is intended to be used as a model 18 | * update specifier. 19 | */ 20 | public interface ModelUpdate { 21 | // Marker interface only. 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/ptoss/cinch/swing/package-info.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | /** 15 | * Classes used to bind Swing components to {@link com.palantir.ptoss.cinch.core.BindableModel} objects - 16 | * see the Cinch Wiki 17 | * for full documentation. 18 | */ 19 | package com.palantir.ptoss.cinch.swing; 20 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/BooleanModel.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch; 15 | 16 | import com.palantir.ptoss.cinch.core.DefaultBindableModel; 17 | 18 | public class BooleanModel extends DefaultBindableModel { 19 | boolean state = false; 20 | 21 | public boolean isState() { 22 | return state; 23 | } 24 | 25 | public void setState(boolean state) { 26 | this.state = state; 27 | update(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/ptoss/cinch/core/Binding.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.core; 15 | 16 | /** 17 | * Interface for Binding objects. 18 | */ 19 | public interface Binding { 20 | /** 21 | * Fires an update on this {@link Binding} object for the specified types. 22 | * 23 | * @param changed varargs of {@link ModelUpdate} types that are being fired by this call. 24 | */ 25 | public & ModelUpdate> void update(T... changed); 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/ptoss/cinch/core/BindingException.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.core; 15 | 16 | /** 17 | * A runtime exception for when a binding fails to bind or fails to perform. 18 | */ 19 | public class BindingException extends RuntimeException { 20 | private static final long serialVersionUID = 1L; 21 | 22 | public BindingException(String msg) { 23 | super(msg); 24 | } 25 | 26 | public BindingException(String msg, Exception ex) { 27 | super(msg, ex); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/negative/NegativeModel.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.negative; 15 | 16 | import com.palantir.ptoss.cinch.core.DefaultBindableModel; 17 | 18 | public class NegativeModel extends DefaultBindableModel { 19 | 20 | public int getBadEnum() { 21 | return 0; 22 | } 23 | 24 | public void setBadEnum() { 25 | //no-op 26 | } 27 | 28 | public void setBadBoolean() { 29 | //no-op 30 | } 31 | 32 | public int getBadBoolean() { 33 | return 0; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/SerializableBindableModel.java: -------------------------------------------------------------------------------- 1 | package com.palantir.ptoss.cinch; 2 | 3 | import com.palantir.ptoss.cinch.core.DefaultBindableModel; 4 | 5 | import java.io.Serializable; 6 | 7 | public class SerializableBindableModel extends DefaultBindableModel implements Serializable { 8 | 9 | private final String data; 10 | 11 | public SerializableBindableModel(String data) { 12 | super(); 13 | this.data = data; 14 | } 15 | 16 | public String getData() { 17 | return data; 18 | } 19 | 20 | @Override 21 | public String toString() { 22 | return "SerializableBindableModel{" + 23 | "data='" + data + '\'' + 24 | '}'; 25 | } 26 | 27 | @Override 28 | public boolean equals(Object o) { 29 | if (this == o) return true; 30 | if (o == null || getClass() != o.getClass()) return false; 31 | 32 | SerializableBindableModel that = (SerializableBindableModel) o; 33 | 34 | return data != null ? data.equals(that.data) : that.data == null; 35 | } 36 | 37 | @Override 38 | public int hashCode() { 39 | return data != null ? data.hashCode() : 0; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/example/superdemo/SuperDemoModel.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.example.superdemo; 15 | 16 | import com.palantir.ptoss.cinch.core.DefaultBindableModel; 17 | 18 | public class SuperDemoModel extends DefaultBindableModel { 19 | 20 | private boolean someBoolean; 21 | 22 | public void setSomeBoolean(boolean someBoolean) { 23 | this.someBoolean = someBoolean; 24 | System.out.println("boolean: " + this.someBoolean); 25 | update(); 26 | } 27 | 28 | public boolean isSomeBoolean() { 29 | return someBoolean; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/ptoss/cinch/core/BindingWiring.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.core; 15 | 16 | import java.util.Collection; 17 | /** 18 | * Interface for classes that perform wiring of bindings. 19 | */ 20 | public interface BindingWiring { 21 | /** 22 | * Performs wiring that this instance performs given the passed {@link BindingContext}. 23 | * @param context the {@link BindingContext} contain all the data about components in this context. 24 | * @return a collection of {@link Binding} objects wired by this {@link BindingWiring} instance. 25 | */ 26 | public Collection wire(BindingContext context); 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/ptoss/cinch/core/NopBindableModel.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.core; 15 | 16 | 17 | /** 18 | * A {@link BindableModel} implementation which does nothing. Can be used as a superclass to 19 | * an immutable class that needs to implement {@link BindableModel}. 20 | */ 21 | public class NopBindableModel implements BindableModel { 22 | 23 | public void bind(Binding to) { 24 | // Do nothing 25 | } 26 | 27 | public void unbind(Binding binding) { 28 | // Do nothing 29 | } 30 | 31 | public & ModelUpdate> void modelUpdated(T... changed) { 32 | // Do nothing 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/EnumTypesTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch; 15 | 16 | import com.palantir.ptoss.cinch.core.ModelUpdate; 17 | import com.palantir.ptoss.util.Reflections; 18 | 19 | import junit.framework.TestCase; 20 | 21 | 22 | public class EnumTypesTest extends TestCase { 23 | 24 | public enum TestEnum implements ModelUpdate { 25 | FOO, 26 | BAR 27 | } 28 | 29 | public void testEnumTypes() throws Exception { 30 | Object o = Reflections.evalEnum(TestEnum.class, "FOO"); 31 | assertTrue("Object is not a ModelUpdate instance",ModelUpdate.class.isAssignableFrom(o.getClass())); 32 | 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/example/superdemo/SubDemo.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.example.superdemo; 15 | 16 | import java.awt.EventQueue; 17 | 18 | 19 | public class SubDemo extends SuperDemo { 20 | 21 | private SubDemo() { 22 | super(); 23 | } 24 | 25 | public static SubDemo create() { 26 | SubDemo demo = new SubDemo(); 27 | demo.showUi(); 28 | return demo; 29 | } 30 | 31 | public static void main(String[] args) { 32 | EventQueue.invokeLater(new Runnable() { 33 | public void run() { 34 | SubDemo.create(); 35 | } 36 | }); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/SimpleModel.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch; 15 | 16 | import com.palantir.ptoss.cinch.core.DefaultBindableModel; 17 | import com.palantir.ptoss.cinch.core.ModelUpdate; 18 | 19 | public class SimpleModel extends DefaultBindableModel { 20 | private boolean simpleBoolean; 21 | 22 | public enum UpdateTypes implements ModelUpdate { 23 | NO_TRIGGER, SPECIFIC, MULTI_1, MULTI_2; 24 | } 25 | 26 | public void setSimpleBoolean(boolean simpleBoolean) { 27 | this.simpleBoolean = simpleBoolean; 28 | update(); 29 | } 30 | 31 | public boolean isSimpleBoolean() { 32 | return simpleBoolean; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/negative/NegativeActionTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.negative; 15 | 16 | import com.palantir.ptoss.cinch.core.Bindings; 17 | import com.palantir.ptoss.cinch.swing.Action; 18 | 19 | import junit.framework.TestCase; 20 | 21 | public class NegativeActionTest extends TestCase { 22 | @Action(call = "failure") 23 | private final Object object = new Object(); 24 | 25 | final Bindings bindings = new Bindings(); 26 | 27 | public void testFailure() { 28 | try { 29 | bindings.bind(this); 30 | fail("should not allow @Action binding to object with no addActionListener"); 31 | } catch (Exception e) { 32 | e.printStackTrace(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/example/Examples.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.example; 15 | 16 | import java.awt.Container; 17 | 18 | import javax.swing.JFrame; 19 | 20 | /** 21 | * Utility code used by the examples. 22 | */ 23 | public class Examples { 24 | 25 | public static void initializeLogging() { 26 | System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "INFO"); 27 | } 28 | 29 | public static JFrame getFrameFor(String title, Container container) { 30 | JFrame frame = new JFrame(title); 31 | frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 32 | frame.setLocation(100, 100); 33 | frame.setSize(400, 600); 34 | 35 | frame.setContentPane(container); 36 | return frame; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/example/package-info.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * A selection of examples using the Cinch framework. 17 | * 18 | *
    19 | *
  • {@link com.palantir.ptoss.cinch.example.superdemo.SuperDemo} - an example showing 20 | * how inheritance effects model binding.
  • 21 | *
  • {@link com.palantir.ptoss.cinch.example.demo.DemoView} - a complex example showing 22 | * all the features of the Cinch framework.
  • 23 | *
  • {@link com.palantir.ptoss.cinch.example.simple.BoundJCheckBoxExample} - simple example 24 | * binding a single control to a model field. Also explores using {@link com.palantir.ptoss.cinch.core.Bindable} 25 | * and {@link com.palantir.ptoss.cinch.swing.Action} annotations for more complex binding. 26 | *
27 | */ 28 | package com.palantir.ptoss.cinch.example; 29 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/ptoss/cinch/core/BindableModel.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.core; 15 | 16 | /** 17 | * A model that can have bindings applied to it. 18 | */ 19 | public interface BindableModel { 20 | /** 21 | * Attach the binding to the model. Whenever the model is updated then the binding will be 22 | * triggered. 23 | * @param toBind 24 | */ 25 | void bind(Binding toBind); 26 | 27 | /** 28 | * Removes the passed binding from this model. 29 | * @param toUnbind 30 | */ 31 | void unbind(Binding toUnbind); 32 | 33 | /** 34 | * Alert the bindings that the model has updated with the specified change types. 35 | * @param changed list of change types to indicate 36 | */ 37 | & ModelUpdate> void modelUpdated(T... changed); 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/negative/WrongTypeTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.negative; 15 | 16 | import javax.swing.JRadioButton; 17 | 18 | import com.palantir.ptoss.cinch.core.Bindings; 19 | import com.palantir.ptoss.cinch.swing.Bound; 20 | 21 | import junit.framework.TestCase; 22 | 23 | public class WrongTypeTest extends TestCase { 24 | 25 | final NegativeModel model = new NegativeModel(); 26 | 27 | @Bound(to = "badEnum") 28 | private final JRadioButton button = new JRadioButton("button"); 29 | 30 | final Bindings bindings = new Bindings(); 31 | 32 | public void testFailure() { 33 | try { 34 | bindings.bind(this); 35 | fail("should not allow binding to wrong type"); 36 | } catch (Exception e) { 37 | System.err.println(e.getMessage()); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/negative/CantFindGetterSetterTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.negative; 15 | 16 | import javax.swing.JRadioButton; 17 | 18 | import com.palantir.ptoss.cinch.core.Bindings; 19 | import com.palantir.ptoss.cinch.swing.Bound; 20 | 21 | import junit.framework.TestCase; 22 | 23 | public class CantFindGetterSetterTest extends TestCase { 24 | 25 | final NegativeModel model = new NegativeModel(); 26 | 27 | @Bound(to = "cantFind") 28 | private final JRadioButton button = new JRadioButton("button"); 29 | 30 | final Bindings bindings = new Bindings(); 31 | 32 | public void testFailure() { 33 | try { 34 | bindings.bind(this); 35 | fail("should not allow binding if can't find getter or setter"); 36 | } catch (Exception e) { 37 | System.err.println(e.getMessage()); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/example/extension/ExtendedBindings.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.example.extension; 15 | 16 | import java.util.List; 17 | 18 | import com.google.common.collect.Lists; 19 | import com.palantir.ptoss.cinch.core.BindingWiring; 20 | import com.palantir.ptoss.cinch.core.Bindings; 21 | 22 | /** 23 | * A sample class that returns extended bindings for including customizations to Cinch. 24 | * 25 | * @see Bindings 26 | * @see ExtendedExample 27 | * @see LoggedModel 28 | * 29 | */ 30 | public class ExtendedBindings { 31 | /** 32 | * Call this instead of {@link Bindings#standard()} to inject the custom bindings. 33 | */ 34 | public static Bindings extendedBindings() { 35 | // start with standard set of bindings 36 | List wirings = Lists.newArrayList(Bindings.STANDARD_BINDINGS); 37 | // add in all additions 38 | wirings.add(new LoggedModel.Wiring()); 39 | return new Bindings(wirings); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/ptoss/cinch/core/Bindable.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.core; 15 | 16 | import java.lang.annotation.ElementType; 17 | import java.lang.annotation.Retention; 18 | import java.lang.annotation.RetentionPolicy; 19 | import java.lang.annotation.Target; 20 | 21 | import com.palantir.ptoss.cinch.swing.Action; 22 | 23 | /** 24 | *

25 | * Marks the annotated field or type as bindable. For a type this makes all methods 26 | * accessible to bindings that have a 'call' parameter like {@link Action}. 27 | *

28 | * Note that this 29 | * allows the methods to be called, but it does not enable the binding of controls to 30 | * fields on the object being marked as bindable. 31 | *

32 | * 33 | * @see BindableModel Information on creating bindable models. 34 | * @see Action Information on using @Action annotations 35 | */ 36 | @Retention(RetentionPolicy.RUNTIME) 37 | @Target({ElementType.FIELD, ElementType.TYPE}) 38 | public @interface Bindable { 39 | // Empty annotation 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/ptoss/cinch/core/NotBindable.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.core; 15 | 16 | import java.lang.annotation.ElementType; 17 | import java.lang.annotation.Retention; 18 | import java.lang.annotation.RetentionPolicy; 19 | import java.lang.annotation.Target; 20 | 21 | /** 22 | * Marks the annotated field as not bindable. In Cinch, all bindable fields are required to be 23 | * marked final. In fact, if a {@link BindableModel} has a field that is not marked 24 | * final, a runtime error will during the call to {@link Bindings#bind(Object)}. To 25 | * allow {@link BindableModel}s to have non-final fields, mark those fields with this annotation. 26 | *

27 | * It can be thought of as similar to {@link SuppressWarnings} as a way to force the coder 28 | * to explicitly specify intention when doing something dangerous. 29 | *

30 | */ 31 | @Retention(RetentionPolicy.RUNTIME) 32 | @Target({ElementType.FIELD}) 33 | public @interface NotBindable { 34 | // Empty annotation. 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/BoundJCheckBoxTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch; 15 | 16 | import javax.swing.JCheckBox; 17 | 18 | import com.palantir.ptoss.cinch.core.Bindings; 19 | import com.palantir.ptoss.cinch.swing.Bound; 20 | 21 | import junit.framework.TestCase; 22 | 23 | public class BoundJCheckBoxTest extends TestCase { 24 | 25 | final BooleanModel model = new BooleanModel(); 26 | 27 | @Bound(to = "state") 28 | final JCheckBox box = new JCheckBox(); 29 | 30 | final Bindings bindings = Bindings.standard(); 31 | 32 | @Override 33 | protected void setUp() throws Exception { 34 | bindings.bind(this); 35 | } 36 | 37 | public void testSimple() { 38 | assertFalse(model.isState()); 39 | assertFalse(box.isSelected()); 40 | model.setState(true); 41 | assertTrue(box.isSelected()); 42 | assertTrue(model.isState()); 43 | box.doClick(); 44 | assertFalse(model.isState()); 45 | assertFalse(box.isSelected()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/negative/InaccessibleControllerMethodTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.negative; 15 | 16 | import javax.swing.JButton; 17 | 18 | import com.palantir.ptoss.cinch.core.Bindable; 19 | import com.palantir.ptoss.cinch.core.Bindings; 20 | import com.palantir.ptoss.cinch.swing.Action; 21 | 22 | import junit.framework.TestCase; 23 | 24 | public class InaccessibleControllerMethodTest extends TestCase { 25 | 26 | // final NegativeModel model = new NegativeModel(); 27 | 28 | @Bindable 29 | final NegativeController controller = new NegativeController(); 30 | 31 | @Action(call = "privateMethod") 32 | private final JButton button = new JButton("button"); 33 | 34 | final Bindings bindings = new Bindings(); 35 | 36 | public void testFailure() { 37 | try { 38 | bindings.bind(this); 39 | fail("should not allow binding to inaccessible controller methods"); 40 | } catch (Exception e) { 41 | // passes 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/BoundJCheckBoxMenuItemTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch; 15 | 16 | import javax.swing.JCheckBoxMenuItem; 17 | 18 | import com.palantir.ptoss.cinch.core.Bindings; 19 | import com.palantir.ptoss.cinch.swing.Bound; 20 | 21 | import junit.framework.TestCase; 22 | 23 | public class BoundJCheckBoxMenuItemTest extends TestCase { 24 | 25 | final BooleanModel model = new BooleanModel(); 26 | 27 | @Bound(to = "state") 28 | final JCheckBoxMenuItem menuItem = new JCheckBoxMenuItem(); 29 | 30 | final Bindings bindings = Bindings.standard(); 31 | 32 | @Override 33 | protected void setUp() throws Exception { 34 | bindings.bind(this); 35 | } 36 | 37 | public void testSimple() { 38 | assertFalse(model.isState()); 39 | assertFalse(menuItem.isSelected()); 40 | model.setState(true); 41 | assertTrue(menuItem.isSelected()); 42 | assertTrue(model.isState()); 43 | menuItem.doClick(); 44 | assertFalse(model.isState()); 45 | assertFalse(menuItem.isSelected()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/ptoss/cinch/core/WiringHarness.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.core; 15 | 16 | import java.beans.IntrospectionException; 17 | import java.lang.annotation.Annotation; 18 | import java.util.Collection; 19 | 20 | /** 21 | * Interface for classes that actually perform a specific wiring. 22 | * @author regs 23 | * 24 | * @param the type of {@link Annotation} that this harness consumes. 25 | * @param the type of component that this harness knows how to wire. 26 | */ 27 | public interface WiringHarness { 28 | /** 29 | * Performs the wiring of a specific component based on the passed binding annotation. 30 | * @param bound annotation containing the data needed to wire this component to the context. 31 | * @param context metadata about the context for wiring. 32 | * @param toWire the component to be wired by this harness. 33 | * @return one or more {@link Binding} objects represent the wirings performed by this harness. 34 | * @throws IllegalAccessException 35 | * @throws IntrospectionException 36 | */ 37 | Collection wire(T bound, BindingContext context, W toWire) 38 | throws IllegalAccessException, IntrospectionException; 39 | } 40 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/AbstractBoundMethodTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch; 15 | 16 | import javax.swing.JButton; 17 | 18 | import com.palantir.ptoss.cinch.core.Bindable; 19 | import com.palantir.ptoss.cinch.core.Bindings; 20 | import com.palantir.ptoss.cinch.swing.Action; 21 | 22 | import junit.framework.TestCase; 23 | 24 | // This test exists because ideally you shouldn't have to specify "this.refresh" as below. 25 | public class AbstractBoundMethodTest extends TestCase { 26 | 27 | @Bindable 28 | private abstract static class Base { 29 | @Action(call = "this.refresh") 30 | JButton button = new JButton("test"); 31 | 32 | abstract public void refresh(); 33 | } 34 | 35 | @Bindable 36 | private static class Impl extends Base { 37 | int refreshCount = 0; 38 | 39 | private final Bindings bindings = new Bindings(); 40 | public Impl() { 41 | bindings.bind(this); 42 | } 43 | 44 | @Override 45 | public void refresh() { 46 | refreshCount++; 47 | } 48 | } 49 | 50 | public void testIt() throws Exception { 51 | Impl impl = new Impl(); 52 | impl.button.doClick(); 53 | assertEquals(1, impl.refreshCount); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/ptoss/cinch/core/ModelUpdates.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.core; 15 | 16 | import com.palantir.ptoss.cinch.swing.Bound; 17 | 18 | /** 19 | * Pre-defined {@link ModelUpdate} types with special meanings to the framework. 20 | * 21 | * @see Bound 22 | * @see CallOnUpdate 23 | */ 24 | public enum ModelUpdates implements ModelUpdate { 25 | 26 | /** 27 | *

28 | * This is used for unspecified update types in the default implementations of {@link BindableModel}. 29 | * Listeners that don't have a specified "on" parameter should react to these model updates. 30 | *

31 | *

32 | * Note: when bound to a specific {@link ModelUpdate}, a control will not 33 | * receive updates not sent specifically to any {@link ModelUpdate} - i.e. default updates. To 34 | * selectively answer a specific {@link ModelUpdate} and also still get default notications, 35 | * add {@link ModelUpdates#UNSPECIFIED} to the list of {@link ModelUpdate}s passed to {@link Bound#on()}. 36 | *

37 | * @see Bound 38 | */ 39 | UNSPECIFIED, 40 | 41 | /** 42 | * This is used to sync all listeners. All listeners, regardless of whether they have an "on" parameter, should 43 | * react to these model updates. 44 | */ 45 | ALL; 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/NotBindableTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch; 15 | 16 | import com.palantir.ptoss.cinch.core.Bindings; 17 | import com.palantir.ptoss.cinch.core.DefaultBindableModel; 18 | import com.palantir.ptoss.cinch.core.NotBindable; 19 | 20 | import junit.framework.TestCase; 21 | 22 | public class NotBindableTest extends TestCase { 23 | 24 | class Model extends DefaultBindableModel { 25 | // 26 | } 27 | 28 | class GoodView { 29 | final Model model = new Model(); 30 | 31 | @NotBindable 32 | Model tempModel; 33 | 34 | final Bindings bindings = new Bindings(); 35 | 36 | public GoodView() { 37 | bindings.bind(this); 38 | } 39 | } 40 | 41 | public void testGoodNotBindable() { 42 | new GoodView(); 43 | } 44 | 45 | class BadView { 46 | final Model model = new Model(); 47 | 48 | Model tempModel; 49 | 50 | final Bindings bindings = new Bindings(); 51 | 52 | public BadView() { 53 | bindings.bind(this); 54 | } 55 | } 56 | 57 | public void testBadNotBindable() { 58 | try { 59 | new BadView(); 60 | fail("should not allow non-final unannotated bindable model"); 61 | } catch (Exception e) { 62 | // needs to throw exception to pass; 63 | e.printStackTrace(); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/BoundJSliderTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch; 15 | 16 | import javax.swing.JSlider; 17 | 18 | import com.palantir.ptoss.cinch.core.Bindings; 19 | import com.palantir.ptoss.cinch.core.DefaultBindableModel; 20 | import com.palantir.ptoss.cinch.swing.Bound; 21 | 22 | import junit.framework.TestCase; 23 | 24 | public class BoundJSliderTest extends TestCase { 25 | 26 | public static class IntegerModel extends DefaultBindableModel { 27 | int value = 0; 28 | 29 | public int getValue() { 30 | return value; 31 | } 32 | 33 | public void setValue(int value) { 34 | this.value = value; 35 | update(); 36 | } 37 | } 38 | 39 | private final IntegerModel model = new IntegerModel(); 40 | 41 | @Bound(to = "value") 42 | private final JSlider slider = new JSlider(); 43 | 44 | private final Bindings bindings = Bindings.standard(); 45 | 46 | @Override 47 | protected void setUp() { 48 | bindings.bind(this); 49 | } 50 | 51 | public void testSimple() { 52 | assertEquals(0, slider.getValue()); 53 | assertEquals(0, model.getValue()); 54 | model.setValue(50); 55 | assertEquals(50, slider.getValue()); 56 | assertEquals(50, model.getValue()); 57 | slider.setValue(75); 58 | assertEquals(75, slider.getValue()); 59 | assertEquals(75, model.getValue()); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/BoundJLabelTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch; 15 | 16 | import javax.swing.JLabel; 17 | 18 | import com.palantir.ptoss.cinch.core.Bindings; 19 | import com.palantir.ptoss.cinch.core.DefaultBindableModel; 20 | import com.palantir.ptoss.cinch.swing.Bound; 21 | 22 | import junit.framework.TestCase; 23 | 24 | public class BoundJLabelTest extends TestCase { 25 | public static class Model extends DefaultBindableModel { 26 | private String string; 27 | 28 | public String getString() { 29 | return string; 30 | } 31 | 32 | public void setString(String string) { 33 | this.string = string; 34 | update(); 35 | } 36 | } 37 | 38 | private final Model model = new Model(); 39 | 40 | @Bound(to = "string") 41 | private final JLabel label = new JLabel(); 42 | 43 | private final Bindings bindings = Bindings.standard(); 44 | 45 | @Override 46 | protected void setUp() throws Exception { 47 | bindings.bind(this); 48 | } 49 | 50 | public void testBinding() { 51 | assertNull(model.getString()); 52 | assertEquals("", label.getText()); 53 | model.setString("string"); 54 | assertEquals("string", model.getString()); 55 | assertEquals("string", label.getText()); 56 | model.setString(null); 57 | assertNull(model.getString()); 58 | assertEquals("", label.getText()); 59 | } 60 | } 61 | 62 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/example/demo/DemoController.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.example.demo; 15 | 16 | import com.google.common.collect.ImmutableList; 17 | import com.palantir.ptoss.cinch.example.demo.DemoModel.DemoEnum; 18 | 19 | public class DemoController { 20 | 21 | private final DemoModel model; 22 | 23 | public int demo1ActionCount = 0; 24 | public int demo2ActionCount = 0; 25 | 26 | public DemoController(final DemoModel model) { 27 | this.model = model; 28 | } 29 | 30 | public void demoAction() { 31 | demo1ActionCount++; 32 | } 33 | 34 | public void demoAction2() { 35 | demo2ActionCount++; 36 | } 37 | 38 | public void setToFoo() { 39 | model.setDemoEnum(DemoEnum.FOO); 40 | } 41 | 42 | int changeCount = 0; 43 | public void changeList() { 44 | final ImmutableList one = ImmutableList.of("The", "Fox", "Foxy", "Jumped", "Over", "Lazy", "Dog"); 45 | final ImmutableList two = ImmutableList.of("Quick", "Quickly", "Quickest", "Brown", "Fox"); 46 | if (++changeCount % 2 != 0) { 47 | model.setDemoList(one); 48 | } else { 49 | model.setDemoList(two); 50 | } 51 | } 52 | 53 | public void selectFox() { 54 | model.setSelectedItem("Fox"); 55 | } 56 | 57 | public int duplicateCount = 0; 58 | public void duplicate() { 59 | duplicateCount++; 60 | } 61 | 62 | public void selectMulti() { 63 | model.setMultiSelectedItems(ImmutableList.of("bravo", "delta")); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/ptoss/util/Throwables.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.util; 15 | 16 | import java.io.InterruptedIOException; 17 | 18 | /** 19 | * A class of utility methods to make exception handling cleaner. 20 | */ 21 | public final class Throwables { 22 | 23 | private Throwables() { /* Static utility methods only. */ } 24 | 25 | /** 26 | * If Throwable is a RuntimeException or Error, rethrow it. If not, throw a 27 | * new PalantirRuntimeException(ex) 28 | */ 29 | public static RuntimeException throwUncheckedException(Throwable ex) { 30 | throwIfInstance(ex, RuntimeException.class); 31 | throwIfInstance(ex, Error.class); 32 | throw createRuntimeException(ex); 33 | } 34 | 35 | private static RuntimeException createRuntimeException(Throwable ex) { 36 | return createRuntimeException(ex.getMessage(), ex); 37 | } 38 | 39 | private static RuntimeException createRuntimeException(String newMessage, Throwable ex) { 40 | if (ex instanceof InterruptedException || ex instanceof InterruptedIOException) { 41 | Thread.currentThread().interrupt(); 42 | } 43 | return new RuntimeException(newMessage, ex); 44 | } 45 | 46 | /** 47 | * if (t instanceof K) throw (K)t; 48 | *

49 | * Note: The runtime type of the thrown exception will be the same as t even if 50 | * clazz is a supertype of t. 51 | */ 52 | @SuppressWarnings("unchecked") 53 | public static void throwIfInstance(Throwable t, Class clazz) throws K { 54 | if ((t != null) && clazz.isAssignableFrom(t.getClass())) { 55 | K kt = (K) t; 56 | throw kt; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/ptoss/cinch/core/DefaultBindableModel.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.core; 15 | 16 | import java.io.IOException; 17 | import java.io.ObjectInputStream; 18 | import java.util.List; 19 | import java.util.concurrent.CopyOnWriteArrayList; 20 | 21 | /** 22 | * Default implementation of {@link BindableModel} - should be subclassed by implementations. 23 | */ 24 | public class DefaultBindableModel implements BindableModel { 25 | 26 | // must be transient because Bindings aren't serializable, and 27 | // subclasses might be serialized. 28 | private transient List bindings; 29 | 30 | public DefaultBindableModel() { 31 | bindings = new CopyOnWriteArrayList(); 32 | } 33 | 34 | /** 35 | * {@inheritDoc} 36 | */ 37 | public void bind(Binding toBind) { 38 | if (!bindings.contains(toBind)) { 39 | bindings.add(toBind); 40 | } 41 | } 42 | 43 | /** 44 | * {@inheritDoc} 45 | */ 46 | public void unbind(Binding toUnbind) { 47 | bindings.remove(toUnbind); 48 | } 49 | 50 | /** 51 | * Removes all bindings from this model. 52 | */ 53 | public void unbindAll() { 54 | bindings.clear(); 55 | } 56 | /** 57 | * {@inheritDoc} 58 | */ 59 | public & ModelUpdate> void modelUpdated(T... changed) { 60 | for (Binding binding : bindings) { 61 | binding.update(changed); 62 | } 63 | } 64 | 65 | /** 66 | * Performs a model update of type {@link ModelUpdates#UNSPECIFIED} - convenience method 67 | * for most models. 68 | */ 69 | public void update() { 70 | this.modelUpdated(ModelUpdates.UNSPECIFIED); 71 | } 72 | 73 | private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { 74 | in.defaultReadObject(); 75 | bindings = new CopyOnWriteArrayList(); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/ptoss/cinch/swing/JLabelWiringHarness.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.swing; 15 | 16 | import java.beans.IntrospectionException; 17 | import java.lang.reflect.Field; 18 | import java.util.Collection; 19 | 20 | import javax.swing.JLabel; 21 | 22 | import com.google.common.collect.ImmutableList; 23 | import com.palantir.ptoss.cinch.core.BindableModel; 24 | import com.palantir.ptoss.cinch.core.Binding; 25 | import com.palantir.ptoss.cinch.core.BindingContext; 26 | import com.palantir.ptoss.cinch.core.ModelUpdate; 27 | import com.palantir.ptoss.cinch.core.WiringHarness; 28 | import com.palantir.ptoss.cinch.swing.Bound.Wiring; 29 | import com.palantir.ptoss.util.Mutator; 30 | 31 | /** 32 | * A {@link WiringHarness} for binding a {@link JLabel} to a value in a {@link BindableModel}. 33 | */ 34 | public class JLabelWiringHarness implements WiringHarness { 35 | public Collection wire(Bound bound, BindingContext context, Field field) 36 | throws IllegalAccessException, IntrospectionException { 37 | JLabel label = context.getFieldObject(field, JLabel.class); 38 | Mutator mutator = Mutator.create(context, bound.to()); 39 | return ImmutableList.of(bindJLabel(mutator, label)); 40 | } 41 | 42 | public static Binding bindJLabel(final Mutator mutator, final JLabel label) { 43 | Binding binding = new Binding() { 44 | public & ModelUpdate> void update(T... changed) { 45 | try { 46 | String text = ""; 47 | Object obj = mutator.get(); 48 | if (obj != null) { 49 | text = obj.toString(); 50 | } 51 | label.setText(text); 52 | } catch (Exception ex) { 53 | Wiring.logger.error("exception in JLabel binding", ex); 54 | } 55 | } 56 | }; 57 | mutator.getModel().bind(binding); 58 | return binding; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/SerializableModelTest.java: -------------------------------------------------------------------------------- 1 | package com.palantir.ptoss.cinch; 2 | 3 | import com.palantir.ptoss.cinch.core.Binding; 4 | import com.palantir.ptoss.cinch.core.ModelUpdate; 5 | import org.apache.commons.lang3.SerializationUtils; 6 | import org.junit.Assert; 7 | import org.junit.Test; 8 | import org.nustaq.serialization.FSTConfiguration; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | public class SerializableModelTest { 13 | 14 | private static final Logger log = LoggerFactory.getLogger(SerializableModelTest.class); 15 | private static FSTConfiguration conf = FSTConfiguration.createDefaultConfiguration(); 16 | 17 | private final Binding testBinding = new Binding() { 18 | @Override 19 | public & ModelUpdate> void update(T... changed) { 20 | log.info("Ran update"); 21 | } 22 | }; 23 | 24 | @Test 25 | public void testJavaSerialize() { 26 | SerializableBindableModel model = new SerializableBindableModel("testing"); 27 | javaRoundTrip(model); 28 | } 29 | 30 | @Test 31 | public void testJavaSerializeWithBinding() { 32 | SerializableBindableModel model = new SerializableBindableModel("testing"); 33 | model.bind(testBinding); 34 | model.update(); 35 | SerializableBindableModel rehydrated = javaRoundTrip(model); 36 | rehydrated.bind(testBinding); 37 | rehydrated.update(); 38 | } 39 | 40 | private SerializableBindableModel javaRoundTrip(SerializableBindableModel model) { 41 | SerializableBindableModel rehydrated = (SerializableBindableModel) SerializationUtils.clone(model); 42 | Assert.assertEquals(model, rehydrated); 43 | return rehydrated; 44 | } 45 | 46 | @Test 47 | public void testFstSerialize() { 48 | SerializableBindableModel model = new SerializableBindableModel("testing"); 49 | fstRoundTrip(model); 50 | } 51 | 52 | @Test 53 | public void testFstSerializeWithBinding() { 54 | SerializableBindableModel model = new SerializableBindableModel("testing"); 55 | model.bind(testBinding); 56 | model.update(); 57 | SerializableBindableModel rehydrated = fstRoundTrip(model); 58 | rehydrated.bind(testBinding); 59 | rehydrated.update(); 60 | } 61 | 62 | private SerializableBindableModel fstRoundTrip(SerializableBindableModel model) { 63 | byte[] bytes = conf.asByteArray(model); 64 | SerializableBindableModel rehydrated = (SerializableBindableModel)conf.asObject(bytes); 65 | Assert.assertEquals(model, rehydrated); 66 | return rehydrated; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/example/demo/DrawingCanvasModel.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.example.demo; 15 | 16 | import java.awt.geom.Line2D; 17 | import java.awt.geom.Point2D; 18 | import java.util.List; 19 | 20 | import com.google.common.collect.Lists; 21 | import com.palantir.ptoss.cinch.core.DefaultBindableModel; 22 | import com.palantir.ptoss.cinch.core.ModelUpdate; 23 | 24 | public class DrawingCanvasModel extends DefaultBindableModel { 25 | 26 | public enum UpdateType implements ModelUpdate { 27 | DATA; 28 | } 29 | 30 | public enum Mode { 31 | POINT, LINE; 32 | } 33 | 34 | private final List lines = Lists.newArrayList(); 35 | private final List points = Lists.newArrayList(); 36 | 37 | private Mode mode = Mode.POINT; 38 | private boolean allowDrag = true; 39 | private Point2D cursor; 40 | 41 | public void addLine(Line2D line) { 42 | lines.add(line); 43 | modelUpdated(UpdateType.DATA); 44 | } 45 | 46 | public List getLines() { 47 | return lines; 48 | } 49 | 50 | public void addPoint(Point2D point) { 51 | points.add(point); 52 | modelUpdated(UpdateType.DATA); 53 | } 54 | 55 | public List getPoints() { 56 | return points; 57 | } 58 | 59 | public void setMode(Mode mode) { 60 | setCursor(null); 61 | this.mode = mode; 62 | update(); 63 | } 64 | 65 | public Mode getMode() { 66 | return mode; 67 | } 68 | 69 | public void clear() { 70 | lines.clear(); 71 | points.clear(); 72 | setCursor(null); 73 | modelUpdated(UpdateType.DATA); 74 | } 75 | 76 | public void setAllowDrag(boolean allowDrag) { 77 | this.allowDrag = allowDrag; 78 | update(); 79 | } 80 | 81 | public boolean isAllowDrag() { 82 | return allowDrag; 83 | } 84 | 85 | public void setCursor(Point2D cursor) { 86 | this.cursor = cursor; 87 | update(); 88 | } 89 | 90 | public Point2D getCursor() { 91 | return cursor; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/AllCinchTests.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch; 15 | 16 | import com.palantir.ptoss.cinch.negative.CantFindGetterSetterTest; 17 | import com.palantir.ptoss.cinch.negative.InaccessibleControllerMethodTest; 18 | import com.palantir.ptoss.cinch.negative.NegativeActionTest; 19 | import com.palantir.ptoss.cinch.negative.WrongTypeTest; 20 | 21 | import com.palantir.ptoss.cinch.swing.BoundTest; 22 | import junit.framework.Test; 23 | import junit.framework.TestSuite; 24 | 25 | public class AllCinchTests { 26 | public static Test suite() { 27 | TestSuite suite = new TestSuite("Palantir Cinch Tests"); 28 | //$JUnit-BEGIN$ 29 | suite.addTestSuite(BindingsTest.class); 30 | suite.addTestSuite(BindingsSubclassTest.class); 31 | suite.addTestSuite(CantFindGetterSetterTest.class); 32 | suite.addTestSuite(WrongTypeTest.class); 33 | suite.addTestSuite(InaccessibleControllerMethodTest.class); 34 | suite.addTestSuite(AbstractBoundMethodTest.class); 35 | suite.addTestSuite(BindingContextIndexTest.class); 36 | suite.addTestSuite(BindingContextTest.class); 37 | suite.addTestSuite(EnabledIfTest.class); 38 | suite.addTestSuite(ActionTest.class); 39 | suite.addTestSuite(ViewSubclassModelNameCollisionTest.class); 40 | suite.addTestSuite(NotBindableTest.class); 41 | suite.addTestSuite(SubclassEdgeCasesTest.class); 42 | suite.addTestSuite(NegativeActionTest.class); 43 | suite.addTestSuite(CallOnUpdateTest.class); 44 | 45 | suite.addTestSuite(BoundJLabelTest.class); 46 | suite.addTestSuite(BoundJComboBoxTest.class); 47 | suite.addTestSuite(BoundJProgressBarTest.class); 48 | suite.addTestSuite(BoundJCheckBoxTest.class); 49 | suite.addTestSuite(BoundJCheckBoxMenuItemTest.class); 50 | suite.addTestSuite(BoundJToggleButtonTest.class); 51 | suite.addTestSuite(BoundJSliderTest.class); 52 | suite.addTestSuite(BoundJTextComponentTest.class); 53 | suite.addTestSuite(BoundJListTest.class); 54 | suite.addTestSuite(BoundTest.class); 55 | //$JUnit-END$ 56 | return suite; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/ptoss/cinch/core/SimpleBinding.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.core; 15 | 16 | import com.google.common.collect.ImmutableSet; 17 | 18 | /** 19 | * An implementation of {@link Binding} that hides the complicated method signature required 20 | * of that interface. Instead, implementors can override the {@link #onUpdate()} function for 21 | * their listener's behavior. Any "on" parameters can be passed in at creation time. 22 | */ 23 | public abstract class SimpleBinding implements Binding { 24 | 25 | private final ImmutableSet on; 26 | 27 | private ImmutableSet changed = ImmutableSet.of(); 28 | 29 | /** 30 | * Default constructor that will fire on any model update. 31 | */ 32 | public & ModelUpdate> SimpleBinding() { 33 | this.on = ImmutableSet.of(); 34 | } 35 | 36 | /** 37 | * Constructor that will make it so this {@link Binding} only fires on the given 38 | * "on" parameters. 39 | * @param 40 | * @param on 41 | */ 42 | public & ModelUpdate> SimpleBinding(T... on) { 43 | this.on = ImmutableSet.copyOf(on); 44 | } 45 | 46 | /** 47 | * {@inheritDoc} 48 | */ 49 | public & ModelUpdate> void update(T... changes) { 50 | boolean fire = false; 51 | if (on.isEmpty()) { 52 | fire = true; 53 | } else { 54 | for (T t : changes) { 55 | if (t == ModelUpdates.ALL || on.contains(t)) { 56 | fire = true; 57 | break; 58 | } 59 | } 60 | } 61 | if (fire) { 62 | this.changed = ImmutableSet.copyOf(changes); 63 | onUpdate(); 64 | } 65 | } 66 | 67 | /** 68 | * Gets the set of {@link ModelUpdate}s that occurred on the last update. 69 | */ 70 | public ImmutableSet getLastChanged() { 71 | return changed; 72 | } 73 | 74 | /** 75 | * This will be called when the model is changed in a way compatible with the "on" 76 | * parameters specified at compile time. 77 | */ 78 | public abstract void onUpdate(); 79 | } 80 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/ActionTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch; 15 | 16 | import java.awt.event.ActionEvent; 17 | import java.awt.event.ActionListener; 18 | 19 | import com.palantir.ptoss.cinch.core.Bindable; 20 | import com.palantir.ptoss.cinch.core.Bindings; 21 | import com.palantir.ptoss.cinch.swing.Action; 22 | 23 | import junit.framework.TestCase; 24 | 25 | @Bindable 26 | public class ActionTest extends TestCase { 27 | 28 | public static class Actionable { 29 | private ActionListener actionListener; 30 | 31 | public void addActionListener(ActionListener actionListener) { 32 | if (this.actionListener != null) { 33 | throw new IllegalStateException("actionListener already set"); 34 | } 35 | this.actionListener = actionListener; 36 | } 37 | 38 | public void doAction() { 39 | if (actionListener != null) { 40 | actionListener.actionPerformed(new ActionEvent(this, 0, null)); 41 | } 42 | } 43 | } 44 | 45 | public static class ActionableSub extends Actionable { 46 | // Subclass. 47 | } 48 | 49 | @Action(call = "checkAction") 50 | Actionable actionable = new Actionable(); 51 | int checkAction = 0; 52 | 53 | @Action(call = "privateCheckAction") 54 | ActionableSub privateActionable = new ActionableSub(); 55 | int privateCheckAction = 0; 56 | 57 | final Bindings bindings = new Bindings(); 58 | 59 | public ActionTest() { 60 | bindings.bind(this); 61 | } 62 | 63 | @SuppressWarnings("unused") // called by binding 64 | private void privateCheckAction() { 65 | ++privateCheckAction; 66 | } 67 | 68 | public void checkAction() { 69 | ++checkAction; 70 | } 71 | 72 | public void testAction1() { 73 | assertEquals(0, checkAction); 74 | actionable.doAction(); 75 | assertEquals(1, checkAction); 76 | actionable.doAction(); 77 | assertEquals(2, checkAction); 78 | } 79 | 80 | public void testAction2() { 81 | assertEquals(0, privateCheckAction); 82 | privateActionable.doAction(); 83 | assertEquals(1, privateCheckAction); 84 | privateActionable.doAction(); 85 | assertEquals(2, privateCheckAction); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/ViewSubclassModelNameCollisionTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch; 15 | 16 | import com.palantir.ptoss.cinch.core.Bindings; 17 | import com.palantir.ptoss.cinch.core.CallOnUpdate; 18 | import com.palantir.ptoss.cinch.core.DefaultBindableModel; 19 | 20 | import junit.framework.TestCase; 21 | 22 | public class ViewSubclassModelNameCollisionTest extends TestCase { 23 | class Model1 extends DefaultBindableModel { 24 | // empty 25 | } 26 | 27 | class Model2 extends DefaultBindableModel { 28 | // empty 29 | } 30 | 31 | class BaseView { 32 | final Model1 model = new Model1(); 33 | } 34 | 35 | class DerivedView extends BaseView { 36 | private final Bindings bindings = new Bindings(); 37 | 38 | final Model2 model = new Model2(); 39 | 40 | final Model2 otherModel = new Model2(); 41 | 42 | int otherCount = 0; 43 | int thisCount = 0; 44 | int superCount = 0; 45 | 46 | public DerivedView() { 47 | bindings.bind(this); // throws an exception 48 | } 49 | 50 | @CallOnUpdate(model = "DerivedView.model") 51 | public void thisCount() { 52 | thisCount++; 53 | } 54 | 55 | @CallOnUpdate(model = "BaseView.model") 56 | public void superCount() { 57 | superCount++; 58 | } 59 | 60 | @CallOnUpdate(model = "otherModel") 61 | public void otherCount() { 62 | otherCount++; 63 | } 64 | 65 | @CallOnUpdate(model = "DerivedView.otherModel") 66 | public void otherCount2() { 67 | otherCount++; 68 | } 69 | } 70 | 71 | public void testViewSubclass() { 72 | DerivedView v = new DerivedView(); 73 | assertEquals(1, v.thisCount); 74 | assertEquals(1, v.superCount); 75 | assertEquals(2, v.otherCount); 76 | v.model.update(); 77 | assertEquals(2, v.thisCount); 78 | assertEquals(1, v.superCount); 79 | assertEquals(2, v.otherCount); 80 | ((BaseView)v).model.update(); 81 | assertEquals(2, v.thisCount); 82 | assertEquals(2, v.superCount); 83 | assertEquals(2, v.otherCount); 84 | v.otherModel.update(); 85 | assertEquals(2, v.thisCount); 86 | assertEquals(2, v.superCount); 87 | assertEquals(4, v.otherCount); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/example/superdemo/SuperDemo.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.example.superdemo; 15 | 16 | import java.awt.EventQueue; 17 | 18 | import javax.swing.JButton; 19 | import javax.swing.JCheckBox; 20 | import javax.swing.JFrame; 21 | import javax.swing.JPanel; 22 | 23 | import com.palantir.ptoss.cinch.core.BindableModel; 24 | import com.palantir.ptoss.cinch.core.Bindings; 25 | import com.palantir.ptoss.cinch.swing.Bound; 26 | import com.palantir.ptoss.cinch.swing.EnabledIf; 27 | 28 | /** 29 | * This example shows off the following features: 30 | *
    31 | *
  1. Binding a control to a model field: box is bound to the someBoolean 32 | * field on model.
  2. 33 | *
  3. The {@link EnabledIf} annotation: button is only enabled when 34 | * someBoolean is set to true.
  4. 35 | *
  5. The implicit model binding: model is implicitly bound since it's an instance of 36 | * {@link BindableModel}.
  6. 37 | *
38 | * @author regs 39 | * 40 | */ 41 | public class SuperDemo { 42 | 43 | protected final JPanel panel = new JPanel(); 44 | @Bound(to = "someBoolean") 45 | private final JCheckBox box = new JCheckBox("Box"); 46 | @EnabledIf(to = "someBoolean") 47 | private final JButton button = new JButton("Button"); 48 | private final Bindings bindings = new Bindings(); 49 | 50 | @SuppressWarnings("unused") // binding 51 | private final SuperDemoModel model = new SuperDemoModel(); 52 | 53 | protected SuperDemo() { 54 | setupPanel(); 55 | bindings.bind(this); 56 | } 57 | 58 | protected void setupPanel() { 59 | // panel.setLayout(new MigLayout()); 60 | panel.add(box); 61 | panel.add(button, "wrap"); 62 | } 63 | 64 | protected void showUi() { 65 | JFrame frame = new JFrame("Demo"); 66 | frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 67 | frame.setLocation(100, 100); 68 | frame.setSize(600, 600); 69 | 70 | frame.setContentPane(panel); 71 | frame.setVisible(true); 72 | } 73 | 74 | public static SuperDemo create() { 75 | SuperDemo demo = new SuperDemo(); 76 | demo.showUi(); 77 | return demo; 78 | } 79 | 80 | public static void main(String[] args) { 81 | EventQueue.invokeLater(new Runnable() { 82 | public void run() { 83 | SuperDemo.create(); 84 | } 85 | }); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/ptoss/cinch/swing/AbstractButtonWiringHarness.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.swing; 15 | 16 | import java.awt.event.ActionEvent; 17 | import java.awt.event.ActionListener; 18 | import java.beans.IntrospectionException; 19 | import java.lang.reflect.Field; 20 | import java.util.Collection; 21 | 22 | import javax.swing.AbstractButton; 23 | import javax.swing.JCheckBox; 24 | import javax.swing.JCheckBoxMenuItem; 25 | import javax.swing.JToggleButton; 26 | 27 | import com.google.common.collect.ImmutableList; 28 | import com.palantir.ptoss.cinch.core.BindableModel; 29 | import com.palantir.ptoss.cinch.core.Binding; 30 | import com.palantir.ptoss.cinch.core.BindingContext; 31 | import com.palantir.ptoss.cinch.core.ModelUpdate; 32 | import com.palantir.ptoss.cinch.core.WiringHarness; 33 | import com.palantir.ptoss.cinch.swing.Bound.Wiring; 34 | import com.palantir.ptoss.util.Mutator; 35 | 36 | /** 37 | * A {@link WiringHarness} for binding an {@link AbstractButton}, such as a 38 | * {@link JCheckBox}, {@link JToggleButton}, or {@link JCheckBoxMenuItem}, 39 | * to a boolean value in a {@link BindableModel}. 40 | */ 41 | public class AbstractButtonWiringHarness implements WiringHarness { 42 | 43 | public Collection wire(Bound bound, BindingContext context, Field field) 44 | throws IllegalAccessException, IntrospectionException { 45 | Mutator mutator = Mutator.create(context, bound.to()); 46 | AbstractButton abstractButton = context.getFieldObject(field, AbstractButton.class); 47 | return ImmutableList.of(bindAbstractButton(mutator, abstractButton)); 48 | } 49 | 50 | public static Binding bindAbstractButton( 51 | final Mutator mutator, final AbstractButton abstractButton) { 52 | abstractButton.addActionListener(new ActionListener() { 53 | public void actionPerformed(ActionEvent e) { 54 | try { 55 | mutator.set(abstractButton.isSelected()); 56 | } catch (Exception ex) { 57 | Wiring.logger.error("exception in AbstractButton binding", ex); 58 | } 59 | } 60 | }); 61 | Binding binding = new Binding() { 62 | public & ModelUpdate> void update(T... changed) { 63 | try { 64 | abstractButton.setSelected((Boolean)mutator.get()); 65 | } catch (Exception ex) { 66 | Wiring.logger.error("exception in AbstractButton binding", ex); 67 | } 68 | } 69 | }; 70 | mutator.getModel().bind(binding); 71 | return binding; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/example/demo/DrawingCanvas.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.example.demo; 15 | 16 | import java.awt.Color; 17 | import java.awt.Graphics; 18 | import java.awt.Graphics2D; 19 | import java.awt.event.MouseAdapter; 20 | import java.awt.event.MouseEvent; 21 | import java.awt.event.MouseMotionAdapter; 22 | import java.awt.geom.Line2D; 23 | import java.awt.geom.Point2D; 24 | 25 | import javax.swing.JComponent; 26 | 27 | import com.palantir.ptoss.cinch.core.Bindings; 28 | import com.palantir.ptoss.cinch.core.CallOnUpdate; 29 | import com.palantir.ptoss.cinch.example.demo.DrawingCanvasModel.Mode; 30 | 31 | public class DrawingCanvas extends JComponent { 32 | private static final long serialVersionUID = 1L; 33 | 34 | private final DrawingCanvasModel model; 35 | private final Bindings bindings = new Bindings(); 36 | 37 | public DrawingCanvas(DrawingCanvasModel model) { 38 | this.model = model; 39 | initializeListeners(); 40 | bindings.bind(this); 41 | } 42 | 43 | private void initializeListeners() { 44 | addMouseListener(new MouseAdapter() { 45 | @Override 46 | public void mousePressed(MouseEvent e) { 47 | processPoint(e); 48 | } 49 | }); 50 | 51 | addMouseMotionListener(new MouseMotionAdapter() { 52 | @Override 53 | public void mouseDragged(MouseEvent e) { 54 | if (model.isAllowDrag()) { 55 | processPoint(e); 56 | } 57 | } 58 | }); 59 | } 60 | 61 | private void processPoint(MouseEvent e) { 62 | if (model.getMode() == Mode.POINT) { 63 | model.addPoint(e.getPoint()); 64 | } else if (model.getMode() == Mode.LINE) { 65 | if (model.getCursor() != null) { 66 | model.addLine(new Line2D.Double( 67 | model.getCursor().getX(), model.getCursor().getY(), e.getX(), e.getY())); 68 | } 69 | model.setCursor(e.getPoint()); 70 | } 71 | } 72 | 73 | @CallOnUpdate(model = "model") 74 | @Override 75 | public void repaint() { 76 | super.repaint(); 77 | } 78 | 79 | @Override 80 | protected void paintComponent(Graphics g) { 81 | Graphics2D g2 = (Graphics2D)g; 82 | g2.setColor(Color.WHITE); 83 | g2.fillRect(0, 0, getWidth(), getHeight()); 84 | g2.setColor(Color.BLACK); 85 | for (Line2D line : model.getLines()) { 86 | g2.draw(line); 87 | } 88 | for (Point2D point : model.getPoints()) { 89 | g2.fillOval((int)point.getX(), (int)point.getY(), 5, 5); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/ptoss/cinch/core/ObjectFieldMethod.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.core; 15 | 16 | import java.lang.reflect.Field; 17 | import java.lang.reflect.Method; 18 | 19 | import com.google.common.base.Function; 20 | 21 | /** 22 | * A simple tuple of an object, a field on that object's class, and a method of that object's class. 23 | */ 24 | public class ObjectFieldMethod { 25 | 26 | /** 27 | * A function that returns the field's name. 28 | */ 29 | public static final Function TO_FIELD_NAME = new Function() { 30 | public String apply(ObjectFieldMethod from) { 31 | return from.getField().getName(); 32 | } 33 | }; 34 | 35 | private final Object object; 36 | private final Field field; 37 | private final Method method; 38 | 39 | /** 40 | * Constructs a tuple of an object, a field on that object, and a method on that object. 41 | * @param object 42 | * @param field 43 | * @param method 44 | */ 45 | public ObjectFieldMethod(Object object, Field field, Method method) { 46 | this.object = object; 47 | this.field = field; 48 | this.method = method; 49 | } 50 | 51 | public Object getObject() { 52 | return object; 53 | } 54 | 55 | public Field getField() { 56 | return field; 57 | } 58 | 59 | public Method getMethod() { 60 | return method; 61 | } 62 | 63 | @Override 64 | public String toString() { 65 | StringBuilder out = new StringBuilder("("); 66 | 67 | // object value 68 | out.append("object="); 69 | if(object != null) { 70 | final String klass = object.getClass().getSimpleName(); 71 | final int hashcode = System.identityHashCode(object); 72 | out.append(klass).append("[").append(hashcode).append("]"); 73 | } else { 74 | out.append("null"); 75 | } 76 | out.append(", "); 77 | 78 | // field value 79 | out.append("field="); 80 | if(field != null) { 81 | final String fieldName = field.getName(); 82 | out.append(fieldName); 83 | } else { 84 | out.append("null"); 85 | } 86 | out.append(", "); 87 | 88 | // method value 89 | out.append("method="); 90 | if(method != null) { 91 | final String methodName = method.getName(); 92 | out.append(methodName); 93 | } else { 94 | out.append("null"); 95 | } 96 | 97 | // finish up 98 | out.append(")"); 99 | 100 | return out.toString(); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/ptoss/cinch/core/WeakBindableModelSupport.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.core; 15 | 16 | import java.lang.ref.WeakReference; 17 | import java.util.List; 18 | import java.util.Set; 19 | import java.util.concurrent.CopyOnWriteArrayList; 20 | 21 | import com.google.common.collect.Sets; 22 | 23 | /** 24 | * A stand-alone implementation of {@link BindableModel}. Uses {@link WeakReference}s to 25 | * attach bindings to models. 26 | */ 27 | public class WeakBindableModelSupport implements BindableModel { 28 | 29 | // It's possible for a binding to kick off a chain of events that culminates in the 30 | // addition of new bindings which can throw a CME when iterating in #modelUpdated. 31 | // We'll use a CopyOnWriteArrayList instead. This seems like a good compromise since 32 | // the number of reads of this list is likely to far outweigh the number of writes. 33 | private final List> bindings = new CopyOnWriteArrayList>(); 34 | 35 | /** 36 | * {@inheritDoc} 37 | */ 38 | public void bind(final Binding binding) { 39 | bindings.add(new WeakReference(binding)); 40 | } 41 | 42 | /** 43 | * Shortcut call for a generic model update. 44 | */ 45 | public void update() { 46 | this.modelUpdated(ModelUpdates.UNSPECIFIED); 47 | } 48 | 49 | /** 50 | * {@inheritDoc} 51 | */ 52 | public & ModelUpdate> void modelUpdated(final T... changed) { 53 | final Set> toRemove = Sets.newHashSet(); 54 | for (final WeakReference weakBinding : bindings) { 55 | final Binding binding = weakBinding.get(); 56 | if (binding != null) { 57 | binding.update(changed); 58 | } else { 59 | toRemove.add(weakBinding); 60 | } 61 | } 62 | if (!toRemove.isEmpty()) { 63 | bindings.removeAll(toRemove); 64 | } 65 | } 66 | 67 | /** 68 | * {@inheritDoc} 69 | */ 70 | public void unbind(Binding toUnbind) { 71 | final Set> toRemove = Sets.newHashSet(); 72 | for (final WeakReference weakBinding : bindings) { 73 | final Binding binding = weakBinding.get(); 74 | if (binding != null) { 75 | if (toUnbind.equals(binding)) { 76 | toRemove.add(weakBinding); 77 | } 78 | } else { 79 | toRemove.add(weakBinding); 80 | } 81 | } 82 | if (!toRemove.isEmpty()) { 83 | bindings.removeAll(toRemove); 84 | } 85 | } 86 | 87 | /** 88 | * Removes all bindings from this model. 89 | */ 90 | public void unbindAll() { 91 | bindings.clear(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/ptoss/cinch/swing/JSliderWiringHarness.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.swing; 15 | 16 | import java.beans.IntrospectionException; 17 | import java.lang.reflect.Field; 18 | import java.util.Collection; 19 | 20 | import javax.swing.JSlider; 21 | import javax.swing.event.ChangeEvent; 22 | import javax.swing.event.ChangeListener; 23 | 24 | import com.google.common.collect.ImmutableList; 25 | import com.palantir.ptoss.cinch.core.BindableModel; 26 | import com.palantir.ptoss.cinch.core.Binding; 27 | import com.palantir.ptoss.cinch.core.BindingContext; 28 | import com.palantir.ptoss.cinch.core.ModelUpdate; 29 | import com.palantir.ptoss.cinch.core.WiringHarness; 30 | import com.palantir.ptoss.cinch.swing.Bound.Wiring; 31 | import com.palantir.ptoss.util.Mutator; 32 | 33 | /** 34 | * A {@link WiringHarness} for binding a {@link JSlider} to an {@link Integer} value in a 35 | * {@link BindableModel}. 36 | */ 37 | public class JSliderWiringHarness implements WiringHarness { 38 | public Collection wire(Bound bound, BindingContext context, Field field) throws IllegalAccessException, IntrospectionException { 39 | String target = bound.to(); 40 | Mutator mutator = Mutator.create(context, target); 41 | JSlider slider = context.getFieldObject(field, JSlider.class); 42 | return ImmutableList.of(bindJSlider(mutator, slider)); 43 | } 44 | 45 | public static Binding bindJSlider(final Mutator mutator, final JSlider slider) { 46 | final ChangeListener changeListener = new ChangeListener() { 47 | public void stateChanged(ChangeEvent e) { 48 | try { 49 | if (!slider.getValueIsAdjusting()) { 50 | mutator.set(slider.getValue()); 51 | } 52 | } catch (Exception ex) { 53 | Wiring.logger.error("exception in JSlider binding", ex); 54 | } 55 | } 56 | }; 57 | slider.addChangeListener(changeListener); 58 | Binding binding = new Binding() { 59 | public & ModelUpdate> void update(T... changed) { 60 | try { 61 | Integer val = (Integer)mutator.get(); 62 | if (val == null) { 63 | val = -1; 64 | } 65 | if (!val.equals(slider.getValue())) { 66 | slider.removeChangeListener(changeListener); 67 | slider.setValue(val); 68 | slider.addChangeListener(changeListener); 69 | } 70 | } catch (Exception ex) { 71 | Wiring.logger.error("exception in JSlider binding", ex); 72 | } 73 | } 74 | }; 75 | mutator.getModel().bind(binding); 76 | return binding; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/BoundJComboBoxTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch; 15 | 16 | import java.util.List; 17 | 18 | import javax.swing.JComboBox; 19 | 20 | import com.google.common.collect.ImmutableList; 21 | import com.palantir.ptoss.cinch.core.Bindings; 22 | import com.palantir.ptoss.cinch.core.DefaultBindableModel; 23 | import com.palantir.ptoss.cinch.swing.Bound; 24 | import com.palantir.ptoss.cinch.swing.BoundSelection; 25 | 26 | import junit.framework.TestCase; 27 | 28 | public class BoundJComboBoxTest extends TestCase { 29 | 30 | private static class JComboTestModel extends DefaultBindableModel { 31 | private String selectedComboItem; 32 | 33 | public List getComboList() { 34 | return ImmutableList.of("Alpha", "Bravo", "Charlie"); 35 | } 36 | 37 | public void setSelectedComboItem(String selectedComboItem) { 38 | this.selectedComboItem = selectedComboItem; 39 | update(); 40 | } 41 | 42 | public String getSelectedComboItem() { 43 | return selectedComboItem; 44 | } 45 | } 46 | 47 | private final JComboTestModel model = new JComboTestModel(); 48 | 49 | public static final String NULL_VALUE = "<--none-->"; 50 | 51 | @Bound(to = "comboList", nullValue = "NULL_VALUE") 52 | @BoundSelection(to = "selectedComboItem", nullValue = "NULL_VALUE") 53 | private final JComboBox nullConstantBox = new JComboBox(); 54 | 55 | @Bound(to = "comboList") 56 | @BoundSelection(to = "selectedComboItem") 57 | private final JComboBox noNullBox = new JComboBox(); 58 | 59 | @Bound(to = "comboList", nullValue = "null value") 60 | @BoundSelection(to = "selectedComboItem", nullValue = "null value") 61 | private final JComboBox directNullBox = new JComboBox(); 62 | 63 | private final Bindings bindings = new Bindings(); 64 | 65 | @Override 66 | protected void setUp() { 67 | bindings.bind(BoundJComboBoxTest.this); 68 | } 69 | 70 | public void testNull() { 71 | assertEquals(4, nullConstantBox.getItemCount()); 72 | assertEquals(NULL_VALUE, nullConstantBox.getSelectedItem()); 73 | model.setSelectedComboItem("Alpha"); 74 | assertEquals("Alpha", nullConstantBox.getSelectedItem()); 75 | model.setSelectedComboItem(null); 76 | assertEquals(NULL_VALUE, nullConstantBox.getSelectedItem()); 77 | } 78 | 79 | // TODO (dcervelli): enforce non-null model? 80 | public void testNoNull() { 81 | assertEquals(3, noNullBox.getItemCount()); 82 | } 83 | 84 | public void testDirectNull() { 85 | assertEquals(4, directNullBox.getItemCount()); 86 | assertEquals("null value", directNullBox.getSelectedItem()); 87 | model.setSelectedComboItem("Alpha"); 88 | assertEquals("Alpha", directNullBox.getSelectedItem()); 89 | model.setSelectedComboItem(null); 90 | assertEquals("null value", directNullBox.getSelectedItem()); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/example/extension/LoggedModel.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.example.extension; 15 | 16 | 17 | import java.lang.annotation.ElementType; 18 | import java.lang.annotation.Retention; 19 | import java.lang.annotation.RetentionPolicy; 20 | import java.lang.annotation.Target; 21 | import java.lang.reflect.Field; 22 | import java.util.Collection; 23 | import java.util.List; 24 | 25 | import org.slf4j.Logger; 26 | import org.slf4j.LoggerFactory; 27 | 28 | import com.google.common.collect.Lists; 29 | import com.palantir.ptoss.cinch.core.BindableModel; 30 | import com.palantir.ptoss.cinch.core.Binding; 31 | import com.palantir.ptoss.cinch.core.BindingContext; 32 | import com.palantir.ptoss.cinch.core.BindingException; 33 | import com.palantir.ptoss.cinch.core.BindingWiring; 34 | import com.palantir.ptoss.cinch.core.ModelUpdate; 35 | 36 | /** 37 | * A simple, example annotation that logs model updates via log4j. This example is meant to be 38 | * a straightforward look at creating a new sort of annotation for use with Cinch. 39 | * 40 | * @see ExtendedExample see ExtendedExample for usage in a program 41 | * @see ExtendedBindings see ExentendBindings for how to wire this annotation into Cinch 42 | */ 43 | @Retention(RetentionPolicy.RUNTIME) 44 | @Target({ElementType.FIELD, ElementType.METHOD}) 45 | public @interface LoggedModel { 46 | 47 | String logger() default "cinch.debug"; 48 | 49 | static class Wiring implements BindingWiring { 50 | public Collection wire(BindingContext context) { 51 | List loggedModels = context.getAnnotatedFields(LoggedModel.class); 52 | List bindings = Lists.newArrayList(); 53 | for (Field field : loggedModels) { 54 | final LoggedModel annotation = field.getAnnotation(LoggedModel.class); 55 | final String loggerParam = annotation.logger(); 56 | final String fieldName = field.getName(); 57 | if (BindableModel.class.isAssignableFrom(field.getType())) { 58 | BindableModel model = context.getFieldObject(field, BindableModel.class); 59 | Binding binding = new Binding() { 60 | public & ModelUpdate> void update(T... changed) { 61 | for (T t : changed){ 62 | Logger logger = LoggerFactory.getLogger(loggerParam); 63 | if (logger.isInfoEnabled()) { 64 | logger.info(t.toString() + ": " + fieldName); 65 | } 66 | } 67 | } 68 | }; 69 | model.bind(binding); 70 | bindings.add(binding); 71 | } else { 72 | throw new BindingException("Can only log a BindableModel."); 73 | } 74 | } 75 | return bindings; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/example/simple/CallOnUpdateExample.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.example.simple; 15 | 16 | import java.awt.Color; 17 | import java.awt.Dimension; 18 | import java.awt.EventQueue; 19 | import java.lang.reflect.InvocationTargetException; 20 | 21 | import javax.swing.JCheckBox; 22 | import javax.swing.JFrame; 23 | import javax.swing.JPanel; 24 | 25 | import com.palantir.ptoss.cinch.core.Bindings; 26 | import com.palantir.ptoss.cinch.core.CallOnUpdate; 27 | import com.palantir.ptoss.cinch.core.DefaultBindableModel; 28 | import com.palantir.ptoss.cinch.example.Examples; 29 | import com.palantir.ptoss.cinch.swing.Bound; 30 | 31 | public class CallOnUpdateExample { 32 | 33 | static class CallOnUpdateModel extends DefaultBindableModel { 34 | boolean yellow; 35 | boolean blue; 36 | 37 | public boolean isYellow() { 38 | return yellow; 39 | } 40 | 41 | public void setYellow(boolean yellow) { 42 | this.yellow = yellow; 43 | update(); 44 | } 45 | 46 | public boolean isBlue() { 47 | return blue; 48 | } 49 | 50 | public void setBlue(boolean blue) { 51 | this.blue = blue; 52 | update(); 53 | } 54 | } 55 | 56 | private final CallOnUpdateModel model = new CallOnUpdateModel(); 57 | 58 | @Bound(to = "yellow") 59 | private final JCheckBox yellow = new JCheckBox("yellow"); 60 | @Bound(to = "blue") 61 | private final JCheckBox blue = new JCheckBox("blue"); 62 | 63 | private final JPanel panel = new JPanel(); 64 | 65 | private final Bindings bindings = Bindings.standard(); 66 | 67 | public CallOnUpdateExample() { 68 | panel.add(yellow); 69 | yellow.setOpaque(false); 70 | panel.add(blue); 71 | blue.setOpaque(false); 72 | panel.setPreferredSize(new Dimension(200, 100)); 73 | bindings.bind(this); 74 | } 75 | 76 | @CallOnUpdate(model = "model") 77 | public void synchBackground() { 78 | if (model.isYellow() && model.isBlue()) { 79 | panel.setBackground(Color.GREEN); 80 | } else if (model.isBlue()) { 81 | panel.setBackground(Color.BLUE); 82 | } else if (model.isYellow()) { 83 | panel.setBackground(Color.YELLOW); 84 | } else { 85 | panel.setBackground(Color.WHITE); 86 | } 87 | } 88 | 89 | public static void main(String[] args) throws InterruptedException, InvocationTargetException { 90 | EventQueue.invokeAndWait(new Runnable() { 91 | public void run() { 92 | CallOnUpdateExample example = new CallOnUpdateExample(); 93 | JFrame frame = Examples.getFrameFor("Cinch CallOnUpdate Example", example.panel); 94 | frame.pack(); 95 | frame.setVisible(true); 96 | } 97 | }); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/ptoss/cinch/swing/JProgressBarWiringHarness.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.swing; 15 | 16 | import java.beans.IntrospectionException; 17 | import java.lang.reflect.Field; 18 | import java.lang.reflect.Method; 19 | import java.util.Collection; 20 | import java.util.Collections; 21 | 22 | import javax.swing.JProgressBar; 23 | 24 | import com.palantir.ptoss.cinch.core.BindableModel; 25 | import com.palantir.ptoss.cinch.core.Binding; 26 | import com.palantir.ptoss.cinch.core.BindingContext; 27 | import com.palantir.ptoss.cinch.core.ModelUpdate; 28 | import com.palantir.ptoss.cinch.core.ObjectFieldMethod; 29 | import com.palantir.ptoss.cinch.core.WiringHarness; 30 | import com.palantir.ptoss.cinch.swing.Bound.Wiring; 31 | 32 | /** 33 | * A {@link WiringHarness} for binding a {@link JProgressBar} to an int value in a {@link BindableModel}. 34 | */ 35 | public class JProgressBarWiringHarness implements WiringHarness { 36 | public Collection wire(Bound bound, BindingContext context, Field field) throws IllegalAccessException, IntrospectionException { 37 | String target = bound.to(); 38 | JProgressBar bar = context.getFieldObject(field, JProgressBar.class); 39 | // ObjectFieldMethod setter = context.findSetter(target); 40 | ObjectFieldMethod getter = context.findGetter(target); 41 | if (getter == null) { 42 | throw new IllegalArgumentException("could not find getter/setter for " + target); 43 | } 44 | BindableModel model = context.getFieldObject(getter.getField(), BindableModel.class); 45 | // verify type parameters 46 | return bindJProgressBar(model, bar, getter.getMethod()); 47 | } 48 | 49 | private static int getValueForObject(Object obj) { 50 | if (obj instanceof Double) { 51 | return (int)Math.round(((Double)obj).doubleValue() * 100); 52 | } else if (obj instanceof Integer) { 53 | return ((Integer)obj).intValue(); 54 | } else if (obj instanceof Float) { 55 | return (int)Math.round(((Float)obj).doubleValue() * 100); 56 | } 57 | return -1; 58 | } 59 | 60 | public static Collection bindJProgressBar(final BindableModel model, final JProgressBar bar, 61 | final Method getter) { 62 | Binding binding = new Binding() { 63 | public & ModelUpdate> void update(T... changed) { 64 | try { 65 | int val = getValueForObject(getter.invoke(model)); 66 | bar.setValue(val); 67 | bar.setIndeterminate(val < 0); 68 | } catch (Exception ex) { 69 | Wiring.logger.error("exception in JTextField binding", ex); 70 | } 71 | } 72 | }; 73 | model.bind(binding); 74 | return Collections.singleton(binding); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/example/extension/ExtendedExample.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.example.extension; 15 | 16 | import java.awt.BorderLayout; 17 | import java.awt.EventQueue; 18 | import java.lang.reflect.InvocationTargetException; 19 | 20 | import javax.swing.JCheckBox; 21 | import javax.swing.JFrame; 22 | import javax.swing.JPanel; 23 | 24 | import com.palantir.ptoss.cinch.core.Bindings; 25 | import com.palantir.ptoss.cinch.core.DefaultBindableModel; 26 | import com.palantir.ptoss.cinch.core.ModelUpdate; 27 | import com.palantir.ptoss.cinch.example.Examples; 28 | import com.palantir.ptoss.cinch.swing.Bound; 29 | 30 | /** 31 | * An example class that uses extended bindings. Note the use of the {@link LoggedModel} 32 | * annotation on the model field. 33 | * @see ExtendedBindings 34 | * @see LoggedModel 35 | */ 36 | public class ExtendedExample { 37 | public static class Model extends DefaultBindableModel { 38 | boolean state; 39 | boolean specific; 40 | 41 | public enum UpdateType implements ModelUpdate { 42 | SPECIFIC; 43 | } 44 | 45 | public void setState(boolean state) { 46 | this.state = state; 47 | update(); 48 | } 49 | 50 | public boolean isState() { 51 | return state; 52 | } 53 | 54 | public void setSpecific(boolean specific) { 55 | this.specific = specific; 56 | modelUpdated(UpdateType.SPECIFIC); 57 | } 58 | 59 | public boolean isSpecific() { 60 | return specific; 61 | } 62 | } 63 | 64 | /* 65 | * By placing this annotation (LoggedModel) on this model, all model updates 66 | * will be sent to the log4j logger 'cinch.debug' at INFO level. 67 | */ 68 | @SuppressWarnings("unused") 69 | @LoggedModel 70 | private final Model model = new Model(); 71 | 72 | @Bound(to = "state") 73 | private final JCheckBox box = new JCheckBox("State"); 74 | 75 | @Bound(to = "specific") 76 | private final JCheckBox specificBox = new JCheckBox("Specific"); 77 | 78 | private final JPanel panel = new JPanel(); 79 | 80 | private final Bindings bindings = ExtendedBindings.extendedBindings(); 81 | 82 | public ExtendedExample() { 83 | panel.setLayout(new BorderLayout()); 84 | panel.add(box, BorderLayout.NORTH); 85 | panel.add(specificBox, BorderLayout.SOUTH); 86 | bindings.bind(this); 87 | } 88 | 89 | public static void main(String[] args) throws InterruptedException, InvocationTargetException { 90 | Examples.initializeLogging(); 91 | EventQueue.invokeAndWait(new Runnable() { 92 | public void run() { 93 | ExtendedExample example = new ExtendedExample(); 94 | JFrame frame = Examples.getFrameFor("Cinch Extended Bindings Example", example.panel); 95 | frame.pack(); 96 | frame.setVisible(true); 97 | } 98 | }); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/ptoss/cinch/core/WeakBindableModel.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.core; 15 | 16 | import java.util.List; 17 | 18 | import com.google.common.base.Objects; 19 | import com.google.common.collect.Lists; 20 | 21 | /** 22 | *

23 | * Superclass for {@link BindableModel} instances, this class handles the binding 24 | * tasks for subclassed models. 25 | *

26 | *

27 | * Basic theory of operation: 28 | * TODO 29 | */ 30 | public class WeakBindableModel implements BindableModel { 31 | /** 32 | * A delegate object that implements all of the logic behind {@link BindableModel}. 33 | */ 34 | private final WeakBindableModelSupport support = new WeakBindableModelSupport(); 35 | /** 36 | * List of strong bindings. This {@link List} holds strong references to all 37 | * of the {@link Binding} objects passed to this model. 38 | */ 39 | private final List strongBindings = Lists.newArrayList(); 40 | 41 | /** 42 | * Binds this model to some component with a {@link Binding} object. This method is called by 43 | * the various {@link WiringHarness} objects to bind individual components to this model. 44 | * 45 | * @param binding 46 | */ 47 | public void bind(Binding binding) { 48 | support.bind(binding); 49 | } 50 | 51 | /** 52 | * Binds this model to a {@link Binding} using strong references. 53 | * 54 | * @param binding 55 | */ 56 | public void bindStrongly(Binding binding) { 57 | support.bind(binding); 58 | strongBindings.add(binding); 59 | } 60 | 61 | /** 62 | * {@inheritDoc} 63 | */ 64 | public & ModelUpdate> void modelUpdated(T... changed) { 65 | support.modelUpdated(changed); 66 | } 67 | 68 | /** 69 | * Fires a model update if the old and new values are different. 70 | * @param oldValue original value to be compared 71 | * @param newValue new value to compare against 72 | * @param A varargs list of enumerated {@link ModelUpdate} types. 73 | */ 74 | public & ModelUpdate> void modelUpdated( 75 | Object oldValue, Object newValue, T... changed) { 76 | if (!Objects.equal(oldValue, newValue)) { 77 | modelUpdated(changed); 78 | } 79 | } 80 | 81 | /** 82 | * Called by the implementing Model to notify all listeners 83 | * that data in the Model has changed. 84 | */ 85 | public void update() { 86 | support.update(); 87 | } 88 | 89 | /** 90 | * {@inheritDoc} 91 | */ 92 | public void unbind(Binding binding) { 93 | support.unbind(binding); 94 | strongBindings.remove(binding); 95 | } 96 | 97 | /** 98 | * Unbinds both weak and strong {@link Binding} from this Model. 99 | * @see BindableModel#unbind(Binding) 100 | */ 101 | public void unbindAll() { 102 | support.unbindAll(); 103 | strongBindings.clear(); 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/ptoss/cinch/swing/JComboBoxWiringHarness.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.swing; 15 | 16 | import java.beans.IntrospectionException; 17 | import java.lang.reflect.Field; 18 | import java.util.Collection; 19 | import java.util.List; 20 | 21 | import javax.swing.DefaultComboBoxModel; 22 | import javax.swing.JComboBox; 23 | 24 | import com.google.common.collect.ImmutableList; 25 | import com.palantir.ptoss.cinch.core.BindableModel; 26 | import com.palantir.ptoss.cinch.core.Binding; 27 | import com.palantir.ptoss.cinch.core.BindingContext; 28 | import com.palantir.ptoss.cinch.core.ModelUpdate; 29 | import com.palantir.ptoss.cinch.core.WiringHarness; 30 | import com.palantir.ptoss.cinch.swing.Bound.Utilities; 31 | import com.palantir.ptoss.cinch.swing.Bound.Wiring; 32 | import com.palantir.ptoss.util.Mutator; 33 | 34 | /** 35 | * A {@link WiringHarness} for binding a {@link JComboBox} to a {@link List} value in a 36 | * {@link BindableModel}. 37 | */ 38 | public class JComboBoxWiringHarness implements WiringHarness { 39 | public Collection wire(Bound bound, BindingContext context, Field field) throws IllegalAccessException, IntrospectionException { 40 | String target = bound.to(); 41 | JComboBox combo = context.getFieldObject(field, JComboBox.class); 42 | Mutator mutator = Mutator.create(context, target); 43 | final String nullValue = (String)Utilities.getNullValue(context, bound.nullValue()); 44 | return ImmutableList.of(bindJComboBox(bound, mutator, combo, nullValue)); 45 | } 46 | 47 | private Binding bindJComboBox(final Bound bound, final Mutator mutator, final JComboBox combo, final String nullValue) { 48 | final List ons = BindingContext.getOnObjects(bound.on(), mutator.getModel()); 49 | Binding binding = new Binding() { 50 | public & ModelUpdate> void update(T... changed) { 51 | if (!BindingContext.isOn(ons, changed)) { 52 | return; 53 | } 54 | try { 55 | updateComboModel(combo, (List)mutator.get(), nullValue); 56 | } catch (Exception ex) { 57 | Wiring.logger.error("exception in JList binding", ex); 58 | } 59 | } 60 | }; 61 | mutator.getModel().bind(binding); 62 | return binding; 63 | } 64 | 65 | private void updateComboModel(JComboBox combo, List newContents, String nullValue) { 66 | int selectedIndex = combo.getSelectedIndex(); 67 | Object selected = combo.getSelectedItem(); 68 | DefaultComboBoxModel comboModel = new DefaultComboBoxModel(); 69 | if (nullValue != null) { 70 | comboModel.addElement(nullValue); 71 | } 72 | for (Object obj : newContents) { 73 | comboModel.addElement(obj); 74 | } 75 | if (comboModel.getIndexOf(selected) != -1) { 76 | comboModel.setSelectedItem(selected); 77 | } else { 78 | if (comboModel.getSize() > selectedIndex) { 79 | comboModel.setSelectedItem(combo.getItemAt(selectedIndex)); 80 | } else if (comboModel.getSize() > 0) { 81 | comboModel.setSelectedItem(combo.getItemAt(comboModel.getSize() - 1)); 82 | } 83 | } 84 | combo.setModel(comboModel); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/BindingContextIndexTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch; 15 | 16 | import java.lang.reflect.InvocationTargetException; 17 | 18 | import com.palantir.ptoss.cinch.core.BindingContext; 19 | import com.palantir.ptoss.cinch.core.DefaultBindableModel; 20 | import com.palantir.ptoss.cinch.core.ObjectFieldMethod; 21 | 22 | import junit.framework.TestCase; 23 | 24 | public class BindingContextIndexTest extends TestCase { 25 | 26 | public static class SimpleModel extends DefaultBindableModel { 27 | private String simpleString; 28 | 29 | public String getSimpleString() { 30 | return simpleString; 31 | } 32 | 33 | public void setSimpleString(String simpleString) { 34 | this.simpleString = simpleString; 35 | update(); 36 | } 37 | } 38 | 39 | public static class SimpleModel2 extends DefaultBindableModel { 40 | private String simpleString; 41 | private String otherString; 42 | 43 | public String getSimpleString() { 44 | return simpleString; 45 | } 46 | 47 | public void setSimpleString(String simpleString) { 48 | this.simpleString = simpleString; 49 | update(); 50 | } 51 | 52 | public String getOtherString() { 53 | return otherString; 54 | } 55 | 56 | public void setOtherString(String otherString) { 57 | this.otherString = otherString; 58 | update(); 59 | } 60 | } 61 | 62 | final SimpleModel model1 = new SimpleModel(); 63 | final SimpleModel model2 = new SimpleModel(); 64 | final SimpleModel2 model = new SimpleModel2(); 65 | 66 | public void testGetterIndex() throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { 67 | model1.setSimpleString("string1"); 68 | model2.setSimpleString("string2"); 69 | model.setOtherString("other"); 70 | BindingContext context = new BindingContext(this); 71 | assertNull(context.findGetter("simpleString")); 72 | ObjectFieldMethod ofm = context.findGetter("model1.simpleString"); 73 | assertEquals("string1", ofm.getMethod().invoke(ofm.getObject(), (Object[])null)); 74 | ofm = context.findGetter("model2.simpleString"); 75 | assertEquals("string2", ofm.getMethod().invoke(ofm.getObject(), (Object[])null)); 76 | ofm = context.findGetter("otherString"); 77 | assertEquals("other", ofm.getMethod().invoke(ofm.getObject(), (Object[])null)); 78 | } 79 | 80 | public void testSetterIndex() throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { 81 | BindingContext context = new BindingContext(this); 82 | assertNull(context.findSetter("simpleString")); 83 | ObjectFieldMethod ofm = context.findSetter("model1.simpleString"); 84 | ofm.getMethod().invoke(ofm.getObject(), new Object[] { "set1" }); 85 | assertEquals("set1", model1.getSimpleString()); 86 | 87 | ofm = context.findSetter("model2.simpleString"); 88 | ofm.getMethod().invoke(ofm.getObject(), new Object[] { "set2" }); 89 | assertEquals("set2", model2.getSimpleString()); 90 | 91 | ofm = context.findSetter("otherString"); 92 | ofm.getMethod().invoke(ofm.getObject(), new Object[] { "setOther" }); 93 | assertEquals("setOther", model.getOtherString()); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/ptoss/cinch/swing/JTextComponentWiringHarness.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.swing; 15 | 16 | import java.beans.IntrospectionException; 17 | import java.lang.reflect.Field; 18 | import java.util.Collection; 19 | 20 | import javax.swing.event.DocumentEvent; 21 | import javax.swing.event.DocumentListener; 22 | import javax.swing.text.JTextComponent; 23 | 24 | import com.google.common.collect.ImmutableList; 25 | import com.palantir.ptoss.cinch.core.BindableModel; 26 | import com.palantir.ptoss.cinch.core.Binding; 27 | import com.palantir.ptoss.cinch.core.BindingContext; 28 | import com.palantir.ptoss.cinch.core.ModelUpdate; 29 | import com.palantir.ptoss.cinch.core.WiringHarness; 30 | import com.palantir.ptoss.cinch.swing.Bound.Wiring; 31 | import com.palantir.ptoss.util.Mutator; 32 | 33 | /** 34 | * A {@link WiringHarness} for binding a {@link JTextComponent} to a {@link String} value in a 35 | * {@link BindableModel}. 36 | */ 37 | public class JTextComponentWiringHarness implements WiringHarness { 38 | public Collection wire(Bound bound, BindingContext context, Field field) throws IllegalAccessException, IntrospectionException { 39 | JTextComponent textComponent = context.getFieldObject(field, JTextComponent.class); 40 | Mutator mutator = Mutator.create(context, bound.to()); 41 | Binding binding = bindJTextComponent(mutator, textComponent); 42 | if (binding == null) { 43 | return ImmutableList.of(); 44 | } 45 | return ImmutableList.of(binding); 46 | } 47 | 48 | public static Binding bindJTextComponent(final Mutator mutator, final JTextComponent textField) { 49 | if (mutator.getSetter() != null) { 50 | textField.getDocument().addDocumentListener(new DocumentListener() { 51 | public void removeUpdate(DocumentEvent e) { 52 | updateModel(); 53 | } 54 | 55 | public void insertUpdate(DocumentEvent e) { 56 | updateModel(); 57 | } 58 | 59 | public void changedUpdate(DocumentEvent e) { 60 | updateModel(); 61 | } 62 | 63 | private void updateModel() { 64 | try { 65 | mutator.set(textField.getText()); 66 | } catch (Exception ex) { 67 | Wiring.logger.error("exception in JTextField binding", ex); 68 | } 69 | } 70 | }); 71 | } 72 | Binding binding = null; 73 | if (mutator.getGetter() != null) { 74 | binding = new Binding() { 75 | public & ModelUpdate> void update(T... changed) { 76 | try { 77 | String string = (String)mutator.get(); 78 | if (string == null) { 79 | string = ""; 80 | } 81 | if (!string.equals(textField.getText())) { 82 | textField.setText(string); 83 | } 84 | } catch (Exception ex) { 85 | Wiring.logger.error("exception in JTextField binding", ex); 86 | } 87 | } 88 | }; 89 | mutator.getModel().bind(binding); 90 | } 91 | return binding; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/ptoss/cinch/core/Bindings.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.core; 15 | 16 | import java.util.Collection; 17 | import java.util.List; 18 | 19 | import com.google.common.collect.ImmutableList; 20 | import com.google.common.collect.Lists; 21 | import com.palantir.ptoss.cinch.swing.Action; 22 | import com.palantir.ptoss.cinch.swing.Bound; 23 | import com.palantir.ptoss.cinch.swing.BoundExtent; 24 | import com.palantir.ptoss.cinch.swing.BoundLocation; 25 | import com.palantir.ptoss.cinch.swing.BoundSelection; 26 | import com.palantir.ptoss.cinch.swing.EnabledIf; 27 | import com.palantir.ptoss.cinch.swing.OnChange; 28 | import com.palantir.ptoss.cinch.swing.OnClick; 29 | import com.palantir.ptoss.cinch.swing.OnFocusChange; 30 | import com.palantir.ptoss.cinch.swing.VisibleIf; 31 | 32 | /** 33 | *

34 | * All {@link Binding}s are called when {@link ModelUpdates#ALL} is called. 35 | *

36 | * If a {@link Bound} component has an "on" parameter then it will only be triggered if the 37 | * specific {@link ModelUpdate} type is triggered. 38 | *

39 | * If a {@link Bound} component has no "on" parameter then it will be triggered by any update 40 | * type (including none) triggered by its {@link BindableModel}. 41 | */ 42 | public class Bindings { 43 | 44 | private final List bindings = Lists.newArrayList(); 45 | 46 | private final ImmutableList wirings; 47 | 48 | /** 49 | * The list of {@link BindingWiring} classes that are standard to this framework. 50 | */ 51 | public static final ImmutableList STANDARD_BINDINGS = ImmutableList.builder() 52 | .add(new Bound.Wiring()) 53 | .add(new BoundSelection.Wiring()) 54 | .add(new BoundExtent.Wiring()) 55 | .add(new BoundLocation.Wiring()) 56 | .add(new CallOnUpdate.Wiring()) 57 | .add(new EnabledIf.Wiring()) 58 | .add(new Action.Wiring()) 59 | .add(new VisibleIf.Wiring()) 60 | .add(new OnClick.Wiring()) 61 | .add(new OnChange.Wiring()) 62 | .add(new OnFocusChange.Wiring()) 63 | .build(); 64 | 65 | public static Bindings standard() { 66 | return new Bindings(STANDARD_BINDINGS); 67 | } 68 | 69 | public Bindings() { 70 | this.wirings = STANDARD_BINDINGS; 71 | } 72 | 73 | public Bindings(Collection wirings) { 74 | this.wirings = ImmutableList.copyOf(wirings); 75 | } 76 | 77 | public void bind(Object object) { 78 | bindWithoutUpdate(object); 79 | updateAll(); 80 | } 81 | 82 | public void bindWithoutUpdate(Object object) { 83 | BindingContext context = new BindingContext(object); 84 | bindings.addAll(createBindings(context)); 85 | } 86 | 87 | public void release(BindableModel model) { 88 | for (Binding binding : bindings) { 89 | model.unbind(binding); 90 | } 91 | } 92 | 93 | protected List createBindings(BindingContext context) { 94 | final List bindingList = Lists.newArrayList(); 95 | for (BindingWiring wiring : wirings) { 96 | bindingList.addAll(wiring.wire(context)); 97 | } 98 | return bindingList; 99 | } 100 | 101 | public void updateAll() { 102 | for (Binding binding : bindings) { 103 | binding.update(ModelUpdates.ALL); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/example/simple/BoundJCheckBoxExample.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.example.simple; 15 | 16 | import java.awt.BorderLayout; 17 | import java.awt.EventQueue; 18 | import java.lang.reflect.InvocationTargetException; 19 | 20 | import javax.swing.JButton; 21 | import javax.swing.JCheckBox; 22 | import javax.swing.JFrame; 23 | import javax.swing.JLabel; 24 | import javax.swing.JPanel; 25 | 26 | import com.palantir.ptoss.cinch.core.Bindable; 27 | import com.palantir.ptoss.cinch.core.Bindings; 28 | import com.palantir.ptoss.cinch.core.DefaultBindableModel; 29 | import com.palantir.ptoss.cinch.example.Examples; 30 | import com.palantir.ptoss.cinch.swing.Action; 31 | import com.palantir.ptoss.cinch.swing.Bound; 32 | 33 | /** 34 | *

35 | * This example shows how controls can be bound to methods on a controller object, 36 | * controller. 37 | *

38 | *

39 | * Note that controller must be marked as {@link Bindable} to enable this behavior. 40 | *

41 | * @see Action 42 | * @see Bindable 43 | * @see Bound 44 | */ 45 | public class BoundJCheckBoxExample { 46 | public static class Model extends DefaultBindableModel { 47 | private boolean state; 48 | 49 | public void setState(boolean state) { 50 | this.state = state; 51 | update(); 52 | } 53 | 54 | public boolean isState() { 55 | return state; 56 | } 57 | } 58 | 59 | public static class Controller { 60 | private final Model model; 61 | 62 | public Controller(Model model) { 63 | this.model = model; 64 | } 65 | 66 | public void setToTrue() { 67 | model.setState(true); 68 | } 69 | 70 | public void setToFalse() { 71 | model.setState(false); 72 | } 73 | } 74 | 75 | private final Model model = new Model(); 76 | @SuppressWarnings("unused") 77 | @Bindable 78 | private final Controller controller = new Controller(model); 79 | 80 | @Bound(to = "state") 81 | private final JCheckBox box = new JCheckBox("State"); 82 | @Bound(to = "state") 83 | private final JLabel stateLabel = new JLabel("?"); 84 | 85 | @Action(call = "setToTrue") 86 | private final JButton trueButton = new JButton("Set True"); 87 | @Action(call = "setToFalse") 88 | private final JButton falseButton = new JButton("Set False"); 89 | 90 | private final JPanel panel = new JPanel(); 91 | 92 | private final Bindings bindings = Bindings.standard(); 93 | 94 | public BoundJCheckBoxExample() { 95 | panel.setLayout(new BorderLayout()); 96 | panel.add(box, BorderLayout.CENTER); 97 | panel.add(stateLabel, BorderLayout.SOUTH); 98 | JPanel buttons = new JPanel(); 99 | buttons.add(trueButton); 100 | buttons.add(falseButton); 101 | panel.add(buttons, BorderLayout.NORTH); 102 | bindings.bind(this); 103 | } 104 | 105 | public static void main(String[] args) throws InterruptedException, InvocationTargetException { 106 | EventQueue.invokeAndWait(new Runnable() { 107 | public void run() { 108 | BoundJCheckBoxExample example = new BoundJCheckBoxExample(); 109 | JFrame frame = Examples.getFrameFor("Cinch JCheckBox Example", example.panel); 110 | frame.pack(); 111 | frame.setVisible(true); 112 | } 113 | }); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/EnabledIfTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch; 15 | 16 | import java.awt.EventQueue; 17 | import java.lang.reflect.InvocationTargetException; 18 | 19 | import javax.swing.JCheckBox; 20 | 21 | import com.palantir.ptoss.cinch.core.Bindings; 22 | import com.palantir.ptoss.cinch.core.DefaultBindableModel; 23 | import com.palantir.ptoss.cinch.swing.EnabledIf; 24 | 25 | import junit.framework.TestCase; 26 | 27 | public class EnabledIfTest extends TestCase { 28 | 29 | private static class SimpleModel extends DefaultBindableModel { 30 | boolean state = false; 31 | 32 | public boolean isState() { 33 | return state; 34 | } 35 | 36 | public void setState(boolean state) { 37 | this.state = state; 38 | update(); 39 | } 40 | } 41 | 42 | private static class OtherModel extends DefaultBindableModel { 43 | boolean otherState = false; 44 | 45 | public boolean isOtherState() { 46 | return otherState; 47 | } 48 | 49 | public void setOtherState(boolean state) { 50 | this.otherState = state; 51 | update(); 52 | } 53 | } 54 | 55 | final OtherModel otherModel = new OtherModel(); 56 | final SimpleModel model1 = new SimpleModel(); 57 | final SimpleModel model2 = new SimpleModel(); 58 | 59 | @EnabledIf(to = "otherState") 60 | final JCheckBox ocb = new JCheckBox(); 61 | @EnabledIf(to = "model1.state") 62 | final JCheckBox cb1 = new JCheckBox(); 63 | @EnabledIf(to = "model2.state") 64 | final JCheckBox cb2 = new JCheckBox(); 65 | 66 | final Bindings bindings = new Bindings(); 67 | 68 | @Override 69 | protected void setUp() throws Exception { 70 | EventQueue.invokeAndWait(new Runnable() { 71 | public void run() { 72 | bindings.bind(EnabledIfTest.this); 73 | } 74 | }); 75 | } 76 | 77 | public void testIt() throws InterruptedException, InvocationTargetException { 78 | EventQueue.invokeAndWait(new Runnable() { 79 | public void run() { 80 | assertFalse(ocb.isEnabled()); 81 | assertFalse(cb1.isEnabled()); 82 | assertFalse(cb2.isEnabled()); 83 | model1.setState(true); 84 | assertFalse(ocb.isEnabled()); 85 | assertTrue(cb1.isEnabled()); 86 | assertFalse(cb2.isEnabled()); 87 | model2.setState(true); 88 | assertFalse(ocb.isEnabled()); 89 | assertTrue(cb1.isEnabled()); 90 | assertTrue(cb2.isEnabled()); 91 | model1.setState(false); 92 | assertFalse(ocb.isEnabled()); 93 | assertFalse(cb1.isEnabled()); 94 | assertTrue(cb2.isEnabled()); 95 | model2.setState(false); 96 | assertFalse(ocb.isEnabled()); 97 | assertFalse(cb1.isEnabled()); 98 | assertFalse(cb2.isEnabled()); 99 | otherModel.setOtherState(true); 100 | assertTrue(ocb.isEnabled()); 101 | assertFalse(cb1.isEnabled()); 102 | assertFalse(cb2.isEnabled()); 103 | otherModel.setOtherState(false); 104 | assertFalse(ocb.isEnabled()); 105 | assertFalse(cb1.isEnabled()); 106 | assertFalse(cb2.isEnabled()); 107 | } 108 | }); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/BoundJTextComponentTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch; 15 | 16 | import javax.swing.JTextField; 17 | 18 | import com.palantir.ptoss.cinch.core.Bindings; 19 | import com.palantir.ptoss.cinch.core.DefaultBindableModel; 20 | import com.palantir.ptoss.cinch.swing.Bound; 21 | 22 | import junit.framework.TestCase; 23 | 24 | public class BoundJTextComponentTest extends TestCase { 25 | 26 | public static class Model extends DefaultBindableModel { 27 | private String readOnly; 28 | private String writeOnly; 29 | private String readWrite; 30 | 31 | public void setReadOnly0(String readOnly) { 32 | this.readOnly = readOnly; 33 | update(); 34 | } 35 | 36 | public String getReadOnly() { 37 | return readOnly; 38 | } 39 | 40 | public String getWriteOnly0() { 41 | return writeOnly; 42 | } 43 | 44 | public void setWriteOnly(String writeOnly) { 45 | this.writeOnly = writeOnly; 46 | update(); 47 | } 48 | 49 | public String getReadWrite() { 50 | return readWrite; 51 | } 52 | 53 | public void setReadWrite(String readWrite) { 54 | this.readWrite = readWrite; 55 | update(); 56 | } 57 | } 58 | 59 | private final Model model = new Model(); 60 | 61 | private final Bindings bindings = new Bindings(); 62 | 63 | @Bound(to = "readOnly") 64 | private final JTextField readField = new JTextField(); 65 | @Bound(to = "writeOnly") 66 | private final JTextField writeField = new JTextField(); 67 | @Bound(to = "readWrite") 68 | private final JTextField readWriteField = new JTextField(); 69 | 70 | @Override 71 | protected void setUp() throws Exception { 72 | bindings.bind(this); 73 | } 74 | 75 | public void testReadWrite() { 76 | assertEquals(null, model.getReadWrite()); 77 | assertEquals("", readWriteField.getText()); 78 | 79 | model.setReadWrite("readWrite"); 80 | assertEquals("readWrite", model.getReadWrite()); 81 | assertEquals("readWrite", readWriteField.getText()); 82 | 83 | readWriteField.setText("fromTextField"); 84 | assertEquals("fromTextField", model.getReadWrite()); 85 | assertEquals("fromTextField", readWriteField.getText()); 86 | } 87 | 88 | public void testReadOnly() { 89 | assertEquals(null, model.getReadOnly()); 90 | assertEquals("", readField.getText()); 91 | 92 | model.setReadOnly0("readOnly"); 93 | assertEquals("readOnly", model.getReadOnly()); 94 | assertEquals("readOnly", readField.getText()); 95 | 96 | readField.setText("shouldNotChange"); 97 | assertEquals("readOnly", model.getReadOnly()); 98 | assertEquals("shouldNotChange", readField.getText()); 99 | 100 | model.setReadOnly0("synch"); 101 | assertEquals("synch", model.getReadOnly()); 102 | assertEquals("synch", readField.getText()); 103 | } 104 | 105 | public void testWriteOnly() { 106 | assertEquals(null, model.getWriteOnly0()); 107 | assertEquals("", writeField.getText()); 108 | 109 | model.setWriteOnly("writeOnly"); 110 | assertEquals("writeOnly", model.getWriteOnly0()); 111 | assertEquals("", writeField.getText()); 112 | 113 | writeField.setText("synch"); 114 | assertEquals("synch", model.getWriteOnly0()); 115 | assertEquals("synch", writeField.getText()); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/BoundJToggleButtonTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch; 15 | 16 | import javax.swing.JRadioButton; 17 | import javax.swing.JToggleButton; 18 | 19 | import com.palantir.ptoss.cinch.core.Bindings; 20 | import com.palantir.ptoss.cinch.core.DefaultBindableModel; 21 | import com.palantir.ptoss.cinch.swing.Bound; 22 | 23 | import junit.framework.TestCase; 24 | 25 | public class BoundJToggleButtonTest extends TestCase { 26 | 27 | private enum Value { 28 | ONE, TWO, THREE; 29 | } 30 | 31 | private static class EnumModel extends DefaultBindableModel { 32 | Value value; 33 | 34 | public Value getValue() { 35 | return value; 36 | } 37 | 38 | public void setValue(Value value) { 39 | this.value = value; 40 | update(); 41 | } 42 | } 43 | 44 | private final EnumModel enumModel = new EnumModel(); 45 | private final BooleanModel model = new BooleanModel(); 46 | 47 | 48 | @Bound(to = "state", value = "true") 49 | private final JRadioButton on = new JRadioButton(); 50 | @Bound(to = "state", value = "false") 51 | private final JRadioButton off = new JRadioButton(); 52 | 53 | @Bound(to = "state") 54 | private final JToggleButton button = new JToggleButton(); 55 | 56 | @Bound(to = "value", value = "ONE") 57 | private final JToggleButton oneButton = new JToggleButton(); 58 | @Bound(to = "value", value = "TWO") 59 | private final JToggleButton twoButton = new JToggleButton(); 60 | @Bound(to = "value", value = "THREE") 61 | private final JToggleButton threeButton = new JToggleButton(); 62 | 63 | private final Bindings bindings = Bindings.standard(); 64 | 65 | @Override 66 | protected void setUp() { 67 | bindings.bind(this); 68 | } 69 | 70 | public void testBoolean() { 71 | assertFalse(button.isSelected()); 72 | assertFalse(model.isState()); 73 | model.setState(true); 74 | assertTrue(model.isState()); 75 | assertTrue(button.isSelected()); 76 | button.doClick(); 77 | assertFalse(button.isSelected()); 78 | assertFalse(model.isState()); 79 | } 80 | 81 | public void testTwoStateBoolean() { 82 | assertFalse(model.isState()); 83 | assertFalse(on.isSelected()); 84 | assertTrue(off.isSelected()); 85 | model.setState(true); 86 | assertTrue(on.isSelected()); 87 | assertFalse(off.isSelected()); 88 | off.doClick(); 89 | assertFalse(on.isSelected()); 90 | assertTrue(off.isSelected()); 91 | } 92 | 93 | public void testEnum() { 94 | assertNull(enumModel.getValue()); 95 | assertFalse(oneButton.isSelected()); 96 | assertFalse(twoButton.isSelected()); 97 | assertFalse(threeButton.isSelected()); 98 | 99 | enumModel.setValue(Value.ONE); 100 | assertEquals(Value.ONE, enumModel.getValue()); 101 | assertTrue(oneButton.isSelected()); 102 | assertFalse(twoButton.isSelected()); 103 | assertFalse(threeButton.isSelected()); 104 | 105 | enumModel.setValue(Value.TWO); 106 | assertEquals(Value.TWO, enumModel.getValue()); 107 | assertFalse(oneButton.isSelected()); 108 | assertTrue(twoButton.isSelected()); 109 | assertFalse(threeButton.isSelected()); 110 | 111 | threeButton.doClick(); 112 | assertEquals(Value.THREE, enumModel.getValue()); 113 | assertFalse(oneButton.isSelected()); 114 | assertFalse(twoButton.isSelected()); 115 | assertTrue(threeButton.isSelected()); 116 | 117 | enumModel.setValue(null); 118 | assertEquals(null, enumModel.getValue()); 119 | assertFalse(oneButton.isSelected()); 120 | assertFalse(twoButton.isSelected()); 121 | assertFalse(threeButton.isSelected()); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/ptoss/util/Mutator.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.util; 15 | 16 | import java.beans.IntrospectionException; 17 | import java.lang.reflect.InvocationTargetException; 18 | 19 | import com.palantir.ptoss.cinch.core.BindableModel; 20 | import com.palantir.ptoss.cinch.core.BindingContext; 21 | import com.palantir.ptoss.cinch.core.ObjectFieldMethod; 22 | 23 | /** 24 | * A class that performs the work of reading and setting values on a bound field. 25 | * 26 | */ 27 | public class Mutator { 28 | /** 29 | * Creates a new Mutator bound to the passed {@link BindingContext}. 30 | * @param context the context for this {@link Mutator} 31 | * @param target the model and field to bind this {@link Mutator} to. 32 | * @return the {@link Mutator} 33 | * @throws IntrospectionException 34 | */ 35 | public static Mutator create(BindingContext context, String target) throws IntrospectionException { 36 | final ObjectFieldMethod getter = context.findGetter(target); 37 | final ObjectFieldMethod setter = context.findSetter(target); 38 | if (getter == null && setter == null) { 39 | throw new IllegalArgumentException("could not find either getter/setter for " + target); 40 | } 41 | BindableModel model = null; 42 | BindableModel getterModel = null; 43 | BindableModel setterModel = null; 44 | if (getter != null) { 45 | getterModel = context.getFieldObject(getter.getField(), BindableModel.class); 46 | model = getterModel; 47 | } 48 | if (setter != null) { 49 | setterModel = context.getFieldObject(setter.getField(), BindableModel.class); 50 | model = setterModel; 51 | } 52 | if (getterModel != null && setterModel != null && getterModel != setterModel) { 53 | throw new IllegalStateException("setter and getter must be on same BindableModel."); 54 | } 55 | return new Mutator(getter, setter, model); 56 | } 57 | 58 | private final ObjectFieldMethod getter; 59 | private final ObjectFieldMethod setter; 60 | private final BindableModel model; 61 | 62 | /** 63 | * @param getter method to use as the getter for this field 64 | * @param setter method to use as the setter for this field 65 | * @param model model object that this {@link Mutator} applies to. 66 | */ 67 | private Mutator(ObjectFieldMethod getter, ObjectFieldMethod setter, BindableModel model) { 68 | this.getter = getter; 69 | this.setter = setter; 70 | this.model = model; 71 | } 72 | 73 | public Object get() throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { 74 | if (getter == null) { 75 | throw new IllegalStateException("can not call get() with no getter."); 76 | } 77 | boolean accessible = getter.getMethod().isAccessible(); 78 | getter.getMethod().setAccessible(true); 79 | Object value = getter.getMethod().invoke(model); 80 | getter.getMethod().setAccessible(accessible); 81 | return value; 82 | } 83 | 84 | public void set(Object value) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { 85 | if (setter == null) { 86 | throw new IllegalStateException("can not call set() with no setter."); 87 | } 88 | boolean accessible = setter.getMethod().isAccessible(); 89 | setter.getMethod().setAccessible(true); 90 | setter.getMethod().invoke(model, value); 91 | setter.getMethod().setAccessible(accessible); 92 | } 93 | 94 | public BindableModel getModel() { 95 | return model; 96 | } 97 | 98 | public ObjectFieldMethod getSetter() { 99 | return setter; 100 | } 101 | 102 | public ObjectFieldMethod getGetter() { 103 | return getter; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/BindingContextTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch; 15 | 16 | import com.palantir.ptoss.cinch.core.Bindable; 17 | import com.palantir.ptoss.cinch.core.BindingContext; 18 | import com.palantir.ptoss.cinch.core.Bindings; 19 | import com.palantir.ptoss.cinch.core.DefaultBindableModel; 20 | import com.palantir.ptoss.cinch.core.NotBindable; 21 | 22 | import junit.framework.TestCase; 23 | 24 | public class BindingContextTest extends TestCase { 25 | 26 | public static class SimpleModel extends DefaultBindableModel { 27 | private boolean simpleBoolean; 28 | 29 | public void setSimpleBoolean(boolean simpleBoolean) { 30 | this.simpleBoolean = simpleBoolean; 31 | update(); 32 | } 33 | 34 | public boolean isSimpleBoolean() { 35 | return simpleBoolean; 36 | } 37 | } 38 | 39 | public static class SimpleController { 40 | public void doSomething() { 41 | // Empty 42 | } 43 | } 44 | 45 | public static class NonfinalModel { 46 | private SimpleModel model = new SimpleModel(); 47 | private final Bindings bindings = new Bindings(); 48 | 49 | public NonfinalModel() { 50 | bindings.bind(this); 51 | } 52 | } 53 | 54 | public static class NonfinalNotBindableModel { 55 | @NotBindable 56 | private SimpleModel model = new SimpleModel(); 57 | private final Bindings bindings = new Bindings(); 58 | 59 | public NonfinalNotBindableModel() { 60 | bindings.bind(this); 61 | } 62 | } 63 | 64 | public static class FinalModel { 65 | private final SimpleModel model = new SimpleModel(); 66 | private final Bindings bindings = new Bindings(); 67 | 68 | public FinalModel() { 69 | bindings.bind(this); 70 | } 71 | } 72 | 73 | public void testModelFinality() { 74 | new FinalModel(); 75 | new NonfinalNotBindableModel(); 76 | try { 77 | new NonfinalModel(); 78 | fail("shouldn't allow non-final models"); 79 | } catch (Exception e) { 80 | e.printStackTrace(); 81 | } 82 | } 83 | 84 | public static class NonfinalController { 85 | @Bindable 86 | private SimpleController controller; 87 | private final Bindings bindings = new Bindings(); 88 | 89 | public NonfinalController() { 90 | bindings.bind(this); 91 | } 92 | } 93 | 94 | public void testControllerFinality() { 95 | try { 96 | new NonfinalController(); 97 | fail("shouldn't allow non-final @Bindables"); 98 | } catch (Exception e) { 99 | e.printStackTrace(); 100 | } 101 | } 102 | 103 | public static final String STRING_1 = "string1"; 104 | public static final Boolean BOOLEAN = Boolean.TRUE; 105 | private static final String PRIVATE_STRING = "privateString"; 106 | private static final String FUNCTION_STRING = getString(); 107 | static int i = 0; 108 | public static String getString() { 109 | return "#" + i++; 110 | } 111 | 112 | public void testConstants() { 113 | BindingContext context = new BindingContext(this); 114 | assertEquals("string1", context.getBindableConstant("STRING_1")); 115 | assertEquals(Boolean.TRUE, context.getBindableConstant("BOOLEAN")); 116 | assertEquals("privateString", context.getBindableConstant("PRIVATE_STRING")); 117 | assertEquals("#0", context.getBindableConstant("FUNCTION_STRING")); 118 | 119 | assertEquals(null, context.getBindableConstant("NOT_FOUND")); 120 | } 121 | 122 | private final SimpleModel model = new SimpleModel(); 123 | 124 | public void testModel() { 125 | BindingContext context = new BindingContext(this); 126 | assertEquals(model, context.getBindableModel("model")); 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/ptoss/cinch/swing/JPasswordFieldWiringHarness.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.swing; 15 | 16 | import com.google.common.base.Preconditions; 17 | import java.beans.IntrospectionException; 18 | import java.lang.reflect.Field; 19 | import java.lang.reflect.Method; 20 | import java.util.Arrays; 21 | import java.util.Collection; 22 | import java.util.Collections; 23 | 24 | import javax.swing.JPasswordField; 25 | import javax.swing.event.DocumentEvent; 26 | import javax.swing.event.DocumentListener; 27 | 28 | import com.palantir.ptoss.cinch.core.BindableModel; 29 | import com.palantir.ptoss.cinch.core.Binding; 30 | import com.palantir.ptoss.cinch.core.BindingContext; 31 | import com.palantir.ptoss.cinch.core.ModelUpdate; 32 | import com.palantir.ptoss.cinch.core.ObjectFieldMethod; 33 | import com.palantir.ptoss.cinch.core.WiringHarness; 34 | import com.palantir.ptoss.cinch.swing.Bound.Wiring; 35 | 36 | // The interface uses char[] but has to make a String under the covers in order to set the password. 37 | // TODO (dcervelli): find a (hacky) way to set the text without throwing it into a String 38 | /** 39 | * A {@link WiringHarness} for binding a {@link JPasswordField} to a value in a 40 | * {@link BindableModel}. 41 | */ 42 | public class JPasswordFieldWiringHarness implements WiringHarness { 43 | public Collection wire(Bound bound, BindingContext context, Field field) throws IllegalAccessException, IntrospectionException { 44 | String target = bound.to(); 45 | JPasswordField pwdField = context.getFieldObject(field, JPasswordField.class); 46 | ObjectFieldMethod setter = context.findSetter(target); 47 | ObjectFieldMethod getter = context.findGetter(target); 48 | if (setter == null || getter == null) { 49 | throw new IllegalArgumentException("could not find getter/setter for " + target); 50 | } 51 | BindableModel model1 = context.getFieldObject(setter.getField(), BindableModel.class); 52 | BindableModel model2 = context.getFieldObject(getter.getField(), BindableModel.class); 53 | Preconditions.checkArgument(model1 == model2, "setter not bound to same field as getter"); 54 | // verify type parameters 55 | return bindJPasswordField(model1, pwdField, getter.getMethod(), setter.getMethod()); 56 | } 57 | 58 | public static Collection bindJPasswordField(final BindableModel model, final JPasswordField pwdField, 59 | final Method getter, final Method setter) { 60 | pwdField.getDocument().addDocumentListener(new DocumentListener() { 61 | public void removeUpdate(DocumentEvent e) { 62 | updateModel(); 63 | } 64 | 65 | public void insertUpdate(DocumentEvent e) { 66 | updateModel(); 67 | } 68 | 69 | public void changedUpdate(DocumentEvent e) { 70 | updateModel(); 71 | } 72 | 73 | private void updateModel() { 74 | try { 75 | setter.invoke(model, pwdField.getPassword()); 76 | } catch (Exception ex) { 77 | Wiring.logger.error("exception in JPasswordField binding", ex); 78 | } 79 | } 80 | }); 81 | Binding binding = new Binding() { 82 | public & ModelUpdate> void update(T... changed) { 83 | try { 84 | char[] charArray = (char[])getter.invoke(model); 85 | if (charArray == null) { 86 | charArray = new char[0]; 87 | } 88 | if (!Arrays.equals(charArray, pwdField.getPassword())) { 89 | pwdField.setText(String.valueOf(charArray)); 90 | } 91 | } catch (Exception ex) { 92 | Wiring.logger.error("exception in JPasswordField binding", ex); 93 | } 94 | } 95 | }; 96 | model.bind(binding); 97 | return Collections.singleton(binding); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/BoundJProgressBarTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch; 15 | 16 | import javax.swing.JProgressBar; 17 | 18 | import com.palantir.ptoss.cinch.core.Bindings; 19 | import com.palantir.ptoss.cinch.core.DefaultBindableModel; 20 | import com.palantir.ptoss.cinch.swing.Bound; 21 | 22 | import junit.framework.TestCase; 23 | 24 | public class BoundJProgressBarTest extends TestCase { 25 | public static class TestModel extends DefaultBindableModel { 26 | 27 | private int intVal = 0; 28 | private double doubleVal = 0d; 29 | private float floatVal = 0f; 30 | public int getIntVal() { 31 | return intVal; 32 | } 33 | public void setIntVal(int intVal) { 34 | this.intVal = intVal; 35 | update(); 36 | } 37 | public double getDoubleVal() { 38 | return doubleVal; 39 | } 40 | public void setDoubleVal(double doubleVal) { 41 | this.doubleVal = doubleVal; 42 | update(); 43 | } 44 | public float getFloatVal() { 45 | return floatVal; 46 | } 47 | public void setFloatVal(float floatVal) { 48 | this.floatVal = floatVal; 49 | update(); 50 | } 51 | } 52 | 53 | private final TestModel model = new TestModel(); 54 | 55 | 56 | @Bound(to = "intVal") 57 | private final JProgressBar intBar = new JProgressBar(); 58 | @Bound(to = "floatVal") 59 | private final JProgressBar floatBar = new JProgressBar(); 60 | @Bound(to = "doubleVal") 61 | private final JProgressBar doubleBar = new JProgressBar(); 62 | 63 | private final Bindings bindings = new Bindings(); 64 | 65 | @Override 66 | protected void setUp() { 67 | bindings.bind(this); 68 | } 69 | 70 | public void testInt() { 71 | assertEquals(0, intBar.getValue()); 72 | assertFalse(intBar.isIndeterminate()); 73 | model.setIntVal(50); 74 | assertEquals(50, intBar.getValue()); 75 | assertFalse(intBar.isIndeterminate()); 76 | model.setIntVal(100); 77 | assertEquals(100, intBar.getValue()); 78 | assertFalse(intBar.isIndeterminate()); 79 | model.setIntVal(-1); 80 | assertTrue(intBar.isIndeterminate()); 81 | model.setIntVal(100); 82 | assertEquals(100, intBar.getValue()); 83 | assertFalse(intBar.isIndeterminate()); 84 | model.setIntVal(200); 85 | assertEquals(100, intBar.getValue()); 86 | assertFalse(intBar.isIndeterminate()); 87 | } 88 | 89 | public void testDouble() { 90 | assertEquals(0, doubleBar.getValue()); 91 | assertFalse(doubleBar.isIndeterminate()); 92 | model.setDoubleVal(0.50); 93 | assertEquals(50, doubleBar.getValue()); 94 | assertFalse(doubleBar.isIndeterminate()); 95 | model.setDoubleVal(1.00); 96 | assertEquals(100, doubleBar.getValue()); 97 | assertFalse(doubleBar.isIndeterminate()); 98 | model.setDoubleVal(-1); 99 | assertTrue(doubleBar.isIndeterminate()); 100 | model.setDoubleVal(1.00); 101 | assertEquals(100, doubleBar.getValue()); 102 | assertFalse(doubleBar.isIndeterminate()); 103 | model.setDoubleVal(2.00); 104 | assertEquals(100, doubleBar.getValue()); 105 | assertFalse(doubleBar.isIndeterminate()); 106 | } 107 | 108 | public void testFloat() { 109 | assertEquals(0, floatBar.getValue()); 110 | assertFalse(floatBar.isIndeterminate()); 111 | model.setFloatVal(0.50f); 112 | assertEquals(50, floatBar.getValue()); 113 | assertFalse(floatBar.isIndeterminate()); 114 | model.setFloatVal(1.00f); 115 | assertEquals(100, floatBar.getValue()); 116 | assertFalse(floatBar.isIndeterminate()); 117 | model.setFloatVal(-1f); 118 | assertTrue(floatBar.isIndeterminate()); 119 | model.setFloatVal(1.00f); 120 | assertEquals(100, floatBar.getValue()); 121 | assertFalse(floatBar.isIndeterminate()); 122 | model.setFloatVal(2.00f); 123 | assertEquals(100, floatBar.getValue()); 124 | assertFalse(floatBar.isIndeterminate()); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/example/simple/BoundJListExample.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.example.simple; 15 | 16 | import java.awt.BorderLayout; 17 | import java.awt.EventQueue; 18 | import java.lang.reflect.InvocationTargetException; 19 | import java.util.List; 20 | 21 | import javax.swing.JButton; 22 | import javax.swing.JFrame; 23 | import javax.swing.JLabel; 24 | import javax.swing.JList; 25 | import javax.swing.JPanel; 26 | 27 | import com.google.common.base.Joiner; 28 | import com.google.common.collect.ImmutableList; 29 | import com.palantir.ptoss.cinch.core.Bindable; 30 | import com.palantir.ptoss.cinch.core.Bindings; 31 | import com.palantir.ptoss.cinch.core.DefaultBindableModel; 32 | import com.palantir.ptoss.cinch.example.Examples; 33 | import com.palantir.ptoss.cinch.swing.Action; 34 | import com.palantir.ptoss.cinch.swing.Bound; 35 | import com.palantir.ptoss.cinch.swing.BoundSelection; 36 | 37 | public class BoundJListExample { 38 | 39 | @SuppressWarnings("unused") 40 | private static class Model extends DefaultBindableModel { 41 | private List items = ImmutableList.of("one", "two", "three", "four"); 42 | private List selectedItems = ImmutableList.of(); 43 | 44 | public List getItems() { 45 | return items; 46 | } 47 | 48 | public void setItems(List items) { 49 | this.items = items; 50 | update(); 51 | } 52 | 53 | public void setSelectedItems(List selectedItems) { 54 | if (selectedItems == null) { 55 | selectedItems = ImmutableList.of(); 56 | } 57 | this.selectedItems = ImmutableList.copyOf(selectedItems); 58 | update(); 59 | } 60 | 61 | public List getSelectedItems() { 62 | return selectedItems; 63 | } 64 | 65 | public String getSelectedString() { 66 | return "selected: " + Joiner.on(",").join(getSelectedItems()); 67 | } 68 | } 69 | 70 | @SuppressWarnings("unused") 71 | private static class Controller { 72 | private final Model model; 73 | 74 | public Controller(Model model) { 75 | this.model = model; 76 | } 77 | 78 | public void changeItems() { 79 | if (model.getItems().contains("one")) { 80 | model.setItems(ImmutableList.of("two", "three", "four", "five", "six")); 81 | } else { 82 | model.setItems(ImmutableList.of("one", "two", "three", "four")); 83 | } 84 | } 85 | } 86 | 87 | private final Model model = new Model(); 88 | 89 | @SuppressWarnings("unused") 90 | @Bindable 91 | private final Controller controller = new Controller(model); 92 | 93 | @Bound(to = "items") 94 | @BoundSelection(to = "selectedItems", multi = true) 95 | private final JList list = new JList(); 96 | 97 | private final JPanel panel = new JPanel(); 98 | 99 | @Action(call = "changeItems") 100 | private final JButton changeItems = new JButton("Change"); 101 | 102 | @Bound(to = "selectedString") 103 | private final JLabel selectedLabel = new JLabel(" "); 104 | 105 | private final Bindings bindings = Bindings.standard(); 106 | 107 | public BoundJListExample() { 108 | initializeInterface(); 109 | bindings.bind(this); 110 | } 111 | 112 | private void initializeInterface() { 113 | panel.setLayout(new BorderLayout()); 114 | panel.add(selectedLabel, BorderLayout.NORTH); 115 | panel.add(list, BorderLayout.CENTER); 116 | panel.add(changeItems, BorderLayout.SOUTH); 117 | 118 | JFrame frame = new JFrame("Demo"); 119 | frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 120 | frame.setLocation(100, 100); 121 | frame.setSize(400, 600); 122 | 123 | frame.setContentPane(panel); 124 | 125 | frame.setVisible(true); 126 | } 127 | 128 | public static void main(String[] args) throws InterruptedException, InvocationTargetException { 129 | Examples.initializeLogging(); 130 | EventQueue.invokeAndWait(new Runnable() { 131 | public void run() { 132 | new BoundJListExample(); 133 | } 134 | }); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/test/java/com/palantir/ptoss/cinch/SubclassEdgeCasesTest.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch; 15 | 16 | import java.awt.BorderLayout; 17 | 18 | import javax.swing.JLabel; 19 | import javax.swing.JPanel; 20 | 21 | import com.palantir.ptoss.cinch.core.Bindings; 22 | import com.palantir.ptoss.cinch.core.DefaultBindableModel; 23 | import com.palantir.ptoss.cinch.swing.Bound; 24 | 25 | import junit.framework.TestCase; 26 | 27 | /** 28 | * This is meant to capture some feedback that was received about using Bindings with subclasses. 29 | */ 30 | public class SubclassEdgeCasesTest extends TestCase { 31 | 32 | class BaseModel extends DefaultBindableModel { 33 | private String displayText; 34 | 35 | public String getDisplayText() { 36 | return displayText; 37 | } 38 | 39 | public void setDisplayText(String value) { 40 | displayText = value; 41 | update(); 42 | } 43 | } 44 | 45 | class CommentableModel extends BaseModel { 46 | private String comment; 47 | 48 | public String getComment() { 49 | return comment; 50 | } 51 | 52 | public void setComment(String value) { 53 | comment = value; 54 | update(); 55 | } 56 | } 57 | 58 | class BaseView { 59 | protected final T model; 60 | protected Bindings bindings = new Bindings(); 61 | 62 | protected JPanel panel = new JPanel(new BorderLayout()); 63 | 64 | // "model." is required to prevent subclasses from clobbering 65 | @Bound(to = "model.displayText") 66 | private final JLabel label = new JLabel(); 67 | 68 | public BaseView(T model) { 69 | this.model = model; 70 | 71 | panel.add(label, BorderLayout.CENTER); 72 | } 73 | } 74 | 75 | class CommentsView extends BaseView { 76 | @Bound(to = "comment") 77 | private JLabel countLabel = new JLabel(); 78 | 79 | public CommentsView(CommentableModel model) { 80 | super(model); 81 | 82 | panel.add(countLabel, BorderLayout.EAST); 83 | 84 | bindings.bind(this); 85 | } 86 | } 87 | 88 | /** 89 | * Bindings are done against the declared type of models, not the runtime type. 90 | */ 91 | public void testReflectionUponDeclaredType() { 92 | CommentableModel model = new CommentableModel(); 93 | // should throw IllegalArgumentException: could not find getter/setter for comment 94 | // because it's performing introspection against the declared type of the model in BaseView, 95 | // which is BaseModel 96 | // even though the runtime type is CommentableModel 97 | try { 98 | CommentsView view = new CommentsView(model); 99 | fail("should have thrown exception"); 100 | } catch (IllegalArgumentException ex) { 101 | assertEquals("could not find either getter/setter for comment", ex.getMessage()); 102 | } 103 | } 104 | 105 | public class CommentsView2 extends BaseView { 106 | 107 | private final CommentableModel commentableModel; 108 | 109 | @Bound(to="comment") 110 | private JLabel countLabel = new JLabel(); 111 | 112 | private Bindings bindings = new Bindings(); 113 | 114 | public CommentsView2(CommentableModel model) { 115 | super(model); 116 | this.commentableModel = model; 117 | 118 | panel.add(countLabel, BorderLayout.EAST); 119 | 120 | bindings.bind(this); 121 | } 122 | } 123 | 124 | /** 125 | * Views that are intended to be subclassed should explicitly name their bound models: 126 | * Example: 127 | *
128 |      * @Bound(to = "model.displayText")
129 |      * 
130 | * instead of 131 | *
132 |      * @Bound(to = "displayText")
133 |      * 
134 | */ 135 | public void testSuperclassExplicitBinding() { 136 | CommentableModel model = new CommentableModel(); 137 | // should throw IllegalArgumentException: could not find getter/setter for displayText 138 | // actually caused by there being more than one displayText property available in the context 139 | CommentsView2 view = new CommentsView2(model); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/ptoss/cinch/swing/OnClick.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.swing; 15 | 16 | import java.awt.event.MouseAdapter; 17 | import java.awt.event.MouseEvent; 18 | import java.awt.event.MouseListener; 19 | import java.lang.annotation.ElementType; 20 | import java.lang.annotation.Retention; 21 | import java.lang.annotation.RetentionPolicy; 22 | import java.lang.annotation.Target; 23 | import java.lang.reflect.Field; 24 | import java.lang.reflect.InvocationTargetException; 25 | import java.lang.reflect.Method; 26 | import java.util.Collection; 27 | import java.util.List; 28 | 29 | import org.slf4j.Logger; 30 | import org.slf4j.LoggerFactory; 31 | 32 | import com.google.common.collect.ImmutableList; 33 | import com.palantir.ptoss.cinch.core.Binding; 34 | import com.palantir.ptoss.cinch.core.BindingContext; 35 | import com.palantir.ptoss.cinch.core.BindingException; 36 | import com.palantir.ptoss.cinch.core.BindingWiring; 37 | import com.palantir.ptoss.cinch.core.ObjectFieldMethod; 38 | 39 | /** 40 | * A binding that will call a method when the on-click action occurs on this component. 41 | */ 42 | @Retention(RetentionPolicy.RUNTIME) 43 | @Target({ElementType.FIELD}) 44 | public @interface OnClick { 45 | String call(); 46 | int count() default 1; 47 | Button button() default Button.LEFT; 48 | /** 49 | * Enum specifying which mouse button is being bound by this binding. 50 | */ 51 | public enum Button { 52 | LEFT(MouseEvent.BUTTON1), CENTER(MouseEvent.BUTTON2), RIGHT(MouseEvent.BUTTON3); 53 | 54 | private final int constant; 55 | 56 | private Button(int constant) { 57 | this.constant = constant; 58 | } 59 | 60 | private int getConstant() { 61 | return constant; 62 | } 63 | } 64 | 65 | /** 66 | * Inner utility class that performs the runtime wiring of all {@link OnClick} bindings. 67 | */ 68 | static class Wiring implements BindingWiring { 69 | private static final Logger logger = LoggerFactory.getLogger(OnClick.class); 70 | 71 | public Collection wire(BindingContext context) { 72 | List actions = context.getAnnotatedFields(OnClick.class); 73 | for (Field field : actions) { 74 | OnClick onClick = field.getAnnotation(OnClick.class); 75 | try { 76 | wire(onClick, field, context); 77 | } catch (Exception e) { 78 | throw new BindingException("could not wire up @OnClick on " + field.getName(), e); 79 | } 80 | } 81 | return ImmutableList.of(); 82 | } 83 | 84 | private static void wire(final OnClick onClick, Field field, BindingContext context) 85 | throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException { 86 | String call = onClick.call(); 87 | if (call == null) { 88 | throw new BindingException("call on @OnClick on " + field.getName() + " must be specified"); 89 | } 90 | Method amlMethod = field.getType().getMethod("addMouseListener", MouseListener.class); 91 | if (amlMethod != null) { 92 | Object actionObject = context.getFieldObject(field, Object.class); 93 | final ObjectFieldMethod ofm = context.getBindableMethod(call); 94 | if (ofm == null) { 95 | throw new BindingException("could not find bindable method: " + call); 96 | } 97 | MouseListener mouseListener = new MouseAdapter() { 98 | @Override 99 | public void mouseClicked(MouseEvent e) { 100 | if (e.getButton() != onClick.button().getConstant() || e.getClickCount() != onClick.count()) { 101 | return; 102 | } 103 | try { 104 | ofm.getMethod().setAccessible(true); 105 | ofm.getMethod().invoke(ofm.getObject()); 106 | } catch (Exception ex) { 107 | logger.error("exception during action firing", ex); 108 | } 109 | } 110 | }; 111 | amlMethod.invoke(actionObject, mouseListener); 112 | } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/ptoss/cinch/core/CallOnUpdate.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.core; 15 | 16 | import java.lang.annotation.ElementType; 17 | import java.lang.annotation.Retention; 18 | import java.lang.annotation.RetentionPolicy; 19 | import java.lang.annotation.Target; 20 | import java.lang.reflect.InvocationTargetException; 21 | import java.lang.reflect.Method; 22 | import java.util.Collection; 23 | import java.util.List; 24 | import java.util.Set; 25 | 26 | import org.slf4j.Logger; 27 | import org.slf4j.LoggerFactory; 28 | 29 | import com.google.common.base.Strings; 30 | import com.google.common.collect.ImmutableList; 31 | import com.google.common.collect.Lists; 32 | 33 | /** 34 | * An annotation to mark that a method should be called when the bound model updates 35 | * in a specific way. The method can have any access modifiers, i.e. it can be private. 36 | */ 37 | @Retention(RetentionPolicy.RUNTIME) 38 | @Target({ElementType.METHOD}) 39 | public @interface CallOnUpdate { 40 | /** 41 | * The model field name to bind to. If this is left blank and the {@link BindingContext} has 42 | * only a single {@link BindableModel} then it will bind to that. 43 | */ 44 | String model() default ""; 45 | 46 | /** 47 | * What model update type to trigger on, or blank (default) for all updates. 48 | */ 49 | String[] on() default ""; //$NON-NLS-1$ 50 | 51 | /** 52 | * Utility class that performs the wiring for {@link CallOnUpdate} annotations. 53 | */ 54 | static class Wiring implements BindingWiring { 55 | private static final Logger logger = LoggerFactory.getLogger(CallOnUpdate.class); 56 | 57 | public Collection wire(final BindingContext context) { 58 | final List methods = context.getAnnotatedParameterlessMethods(CallOnUpdate.class); 59 | final List bindings = Lists.newArrayList(); 60 | for (final ObjectFieldMethod method : methods) { 61 | final CallOnUpdate callOnUpdate = method.getMethod().getAnnotation(CallOnUpdate.class); 62 | bindings.addAll(wire(callOnUpdate, context, method)); 63 | } 64 | return bindings; 65 | } 66 | 67 | private static Collection wire(final CallOnUpdate callOnUpdate, final BindingContext context, final ObjectFieldMethod method) { 68 | final String to = callOnUpdate.model(); 69 | final BindableModel model; 70 | if (Strings.isNullOrEmpty(to)) { 71 | Set models = context.getBindableModels(); 72 | if (models.size() != 1) { 73 | throw new BindingException("more than one bindable model for empty 'to'"); //$NON-NLS-1$ 74 | } 75 | model = models.iterator().next(); 76 | } else { 77 | model = context.getBindableModel(to); 78 | if (model == null) { 79 | throw new BindingException("can't find method to bind to: " + to); //$NON-NLS-1$ 80 | } 81 | } 82 | final String[] ons = callOnUpdate.on(); 83 | List onObjects = BindingContext.getOnObjects(ons, model); 84 | Binding binding = makeBinding(method, onObjects); 85 | model.bind(binding); 86 | return ImmutableList.of(binding); 87 | } 88 | 89 | private static Binding makeBinding(final ObjectFieldMethod method, final List onObjects) { 90 | final Method actualMethod = method.getMethod(); 91 | actualMethod.setAccessible(true); 92 | final Binding binding = new Binding() { 93 | public & ModelUpdate> void update(final T... changed) { 94 | if (!BindingContext.isOn(onObjects, changed)) { 95 | return; 96 | } 97 | try { 98 | actualMethod.invoke(method.getObject()); 99 | } catch (final InvocationTargetException itex) { 100 | logger.error("exception during CallOnUpdate firing", itex.getCause()); //$NON-NLS-1$ 101 | } catch (final Exception e) { 102 | logger.error("exception in method binding", e); //$NON-NLS-1$ 103 | } 104 | } 105 | }; 106 | return binding; 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/ptoss/cinch/swing/VisibleIf.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.swing; 15 | 16 | import java.beans.IntrospectionException; 17 | import java.lang.annotation.ElementType; 18 | import java.lang.annotation.Retention; 19 | import java.lang.annotation.RetentionPolicy; 20 | import java.lang.annotation.Target; 21 | import java.lang.reflect.Field; 22 | import java.lang.reflect.Method; 23 | import java.util.Collection; 24 | import java.util.Collections; 25 | import java.util.List; 26 | 27 | import org.slf4j.Logger; 28 | import org.slf4j.LoggerFactory; 29 | 30 | import com.google.common.collect.Lists; 31 | import com.palantir.ptoss.cinch.core.BindableModel; 32 | import com.palantir.ptoss.cinch.core.Binding; 33 | import com.palantir.ptoss.cinch.core.BindingContext; 34 | import com.palantir.ptoss.cinch.core.BindingException; 35 | import com.palantir.ptoss.cinch.core.BindingWiring; 36 | import com.palantir.ptoss.cinch.core.ModelUpdate; 37 | import com.palantir.ptoss.cinch.core.ObjectFieldMethod; 38 | 39 | /** 40 | * A binding that will set the Visible state of the annotated component to the state of a model 41 | * boolean. The component must have a "setVisible" method that takes a boolean. 42 | */ 43 | @Retention(RetentionPolicy.RUNTIME) 44 | @Target({ElementType.FIELD}) 45 | public @interface VisibleIf { 46 | /** 47 | * Enum to specify if normal or inverted comparisons should be used. 48 | */ 49 | public enum Type { NORMAL, INVERTED }; 50 | /** 51 | * The model boolean property to bind to. 52 | */ 53 | String to(); 54 | 55 | /** 56 | * Whether or not to invert the boolean. 57 | */ 58 | Type type() default Type.NORMAL; 59 | 60 | /** 61 | * Inner utility class that performs the runtime wiring of all {@link VisibleIf} bindings. 62 | */ 63 | public static class Wiring implements BindingWiring { 64 | private static final Logger logger = LoggerFactory.getLogger(VisibleIf.class); 65 | 66 | public Collection wire(final BindingContext context) { 67 | final List actions = context.getAnnotatedFields(VisibleIf.class); 68 | final List bindings = Lists.newArrayList(); 69 | for (final Field field : actions) { 70 | final VisibleIf action = field.getAnnotation(VisibleIf.class); 71 | final String to = action.to(); 72 | final boolean invert = (action.type() == Type.INVERTED); 73 | try { 74 | bindings.addAll(wire(to, field, context, invert)); 75 | } catch (final Exception e) { 76 | throw new BindingException("could not wire up @VisibleIf on " + field.getName(), e); 77 | } 78 | } 79 | return bindings; 80 | } 81 | 82 | private static Collection wire(final String to, final Field field, final BindingContext context, final boolean invert) 83 | throws SecurityException, NoSuchMethodException, IllegalArgumentException, IntrospectionException { 84 | final Method setVisibleMethod = field.getType().getMethod("setVisible", boolean.class); 85 | if (setVisibleMethod == null) { 86 | throw new BindingException("no setVisible call on VisibleIf field: " + field); 87 | } 88 | final Object setVisibleObject = context.getFieldObject(field, Object.class); 89 | final ObjectFieldMethod getter = context.findGetter(to); 90 | if (getter == null) { 91 | throw new BindingException("could not find bindable property: " + to); 92 | } 93 | if (getter.getMethod().getReturnType() != boolean.class) { 94 | throw new BindingException("VisibleIf binding must return boolean: " + to); 95 | } 96 | final Binding binding = new Binding() { 97 | public & ModelUpdate> void update(final T... changed) { 98 | try { 99 | getter.getMethod().setAccessible(true); 100 | boolean visible = (Boolean)getter.getMethod().invoke(getter.getObject()); 101 | if (invert) { 102 | visible = !visible; 103 | } 104 | setVisibleMethod.invoke(setVisibleObject, visible); 105 | } catch (final Exception e) { 106 | Wiring.logger.error("exception during VisibleIf binding", e); 107 | } 108 | } 109 | }; 110 | ((BindableModel)getter.getObject()).bind(binding); 111 | return Collections.singleton(binding); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/ptoss/cinch/swing/JToggleButtonWiringHarness.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.swing; 15 | 16 | import java.awt.event.ActionEvent; 17 | import java.awt.event.ActionListener; 18 | import java.beans.IntrospectionException; 19 | import java.lang.reflect.Field; 20 | import java.util.Collection; 21 | 22 | import javax.swing.AbstractButton; 23 | import javax.swing.JToggleButton; 24 | 25 | import com.google.common.base.Strings; 26 | import com.google.common.collect.ImmutableList; 27 | import com.palantir.ptoss.cinch.core.BindableModel; 28 | import com.palantir.ptoss.cinch.core.Binding; 29 | import com.palantir.ptoss.cinch.core.BindingContext; 30 | import com.palantir.ptoss.cinch.core.BindingException; 31 | import com.palantir.ptoss.cinch.core.ModelUpdate; 32 | import com.palantir.ptoss.cinch.core.WiringHarness; 33 | import com.palantir.ptoss.cinch.swing.Bound.Wiring; 34 | import com.palantir.ptoss.util.Mutator; 35 | import com.palantir.ptoss.util.Reflections; 36 | 37 | /** 38 | * A {@link WiringHarness} for binding a {@link JToggleButton} to an {@link Enum} value in a 39 | * {@link BindableModel}. 40 | */ 41 | public class JToggleButtonWiringHarness implements WiringHarness { 42 | public Collection wire(Bound bound, BindingContext context, Field field) 43 | throws IllegalAccessException, IntrospectionException { 44 | String target = bound.to(); 45 | Mutator mutator = Mutator.create(context, target); 46 | JToggleButton toggle = context.getFieldObject(field, JToggleButton.class); 47 | 48 | Class[] paramTypes = mutator.getSetter().getMethod().getParameterTypes(); 49 | if (paramTypes.length == 1 && paramTypes[0].isEnum()) { 50 | Class enumType = paramTypes[0]; 51 | String value = bound.value(); 52 | return ImmutableList.of(bindJToggleButtonToEnum(value, enumType, mutator, toggle)); 53 | } else if (paramTypes.length == 1 && paramTypes[0] == boolean.class) { 54 | String value = bound.value(); 55 | if (Strings.isNullOrEmpty(value)) { 56 | return ImmutableList.of(AbstractButtonWiringHarness.bindAbstractButton(mutator, toggle)); 57 | } else { 58 | return ImmutableList.of(bindJToggleButtonToBoolean(bound.value(), mutator, toggle)); 59 | } 60 | } else { 61 | throw new BindingException("can only bind JToggleButtons to enums or booleans"); //$NON-NLS-1$ 62 | } 63 | } 64 | 65 | public static Binding bindJToggleButtonToBoolean(String value, 66 | final Mutator mutator, final AbstractButton button) { 67 | final boolean booleanValue = Boolean.valueOf(value); 68 | button.addActionListener(new ActionListener() { 69 | public void actionPerformed(ActionEvent e) { 70 | try { 71 | mutator.set(booleanValue); 72 | } catch (Exception ex) { 73 | Wiring.logger.error("exception in JRadioButton binding", ex); //$NON-NLS-1$ 74 | } 75 | } 76 | }); 77 | 78 | Binding binding = new Binding() { 79 | public & ModelUpdate> void update(T... changed) { 80 | try { 81 | button.setSelected(mutator.get().equals(Boolean.valueOf(booleanValue))); 82 | } catch (Exception ex) { 83 | Wiring.logger.error("exception in JRadioButton binding", ex); //$NON-NLS-1$ 84 | } 85 | } 86 | }; 87 | mutator.getModel().bind(binding); 88 | return binding; 89 | } 90 | 91 | public static Binding bindJToggleButtonToEnum(final String value, final Class enumType, 92 | final Mutator mutator, final AbstractButton button) { 93 | final Object enumValue = Reflections.evalEnum(enumType, value); 94 | button.addActionListener(new ActionListener() { 95 | public void actionPerformed(ActionEvent e) { 96 | try { 97 | mutator.set(enumValue); 98 | } catch (Exception ex) { 99 | Wiring.logger.error("exception in JToggleButton binding", ex); //$NON-NLS-1$ 100 | } 101 | } 102 | }); 103 | 104 | Binding binding = new Binding() { 105 | public & ModelUpdate> void update(T... changed) { 106 | try { 107 | button.setSelected(mutator.get() == enumValue); 108 | } catch (Exception ex) { 109 | Wiring.logger.error("exception in JToggleButton binding", ex); //$NON-NLS-1$ 110 | } 111 | } 112 | }; 113 | mutator.getModel().bind(binding); 114 | return binding; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/com/palantir/ptoss/cinch/swing/EnabledIf.java: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Palantir Technologies 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package com.palantir.ptoss.cinch.swing; 15 | 16 | import java.beans.IntrospectionException; 17 | import java.lang.annotation.ElementType; 18 | import java.lang.annotation.Retention; 19 | import java.lang.annotation.RetentionPolicy; 20 | import java.lang.annotation.Target; 21 | import java.lang.reflect.Field; 22 | import java.lang.reflect.Method; 23 | import java.util.Collection; 24 | import java.util.Collections; 25 | import java.util.List; 26 | 27 | import org.slf4j.Logger; 28 | import org.slf4j.LoggerFactory; 29 | 30 | import com.google.common.collect.Lists; 31 | import com.palantir.ptoss.cinch.core.BindableModel; 32 | import com.palantir.ptoss.cinch.core.Binding; 33 | import com.palantir.ptoss.cinch.core.BindingContext; 34 | import com.palantir.ptoss.cinch.core.BindingException; 35 | import com.palantir.ptoss.cinch.core.BindingWiring; 36 | import com.palantir.ptoss.cinch.core.Bindings; 37 | import com.palantir.ptoss.cinch.core.ModelUpdate; 38 | import com.palantir.ptoss.cinch.core.ObjectFieldMethod; 39 | 40 | /** 41 | * A binding that will set the enabled state of the annotated component to the state of a model 42 | * boolean. The component must have a "setEnabled" method that takes a boolean. 43 | */ 44 | @Retention(RetentionPolicy.RUNTIME) 45 | @Target({ElementType.FIELD}) 46 | public @interface EnabledIf { 47 | /** 48 | * Enum to specify if normal or inverted comparisons should be used. 49 | */ 50 | public enum Type { NORMAL, INVERTED }; 51 | /** 52 | * The model boolean property to bind to. 53 | */ 54 | String to(); 55 | 56 | /** 57 | * Whether or not to invert the boolean. 58 | */ 59 | Type type() default Type.NORMAL; 60 | 61 | /** 62 | * Inner utility class that performs the runtime wiring of all {@link EnabledIf} bindings. 63 | * 64 | * @see Bindings#STANDARD_BINDINGS 65 | */ 66 | public static class Wiring implements BindingWiring { 67 | private static final Logger logger = LoggerFactory.getLogger(EnabledIf.class); 68 | 69 | public Collection wire(final BindingContext context) { 70 | final List actions = context.getAnnotatedFields(EnabledIf.class); 71 | final List bindings = Lists.newArrayList(); 72 | for (final Field field : actions) { 73 | final EnabledIf action = field.getAnnotation(EnabledIf.class); 74 | final String to = action.to(); 75 | final boolean invert = (action.type() == Type.INVERTED); 76 | try { 77 | bindings.addAll(wire(to, field, context, invert)); 78 | } catch (final Exception e) { 79 | throw new BindingException("could not wire up @EnabledIf on " + field.getName(), e); 80 | } 81 | } 82 | return bindings; 83 | } 84 | 85 | private static Collection wire(final String to, final Field field, final BindingContext context, final boolean invert) 86 | throws SecurityException, NoSuchMethodException, IllegalArgumentException, IntrospectionException { 87 | final Method setEnabledMethod = field.getType().getMethod("setEnabled", boolean.class); 88 | if (setEnabledMethod == null) { 89 | throw new BindingException("no setEnabled call on EnabledIf field: " + field); 90 | } 91 | final Object setEnabledObject = context.getFieldObject(field, Object.class); 92 | final ObjectFieldMethod getter = context.findGetter(to); 93 | if (getter == null) { 94 | throw new BindingException("could not find bindable property: " + to); 95 | } 96 | if (getter.getMethod().getReturnType() != boolean.class) { 97 | throw new BindingException("EnabledIf binding must return boolean: " + to); 98 | } 99 | getter.getMethod().setAccessible(true); 100 | final Binding binding = new Binding() { 101 | public & ModelUpdate> void update(final T... changed) { 102 | try { 103 | boolean enabled = (Boolean)getter.getMethod().invoke(getter.getObject()); 104 | if (invert) { 105 | enabled = !enabled; 106 | } 107 | setEnabledMethod.invoke(setEnabledObject, enabled); 108 | } catch (final Exception e) { 109 | Wiring.logger.error("exception during EnabledIf binding", e); 110 | } 111 | } 112 | }; 113 | ((BindableModel)getter.getObject()).bind(binding); 114 | return Collections.singleton(binding); 115 | } 116 | } 117 | } 118 | --------------------------------------------------------------------------------